Laravel项目PHPUnit测试时出现SQL语法错误求助
Laravel测试时SQL语法错误:CONCAT/UPPER/RIGHT函数兼容性问题
问题背景
我在Laravel项目中实现了一个表单查询逻辑:通过拼接用户名后3位(转为大写)与ID生成编码,以此查询表单。应用运行时该查询正常,但PHPUnit测试时出现SQL语法错误:
SQLSTATE[HY000]: General error: 1 near "(": syntax error (SQL: select * from "forms" where "factual_checkout_at" is null and "plan_checkout_at" >= ? and CONCAT(UPPER(RIGHT(name, 3)), id) = ? and "forms"."deleted_at" is null and "forms"."type" in (?, ?, ?) limit 1 )
查询实现代码如下:
$form = Form::whereNull('factual_checkout_at') ->where('plan_checkout_at', '>=', Carbon::today()) ->whereRaw('CONCAT(UPPER(RIGHT(name, 3)), id) = ?', $code) ->firstOrFail(); return new FormResource($form);
问题根源
这个问题其实很常见——你的测试环境和运行环境使用的数据库不一致!大概率你开发/生产用的是MySQL(或PostgreSQL),但PHPUnit测试用的是SQLite。
两者的SQL函数存在关键差异:
- MySQL的
RIGHT(name, 3)用来取字符串末尾3位,但SQLite并不支持RIGHT()函数,它需要用SUBSTR(name, -3)来实现相同效果 - 虽然SQLite 3.33.0+开始支持
CONCAT(),但如果你的测试环境SQLite版本较低,也可能触发问题,但这里核心问题是RIGHT()函数的兼容性
解决办法
有两种可行的方案,你可以根据自己的情况选择:
方案1:编写兼容多数据库的SQL语句
用SQLite和MySQL都支持的语法替代RIGHT(),修改whereRaw部分:
$form = Form::whereNull('factual_checkout_at') ->where('plan_checkout_at', '>=', Carbon::today()) ->whereRaw('CONCAT(UPPER(SUBSTR(name, -3)), id) = ?', [$code]) ->firstOrFail();
SUBSTR(name, -3)在MySQL和SQLite中都表示取name字段的最后3个字符,完美兼容。
如果你想更贴合Laravel的查询构造器风格,也可以拆分成链式调用:
$form = Form::whereNull('factual_checkout_at') ->where('plan_checkout_at', '>=', Carbon::today()) ->where(function ($query) use ($code) { $query->whereRaw('CONCAT(UPPER(SUBSTR(name, -3)), id) = ?', [$code]); }) ->firstOrFail();
方案2:统一测试环境的数据库
直接把PHPUnit测试用的数据库改成和开发环境一致的(比如MySQL),这样就不需要修改代码了。你只需要在项目根目录的phpunit.xml文件中调整数据库配置:
<php> <env name="DB_CONNECTION" value="mysql"/> <env name="DB_DATABASE" value="your_test_database_name"/> <env name="DB_USERNAME" value="your_db_username"/> <env name="DB_PASSWORD" value="your_db_password"/> </php>
验证
修改完成后重新运行PHPUnit测试,语法错误应该就会消失了。本质上这就是不同数据库的SQL语法兼容性问题,只要保证测试和运行环境的数据库一致,或者使用兼容的语法就能解决。
内容的提问来源于stack exchange,提问作者Gregorio
相关产品推荐
相关产品推荐

