|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
//pdump(DBStaticUser::getMaxId()); |
|
|
|
|
|
|
4
|
|
|
//pdump(DBStaticUser::getRecordById(67)); |
|
5
|
|
|
//pdump(DBStaticUser::filterIdListStringRepack('2,3,5,67')); |
|
6
|
|
|
|
|
7
|
|
|
|
|
8
|
|
|
/** |
|
9
|
|
|
* Class DbSqlStatement |
|
10
|
|
|
* |
|
11
|
|
|
* @method static DbSqlStatement fields(array $value, int $mergeStrategy = HelperArray::ARRAY_REPLACE) |
|
12
|
|
|
* @method static DbSqlStatement where(array $value, int $mergeStrategy = HelperArray::ARRAY_REPLACE) |
|
13
|
|
|
* @method static DbSqlStatement groupBy(array $value, int $mergeStrategy = HelperArray::ARRAY_REPLACE) |
|
14
|
|
|
* @method static DbSqlStatement orderBy(array $value, int $mergeStrategy = HelperArray::ARRAY_REPLACE) |
|
15
|
|
|
* @method static DbSqlStatement having(array $value, int $mergeStrategy = HelperArray::ARRAY_REPLACE) |
|
16
|
|
|
* |
|
17
|
|
|
*/ |
|
18
|
|
|
class DbSqlStatement { |
|
19
|
|
|
|
|
20
|
|
|
const SELECT = 'SELECT'; |
|
21
|
|
|
|
|
22
|
|
|
protected static $allowedOperations = array( |
|
23
|
|
|
self::SELECT, |
|
24
|
|
|
); |
|
25
|
|
|
|
|
26
|
|
|
/** |
|
27
|
|
|
* @var db_mysql $db |
|
28
|
|
|
*/ |
|
29
|
|
|
protected $db; |
|
30
|
|
|
|
|
31
|
|
|
public $operation = ''; |
|
32
|
|
|
|
|
33
|
|
|
public $table = ''; |
|
34
|
|
|
public $alias = ''; |
|
35
|
|
|
|
|
36
|
|
|
public $idField = ''; |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* @var array |
|
40
|
|
|
*/ |
|
41
|
|
|
public $fields = array(); |
|
42
|
|
|
|
|
43
|
|
|
public $where = array(); |
|
44
|
|
|
public $groupBy = array(); |
|
45
|
|
|
public $orderBy = array(); |
|
46
|
|
|
public $having = array(); |
|
47
|
|
|
|
|
48
|
|
|
public $limit = 0; |
|
49
|
|
|
public $offset = 0; |
|
50
|
|
|
|
|
51
|
|
|
public $fetchOne = false; |
|
52
|
|
|
public $forUpdate = false; |
|
53
|
|
|
public $skipLock = false; |
|
54
|
|
|
|
|
55
|
|
|
protected $_compiledQuery = array(); |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* @param db_mysql|null $db |
|
59
|
|
|
* @param string $className |
|
60
|
|
|
* |
|
61
|
|
|
* @return DbSqlStatement |
|
62
|
|
|
*/ |
|
63
|
|
|
public static function build($db = null, $className = '') { |
|
64
|
|
|
$result = new self($db); |
|
65
|
|
|
if (!empty($className) && is_string($className)) { |
|
66
|
|
|
$result->getParamsFromStaticClass($className); |
|
67
|
|
|
} |
|
68
|
|
|
|
|
69
|
|
|
return $result; |
|
70
|
1 |
|
} |
|
71
|
1 |
|
|
|
72
|
1 |
|
/** |
|
73
|
1 |
|
* DbSqlStatement constructor. |
|
74
|
1 |
|
* |
|
75
|
|
|
* @param db_mysql|null $db |
|
76
|
1 |
|
*/ |
|
77
|
|
|
public function __construct($db = null) { |
|
78
|
|
|
$this->db = (!empty($db) && $db instanceof db_mysql) || !class_exists('classSupernova', false) ? $db : classSupernova::$db; |
|
79
|
|
|
} |
|
80
|
|
|
|
|
81
|
|
|
/** |
|
82
|
|
|
* Resets statement |
|
83
|
|
|
* |
|
84
|
3 |
|
* @param bool $full |
|
85
|
3 |
|
* |
|
86
|
3 |
|
* @return $this |
|
87
|
|
|
*/ |
|
88
|
|
|
// TODO - UNITTEST |
|
89
|
|
|
protected function _reset($full = true) { |
|
90
|
|
|
if ($full) { |
|
91
|
|
|
$this->operation = ''; |
|
92
|
|
|
$this->table = ''; |
|
93
|
|
|
$this->alias = ''; |
|
94
|
|
|
$this->idField = ''; |
|
95
|
|
|
} |
|
96
|
1 |
|
|
|
97
|
1 |
|
$this->fields = array(); |
|
98
|
1 |
|
$this->where = array(); |
|
99
|
1 |
|
$this->groupBy = array(); |
|
100
|
1 |
|
$this->orderBy = array(); |
|
101
|
1 |
|
$this->having = array(); |
|
102
|
1 |
|
|
|
103
|
|
|
$this->limit = 0; |
|
104
|
1 |
|
$this->offset = 0; |
|
105
|
1 |
|
|
|
106
|
1 |
|
$this->fetchOne = false; |
|
107
|
1 |
|
$this->forUpdate = false; |
|
108
|
1 |
|
$this->skipLock = false; |
|
109
|
|
|
|
|
110
|
1 |
|
return $this; |
|
111
|
1 |
|
} |
|
112
|
|
|
|
|
113
|
1 |
|
/** |
|
114
|
1 |
|
* @param string $fieldName |
|
115
|
1 |
|
* |
|
116
|
|
|
* @return $this |
|
117
|
1 |
|
*/ |
|
118
|
|
|
public function setIdField($fieldName) { |
|
119
|
|
|
$this->idField = $fieldName; |
|
120
|
|
|
|
|
121
|
|
|
return $this; |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
|
/** |
|
125
|
2 |
|
* @param string $alias |
|
126
|
2 |
|
* |
|
127
|
|
|
* @return $this |
|
128
|
2 |
|
*/ |
|
129
|
|
|
public function fromAlias($alias) { |
|
130
|
|
|
$this->alias = $alias; |
|
131
|
|
|
|
|
132
|
|
|
return $this; |
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
|
|
/** |
|
136
|
1 |
|
* @param int $limit |
|
137
|
1 |
|
* |
|
138
|
|
|
* @return $this |
|
139
|
1 |
|
*/ |
|
140
|
|
|
public function limit($limit) { |
|
141
|
|
|
$this->limit = is_numeric($limit) ? $limit : 0; |
|
|
|
|
|
|
142
|
|
|
|
|
143
|
|
|
return $this; |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
/** |
|
147
|
2 |
|
* @param int $offset |
|
148
|
2 |
|
* |
|
149
|
|
|
* @return $this |
|
150
|
2 |
|
*/ |
|
151
|
|
|
public function offset($offset) { |
|
152
|
|
|
$this->offset = is_numeric($offset) ? $offset : 0; |
|
|
|
|
|
|
153
|
|
|
|
|
154
|
|
|
return $this; |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
|
|
158
|
2 |
|
/** |
|
159
|
2 |
|
* @param string $tableName |
|
160
|
|
|
* @param string $alias |
|
161
|
2 |
|
* |
|
162
|
|
|
* @return $this |
|
163
|
|
|
*/ |
|
164
|
|
|
public function from($tableName, $alias = '') { |
|
165
|
|
|
$this->table = $tableName; |
|
166
|
|
|
$this->fromAlias($alias); |
|
167
|
|
|
|
|
168
|
|
|
return $this; |
|
169
|
|
|
} |
|
170
|
|
|
|
|
171
|
2 |
|
/** |
|
172
|
2 |
|
* @param string $params |
|
173
|
2 |
|
* |
|
174
|
|
|
* @return $this |
|
175
|
2 |
|
*/ |
|
176
|
|
|
public function getParamsFromStaticClass($params) { |
|
177
|
|
|
if (is_string($params) && $params && class_exists($params)) { |
|
178
|
|
|
$this->from($params::$_table); |
|
179
|
|
|
$this->setIdField($params::$_idField); |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
return $this; |
|
183
|
2 |
|
} |
|
184
|
2 |
|
|
|
185
|
2 |
|
|
|
186
|
2 |
|
/** |
|
187
|
2 |
|
* @return self |
|
188
|
|
|
*/ |
|
189
|
2 |
|
public function select() { |
|
190
|
|
|
$this->operation = DbSqlStatement::SELECT; |
|
191
|
|
|
if (empty($this->fields)) { |
|
192
|
|
|
$this->fields = array('*'); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
return $this; |
|
196
|
3 |
|
} |
|
197
|
3 |
|
|
|
198
|
3 |
|
public function __call($name, $arguments) { |
|
199
|
3 |
|
// TODO: Implement __call() method. |
|
200
|
3 |
|
if (in_array($name, array('fields', 'where', 'groupBy', 'orderBy', 'having'))) { |
|
201
|
|
|
array_unshift($arguments, ''); |
|
202
|
3 |
|
$arguments[0] = &$this->$name; |
|
203
|
|
|
call_user_func_array('HelperArray::merge', $arguments); |
|
204
|
|
|
} |
|
205
|
3 |
|
|
|
206
|
|
|
return $this; |
|
207
|
3 |
|
} |
|
208
|
3 |
|
|
|
209
|
3 |
|
/** |
|
210
|
3 |
|
* Make statement fetch only one record |
|
211
|
3 |
|
* |
|
212
|
|
|
* @return $this |
|
213
|
3 |
|
*/ |
|
214
|
|
|
public function fetchOne($fetchOne = true) { |
|
215
|
|
|
$this->fetchOne = $fetchOne; |
|
216
|
|
|
|
|
217
|
|
|
return $this; |
|
218
|
|
|
} |
|
219
|
|
|
|
|
220
|
|
|
/** |
|
221
|
2 |
|
* @return $this |
|
222
|
2 |
|
*/ |
|
223
|
|
|
public function forUpdate($forUpdate = true) { |
|
224
|
2 |
|
$this->forUpdate = $forUpdate; |
|
225
|
|
|
|
|
226
|
|
|
return $this; |
|
227
|
|
|
} |
|
228
|
|
|
|
|
229
|
|
|
/** |
|
230
|
1 |
|
* @return $this |
|
231
|
1 |
|
*/ |
|
232
|
|
|
public function skipLock($skipLock = true) { |
|
233
|
1 |
|
$this->skipLock = $skipLock; |
|
234
|
|
|
|
|
235
|
|
|
return $this; |
|
236
|
|
|
} |
|
237
|
|
|
|
|
238
|
|
|
|
|
239
|
1 |
|
/** |
|
240
|
1 |
|
* @param array $array |
|
241
|
|
|
* |
|
242
|
1 |
|
* @return array |
|
243
|
|
|
*/ |
|
244
|
|
|
protected function arrayEscape(&$array) { |
|
245
|
|
|
$result = array(); |
|
246
|
|
|
foreach ($array as $key => &$value) { |
|
247
|
|
|
$result[$key] = $this->stringEscape($value); |
|
248
|
|
|
} |
|
249
|
|
|
|
|
250
|
|
|
return $result; |
|
251
|
2 |
|
} |
|
252
|
2 |
|
|
|
253
|
1 |
|
protected function compileFrom() { |
|
254
|
|
|
$this->_compiledQuery[] = 'FROM `{{' . $this->stringEscape($this->table) . '}}`'; |
|
255
|
|
|
if (!empty($this->alias)) { |
|
256
|
1 |
|
$this->_compiledQuery[] = 'AS `' . $this->stringEscape($this->alias) . '`'; |
|
257
|
1 |
|
} |
|
258
|
|
|
} |
|
259
|
|
|
|
|
260
|
|
|
protected function compileJoin() { |
|
261
|
|
|
} |
|
262
|
|
|
|
|
263
|
|
|
protected function compileWhere() { |
|
264
|
|
|
// TODO - fields should be escaped !! |
|
265
|
|
|
!empty($this->where) ? $this->_compiledQuery[] = 'WHERE ' . implode(' AND ', $this->where) : false; |
|
266
|
|
|
} |
|
267
|
|
|
|
|
268
|
|
|
protected function compileGroupBy() { |
|
269
|
|
|
// TODO - fields should be escaped !! |
|
270
|
|
|
!empty($this->groupBy) ? $this->_compiledQuery[] = 'GROUP BY ' . implode(',', $this->arrayEscape($this->groupBy)) : false; |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
protected function compileOrderBy() { |
|
274
|
|
|
// TODO - fields should be escaped !! |
|
275
|
|
|
!empty($this->orderBy) ? $this->_compiledQuery[] = 'ORDER BY ' . implode(',', $this->arrayEscape($this->orderBy)) : false; |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
protected function compileHaving() { |
|
279
|
|
|
// TODO - fields should be escaped !! |
|
280
|
|
|
!empty($this->having) ? $this->_compiledQuery[] = 'HAVING ' . implode(' AND ', $this->having) : false; |
|
281
|
|
|
} |
|
282
|
|
|
|
|
283
|
|
|
protected function compileLimit() { |
|
284
|
|
|
// TODO - fields should be escaped !! |
|
285
|
|
|
if($limit = $this->fetchOne ? 1 : $this->limit) { |
|
286
|
|
|
$this->_compiledQuery[] = 'LIMIT ' . $limit . (!empty($this->offset) ? ' OFFSET ' . $this->offset : ''); |
|
287
|
|
|
} |
|
288
|
|
|
} |
|
289
|
|
|
|
|
290
|
|
|
protected function compileForUpdate() { |
|
291
|
|
|
$this->_compiledQuery[] = |
|
292
|
|
|
// forUpdate flag forces select with row locking - didn't look at skipLock flag |
|
293
|
|
|
$this->forUpdate |
|
294
|
|
|
|| |
|
295
|
|
|
// Also row locked when transaction is up and skipLock flag is not set |
|
296
|
|
|
(classSupernova::db_transaction_check(false) && !$this->skipLock) ? 'FOR UPDATE' : ''; |
|
297
|
|
|
} |
|
298
|
|
|
|
|
299
|
|
|
/** |
|
300
|
|
|
* @return string |
|
301
|
|
|
* @throws ExceptionDbOperationEmpty |
|
302
|
|
|
* @throws ExceptionDbOperationRestricted |
|
303
|
|
|
*/ |
|
304
|
|
|
public function __toString() { |
|
305
|
16 |
|
if (empty($this->operation)) { |
|
306
|
16 |
|
throw new ExceptionDbOperationEmpty(); |
|
307
|
|
|
} |
|
308
|
16 |
|
|
|
309
|
16 |
|
if (!in_array($this->operation, self::$allowedOperations)) { |
|
310
|
15 |
|
throw new ExceptionDbOperationRestricted(); |
|
311
|
15 |
|
} |
|
312
|
13 |
|
|
|
313
|
13 |
|
$this->_compiledQuery = array(); |
|
314
|
16 |
|
|
|
315
|
|
|
$this->_compiledQuery[] = $this->stringEscape($this->operation); |
|
316
|
16 |
|
$this->_compiledQuery[] = $this->selectFieldsToString($this->fields); |
|
317
|
3 |
|
|
|
318
|
|
|
$this->compileFrom(); |
|
319
|
|
|
$this->compileJoin(); |
|
320
|
13 |
|
$this->compileWhere(); |
|
321
|
|
|
$this->compileGroupBy(); |
|
322
|
|
|
$this->compileOrderBy(); |
|
323
|
|
|
$this->compileHaving(); |
|
324
|
|
|
$this->compileLimit(); |
|
325
|
|
|
$this->compileForUpdate(); |
|
326
|
|
|
|
|
327
|
|
|
return implode(' ', $this->_compiledQuery); |
|
328
|
13 |
|
} |
|
329
|
13 |
|
|
|
330
|
4 |
|
/** |
|
331
|
13 |
|
* @param array|mixed $fields |
|
332
|
2 |
|
* |
|
333
|
11 |
|
* @return string |
|
334
|
2 |
|
* @throws ExceptionDBFieldEmpty |
|
335
|
2 |
|
*/ |
|
336
|
9 |
|
protected function selectFieldsToString($fields) { |
|
337
|
|
|
HelperArray::makeArrayRef($fields); |
|
338
|
|
|
|
|
339
|
13 |
|
$result = array(); |
|
340
|
|
|
foreach ($fields as $fieldName) { |
|
341
|
|
|
$string = $this->processField($fieldName); |
|
342
|
|
|
if ($string !== '') { |
|
343
|
|
|
$result[] = $string; |
|
344
|
|
|
} |
|
345
|
|
|
} |
|
346
|
|
|
|
|
347
|
9 |
|
if (empty($result)) { |
|
348
|
9 |
|
throw new ExceptionDBFieldEmpty(); |
|
349
|
|
|
} |
|
350
|
|
|
|
|
351
|
9 |
|
return implode(',', $result); |
|
352
|
|
|
} |
|
353
|
8 |
|
|
|
354
|
9 |
|
/** |
|
355
|
|
|
* @param mixed $fieldName |
|
356
|
7 |
|
* |
|
357
|
9 |
|
* @return string |
|
358
|
|
|
*/ |
|
359
|
3 |
|
protected function processFieldString($fieldName) { |
|
360
|
3 |
|
$result = (string)$fieldName; |
|
361
|
|
|
if ( |
|
362
|
9 |
|
$result != '' |
|
363
|
|
|
&& |
|
364
|
|
|
// Literals plays as they are - they do properly format by itself |
|
365
|
|
|
!($fieldName instanceof DbSqlLiteral) |
|
366
|
|
|
) { |
|
367
|
|
|
// Other should be formatted |
|
368
|
|
|
$result = '`' . $this->stringEscape($result) . '`'; |
|
369
|
|
|
} |
|
370
|
|
|
|
|
371
|
|
|
return $result; |
|
372
|
|
|
} |
|
373
|
|
|
|
|
374
|
|
|
/** |
|
375
|
|
|
* @param mixed $fieldName |
|
376
|
|
|
* |
|
377
|
|
|
* @return string |
|
378
|
|
|
*/ |
|
379
|
|
|
protected function processField($fieldName) { |
|
380
|
|
|
if (is_bool($fieldName)) { |
|
381
|
|
|
$result = (string)intval($fieldName); |
|
382
|
|
|
} elseif (is_numeric($fieldName)) { |
|
383
|
|
|
$result = $fieldName; |
|
384
|
|
|
} elseif (is_null($fieldName)) { |
|
385
|
|
|
$result = 'NULL'; |
|
386
|
|
|
} elseif ($fieldName === '*') { |
|
387
|
|
|
$result = '*'; |
|
388
|
|
|
} else { |
|
389
|
|
|
// Field has other type - string or should be convertible to string |
|
390
|
|
|
$result = $this->processFieldString($fieldName); |
|
391
|
|
|
} |
|
392
|
|
|
|
|
393
|
|
|
return $result; |
|
394
|
|
|
} |
|
395
|
|
|
|
|
396
|
|
|
|
|
397
|
|
|
/** |
|
398
|
|
|
* @param $string |
|
399
|
|
|
* |
|
400
|
|
|
* @return mixed|string |
|
401
|
|
|
*/ |
|
402
|
|
|
protected function stringEscape($string) { |
|
403
|
|
|
return |
|
404
|
|
|
method_exists($this->db, 'db_escape') |
|
405
|
|
|
? $this->db->db_escape($string) |
|
406
|
|
|
: str_replace('`', '\`', addslashes($string)); |
|
407
|
|
|
} |
|
408
|
|
|
|
|
409
|
|
|
} |
|
410
|
|
|
|
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.