1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Created by Gorlum 07.08.2016 2:36 |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
namespace DBAL; |
7
|
|
|
|
8
|
|
|
use \HelperArray; |
9
|
|
|
use \db_mysql; |
10
|
|
|
use \classSupernova; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Class DbQuery |
14
|
|
|
* |
15
|
|
|
* New replacement for DbQueryConstructor |
16
|
|
|
* Simplified version |
17
|
|
|
* Chained calls - "Fluid interface" |
18
|
|
|
* |
19
|
|
|
* @package DBAL |
20
|
|
|
*/ |
21
|
|
|
class DbQuery { |
22
|
|
|
|
23
|
|
|
const SELECT = 'SELECT'; |
24
|
|
|
const REPLACE = 'REPLACE'; |
25
|
|
|
const INSERT = 'INSERT'; |
26
|
|
|
const INSERT_IGNORE = 'INSERT IGNORE'; |
27
|
|
|
const UPDATE = 'UPDATE'; |
28
|
|
|
const DELETE = 'DELETE'; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var db_mysql |
32
|
|
|
*/ |
33
|
|
|
protected $db; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Which command would be performed |
37
|
|
|
* |
38
|
|
|
* @var string $command |
39
|
|
|
*/ |
40
|
|
|
protected $command; |
41
|
|
|
|
42
|
|
|
protected $table = ''; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Contains field names integer keyed |
46
|
|
|
* |
47
|
|
|
* For SELECT {fields} FROM |
48
|
|
|
* For INSERT/REPLACE {fields} UPDATE ... |
49
|
|
|
* |
50
|
|
|
* @var array $fields |
51
|
|
|
*/ |
52
|
|
|
protected $fields = array(); |
53
|
|
|
protected $where = array(); |
54
|
|
|
protected $whereDanger = array(); |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Contain array of values - fielded or not |
58
|
|
|
* |
59
|
|
|
* For INSERT/REPLACE ... SET, UPDATE ... SET contains fieldName => value |
60
|
|
|
* For INSERT/REPLACE ... VALUES contains values[][] |
61
|
|
|
* |
62
|
|
|
* @var array |
63
|
|
|
*/ |
64
|
|
|
protected $values = array(); |
65
|
|
|
/** |
66
|
|
|
* Contain array of DANGER values for batch INSERT/REPLACE |
67
|
|
|
* |
68
|
|
|
* @var string[] |
69
|
|
|
*/ |
70
|
|
|
protected $valuesDanger = array(); |
71
|
|
|
protected $adjust = array(); |
72
|
|
|
protected $adjustDanger = array(); |
73
|
|
|
|
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Variable for incremental query build |
77
|
|
|
* |
78
|
|
|
* @var string[] $build |
79
|
|
|
*/ |
80
|
|
|
protected $build = array(); |
81
|
|
|
|
82
|
|
|
protected $isOneRow = false; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* DbQuery constructor. |
86
|
|
|
* |
87
|
|
|
* @param null|\db_mysql $db |
88
|
|
|
*/ |
89
|
|
|
// TODO - $db should be supplied externally |
90
|
1 |
|
public function __construct($db = null) { |
91
|
1 |
|
$this->db = empty($db) ? classSupernova::$gc->db : $db; |
92
|
1 |
|
} |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* @param null|db_mysql $db |
96
|
|
|
* |
97
|
|
|
* @return static |
98
|
|
|
*/ |
99
|
1 |
|
public static function build($db = null) { |
100
|
1 |
|
return new static($db); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
|
104
|
1 |
|
public function select() { |
105
|
1 |
|
$this->build = array(); |
106
|
|
|
|
107
|
1 |
|
$this->buildCommand(static::SELECT); |
108
|
1 |
|
$this->build[] = ' *'; |
109
|
1 |
|
$this->build[] = " FROM " . $this->quoteTable($this->table); |
110
|
1 |
|
$this->buildWhere(); |
111
|
1 |
|
$this->buildLimit(); |
112
|
|
|
|
113
|
1 |
|
return $this->__toString(); |
114
|
|
|
} |
115
|
|
|
|
116
|
1 |
|
public function delete() { |
117
|
1 |
|
$this->build = array(); |
118
|
|
|
|
119
|
1 |
|
$this->buildCommand(static::DELETE); |
120
|
1 |
|
$this->buildWhere(); |
121
|
1 |
|
$this->buildLimit(); |
122
|
|
|
|
123
|
1 |
|
return $this->__toString(); |
124
|
|
|
} |
125
|
|
|
|
126
|
1 |
|
public function update() { |
127
|
1 |
|
$this->build = array(); |
128
|
|
|
|
129
|
1 |
|
$this->buildCommand(static::UPDATE); |
130
|
1 |
|
$this->buildSetFields(); |
131
|
1 |
|
$this->buildWhere(); |
132
|
1 |
|
$this->buildLimit(); |
133
|
|
|
|
134
|
1 |
|
return $this->__toString(); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* @param int $replace |
139
|
|
|
* |
140
|
|
|
* @return string |
141
|
|
|
*/ |
142
|
2 |
|
protected function setInsertCommand($replace) { |
143
|
|
|
switch($replace) { |
144
|
2 |
|
case DB_INSERT_IGNORE: |
145
|
2 |
|
$result = static::INSERT_IGNORE; |
146
|
2 |
|
break; |
147
|
2 |
|
case DB_INSERT_REPLACE: |
148
|
2 |
|
$result = static::REPLACE; |
149
|
2 |
|
break; |
150
|
2 |
|
default: |
151
|
2 |
|
$result = static::INSERT; |
152
|
2 |
|
break; |
153
|
2 |
|
} |
154
|
|
|
|
155
|
2 |
|
return $result; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
|
159
|
2 |
|
public function insert($replace = DB_INSERT_PLAIN, $forceSingleInsert = false) { |
160
|
2 |
|
$this->build = array(); |
161
|
2 |
|
$this->buildCommand($this->setInsertCommand($replace)); |
162
|
|
|
|
163
|
2 |
|
if(!$forceSingleInsert && is_array($this->fields) && !empty($this->fields)) { |
164
|
|
|
// If there are fields - it's batch insert... unless it forced single insert |
165
|
1 |
|
$this->build[] = " ("; |
166
|
1 |
|
$this->buildFieldNames(); // used $this->fields |
167
|
1 |
|
$this->build[] = ") VALUES "; |
168
|
1 |
|
$this->buildValuesVector(); // $this->valuesDanger + $this->values |
|
|
|
|
169
|
1 |
|
} else { |
170
|
|
|
// Otherwise - it's single field insert |
171
|
1 |
|
$this->buildSetFields(); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
|
175
|
2 |
|
return $this->__toString(); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* @param $table |
181
|
|
|
* |
182
|
|
|
* @return $this |
183
|
|
|
*/ |
184
|
1 |
|
public function setTable($table) { |
185
|
1 |
|
$this->table = $table; |
186
|
|
|
|
187
|
1 |
|
return $this; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* @param bool $oneRow - DB_RECORDS_ALL || DB_RECORD_ONE |
192
|
|
|
* |
193
|
|
|
* @return $this |
194
|
|
|
*/ |
195
|
1 |
|
public function setOneRow($oneRow = DB_RECORDS_ALL) { |
196
|
1 |
|
$this->isOneRow = $oneRow; |
197
|
|
|
|
198
|
1 |
|
return $this; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* @param array|array[] $values |
203
|
|
|
* |
204
|
|
|
* @return $this |
205
|
|
|
*/ |
206
|
1 |
|
public function setValues($values = array()) { |
207
|
1 |
|
HelperArray::merge($this->values, $values, HelperArray::MERGE_PHP); |
208
|
|
|
|
209
|
1 |
|
return $this; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* @param array $values |
214
|
|
|
* |
215
|
|
|
* @return $this |
216
|
|
|
*/ |
217
|
1 |
|
public function setValuesDanger($values = array()) { |
218
|
1 |
|
HelperArray::merge($this->valuesDanger, $values, HelperArray::MERGE_PHP); |
219
|
|
|
|
220
|
1 |
|
return $this; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* @param array $values |
225
|
|
|
* |
226
|
|
|
* @return $this |
227
|
|
|
*/ |
228
|
1 |
|
public function setAdjust($values = array()) { |
229
|
1 |
|
HelperArray::merge($this->adjust, $values, HelperArray::MERGE_PHP); |
230
|
|
|
|
231
|
1 |
|
return $this; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* @param array $values |
236
|
|
|
* |
237
|
|
|
* @return $this |
238
|
|
|
*/ |
239
|
1 |
|
public function setAdjustDanger($values = array()) { |
240
|
1 |
|
HelperArray::merge($this->adjustDanger, $values, HelperArray::MERGE_PHP); |
241
|
|
|
|
242
|
1 |
|
return $this; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @param array $fields |
247
|
|
|
* |
248
|
|
|
* @return $this |
249
|
|
|
*/ |
250
|
1 |
|
public function setFields($fields = array()) { |
251
|
1 |
|
HelperArray::merge($this->fields, $fields, HelperArray::MERGE_PHP); |
252
|
|
|
|
253
|
1 |
|
return $this; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* Merges WHERE array as array_merge() |
258
|
|
|
* |
259
|
|
|
* @param array $whereArray |
260
|
|
|
* |
261
|
|
|
* @return $this |
262
|
|
|
*/ |
263
|
1 |
|
public function setWhereArray($whereArray = array()) { |
264
|
1 |
|
HelperArray::merge($this->where, $whereArray, HelperArray::MERGE_PHP); |
265
|
|
|
|
266
|
1 |
|
return $this; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Sets DANGER array - where values should be escaped BEFORE entering DBAL |
271
|
|
|
* |
272
|
|
|
* Deprecated - all values should pass through DBAL |
273
|
|
|
* |
274
|
|
|
* @param array $whereArrayDanger |
275
|
|
|
* |
276
|
|
|
* @return $this |
277
|
|
|
*/ |
278
|
1 |
|
public function setWhereArrayDanger($whereArrayDanger = array()) { |
279
|
1 |
|
HelperArray::merge($this->whereDanger, $whereArrayDanger, HelperArray::MERGE_PHP); |
280
|
|
|
|
281
|
1 |
|
return $this; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* Wrapper for db_escape() |
287
|
|
|
* |
288
|
|
|
* @param $string |
289
|
|
|
* |
290
|
|
|
* @return string |
291
|
|
|
*/ |
292
|
1 |
|
protected function escape($string) { |
293
|
1 |
|
return $this->db->db_escape($string); |
294
|
|
|
} |
295
|
|
|
|
296
|
1 |
|
protected function escapeEmulator($value) { |
297
|
|
|
// Characters encoded are NUL (ASCII 0), \n, \r, \, ', ", and Control-Z. |
298
|
1 |
|
return str_replace( |
299
|
1 |
|
array("\\", "\0", "\n", "\r", "'", "\"", "\z",), |
300
|
1 |
|
array('\\\\', '\0', '\n', '\r', '\\\'', '\"', '\z',), |
301
|
|
|
$value |
302
|
1 |
|
); |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
/** |
306
|
|
|
* Escaping string value and quoting it |
307
|
|
|
* |
308
|
|
|
* @param mixed $value |
309
|
|
|
* |
310
|
|
|
* @return string |
311
|
|
|
*/ |
312
|
1 |
|
protected function stringValue($value) { |
313
|
1 |
|
return "'" . $this->escape((string)$value) . "'"; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Quote mysql DB identifier |
318
|
|
|
* |
319
|
|
|
* @param mixed $fieldName |
320
|
|
|
* |
321
|
|
|
* @return string |
322
|
|
|
*/ |
323
|
1 |
|
public function quote($fieldName) { |
324
|
1 |
|
return "`" . $this->escape((string)$fieldName) . "`"; |
325
|
|
|
} |
326
|
|
|
|
327
|
1 |
|
public function makeAdjustString($fieldValue, $fieldName) { |
328
|
1 |
|
return is_int($fieldName) |
329
|
1 |
|
? $fieldValue |
330
|
1 |
|
: (($fieldNameQuoted = $this->quote($fieldName)) . " = " . |
331
|
1 |
|
$fieldNameQuoted . " + (" . $this->castAsDbValue($fieldValue) . ")"); |
332
|
|
|
} |
333
|
|
|
|
334
|
1 |
|
public function makeFieldEqualValue($fieldValue, $fieldName) { |
335
|
1 |
|
return is_int($fieldName) |
336
|
1 |
|
? $fieldValue |
337
|
1 |
|
: ($this->quote($fieldName) . " = " . $this->castAsDbValue($fieldValue)); |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
/** |
341
|
|
|
* Quote table name with `{{ }}` |
342
|
|
|
* |
343
|
|
|
* @param mixed $tableName |
344
|
|
|
* |
345
|
|
|
* @return string |
346
|
|
|
*/ |
347
|
1 |
|
protected function quoteTable($tableName) { |
348
|
1 |
|
return "`{{" . $this->escape((string)$tableName) . "}}`"; |
349
|
|
|
} |
350
|
|
|
|
351
|
13 |
|
public function castAsDbValue($value) { |
352
|
13 |
|
switch(gettype($value)) { |
353
|
13 |
|
case TYPE_INTEGER: |
354
|
13 |
|
case TYPE_DOUBLE: |
355
|
|
|
// do nothing |
356
|
4 |
|
break; |
357
|
|
|
|
358
|
9 |
|
case TYPE_BOOLEAN: |
359
|
2 |
|
$value = $value ? 1 : 0; |
360
|
2 |
|
break; |
361
|
|
|
|
362
|
7 |
|
case TYPE_NULL: |
363
|
1 |
|
$value = 'NULL'; |
364
|
1 |
|
break; |
365
|
|
|
|
366
|
6 |
|
case TYPE_EMPTY: |
367
|
|
|
// No-type defaults to string |
368
|
|
|
/** @noinspection PhpMissingBreakStatementInspection */ |
369
|
6 |
|
case TYPE_ARRAY: |
370
|
2 |
|
$value = serialize($value); |
371
|
|
|
// Continuing with serialized array value |
372
|
6 |
|
case TYPE_STRING: |
373
|
6 |
|
default: |
374
|
6 |
|
$value = $this->stringValue($value); |
375
|
6 |
|
break; |
376
|
13 |
|
} |
377
|
|
|
|
378
|
13 |
|
return $value; |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
|
382
|
7 |
|
protected function buildCommand($command) { |
383
|
7 |
|
switch($this->command = $command) { |
384
|
7 |
View Code Duplication |
case static::UPDATE: |
|
|
|
|
385
|
1 |
|
$this->build[] = $this->command . " " . $this->quoteTable($this->table); |
386
|
1 |
|
break; |
387
|
|
|
|
388
|
6 |
View Code Duplication |
case static::DELETE: |
|
|
|
|
389
|
1 |
|
$this->build[] = $this->command . " FROM " . $this->quoteTable($this->table); |
390
|
1 |
|
break; |
391
|
|
|
|
392
|
5 |
|
case static::REPLACE: |
393
|
5 |
|
case static::INSERT_IGNORE: |
394
|
5 |
|
case static::INSERT: |
395
|
3 |
|
$this->build[] = $this->command . " INTO " . $this->quoteTable($this->table); |
396
|
3 |
|
break; |
397
|
|
|
|
398
|
2 |
|
case static::SELECT: |
399
|
1 |
|
$this->build[] = $this->command; |
400
|
1 |
|
break; |
401
|
7 |
|
} |
402
|
7 |
|
} |
403
|
|
|
|
404
|
|
|
// UPDATE and INSERT ... SET |
405
|
1 |
|
protected function buildSetFields() { |
406
|
1 |
|
$safeFields = array(); |
407
|
|
|
// Sets overwritten by Adjusts |
408
|
1 |
|
if ($safeValuesDanger = implode(',', $this->valuesDanger)) { |
409
|
1 |
|
$safeFields[] = &$safeValuesDanger; |
410
|
1 |
|
} |
411
|
1 |
|
if ($safeFieldsEqualValues = implode(',', HelperArray::map($this->values, array($this, 'makeFieldEqualValue'), true))) { |
412
|
1 |
|
$safeFields[] = &$safeFieldsEqualValues; |
413
|
1 |
|
} |
414
|
1 |
|
if ($safeAdjustDanger = implode(',', $this->adjustDanger)) { |
415
|
1 |
|
$safeFields[] = &$safeAdjustDanger; |
416
|
1 |
|
} |
417
|
1 |
|
if ($safeAdjust = implode(',', HelperArray::map($this->adjust, array($this, 'makeAdjustString'), true))) { |
418
|
1 |
|
$safeFields[] = &$safeAdjust; |
419
|
1 |
|
} |
420
|
1 |
|
$safeFieldsString = implode(',', $safeFields); |
421
|
|
|
|
422
|
1 |
|
if (!empty($safeFieldsString)) { |
423
|
1 |
|
$this->build[] = ' SET '; |
424
|
1 |
|
$this->build[] = $safeFieldsString; |
425
|
1 |
|
} |
426
|
1 |
|
} |
427
|
|
|
|
428
|
|
|
// INSERT ... VALUES |
429
|
1 |
|
protected function buildFieldNames() { |
430
|
1 |
|
$this->build[] = implode(',', HelperArray::map($this->fields, array($this, 'quote'))); |
431
|
1 |
|
} |
432
|
|
|
|
433
|
|
|
/** |
434
|
|
|
* Vector values is for batch INSERT/REPLACE |
435
|
|
|
*/ |
436
|
|
|
// TODO - CHECK! |
437
|
1 |
|
protected function buildValuesVector() { |
438
|
1 |
|
$compiled = array(); |
439
|
|
|
|
440
|
1 |
|
if (!empty($this->valuesDanger)) { |
441
|
1 |
|
$compiled = $this->valuesDanger; |
442
|
1 |
|
} |
443
|
|
|
|
444
|
1 |
|
foreach ($this->values as $valuesVector) { |
445
|
1 |
|
$compiled[] = '(' . implode(',', HelperArray::map($valuesVector, array($this, 'castAsDbValue'))) . ')'; |
446
|
1 |
|
} |
447
|
|
|
|
448
|
1 |
|
$this->build[] = implode(',', $compiled); |
449
|
1 |
|
} |
450
|
|
|
|
451
|
|
|
|
452
|
1 |
|
protected function buildWhere() { |
453
|
1 |
|
$safeWhere = implode( |
454
|
1 |
|
' AND ', |
455
|
1 |
|
$this->whereDanger + |
456
|
1 |
|
HelperArray::map($this->where, array($this, 'makeFieldEqualValue'), true) |
457
|
1 |
|
); |
458
|
|
|
|
459
|
1 |
|
if (!empty($safeWhere)) { |
460
|
1 |
|
$this->build[] = " WHERE {$safeWhere}"; |
461
|
1 |
|
} |
462
|
1 |
|
} |
463
|
|
|
|
464
|
1 |
|
protected function buildLimit() { |
465
|
1 |
|
if ($this->isOneRow == DB_RECORD_ONE) { |
466
|
1 |
|
$this->build[] = ' LIMIT 1'; |
467
|
1 |
|
} |
468
|
1 |
|
} |
469
|
|
|
|
470
|
|
|
|
471
|
7 |
|
public function __toString() { |
472
|
7 |
|
return implode('', $this->build); |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
} |
476
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.