php MySQL datediff函数在用于单元测试的SQLite中不可用

cgvd09ve  于 2023-04-19  发布在  PHP
关注(0)|答案(2)|浏览(88)

我在我的Laravel项目中使用了以下Raw查询。

$statisticsInRangeDate = $myModel->selectRaw(
    "floor(datediff(created_at, '{$dateInfo['currentRangeDate']}')".
    " / {$dateInfo['daysRange']}) * {$dateInfo['daysRange']} AS diff_days_range, count(*) as total_clicks, ".
    'min(created_at) ,max(created_at)'
)
    ->groupByRaw('diff_days_range')
    ->orderByRaw('diff_days_range DESC')
    ->get();

原始查询为:

select
    floor(datediff(created_at, '2023-04-04') / 3) * 3 AS diff_days_range,
    count(*) as total_install,
    min(created_at),
    max(created_at) 
from sdk_clicks 
where sdk_clicks.application_id = 1
    and sdk_clicks.application_id is not null
    and created_at >= '2023-04-01'
    and created_at < '2023-04-07'
    and sdk_clicks.deleted_at is null
group by diff_days_range
order by diff_days_range

项目的数据库是mysql,我得到了必要的输出,但是当我运行phpunit测试时,我得到了以下错误,该测试使用了sqlite内存数据库以提高速度。
Illuminate\Database\QueryException:SQLSTATE[HY000]:一般错误:1无此功能:datediff(SQL:选择楼层...
我知道这个函数在sqlite中不存在,应该使用julianday('now') - julianday(DateCreated)来计算时间差,但我正在寻找通过检测数据库类型和使用适当函数来自动解决这个问题的最佳解决方案。
注意:由于高速测试的需要,我想使用内存和sqlite数据库

xpszyzbs

xpszyzbs1#

PHP提供了在sqlite中创建用户定义函数的能力。这是使用PDO::sqliteCreateFunction() function完成的。
基本上,您在sqlite中定义了一个函数,它将调用您提供的PHP代码。
你的代码看起来像这样:

$pdo = DB::connection()->getPdo();

// Only do this if using sqlite.
if ($pdo->getAttribute(PDO::ATTR_DRIVER_NAME) != 'sqlite') {
    return;
}

$pdo->sqliteCreateFunction(
    'datediff', // name of the sqlite function to create
    function ($first, $second) {
        $dateFirst = Carbon\Carbon::parse($first)->startOfDay();
        $dateSecond = Carbon\Carbon::parse($second)->startOfDay();
        
        return $dateSecond->diffInDays($dateFirst, false);
    }, // PHP callback function implementation
    2 // number of parameters the sqlite function takes
);

在执行查询之前将其添加到测试套件中的某个地方,您应该会很好。

1dkrff03

1dkrff032#

考虑到使用Macro in laravel的可能性,它允许我向Illuminate\Database\Eloquent\Builder添加新功能。
在AppServiceProvider.php中,我创建了宏rangeDateDiff():

Builder::macro('rangeDateDiff',
            function ($firstDate, $secondDate, $daysRange = 1, string $as = 'diff_days_range') {
            if ($this->getConnection()->getDriverName() == 'sqlite') {
                $this->addSelect(\DB::raw(
                    "floor (cast (julianday(strftime('%Y-%m-%d', {$firstDate})) - julianday(strftime('%Y-%m-%d', {$secondDate})) As Integer)"
                    . " / {$daysRange}) * {$daysRange} AS {$as}")
                );
            } else {
                $this->addSelect(\DB::raw(
                    "floor(datediff({$firstDate}, {$secondDate})"
                    . " / {$daysRange}) * {$daysRange} AS {$as}")
                );
            }

            return $this;
        });

然后在Eloquent模型中使用这个新函数globaly:

$sdkClicks = $this->application->sdkClicks()
            ->where('created_at', '>=', $dateInfo['previousRangeDate'])
            ->where('created_at', '<', $dateInfo['toDate']->format('Y-m-d'));

        $sdkClicks->rangeDateDiff('created_at', "'{$dateInfo['currentRangeDate']}'", $dateInfo['daysRange'])
            ->groupByRaw('diff_days_range')
            ->orderByRaw('diff_days_range DESC')->get();

这种方法的优点是更干净,最重要的是,如果项目中需要使用另一种类型的数据库,代码更易于维护

相关问题