Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve SQLite LIKE performance #1209

Merged
merged 5 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions src/Persistence/Sql/Sqlite/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ function ($sqlLeft, $sqlRight) use ($operator, $allowCastRight) {
private function _renderConditionIsCaseInsensitive(string $sql, bool $negate): string
{
return '(select __atk4_case_v__ ' . ($negate ? '!' : '') . '= ' . $this->escapeStringLiteral('a')
. ' from (select ' . $sql . ' __atk4_case_v__ where 1 = 0 union all select '
. ' from (select ' . $sql . ' __atk4_case_v__ where 0 union all select '
. $this->escapeStringLiteral('A') . ') __atk4_case_tmp__)';
}

Expand All @@ -109,11 +109,14 @@ function ($sqlLeft, $sqlRight) {
return 'regexp_replace(' . $sql . ', ' . $this->escapeStringLiteral($search) . ', ' . $this->escapeStringLiteral($replacement) . ')';
};

return '(('
. $this->_renderConditionIsCaseInsensitive($sqlLeft, true) // workaround "_" matching more than one byte - https://dbfiddle.uk/Dnq8BXGy
. ' or '
. parent::_renderConditionLikeOperator(false, $sqlLeft, $sqlRight)
. ') and ' . $this->_renderConditionRegexpOperator(
return 'case when '
// workaround "_" matching more than one byte in BLOB - https://dbfiddle.uk/Dnq8BXGy
. 'case when instr(' . $sqlRight . ', ' . $this->escapeStringLiteral('_') . ') != 0 then 1 else '
. parent::_renderConditionLikeOperator(
false,
$sqlLeft,
$sqlRight
) . ' end then ' . $this->_renderConditionRegexpOperator(
false,
$sqlLeft,
'concat(' . $this->escapeStringLiteral('^') . ',' . $regexReplaceSqlFx(
Expand All @@ -129,7 +132,7 @@ function ($sqlLeft, $sqlRight) {
'(?<!\\\)(\\\\\\\)*\K\\\(?=[_%])',
''
) . ', ' . $this->escapeStringLiteral('$') . ')'
) . ')';
) . ' when ' . $sqlLeft . ' is not null then 0 end';
}
);
}
Expand Down
12 changes: 6 additions & 6 deletions tests/Persistence/Sql/QueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -864,17 +864,17 @@ public function testWhereSpecialValues(): void
);
self::assertSame(
<<<'EOF'
where (((select __atk4_case_v__ != 'a' from (select `name` __atk4_case_v__ where 1 = 0 union all select 'A') __atk4_case_tmp__) or `name` like regexp_replace(:a, '(\\[\\_%])|(\\)', '\1\2\2') escape '\') and regexp_like(`name`, concat(case when (select __atk4_case_v__ = 'a' from (select `name` __atk4_case_v__ where 1 = 0 union all select 'A') __atk4_case_tmp__) then '' else '(?-iu)' end, concat('^',regexp_replace(regexp_replace(regexp_replace(regexp_replace(:a, '\\(?:(?=[_%])|\K\\)|(?=[.\\+*?[^\]$(){}|])', '\'), '(?<!\\)(\\\\)*\K_', '.'), '(?<!\\)(\\\\)*\K%', '.*'), '(?<!\\)(\\\\)*\K\\(?=[_%])', ''), '$')), 'is'))
where case when case when instr(:a, '_') != 0 then 1 else `name` like regexp_replace(:a, '(\\[\\_%])|(\\)', '\1\2\2') escape '\' end then regexp_like(`name`, concat(case when (select __atk4_case_v__ = 'a' from (select `name` __atk4_case_v__ where 0 union all select 'A') __atk4_case_tmp__) then '' else '(?-iu)' end, concat('^',regexp_replace(regexp_replace(regexp_replace(regexp_replace(:a, '\\(?:(?=[_%])|\K\\)|(?=[.\\+*?[^\]$(){}|])', '\'), '(?<!\\)(\\\\)*\K_', '.'), '(?<!\\)(\\\\)*\K%', '.*'), '(?<!\\)(\\\\)*\K\\(?=[_%])', ''), '$')), 'is') when `name` is not null then 0 end
EOF,
(new SqliteQuery('[where]'))->where('name', 'like', 'foo')->render()[0]
);
self::assertSame(
version_compare(SqliteConnection::getDriverVersion(), '3.45') < 0
? <<<'EOF'
where (((select __atk4_case_v__ != 'a' from (select sum("a") __atk4_case_v__ where 1 = 0 union all select 'A') __atk4_case_tmp__) or sum("a") like regexp_replace(sum("b"), '(\\[\\_%])|(\\)', '\1\2\2') escape '\') and regexp_like(sum("a"), concat(case when (select __atk4_case_v__ = 'a' from (select sum("a") __atk4_case_v__ where 1 = 0 union all select 'A') __atk4_case_tmp__) then '' else '(?-iu)' end, concat('^',regexp_replace(regexp_replace(regexp_replace(regexp_replace(sum("b"), '\\(?:(?=[_%])|\K\\)|(?=[.\\+*?[^\]$(){}|])', '\'), '(?<!\\)(\\\\)*\K_', '.'), '(?<!\\)(\\\\)*\K%', '.*'), '(?<!\\)(\\\\)*\K\\(?=[_%])', ''), '$')), 'is'))
where case when case when instr(sum("b"), '_') != 0 then 1 else sum("a") like regexp_replace(sum("b"), '(\\[\\_%])|(\\)', '\1\2\2') escape '\' end then regexp_like(sum("a"), concat(case when (select __atk4_case_v__ = 'a' from (select sum("a") __atk4_case_v__ where 0 union all select 'A') __atk4_case_tmp__) then '' else '(?-iu)' end, concat('^',regexp_replace(regexp_replace(regexp_replace(regexp_replace(sum("b"), '\\(?:(?=[_%])|\K\\)|(?=[.\\+*?[^\]$(){}|])', '\'), '(?<!\\)(\\\\)*\K_', '.'), '(?<!\\)(\\\\)*\K%', '.*'), '(?<!\\)(\\\\)*\K\\(?=[_%])', ''), '$')), 'is') when sum("a") is not null then 0 end
EOF
: <<<'EOF'
where (select (((select __atk4_case_v__ != 'a' from (select `__atk4_reuse_left__` __atk4_case_v__ where 1 = 0 union all select 'A') __atk4_case_tmp__) or `__atk4_reuse_left__` like regexp_replace(`__atk4_reuse_right__`, '(\\[\\_%])|(\\)', '\1\2\2') escape '\') and regexp_like(`__atk4_reuse_left__`, concat(case when (select __atk4_case_v__ = 'a' from (select `__atk4_reuse_left__` __atk4_case_v__ where 1 = 0 union all select 'A') __atk4_case_tmp__) then '' else '(?-iu)' end, concat('^',regexp_replace(regexp_replace(regexp_replace(regexp_replace(`__atk4_reuse_right__`, '\\(?:(?=[_%])|\K\\)|(?=[.\\+*?[^\]$(){}|])', '\'), '(?<!\\)(\\\\)*\K_', '.'), '(?<!\\)(\\\\)*\K%', '.*'), '(?<!\\)(\\\\)*\K\\(?=[_%])', ''), '$')), 'is')) from (select sum("a") `__atk4_reuse_left__`, sum("b") `__atk4_reuse_right__`) `__atk4_reuse_tmp__`)
where (select case when case when instr(`__atk4_reuse_right__`, '_') != 0 then 1 else `__atk4_reuse_left__` like regexp_replace(`__atk4_reuse_right__`, '(\\[\\_%])|(\\)', '\1\2\2') escape '\' end then regexp_like(`__atk4_reuse_left__`, concat(case when (select __atk4_case_v__ = 'a' from (select `__atk4_reuse_left__` __atk4_case_v__ where 0 union all select 'A') __atk4_case_tmp__) then '' else '(?-iu)' end, concat('^',regexp_replace(regexp_replace(regexp_replace(regexp_replace(`__atk4_reuse_right__`, '\\(?:(?=[_%])|\K\\)|(?=[.\\+*?[^\]$(){}|])', '\'), '(?<!\\)(\\\\)*\K_', '.'), '(?<!\\)(\\\\)*\K%', '.*'), '(?<!\\)(\\\\)*\K\\(?=[_%])', ''), '$')), 'is') when `__atk4_reuse_left__` is not null then 0 end from (select sum("a") `__atk4_reuse_left__`, sum("b") `__atk4_reuse_right__`) `__atk4_reuse_tmp__`)
EOF,
(new SqliteQuery('[where]'))->where($this->e('sum({})', ['a']), 'like', $this->e('sum({})', ['b']))->render()[0]
);
Expand Down Expand Up @@ -928,17 +928,17 @@ public function testWhereSpecialValues(): void
);
self::assertSame(
<<<'EOF'
where regexp_like(`name`, concat(case when (select __atk4_case_v__ = 'a' from (select `name` __atk4_case_v__ where 1 = 0 union all select 'A') __atk4_case_tmp__) then '' else '(?-iu)' end, :a), 'is')
where regexp_like(`name`, concat(case when (select __atk4_case_v__ = 'a' from (select `name` __atk4_case_v__ where 0 union all select 'A') __atk4_case_tmp__) then '' else '(?-iu)' end, :a), 'is')
EOF,
(new SqliteQuery('[where]'))->where('name', 'regexp', 'foo')->render()[0]
);
self::assertSame(
version_compare(SqliteConnection::getDriverVersion(), '3.45') < 0
? <<<'EOF'
where regexp_like(sum("a"), concat(case when (select __atk4_case_v__ = 'a' from (select sum("a") __atk4_case_v__ where 1 = 0 union all select 'A') __atk4_case_tmp__) then '' else '(?-iu)' end, sum("b")), 'is')
where regexp_like(sum("a"), concat(case when (select __atk4_case_v__ = 'a' from (select sum("a") __atk4_case_v__ where 0 union all select 'A') __atk4_case_tmp__) then '' else '(?-iu)' end, sum("b")), 'is')
EOF
: <<<'EOF'
where (select regexp_like(`__atk4_reuse_left__`, concat(case when (select __atk4_case_v__ = 'a' from (select `__atk4_reuse_left__` __atk4_case_v__ where 1 = 0 union all select 'A') __atk4_case_tmp__) then '' else '(?-iu)' end, sum("b")), 'is') from (select sum("a") `__atk4_reuse_left__`) `__atk4_reuse_tmp__`)
where (select regexp_like(`__atk4_reuse_left__`, concat(case when (select __atk4_case_v__ = 'a' from (select `__atk4_reuse_left__` __atk4_case_v__ where 0 union all select 'A') __atk4_case_tmp__) then '' else '(?-iu)' end, sum("b")), 'is') from (select sum("a") `__atk4_reuse_left__`) `__atk4_reuse_tmp__`)
EOF,
(new SqliteQuery('[where]'))->where($this->e('sum({})', ['a']), 'regexp', $this->e('sum({})', ['b']))->render()[0]
);
Expand Down
Loading