SQLParserUtilsTest.php 24.2 KB
Newer Older
1 2 3 4 5
<?php

namespace Doctrine\Tests\DBAL;

use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\ParameterType;
7
use Doctrine\DBAL\SQLParserUtils;
Sergei Morozov's avatar
Sergei Morozov committed
8
use Doctrine\DBAL\SQLParserUtilsException;
Sergei Morozov's avatar
Sergei Morozov committed
9
use Doctrine\Tests\DbalTestCase;
10 11 12

/**
 * @group DBAL-78
13
 * @group DDC-1372
14
 */
Sergei Morozov's avatar
Sergei Morozov committed
15
class SQLParserUtilsTest extends DbalTestCase
16
{
17 18 19
    /**
     * @return mixed[][]
     */
20
    public static function dataGetPlaceholderPositions(): iterable
21
    {
Sergei Morozov's avatar
Sergei Morozov committed
22
        return [
23
            // none
Sergei Morozov's avatar
Sergei Morozov committed
24 25
            ['SELECT * FROM Foo', true, []],
            ['SELECT * FROM Foo', false, []],
26

27
            // Positionals
Sergei Morozov's avatar
Sergei Morozov committed
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
            ['SELECT ?', true, [7]],
            ['SELECT * FROM Foo WHERE bar IN (?, ?, ?)', true, [32, 35, 38]],
            ['SELECT ? FROM ?', true, [7, 14]],
            ['SELECT "?" FROM foo', true, []],
            ["SELECT '?' FROM foo", true, []],
            ['SELECT `?` FROM foo', true, []], // Ticket DBAL-552
            ['SELECT [?] FROM foo', true, []],
            ["SELECT 'Doctrine\DBAL?' FROM foo", true, []], // Ticket DBAL-558
            ['SELECT "Doctrine\DBAL?" FROM foo', true, []], // Ticket DBAL-558
            ['SELECT `Doctrine\DBAL?` FROM foo', true, []], // Ticket DBAL-558
            ['SELECT [Doctrine\DBAL?] FROM foo', true, []], // Ticket DBAL-558
            ['SELECT "?" FROM foo WHERE bar = ?', true, [32]],
            ["SELECT '?' FROM foo WHERE bar = ?", true, [32]],
            ['SELECT `?` FROM foo WHERE bar = ?', true, [32]], // Ticket DBAL-552
            ['SELECT [?] FROM foo WHERE bar = ?', true, [32]],
            ['SELECT * FROM foo WHERE jsonb_exists_any(foo.bar, ARRAY[?])', true, [56]], // Ticket GH-2295
            ["SELECT 'Doctrine\DBAL?' FROM foo WHERE bar = ?", true, [45]], // Ticket DBAL-558
            ['SELECT "Doctrine\DBAL?" FROM foo WHERE bar = ?', true, [45]], // Ticket DBAL-558
            ['SELECT `Doctrine\DBAL?` FROM foo WHERE bar = ?', true, [45]], // Ticket DBAL-558
            ['SELECT [Doctrine\DBAL?] FROM foo WHERE bar = ?', true, [45]], // Ticket DBAL-558
            ["SELECT * FROM FOO WHERE bar = 'it\\'s a trap? \\\\' OR bar = ?\nAND baz = \"\\\"quote\\\" me on it? \\\\\" OR baz = ?", true, [58, 104]],
            ['SELECT * FROM foo WHERE foo = ? AND bar = ?', true, [1 => 42, 0 => 30]], // explicit keys
50

51
            // named
Sergei Morozov's avatar
Sergei Morozov committed
52 53 54 55 56 57 58 59 60 61 62
            ['SELECT :foo FROM :bar', false, [7 => 'foo', 17 => 'bar']],
            ['SELECT * FROM Foo WHERE bar IN (:name1, :name2)', false, [32 => 'name1', 40 => 'name2']],
            ['SELECT ":foo" FROM Foo WHERE bar IN (:name1, :name2)', false, [37 => 'name1', 45 => 'name2']],
            ["SELECT ':foo' FROM Foo WHERE bar IN (:name1, :name2)", false, [37 => 'name1', 45 => 'name2']],
            ['SELECT :foo_id', false, [7 => 'foo_id']], // Ticket DBAL-231
            ['SELECT @rank := 1', false, []], // Ticket DBAL-398
            ['SELECT @rank := 1 AS rank, :foo AS foo FROM :bar', false, [27 => 'foo', 44 => 'bar']], // Ticket DBAL-398
            ['SELECT * FROM Foo WHERE bar > :start_date AND baz > :start_date', false, [30 => 'start_date', 52 => 'start_date']], // Ticket GH-113
            ['SELECT foo::date as date FROM Foo WHERE bar > :start_date AND baz > :start_date', false, [46 => 'start_date', 68 => 'start_date']], // Ticket GH-259
            ['SELECT `d.ns:col_name` FROM my_table d WHERE `d.date` >= :param1', false, [57 => 'param1']], // Ticket DBAL-552
            ['SELECT [d.ns:col_name] FROM my_table d WHERE [d.date] >= :param1', false, [57 => 'param1']], // Ticket DBAL-552
63 64
            ['SELECT * FROM foo WHERE jsonb_exists_any(foo.bar, ARRAY[:foo])', false, [56 => 'foo']], // Ticket GH-2295
            ['SELECT * FROM foo WHERE jsonb_exists_any(foo.bar, array[:foo])', false, [56 => 'foo']],
65 66 67 68
            ['SELECT table.field1, ARRAY[\'3\'] FROM schema.table table WHERE table.f1 = :foo AND ARRAY[\'3\']', false, [73 => 'foo']],
            ['SELECT table.field1, ARRAY[\'3\']::integer[] FROM schema.table table WHERE table.f1 = :foo AND ARRAY[\'3\']::integer[]', false, [84 => 'foo']],
            ['SELECT table.field1, ARRAY[:foo] FROM schema.table table WHERE table.f1 = :bar AND ARRAY[\'3\']', false, [27 => 'foo', 74 => 'bar']],
            ['SELECT table.field1, ARRAY[:foo]::integer[] FROM schema.table table WHERE table.f1 = :bar AND ARRAY[\'3\']::integer[]', false, [27 => 'foo', 85 => 'bar']],
Sergei Morozov's avatar
Sergei Morozov committed
69 70
            [
                <<<'SQLDATA'
71 72 73 74 75 76
SELECT * FROM foo WHERE 
bar = ':not_a_param1 ''":not_a_param2"'''
OR bar=:a_param1
OR bar=:a_param2||':not_a_param3'
OR bar=':not_a_param4 '':not_a_param5'' :not_a_param6'
OR bar=''
andreas's avatar
andreas committed
77
OR bar=:a_param3
78
SQLDATA
Grégoire Paris's avatar
Grégoire Paris committed
79
,
Sergei Morozov's avatar
Sergei Morozov committed
80 81 82 83 84 85 86 87 88 89 90 91
                false,
                [
                    74 => 'a_param1',
                    91 => 'a_param2',
                    190 => 'a_param3',
                ],
            ],
            ["SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE '\\\\') AND (data.description LIKE :condition_1 ESCAPE '\\\\') ORDER BY id ASC", false, [121 => 'condition_0', 174 => 'condition_1']],
            ['SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE "\\\\") AND (data.description LIKE :condition_1 ESCAPE "\\\\") ORDER BY id ASC', false, [121 => 'condition_0', 174 => 'condition_1']],
            ['SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE "\\\\") AND (data.description LIKE :condition_1 ESCAPE \'\\\\\') ORDER BY id ASC', false, [121 => 'condition_0', 174 => 'condition_1']],
            ['SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE `\\\\`) AND (data.description LIKE :condition_1 ESCAPE `\\\\`) ORDER BY id ASC', false, [121 => 'condition_0', 174 => 'condition_1']],
            ['SELECT data.age AS age, data.id AS id, data.name AS name, data.id AS id FROM test_data data WHERE (data.description LIKE :condition_0 ESCAPE \'\\\\\') AND (data.description LIKE :condition_1 ESCAPE `\\\\`) ORDER BY id ASC', false, [121 => 'condition_0', 174 => 'condition_1']],
92
            ["SELECT * FROM Foo WHERE (foo.bar LIKE :condition_0 ESCAPE '\') AND (foo.baz = :condition_1) AND (foo.bak LIKE :condition_2 ESCAPE '\')", false, [38 => 'condition_0', 78 => 'condition_1', 110 => 'condition_2']],
Sergei Morozov's avatar
Sergei Morozov committed
93
        ];
94
    }
95

96
    /**
97 98
     * @param int[] $expectedParamPos
     *
99 100
     * @dataProvider dataGetPlaceholderPositions
     */
101
    public function testGetPlaceholderPositions(string $query, bool $isPositional, array $expectedParamPos): void
102 103
    {
        $actualParamPos = SQLParserUtils::getPlaceholderPositions($query, $isPositional);
104
        self::assertEquals($expectedParamPos, $actualParamPos);
105
    }
106

107 108 109
    /**
     * @return mixed[][]
     */
110
    public static function dataExpandListParameters(): iterable
111
    {
Sergei Morozov's avatar
Sergei Morozov committed
112
        return [
113
            'Positional: Very simple with one needle' => [
Sergei Morozov's avatar
Sergei Morozov committed
114 115 116
                'SELECT * FROM Foo WHERE foo IN (?)',
                [[1, 2, 3]],
                [Connection::PARAM_INT_ARRAY],
117
                'SELECT * FROM Foo WHERE foo IN (?, ?, ?)',
Sergei Morozov's avatar
Sergei Morozov committed
118 119 120
                [1, 2, 3],
                [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER],
            ],
121
            'Positional: One non-list before d one after list-needle' => [
Sergei Morozov's avatar
Sergei Morozov committed
122 123 124
                'SELECT * FROM Foo WHERE foo = ? AND bar IN (?)',
                ['string', [1, 2, 3]],
                [ParameterType::STRING, Connection::PARAM_INT_ARRAY],
125
                'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?)',
Sergei Morozov's avatar
Sergei Morozov committed
126 127 128
                ['string', 1, 2, 3],
                [ParameterType::STRING, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER],
            ],
129
            'Positional: One non-list after list-needle' => [
Sergei Morozov's avatar
Sergei Morozov committed
130 131 132
                'SELECT * FROM Foo WHERE bar IN (?) AND baz = ?',
                [[1, 2, 3], 'foo'],
                [Connection::PARAM_INT_ARRAY, ParameterType::STRING],
133
                'SELECT * FROM Foo WHERE bar IN (?, ?, ?) AND baz = ?',
Sergei Morozov's avatar
Sergei Morozov committed
134 135 136
                [1, 2, 3, 'foo'],
                [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING],
            ],
137
            'Positional: One non-list before and one after list-needle' => [
Sergei Morozov's avatar
Sergei Morozov committed
138 139 140
                'SELECT * FROM Foo WHERE foo = ? AND bar IN (?) AND baz = ?',
                [1, [1, 2, 3], 4],
                [ParameterType::INTEGER, Connection::PARAM_INT_ARRAY, ParameterType::INTEGER],
141
                'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?) AND baz = ?',
Sergei Morozov's avatar
Sergei Morozov committed
142 143
                [1, 1, 2, 3, 4],
                [
144 145 146 147 148
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
Sergei Morozov's avatar
Sergei Morozov committed
149 150
                ],
            ],
151
            'Positional: Two lists' => [
Sergei Morozov's avatar
Sergei Morozov committed
152 153 154
                'SELECT * FROM Foo WHERE foo IN (?, ?)',
                [[1, 2, 3], [4, 5]],
                [Connection::PARAM_INT_ARRAY, Connection::PARAM_INT_ARRAY],
155
                'SELECT * FROM Foo WHERE foo IN (?, ?, ?, ?, ?)',
Sergei Morozov's avatar
Sergei Morozov committed
156 157
                [1, 2, 3, 4, 5],
                [
158 159 160 161 162
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
Sergei Morozov's avatar
Sergei Morozov committed
163 164
                ],
            ],
165
            'Positional: Empty "integer" array (DDC-1978)' => [
Sergei Morozov's avatar
Sergei Morozov committed
166 167 168
                'SELECT * FROM Foo WHERE foo IN (?)',
                [[]],
                [Connection::PARAM_INT_ARRAY],
169
                'SELECT * FROM Foo WHERE foo IN (NULL)',
Sergei Morozov's avatar
Sergei Morozov committed
170 171 172
                [],
                [],
            ],
173
            'Positional: Empty "str" array (DDC-1978)' => [
Sergei Morozov's avatar
Sergei Morozov committed
174 175 176
                'SELECT * FROM Foo WHERE foo IN (?)',
                [[]],
                [Connection::PARAM_STR_ARRAY],
177
                'SELECT * FROM Foo WHERE foo IN (NULL)',
Sergei Morozov's avatar
Sergei Morozov committed
178 179 180
                [],
                [],
            ],
181
            'Positional: explicit keys for params and types' => [
182
                'SELECT * FROM Foo WHERE foo = ? AND bar = ? AND baz = ?',
Sergei Morozov's avatar
Sergei Morozov committed
183 184 185 186
                [1 => 'bar', 2 => 'baz', 0 => 1],
                [2 => ParameterType::STRING, 1 => ParameterType::STRING],
                'SELECT * FROM Foo WHERE foo = ? AND bar = ? AND baz = ?',
                [1 => 'bar', 0 => 1, 2 => 'baz'],
187
                [1 => ParameterType::STRING, 2 => ParameterType::STRING, 0 => null],
Sergei Morozov's avatar
Sergei Morozov committed
188
            ],
189
            'Positional: explicit keys for array params and array types' => [
Sergei Morozov's avatar
Sergei Morozov committed
190 191 192
                'SELECT * FROM Foo WHERE foo IN (?) AND bar IN (?) AND baz = ?',
                [1 => ['bar1', 'bar2'], 2 => true, 0 => [1, 2, 3]],
                [2 => ParameterType::BOOLEAN, 1 => Connection::PARAM_STR_ARRAY, 0 => Connection::PARAM_INT_ARRAY],
193
                'SELECT * FROM Foo WHERE foo IN (?, ?, ?) AND bar IN (?, ?) AND baz = ?',
Sergei Morozov's avatar
Sergei Morozov committed
194 195
                [1, 2, 3, 'bar1', 'bar2', true],
                [
196 197 198 199 200 201
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::STRING,
                    ParameterType::STRING,
                    ParameterType::BOOLEAN,
Sergei Morozov's avatar
Sergei Morozov committed
202 203
                ],
            ],
204
            'Positional starts from 1: One non-list before and one after list-needle' => [
Sergei Morozov's avatar
Sergei Morozov committed
205 206 207
                'SELECT * FROM Foo WHERE foo = ? AND bar IN (?) AND baz = ? AND foo IN (?)',
                [1 => 1, 2 => [1, 2, 3], 3 => 4, 4 => [5, 6]],
                [
208 209 210 211
                    1 => ParameterType::INTEGER,
                    2 => Connection::PARAM_INT_ARRAY,
                    3 => ParameterType::INTEGER,
                    4 => Connection::PARAM_INT_ARRAY,
Sergei Morozov's avatar
Sergei Morozov committed
212
                ],
213
                'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?) AND baz = ? AND foo IN (?, ?)',
Sergei Morozov's avatar
Sergei Morozov committed
214 215
                [1, 1, 2, 3, 4, 5, 6],
                [
216 217 218 219 220 221 222
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
Sergei Morozov's avatar
Sergei Morozov committed
223 224
                ],
            ],
225
            'Named: Very simple with param int' => [
Sergei Morozov's avatar
Sergei Morozov committed
226 227 228
                'SELECT * FROM Foo WHERE foo = :foo',
                ['foo' => 1],
                ['foo' => ParameterType::INTEGER],
229
                'SELECT * FROM Foo WHERE foo = ?',
Sergei Morozov's avatar
Sergei Morozov committed
230 231 232
                [1],
                [ParameterType::INTEGER],
            ],
233
            'Named: Very simple with param int and string' => [
Sergei Morozov's avatar
Sergei Morozov committed
234 235 236
                'SELECT * FROM Foo WHERE foo = :foo AND bar = :bar',
                ['bar' => 'Some String','foo' => 1],
                ['foo' => ParameterType::INTEGER, 'bar' => ParameterType::STRING],
237
                'SELECT * FROM Foo WHERE foo = ? AND bar = ?',
Sergei Morozov's avatar
Sergei Morozov committed
238 239 240
                [1,'Some String'],
                [ParameterType::INTEGER, ParameterType::STRING],
            ],
241
            'Named: Very simple with one needle' => [
Sergei Morozov's avatar
Sergei Morozov committed
242 243 244
                'SELECT * FROM Foo WHERE foo IN (:foo)',
                ['foo' => [1, 2, 3]],
                ['foo' => Connection::PARAM_INT_ARRAY],
245
                'SELECT * FROM Foo WHERE foo IN (?, ?, ?)',
Sergei Morozov's avatar
Sergei Morozov committed
246 247 248
                [1, 2, 3],
                [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER],
            ],
249
            'Named: One non-list before d one after list-needle' => [
Sergei Morozov's avatar
Sergei Morozov committed
250 251 252
                'SELECT * FROM Foo WHERE foo = :foo AND bar IN (:bar)',
                ['foo' => 'string', 'bar' => [1, 2, 3]],
                ['foo' => ParameterType::STRING, 'bar' => Connection::PARAM_INT_ARRAY],
253
                'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?)',
Sergei Morozov's avatar
Sergei Morozov committed
254 255 256
                ['string', 1, 2, 3],
                [ParameterType::STRING, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER],
            ],
257
            'Named: One non-list after list-needle' => [
Sergei Morozov's avatar
Sergei Morozov committed
258 259 260
                'SELECT * FROM Foo WHERE bar IN (:bar) AND baz = :baz',
                ['bar' => [1, 2, 3], 'baz' => 'foo'],
                ['bar' => Connection::PARAM_INT_ARRAY, 'baz' => ParameterType::STRING],
261
                'SELECT * FROM Foo WHERE bar IN (?, ?, ?) AND baz = ?',
Sergei Morozov's avatar
Sergei Morozov committed
262 263 264
                [1, 2, 3, 'foo'],
                [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING],
            ],
265
            'Named: One non-list before and one after list-needle' => [
Sergei Morozov's avatar
Sergei Morozov committed
266 267
                'SELECT * FROM Foo WHERE foo = :foo AND bar IN (:bar) AND baz = :baz',
                ['bar' => [1, 2, 3],'foo' => 1, 'baz' => 4],
268 269 270 271 272
                [
                    'bar' => Connection::PARAM_INT_ARRAY,
                    'foo' => ParameterType::INTEGER,
                    'baz' => ParameterType::INTEGER,
                ],
273
                'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?) AND baz = ?',
Sergei Morozov's avatar
Sergei Morozov committed
274
                [1, 1, 2, 3, 4],
275 276 277 278 279 280 281
                [
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                ],
Sergei Morozov's avatar
Sergei Morozov committed
282
            ],
283
            'Named: Two lists' => [
Sergei Morozov's avatar
Sergei Morozov committed
284 285 286
                'SELECT * FROM Foo WHERE foo IN (:a, :b)',
                ['b' => [4, 5],'a' => [1, 2, 3]],
                ['a' => Connection::PARAM_INT_ARRAY, 'b' => Connection::PARAM_INT_ARRAY],
287
                'SELECT * FROM Foo WHERE foo IN (?, ?, ?, ?, ?)',
Sergei Morozov's avatar
Sergei Morozov committed
288
                [1, 2, 3, 4, 5],
289 290 291 292 293 294 295
                [
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                ],
Sergei Morozov's avatar
Sergei Morozov committed
296
            ],
297
            'Named: With the same name arg type string' => [
Sergei Morozov's avatar
Sergei Morozov committed
298 299 300
                'SELECT * FROM Foo WHERE foo <> :arg AND bar = :arg',
                ['arg' => 'Some String'],
                ['arg' => ParameterType::STRING],
301
                'SELECT * FROM Foo WHERE foo <> ? AND bar = ?',
Sergei Morozov's avatar
Sergei Morozov committed
302 303 304
                ['Some String','Some String'],
                [ParameterType::STRING,ParameterType::STRING],
            ],
305
            'Named: With the same name arg' => [
Sergei Morozov's avatar
Sergei Morozov committed
306 307 308
                'SELECT * FROM Foo WHERE foo IN (:arg) AND NOT bar IN (:arg)',
                ['arg' => [1, 2, 3]],
                ['arg' => Connection::PARAM_INT_ARRAY],
309
                'SELECT * FROM Foo WHERE foo IN (?, ?, ?) AND NOT bar IN (?, ?, ?)',
Sergei Morozov's avatar
Sergei Morozov committed
310
                [1, 2, 3, 1, 2, 3],
311 312 313 314 315 316 317 318
                [
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                    ParameterType::INTEGER,
                ],
Sergei Morozov's avatar
Sergei Morozov committed
319
            ],
320
            'Named: Same name, other name in between (DBAL-299)' => [
Sergei Morozov's avatar
Sergei Morozov committed
321 322 323
                'SELECT * FROM Foo WHERE (:foo = 2) AND (:bar = 3) AND (:foo = 2)',
                ['foo' => 2,'bar' => 3],
                ['foo' => ParameterType::INTEGER,'bar' => ParameterType::INTEGER],
324
                'SELECT * FROM Foo WHERE (? = 2) AND (? = 3) AND (? = 2)',
Sergei Morozov's avatar
Sergei Morozov committed
325 326 327
                [2, 3, 2],
                [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER],
            ],
328
            'Named: Empty "integer" array (DDC-1978)' => [
Sergei Morozov's avatar
Sergei Morozov committed
329 330 331
                'SELECT * FROM Foo WHERE foo IN (:foo)',
                ['foo' => []],
                ['foo' => Connection::PARAM_INT_ARRAY],
332
                'SELECT * FROM Foo WHERE foo IN (NULL)',
Sergei Morozov's avatar
Sergei Morozov committed
333 334 335
                [],
                [],
            ],
336
            'Named: Two empty "str" array (DDC-1978)' => [
Sergei Morozov's avatar
Sergei Morozov committed
337 338 339
                'SELECT * FROM Foo WHERE foo IN (:foo) OR bar IN (:bar)',
                ['foo' => [], 'bar' => []],
                ['foo' => Connection::PARAM_STR_ARRAY, 'bar' => Connection::PARAM_STR_ARRAY],
340
                'SELECT * FROM Foo WHERE foo IN (NULL) OR bar IN (NULL)',
Sergei Morozov's avatar
Sergei Morozov committed
341 342 343 344 345 346 347
                [],
                [],
            ],
            [
                'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar OR baz = :baz',
                ['foo' => [1, 2], 'bar' => 'bar', 'baz' => 'baz'],
                ['foo' => Connection::PARAM_INT_ARRAY, 'baz' => 'string'],
348
                'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ? OR baz = ?',
Sergei Morozov's avatar
Sergei Morozov committed
349 350 351 352 353 354 355
                [1, 2, 'bar', 'baz'],
                [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING, 'string'],
            ],
            [
                'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar',
                ['foo' => [1, 2], 'bar' => 'bar'],
                ['foo' => Connection::PARAM_INT_ARRAY],
356
                'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?',
Sergei Morozov's avatar
Sergei Morozov committed
357 358 359
                [1, 2, 'bar'],
                [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING],
            ],
360
            'Params/types with colons' => [
Sergei Morozov's avatar
Sergei Morozov committed
361 362 363
                'SELECT * FROM Foo WHERE foo = :foo OR bar = :bar',
                [':foo' => 'foo', ':bar' => 'bar'],
                [':foo' => ParameterType::INTEGER],
364
                'SELECT * FROM Foo WHERE foo = ? OR bar = ?',
Sergei Morozov's avatar
Sergei Morozov committed
365 366 367 368 369 370 371
                ['foo', 'bar'],
                [ParameterType::INTEGER, ParameterType::STRING],
            ],
            [
                'SELECT * FROM Foo WHERE foo = :foo OR bar = :bar',
                [':foo' => 'foo', ':bar' => 'bar'],
                [':foo' => ParameterType::INTEGER, 'bar' => ParameterType::INTEGER],
372
                'SELECT * FROM Foo WHERE foo = ? OR bar = ?',
Sergei Morozov's avatar
Sergei Morozov committed
373 374 375 376 377 378 379
                ['foo', 'bar'],
                [ParameterType::INTEGER, ParameterType::INTEGER],
            ],
            [
                'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar',
                [':foo' => [1, 2], ':bar' => 'bar'],
                ['foo' => Connection::PARAM_INT_ARRAY],
380
                'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?',
Sergei Morozov's avatar
Sergei Morozov committed
381 382 383 384 385 386 387
                [1, 2, 'bar'],
                [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING],
            ],
            [
                'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar',
                ['foo' => [1, 2], 'bar' => 'bar'],
                [':foo' => Connection::PARAM_INT_ARRAY],
388
                'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?',
Sergei Morozov's avatar
Sergei Morozov committed
389 390 391
                [1, 2, 'bar'],
                [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING],
            ],
392
            'Null valued parameters (DBAL-522)' => [
393
                'INSERT INTO Foo (foo, bar) values (:foo, :bar)',
Sergei Morozov's avatar
Sergei Morozov committed
394 395
                ['foo' => 1, 'bar' => null],
                [':foo' => ParameterType::INTEGER, ':bar' => ParameterType::NULL],
396
                'INSERT INTO Foo (foo, bar) values (?, ?)',
Sergei Morozov's avatar
Sergei Morozov committed
397 398 399 400
                [1, null],
                [ParameterType::INTEGER, ParameterType::NULL],
            ],
            [
401
                'INSERT INTO Foo (foo, bar) values (?, ?)',
Sergei Morozov's avatar
Sergei Morozov committed
402 403
                [1, null],
                [ParameterType::INTEGER, ParameterType::NULL],
404
                'INSERT INTO Foo (foo, bar) values (?, ?)',
Sergei Morozov's avatar
Sergei Morozov committed
405 406 407
                [1, null],
                [ParameterType::INTEGER, ParameterType::NULL],
            ],
408
            'Escaped single quotes SQL- and C-Style (DBAL-1205)' => [
409
                "SELECT * FROM Foo WHERE foo = :foo||''':not_a_param''\\'' OR bar = ''':not_a_param''\\'':bar",
Sergei Morozov's avatar
Sergei Morozov committed
410 411
                [':foo' => 1, ':bar' => 2],
                [':foo' => ParameterType::INTEGER, 'bar' => ParameterType::INTEGER],
412
                'SELECT * FROM Foo WHERE foo = ?||\'\'\':not_a_param\'\'\\\'\' OR bar = \'\'\':not_a_param\'\'\\\'\'?',
Sergei Morozov's avatar
Sergei Morozov committed
413 414 415
                [1, 2],
                [ParameterType::INTEGER, ParameterType::INTEGER],
            ],
416 417 418 419 420 421 422 423
            [
                'SELECT NULL FROM dummy WHERE ? IN (?)',
                ['foo', ['bar', 'baz']],
                [1 => Connection::PARAM_STR_ARRAY],
                'SELECT NULL FROM dummy WHERE ? IN (?, ?)',
                ['foo', 'bar', 'baz'],
                [null, ParameterType::STRING, ParameterType::STRING],
            ],
Sergei Morozov's avatar
Sergei Morozov committed
424
        ];
425
    }
426

427
    /**
428 429 430 431 432
     * @param mixed[] $params
     * @param mixed[] $types
     * @param mixed[] $expectedParams
     * @param mixed[] $expectedTypes
     *
433 434
     * @dataProvider dataExpandListParameters
     */
435 436 437 438 439 440 441
    public function testExpandListParameters(
        string $query,
        array $params,
        array $types,
        string $expectedQuery,
        array $expectedParams,
        array $expectedTypes
442
    ): void {
443
        [$query, $params, $types] = SQLParserUtils::expandListParameters($query, $params, $types);
444

Sergei Morozov's avatar
Sergei Morozov committed
445 446 447
        self::assertEquals($expectedQuery, $query, 'Query was not rewritten correctly.');
        self::assertEquals($expectedParams, $params, 'Params dont match');
        self::assertEquals($expectedTypes, $types, 'Types dont match');
448
    }
449

450 451 452
    /**
     * @return mixed[][]
     */
453
    public static function dataQueryWithMissingParameters(): iterable
454
    {
Sergei Morozov's avatar
Sergei Morozov committed
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
        return [
            [
                'SELECT * FROM foo WHERE bar = :param',
                ['other' => 'val'],
                [],
            ],
            [
                'SELECT * FROM foo WHERE bar = :param',
                [],
                [],
            ],
            [
                'SELECT * FROM foo WHERE bar = :param',
                [],
                ['param' => Connection::PARAM_INT_ARRAY],
            ],
            [
                'SELECT * FROM foo WHERE bar = :param',
                [],
                [':param' => Connection::PARAM_INT_ARRAY],
            ],
            [
                'SELECT * FROM foo WHERE bar = :param',
                [],
                ['bar' => Connection::PARAM_INT_ARRAY],
            ],
            [
                'SELECT * FROM foo WHERE bar = :param',
                ['bar' => 'value'],
                ['bar' => Connection::PARAM_INT_ARRAY],
            ],
        ];
487 488 489
    }

    /**
490 491 492
     * @param mixed[] $params
     * @param mixed[] $types
     *
493 494
     * @dataProvider dataQueryWithMissingParameters
     */
495
    public function testExceptionIsThrownForMissingParam(string $query, array $params, array $types = []): void
496
    {
Sergei Morozov's avatar
Sergei Morozov committed
497
        $this->expectException(SQLParserUtilsException::class);
498
        $this->expectExceptionMessage('Value for :param not found in params array. Params array key should be "param"');
499 500 501

        SQLParserUtils::expandListParameters($query, $params, $types);
    }
502
}