1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Data\Persistence\Sql\Sqlite;
6:
7: use Atk4\Data\Persistence\Sql\Query as BaseQuery;
8:
9: class Query extends BaseQuery
10: {
11: use ExpressionTrait;
12:
13: protected string $identifierEscapeChar = '`';
14: protected string $expressionClass = Expression::class;
15:
16: protected string $templateTruncate = 'delete [from] [tableNoalias]';
17:
18: private function _renderConditionBinaryCheckNumericSql(string $sql): string
19: {
20: return 'typeof(' . $sql . ') in (\'integer\', \'real\')';
21: }
22:
23: /**
24: * https://dba.stackexchange.com/questions/332585/sqlite-comparison-of-the-same-operand-types-behaves-differently
25: * https://sqlite.org/forum/forumpost/5f1135146fbc37ab .
26: */
27: #[\Override]
28: protected function _renderConditionBinary(string $operator, string $sqlLeft, string $sqlRight): string
29: {
30: // TODO deduplicate the duplicated SQL using https://sqlite.org/forum/info/c9970a37edf11cd1
31: // https://github.com/sqlite/sqlite/commit/5e4233a9e48b124d4d342b757b34e4ae849f5cf8
32: // expected to be supported since SQLite v3.45.0
33:
34: /** @var bool */
35: $allowCastLeft = true;
36: $allowCastRight = !in_array($operator, ['in', 'not in'], true);
37:
38: $res = '';
39: if ($allowCastLeft) {
40: $res .= 'case when ' . $this->_renderConditionBinaryCheckNumericSql($sqlLeft)
41: . ' then ' . parent::_renderConditionBinary($operator, 'cast(' . $sqlLeft . ' as numeric)', $sqlRight)
42: . ' else ';
43: }
44: if ($allowCastRight) {
45: $res .= 'case when ' . $this->_renderConditionBinaryCheckNumericSql($sqlRight)
46: . ' then ' . parent::_renderConditionBinary($operator, $sqlLeft, 'cast(' . $sqlRight . ' as numeric)')
47: . ' else ';
48: }
49: $res .= parent::_renderConditionBinary($operator, $sqlLeft, $sqlRight);
50: if ($allowCastRight) {
51: $res .= ' end';
52: }
53: if ($allowCastLeft) {
54: $res .= ' end';
55: }
56:
57: return $res;
58: }
59:
60: #[\Override]
61: protected function _renderConditionInOperator(bool $negated, string $sqlLeft, array $sqlValues): string
62: {
63: $res = '(' . implode(' or ', array_map(fn ($v) => $this->_renderConditionBinary('=', $sqlLeft, $v), $sqlValues)) . ')';
64: if ($negated) {
65: $res = 'not' . $res;
66: }
67:
68: return $res;
69: }
70:
71: #[\Override]
72: public function groupConcat($field, string $separator = ',')
73: {
74: return $this->expr('group_concat({}, [])', [$field, $separator]);
75: }
76: }
77: