Perl DBI (MySQL) 在现成的表达式中放置单引号,而不是实际参数
尝试将简单的查询作为现成的语句进行,但没有结果。代码如下:
package sqltest; use DBI; DBI->trace(2); my $dbh = DBI->connect('dbi:mysql:database=test;host=***;port=3306','the_username','****'); my $prep = 'SELECT me.id,me.session_data,me.expires FROM sys_session me WHERE me.id = ?'; $dbh->{RaiseError} = 1; my $sth = $dbh->prepare($prep); $sth->bind_param(1,'session:06b6d2138df949524092eefc066ee5ab3598bf96'); $sth->execute; DBI::dump_results($sth);
MySQL 服务器返回 '' 附近语法错误。
DBI 跟踪输出显示
-> bind_param for DBD::mysql::st (DBI::st=HASH(0x21e35cc)~0x21e34f4 1 'session:06b6d2138df949524092eefc066ee5ab3598bf96') thr#3ccdb4 Called: dbd_bind_ph <- bind_param= ( 1 ) [1 items] at perl_test_dbi_params.pl line 10 [...] >parse_params statement SELECT me.id,me.expires FROM sys_session me WHERE me.id = ? Binding parameters: SELECT me.id,me.expires FROM sys_session me WHERE me.id = ' [...] DBD::mysql::st execute failed: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1
因此,在我看来,该声明并未按应有的方式起草。
当我发送不带参数的查询时,它按预期工作。
我在这里错过了什么?
DBI 版本是 DBI 1.637-ithread,MySQL 版本是 5.5.57-0 deb8u1
使用 Windows perl 5,版本 26,针对 MSWin32-x86-multi-thread-64int、Subversion 1() 构建 和Ubuntu perl 5 版本 22,Subversion 1() 构建 x86_64-linux-gnu-thread-multi
EDIT1:
上下文:我使用 Catalyst 和 Catalyst: :Plugin::S :Store ::DBIC 注意到了这一点问题。这里,id 列的类型为 Varchar(72),包含会话 ID。
EDIT2:
> DBD::mysql 版本为 4.043
> 执行bind('session: foo'); via $sth-> 会导致同样的问题
> 通过 $sth->bind_param('session: foo',SQL_VARCHAR); 绑定导致同样的问题
>绑定数字字段有效,但只有在显式类型定义的情况下才有效 $sth->bind_param(1,1512407082,SQL_INTEGER);
编辑3:
我发现自己做了更多的测试时间,但没有令人满意的结果:
我能够在旧服务器上进行测试并且它有效。 DBI 和 DBD::mysql 的版本是相同的,但我注意到服务器正在使用 MySQL 5.5 客户端,在 DBI 跟踪中报告为 MYSQL_VERSION_ID 50557,而我的两个原始测试服务器都使用 MySQL 5.7 MYSQL_VERSION_ID 50720 和MYSQL_VERSION_ID 50716
> 使用 $dbh->{mysql_server_prepare} = 1;有用!也许这会对发现此问题的人有所帮助,但我宁愿现在找到问题的真正原因
从您的跟踪日志中,看起来问号占位符(?)已被 DBD 中的撇号替换: :mysql(')。所以这是一个纯粹的 DBD::mysql 错误。乍一看它根本没有任何意义......因为占位符被两个撇号括起来的参数替换。
可以在那里找到执行这个占位符替换字符所需的代码:
*ptr++ = '\''; ptr += mysql_real_escape_string(sock,ptr,valbuf,vallen); *ptr++ = '\'';
所以问题是,上面的C代码可以在*ptr缓冲区中产生撇号吗?答案是肯定的,当 mysql_real_escape_string() 返回一个等于指针大小减 1 的整数值时 - 模拟当两个转义字符写入 *ptr 缓冲区中的同一位置时递减 1 的数值行为。
有这种情况吗?是的,可以,因为 Oracle 更改了 MySQL 5.7.6 客户端库中 mysql_real_escape_string() C 函数的 API:
https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-6.html#mysqld-5-7-6-feature
不兼容的更改:新的C API函数,引入 mysql_real_escape_string_quote() 作为 mysql_real_escape_string() 的替代品,因为当启用 NO_BACKSLASH_ESCAPES SQL 模式时,后一个函数可能无法正确编码字符。在这种情况下,mysql_real_escape_string() 无法转义引号,除非将引号加倍,并且要正确执行此操作,它需要了解更多有关引号上下文的信息。 mysql_real_escape_string_quote() 使用额外的参数来指定引用上下文。有关使用信息,请参阅 mysql_real_escape_string_quote()。
mysql_real_escape_string for MySQL 5.7.6 ()文档说:
https://dev.mysql.com/doc/refman/5.7/en/mysql-real-escape-string.html
返回值:要放置在 to 参数中的编码字符串的长度,不包括终止符空字节,如果发生错误则为 -1。
因此,如果MySQL服务器上启用了NO_BACKSLASH_ESCAPES SQL模式,MySQL 5.7.6客户端的mysql_real_escape_string()将无法正常工作并返回错误,因此-1转换为unsigned long。 unsigned long 与 32 位和 64 位 x86 平台上的指针大小相同,这会在 DBD::mysql 驱动程序的 C 代码上方生成一个撇号。
现在我在下面的拉取请求中修复了这个 DBD::MariaDB 驱动程序(DBD::mysql 的一个分支)的问题:
所以当使用 MySQL 5.7 客户端库编译时,DBD::MariaDB 也是兼容的。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。