Функция MySQL datediff недоступна в SQLite, используемом для модульных тестов

Я использовал следующий необработанный запрос в своем проекте Laravel.

$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.

Я скажу, что ответ довольно очевиден ... вы не можете использовать SQLite или просто не использовать RAW sql, а использовать фасад БД или модель и позволить этому УПРАВЛЯТЬ запросом по назначению ... Старайтесь избегать необработанного sql как сколько можно...

matiaslauriti 13.04.2023 17:30

Вы также можете рассмотреть возможность использования MySQL, но в своей тестовой среде определите таблицы с помощью механизма хранения MEMORY.

Bill Karwin 13.04.2023 20:10

Если вам нужно протестировать код, который в значительной степени зависит от неосновного материала sql, вам будет лучше создать отдельную базу данных mysql только для целей тестирования. Это будет медленнее, чем база данных sqlite, но вы будете уверены, что запросы ведут себя одинаково. Использование механизма хранения в памяти может смягчить это, но, по моему опыту, это не всегда работает должным образом.

IGP 14.04.2023 05:20
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
0
3
120
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

PHP предоставляет возможность создавать пользовательские функции в sqlite. Это делается с помощью функции PDO::sqliteCreateFunction().

По сути, вы определяете функцию внутри 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
);

Добавьте это где-нибудь в свой набор тестов перед выполнением запроса, и все будет хорошо.

Это правда, но идея автора состоит в том, чтобы быть быстрым, создание собственного метода и использование PHP в середине побеждает цель тестов «производительности» / «быстрых» (БД против PHP), так что это решение, если пользователь не хочет использовать MySQL и по-прежнему хочет продолжить работу с SQLite. В этом случае я думаю, что это сумасшествие, добавляя этот материал, чтобы «тесты выполнялись быстро с SQLite», вы как бы разрабатываете метод для БД, что никому не нужно, но тем не менее, это правильный ответ для любой сумасшедший, который хочет по-прежнему следовать этому пути (не используя настройку производства, а для локальных тестов)

matiaslauriti 13.04.2023 22:39

@matiaslauriti, база данных sqlite в памяти с UDF по-прежнему быстрее, чем база данных mysql, поэтому нет, она не противоречит цели OP - быстрому набору тестов. Кроме того, любое снижение производительности будет происходить только в тех тестах, которые запускают запрос с UDF. Остальная часть набора тестов не пострадает; в отличие от преобразования всего набора тестов для использования более медленной базы данных mysql.

patricus 16.04.2023 16:41
Ответ принят как подходящий

Учитывая возможность использования макроса в 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:

$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();

Преимущество этого метода в том, что он более чистый, и, что наиболее важно, если в проекте необходимо использовать другой тип базы данных, код более удобен в сопровождении.

Вы можете принять свой ответ, чтобы будущие посетители сразу увидели, что проблема решена.

Lajos Arpad 19.04.2023 18:13

Спасибо @LajosArpad за комментарий и голосование, я ждал, когда другие люди проголосуют за мой ответ и подтвердят ответ с большей уверенностью.

ehsan ranjbari 20.04.2023 07:52

Другие вопросы по теме