1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PhpBoot\DB\impls; |
4
|
|
|
use PhpBoot\DB\DB; |
5
|
|
|
use PhpBoot\DB\Exceptions\DBException; |
6
|
|
|
use PhpBoot\DB\NestedStringCut; |
7
|
|
|
use PhpBoot\DB\Raw; |
8
|
|
|
use PhpBoot\DB\rules\basic\BasicRule; |
9
|
|
|
use PhpBoot\DB\Context; |
10
|
|
|
|
11
|
|
|
class ExecResult{ |
12
|
28 |
|
public function __construct($success, $pdo, $st){ |
13
|
28 |
|
$this->pdo = $pdo; |
14
|
28 |
|
$this->st = $st; |
15
|
28 |
|
$this->success = $success; |
16
|
28 |
|
$this->rows = $this->st->rowCount(); |
17
|
28 |
|
} |
18
|
1 |
|
public function lastInsertId($name=null){ |
19
|
1 |
|
return $this->pdo->lastInsertId($name); |
20
|
|
|
} |
21
|
|
|
/** |
22
|
|
|
* @var bool |
23
|
|
|
* true on success or false on failure. |
24
|
|
|
*/ |
25
|
|
|
public $success; |
26
|
|
|
/** |
27
|
|
|
* @var int |
28
|
|
|
* the number of rows. |
29
|
|
|
*/ |
30
|
|
|
public $rows; |
31
|
|
|
/** |
32
|
|
|
* |
33
|
|
|
* @var \PDO |
34
|
|
|
*/ |
35
|
|
|
public $pdo; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var \PDOStatement |
39
|
|
|
*/ |
40
|
|
|
public $st; |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
class SelectImpl |
44
|
|
|
{ |
45
|
28 |
|
static public function select($context, $columns){ |
46
|
28 |
|
$context->appendSql("SELECT $columns"); |
47
|
28 |
|
} |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
class FromImpl |
51
|
|
|
{ |
52
|
27 |
|
static public function from($context, $tables,$as=null){ |
53
|
27 |
|
if($tables instanceof BasicRule){ |
54
|
1 |
|
$context->appendSql("FROM (".$tables->context->sql.')'); |
55
|
1 |
|
$context->params = array_merge($context->params,$tables->context->params); |
56
|
1 |
|
}else { |
57
|
27 |
|
$context->appendSql("FROM ".DB::wrap($tables)); |
58
|
|
|
} |
59
|
27 |
|
if($as){ |
60
|
|
|
$context->appendSql("AS ".DB::wrap($as)); |
61
|
|
|
} |
62
|
27 |
|
} |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
class DeleteImpl |
66
|
|
|
{ |
67
|
7 |
|
static public function deleteFrom($context, $from) |
68
|
|
|
{ |
69
|
7 |
|
$context->appendSql("DELETE FROM ".DB::wrap($from)); |
70
|
7 |
|
} |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
class JoinImpl |
74
|
|
|
{ |
75
|
5 |
|
static public function join($context, $type, $table) { |
76
|
5 |
|
$table = DB::wrap($table); |
77
|
5 |
|
if($type){ |
78
|
3 |
|
$context->appendSql("$type JOIN $table"); |
79
|
3 |
|
}else{ |
80
|
2 |
|
$context->appendSql("JOIN $table"); |
81
|
|
|
} |
82
|
5 |
|
} |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
class JoinOnImpl |
86
|
|
|
{ |
87
|
5 |
|
static public function on($context, $condition) { |
88
|
5 |
|
$context->appendSql("ON $condition"); |
89
|
5 |
|
} |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
class ForUpdateImpl |
93
|
|
|
{ |
94
|
2 |
|
static public function forUpdate($context){ |
95
|
2 |
|
$context->appendSql("FOR UPDATE"); |
96
|
2 |
|
} |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
class ForUpdateOfImpl |
100
|
|
|
{ |
101
|
1 |
|
static public function of($context, $column){ |
102
|
1 |
|
$column = DB::wrap($column); |
103
|
1 |
|
$context->appendSql("OF $column"); |
104
|
1 |
|
} |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
class InsertImpl |
108
|
|
|
{ |
109
|
8 |
|
static public function insertInto($context, $table) { |
110
|
8 |
|
$table = DB::wrap($table); |
111
|
8 |
|
$context->appendSql("INSERT INTO $table"); |
112
|
8 |
|
} |
113
|
|
|
} |
114
|
|
|
class ReplaceImpl |
115
|
|
|
{ |
116
|
2 |
|
static public function replaceInto($context, $table) { |
117
|
2 |
|
$table = DB::wrap($table); |
118
|
2 |
|
$context->appendSql("REPLACE INTO $table"); |
119
|
2 |
|
} |
120
|
|
|
} |
121
|
|
|
class ValuesImpl |
122
|
|
|
{ |
123
|
7 |
|
static public function values(Context $context, array $values){ |
124
|
7 |
|
$params = []; |
125
|
7 |
|
$stubs = []; |
126
|
7 |
|
foreach ($values as $v){ |
127
|
7 |
|
if(is_a($v, Raw::class)){//直接拼接sql,不需要转义 |
128
|
4 |
|
$stubs[]=$v->get(); |
129
|
4 |
|
}else{ |
130
|
7 |
|
$stubs[]='?'; |
131
|
7 |
|
$params[] = $v; |
132
|
|
|
} |
133
|
7 |
|
} |
134
|
7 |
|
$stubs = implode(',', $stubs); |
135
|
|
|
|
136
|
7 |
View Code Duplication |
if(array_keys($values) === range(0, count($values) - 1)){ |
|
|
|
|
137
|
|
|
//VALUES(val0, val1, val2) |
138
|
4 |
|
$context->appendSql("VALUES($stubs)"); |
139
|
|
|
|
140
|
4 |
|
}else{ |
141
|
|
|
//(col0, col1, col2) VALUES(val0, val1, val2) |
142
|
|
|
$columns = implode(',', array_map(function($k){return DB::wrap($k);}, array_keys($values))); |
143
|
3 |
|
$context->appendSql("($columns) VALUES($stubs)",false); |
144
|
|
|
} |
145
|
7 |
|
$context->appendParams($params); |
146
|
7 |
|
} |
147
|
3 |
|
static public function batchValues(Context $context, array $values) |
148
|
|
|
{ |
149
|
3 |
|
$count = count($values); |
150
|
3 |
|
if($count == 0){ |
151
|
|
|
return; |
152
|
|
|
} |
153
|
3 |
|
$keys = array_keys($values[0]); |
154
|
3 |
|
$row = implode(',', self::toSql(array_values($values[0]))); |
155
|
3 |
View Code Duplication |
if($keys === range(0, count($keys) - 1)){ |
|
|
|
|
156
|
|
|
//VALUES(val0, val1, val2) |
157
|
2 |
|
$context->appendSql("VALUES($row)"); |
158
|
2 |
|
}else{ |
159
|
|
|
//(col0, col1, col2) VALUES(val0, val1, val2) |
160
|
|
|
$columns = implode(',', array_map(function($k){return DB::wrap($k);}, $keys)); |
161
|
1 |
|
$context->appendSql("($columns) VALUES($row)",false); |
162
|
|
|
} |
163
|
3 |
|
for($i=1; $i<$count; $i++){ |
164
|
1 |
|
$value = self::pick($keys, $values[$i]); |
165
|
1 |
|
$row = implode(',', self::toSql($value)); |
166
|
1 |
|
$context->appendSql(", ($row)",false); |
167
|
1 |
|
} |
168
|
3 |
|
} |
169
|
|
|
|
170
|
1 |
|
static protected function pick(array $keys, array $values) |
171
|
|
|
{ |
172
|
1 |
|
$res = []; |
173
|
1 |
|
foreach ($keys as $key){ |
174
|
1 |
|
array_key_exists($key, $values) or \PhpBoot\abort("key $key not exist from the given array"); |
175
|
1 |
|
$res[$key] = $values[$key]; |
176
|
1 |
|
} |
177
|
1 |
|
return $res; |
178
|
|
|
} |
179
|
3 |
|
static protected function toSql(array $values) |
180
|
|
|
{ |
181
|
3 |
|
foreach ($values as &$v){ |
182
|
3 |
|
if($v instanceof Raw){ |
183
|
3 |
|
$v = $v->get(); |
184
|
3 |
|
}elseif(is_bool($v)){ |
185
|
3 |
|
$v = $v?'true':'false'; |
186
|
3 |
|
}elseif(!in_array(gettype($v), ['integer', 'boolean', 'double', 'float'])){ |
187
|
3 |
|
$v = (string)$v; |
188
|
3 |
|
$v = str_replace("\\", "\\\\", $v); |
189
|
3 |
|
$v = str_replace("'", "\\'", $v); |
190
|
3 |
|
$v = "'$v'"; |
191
|
3 |
|
} |
192
|
3 |
|
} |
193
|
3 |
|
return $values; |
194
|
|
|
} |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
class UpdateImpl |
198
|
|
|
{ |
199
|
11 |
|
static public function update($context, $table){ |
200
|
11 |
|
$table = DB::wrap($table); |
201
|
11 |
|
$context->appendSql("UPDATE $table"); |
202
|
11 |
|
} |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
class UpdateSetImpl |
206
|
|
|
{ |
207
|
11 |
|
public function set(Context $context, $expr, $args){ |
208
|
11 |
|
if(is_string($expr)){ |
209
|
|
|
return $this->setExpr($context, $expr, $args); |
210
|
|
|
}else{ |
211
|
11 |
|
return $this->setArgs($context, $expr); |
212
|
|
|
} |
213
|
|
|
} |
214
|
|
|
|
215
|
|
View Code Duplication |
public function setExpr(Context $context, $expr, $args){ |
|
|
|
|
216
|
|
|
if($this->first){ |
217
|
|
|
$this->first = false; |
218
|
|
|
$prefix = 'SET '; |
219
|
|
|
}else{ |
220
|
|
|
$prefix = ','; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
$context->appendSql("$prefix$expr",$prefix == 'SET '); |
224
|
|
|
$context->appendParams($args); |
225
|
|
|
|
226
|
|
|
} |
227
|
11 |
View Code Duplication |
public function setArgs(Context $context, $values){ |
|
|
|
|
228
|
11 |
|
$set = []; |
229
|
11 |
|
$params = []; |
230
|
11 |
|
foreach ($values as $k=>$v){ |
231
|
11 |
|
$k = DB::wrap($k); |
232
|
11 |
|
if(is_a($v, Raw::class)){//直接拼接sql,不需要转义 |
233
|
1 |
|
$set[]= "$k=".$v->get(); |
234
|
1 |
|
}else{ |
235
|
11 |
|
$set[]= "$k=?"; |
236
|
11 |
|
$params[]=$v; |
237
|
|
|
} |
238
|
11 |
|
} |
239
|
11 |
|
if($this->first){ |
240
|
11 |
|
$this->first = false; |
241
|
11 |
|
$context->appendSql('SET '.implode(',', $set)); |
242
|
11 |
|
$context->appendParams($params); |
243
|
11 |
|
}else{ |
244
|
|
|
$context->appendSql(','.implode(',', $set),false); |
245
|
|
|
$context->appendParams($params); |
246
|
|
|
} |
247
|
11 |
|
} |
248
|
|
|
private $first=true; |
249
|
|
|
} |
250
|
|
|
class OrderByImpl |
251
|
|
|
{ |
252
|
7 |
|
public function orderByArgs(Context $context, $orders){ |
253
|
7 |
|
if(empty($orders)){ |
254
|
|
|
return $this; |
255
|
|
|
} |
256
|
7 |
|
$params = array(); |
257
|
7 |
|
foreach ($orders as $k=>$v){ |
258
|
7 |
|
if(is_integer($k)){ |
259
|
6 |
|
$params[] = DB::wrap($v); |
260
|
6 |
|
}else{ |
261
|
2 |
|
$k = DB::wrap($k); |
262
|
|
|
|
263
|
2 |
|
$v = strtoupper($v); |
264
|
2 |
|
($v =='DESC' || $v =='ASC') or \PhpBoot\abort( new \InvalidArgumentException("invalid params for orderBy(".json_encode($orders).")")); |
265
|
|
|
|
266
|
2 |
|
$params[] = "$k $v"; |
267
|
|
|
} |
268
|
7 |
|
} |
269
|
7 |
|
if($this->first){ |
270
|
7 |
|
$this->first = false; |
271
|
7 |
|
$context->appendSql('ORDER BY '.implode(',', $params)); |
272
|
7 |
|
}else{ |
273
|
1 |
|
$context->appendSql(','.implode(',', $params),false); |
274
|
|
|
} |
275
|
7 |
|
return $this; |
276
|
|
|
} |
277
|
7 |
|
public function orderBy(Context $context, $column, $order=null){ |
278
|
7 |
|
if(is_string($column)){ |
279
|
7 |
|
if($order === null){ |
280
|
6 |
|
$column = [$column]; |
281
|
6 |
|
}else{ |
282
|
2 |
|
$column = [$column=>$order]; |
283
|
|
|
} |
284
|
7 |
|
} |
285
|
7 |
|
return $this->orderByArgs($context, $column); |
286
|
|
|
|
287
|
|
|
|
288
|
|
|
} |
289
|
|
|
private $first=true; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
class LimitImpl |
293
|
|
|
{ |
294
|
3 |
|
static public function limit(Context $context, $size){ |
295
|
3 |
|
$intSize = intval($size); |
296
|
3 |
|
strval($intSize) == $size or \PhpBoot\abort( |
297
|
|
|
new \InvalidArgumentException("invalid params for limit($size)")); |
298
|
3 |
|
$context->appendSql("LIMIT $size"); |
299
|
3 |
|
} |
300
|
1 |
|
static public function limitWithOffset(Context $context, $start, $size){ |
301
|
1 |
|
$intStart = intval($start); |
302
|
1 |
|
$intSize = intval($size); |
303
|
1 |
|
strval($intStart) == $start && strval($intSize) == $size or \PhpBoot\abort( |
304
|
|
|
new \InvalidArgumentException("invalid params for limit($start, $size)")); |
305
|
1 |
|
$context->appendSql("LIMIT $start,$size"); |
306
|
1 |
|
} |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
class WhereImpl{ |
310
|
|
|
|
311
|
2 |
|
static private function findQ($str,$offset = 0,$no=0){ |
312
|
2 |
|
$found = strpos($str, '?', $offset); |
313
|
2 |
|
if($no == 0 || $found === false){ |
314
|
2 |
|
return $found; |
315
|
|
|
} |
316
|
1 |
|
return self::findQ($str, $found+1, $no-1); |
317
|
|
|
} |
318
|
|
|
|
319
|
31 |
|
static public function where(Context $context, $prefix, $expr, $args){ |
320
|
31 |
|
if(empty($expr)){ |
321
|
1 |
|
return; |
322
|
|
|
} |
323
|
30 |
|
if(is_callable($expr)){ |
324
|
3 |
|
self::conditionClosure($context,$prefix, $expr); |
325
|
30 |
|
}elseif (is_string($expr)){ |
326
|
15 |
|
self::condition($context, $prefix, $expr, $args); |
327
|
15 |
|
}else{ |
328
|
17 |
|
self::conditionArgs($context, $prefix, $expr); |
329
|
|
|
} |
330
|
|
|
|
331
|
30 |
|
} |
332
|
|
|
|
333
|
3 |
|
static public function conditionClosure(Context $context, $prefix, callable $callback){ |
334
|
3 |
|
$context->appendSql($prefix.' ('); |
335
|
3 |
|
$callback($context); |
336
|
3 |
|
$context->appendSql(')'); |
337
|
3 |
|
} |
338
|
|
|
/** |
339
|
|
|
* find like Mongodb query glossary |
340
|
|
|
* whereArray( |
341
|
|
|
* [ |
342
|
|
|
* 'id'=>['>'=>1], |
343
|
|
|
* 'name'=>'cym', |
344
|
|
|
* ] |
345
|
|
|
* ) |
346
|
|
|
* 支持的操作符有 |
347
|
|
|
* = 'id'=>['=' => 1] |
348
|
|
|
* > 'id'=>['>' => 1] |
349
|
|
|
* < 'id'=>['<' => 1] |
350
|
|
|
* <> 'id'=>['<>' => 1] |
351
|
|
|
* >= 'id'=>['>=' => 1] |
352
|
|
|
* <= 'id'=>['<=' => 1] |
353
|
|
|
* BETWEEN 'id'=>['BETWEEN' => [1 ,2]] |
354
|
|
|
* LIKE 'id'=>['LIKE' => '1%'] |
355
|
|
|
* IN 'id'=>['IN' => [1,2,3]] |
356
|
|
|
* NOT IN 'id'=>['NOT IN' => [1,2,3]] |
357
|
|
|
* @return void |
358
|
|
|
*/ |
359
|
17 |
|
static public function conditionArgs(Context $context, $prefix, $args=[]){ |
360
|
17 |
|
if($args ===null){ |
361
|
|
|
return ; |
362
|
|
|
} |
363
|
17 |
|
$exprs = array(); |
364
|
17 |
|
$params = array(); |
365
|
17 |
|
foreach ($args as $k => $v){ |
366
|
17 |
|
$k = DB::wrap($k); |
367
|
17 |
|
if(!is_array($v)){ |
368
|
17 |
|
$v = ['='=>$v]; |
369
|
17 |
|
} |
370
|
|
|
|
371
|
17 |
|
$ops = ['=', '>', '<', '<>', '>=', '<=', 'IN', 'NOT IN', 'BETWEEN', 'LIKE']; |
372
|
17 |
|
$op = array_keys($v)[0]; |
373
|
17 |
|
$op = strtoupper($op); |
374
|
|
|
|
375
|
17 |
|
false !== array_search($op, $ops) or \PhpBoot\abort( |
376
|
|
|
new \InvalidArgumentException("invalid param $op for whereArgs")); |
377
|
|
|
|
378
|
17 |
|
$var = array_values($v)[0]; |
379
|
17 |
|
if($op == 'IN' || $op == 'NOT IN'){ |
380
|
2 |
|
$stubs = []; |
381
|
|
|
|
382
|
2 |
|
if($var instanceof BasicRule){ |
383
|
1 |
|
$stubs = "({$var->context->sql})"; |
384
|
1 |
|
$params = array_merge($params, $var->context->params); |
385
|
1 |
|
$exprs[] = "$k $op $stubs"; |
386
|
1 |
|
}else{ |
387
|
1 |
|
foreach ($var as $i){ |
388
|
1 |
|
if(is_a($i, Raw::class)){ |
389
|
1 |
|
$stubs[]=strval($i); |
390
|
1 |
View Code Duplication |
}elseif($i instanceof BasicRule){ |
|
|
|
|
391
|
|
|
$stubs = "({$i->context->sql})"; |
392
|
|
|
$params = array_merge($params, $i->context->params); |
393
|
|
|
}else{ |
394
|
1 |
|
$stubs[]='?'; |
395
|
1 |
|
$params[] = $i; |
396
|
|
|
} |
397
|
1 |
|
} |
398
|
1 |
|
$stubs = implode(',', $stubs); |
399
|
1 |
|
$exprs[] = "$k $op ($stubs)"; |
400
|
|
|
} |
401
|
17 |
|
}else if($op == 'BETWEEN'){ |
402
|
2 |
|
$cond = "$k BETWEEN"; |
403
|
2 |
View Code Duplication |
if(is_a($var[0], Raw::class)){ |
|
|
|
|
404
|
|
|
$cond = "$cond ".strval($var[0]); |
405
|
2 |
|
}elseif($var[0] instanceof BasicRule){ |
406
|
1 |
|
$cond = "$cond ({$var[0]->context->sql})"; |
407
|
1 |
|
$params = array_merge($params, $var[0]->context->params); |
408
|
1 |
|
}else{ |
409
|
1 |
|
$cond = "$cond ?"; |
410
|
1 |
|
$params[] = $var[0]; |
411
|
|
|
} |
412
|
2 |
View Code Duplication |
if(is_a($var[1], Raw::class)){ |
|
|
|
|
413
|
1 |
|
$cond = "$cond AND ".strval($var[1]); |
414
|
2 |
|
}elseif($var[1] instanceof BasicRule){ |
415
|
1 |
|
$cond = "$cond AND ({$var[1]->context->sql})"; |
416
|
1 |
|
$params = array_merge($params, $var[1]->context->params); |
417
|
1 |
|
}else{ |
418
|
|
|
$cond = "$cond AND ?"; |
419
|
|
|
$params[] = $var[1]; |
420
|
|
|
} |
421
|
2 |
|
$exprs[] = $cond; |
422
|
2 |
|
}else{ |
423
|
17 |
|
if(is_a($var, Raw::class)){ |
424
|
1 |
|
$exprs[] = "$k $op ".strval($var); |
425
|
17 |
|
}elseif($var instanceof BasicRule){ |
426
|
|
|
$exprs[] = "$k $op {$var->context->sql}"; |
427
|
|
|
$params = array_merge($params, $var->context->params); |
428
|
|
|
}else{ |
429
|
17 |
|
$exprs[] = "$k $op ?"; |
430
|
17 |
|
$params[] = $var; |
431
|
|
|
} |
432
|
|
|
} |
433
|
17 |
|
} |
434
|
|
|
|
435
|
17 |
|
self::condition($context, $prefix, implode(' AND ', $exprs), $params); |
436
|
17 |
|
} |
437
|
30 |
|
static public function condition(Context $context, $prefix, $expr, $args){ |
438
|
30 |
|
if(!empty($expr)){ |
439
|
30 |
|
$expr = "($expr)"; |
440
|
30 |
|
if($args){ |
441
|
|
|
//因为PDO不支持绑定数组变量, 这里需要手动展开数组 |
442
|
|
|
//也就是说把 where("id IN(?)", [1,2]) 展开成 where("id IN(?,?)", 1,2) |
443
|
29 |
|
$cutted = null; |
444
|
29 |
|
$cut = null; |
445
|
29 |
|
$toReplace = array(); |
446
|
|
|
|
447
|
29 |
|
$newArgs=array(); |
448
|
|
|
//找到所有数组对应的?符位置 |
449
|
29 |
|
foreach ($args as $k =>$arg){ |
450
|
29 |
|
if(is_array($arg) || is_a($arg, Raw::class) || is_a($arg, BasicRule::class)){ |
451
|
|
|
|
452
|
2 |
|
if(!$cutted){ |
|
|
|
|
453
|
2 |
|
$cut = new NestedStringCut($expr); |
454
|
2 |
|
$cutted = $cut->getText(); |
455
|
2 |
|
} |
456
|
|
|
//找到第$k个?符 |
457
|
2 |
|
$pos = self::findQ($cutted, 0, $k); |
458
|
2 |
|
$pos = $cut->mapPos($pos); |
459
|
2 |
|
$pos !== false or \PhpBoot\abort( |
460
|
|
|
new \InvalidArgumentException("unmatched params and ? @ $expr")); |
461
|
|
|
|
462
|
2 |
|
if(is_array($arg)){ |
463
|
1 |
|
$stubs = []; |
464
|
1 |
|
foreach ($arg as $i){ |
465
|
1 |
|
if(is_a($i, Raw::class)){ |
466
|
1 |
|
$stubs[] = strval($i); |
467
|
1 |
|
}else{ |
468
|
1 |
|
$stubs[] = '?'; |
469
|
1 |
|
$newArgs[] = $i; |
470
|
|
|
} |
471
|
1 |
|
} |
472
|
1 |
|
$stubs = implode(',', $stubs); |
473
|
2 |
View Code Duplication |
}elseif($arg instanceof BasicRule){ |
|
|
|
|
474
|
1 |
|
$stubs = "({$arg->context->sql})"; |
475
|
1 |
|
$newArgs = array_merge($newArgs, $arg->context->params); |
476
|
1 |
|
}else{ |
477
|
1 |
|
$stubs = strval($arg); |
478
|
|
|
} |
479
|
2 |
|
$toReplace[] = [$pos, $stubs]; |
480
|
|
|
|
481
|
2 |
|
}else{ |
482
|
29 |
|
$newArgs[]=$arg; |
483
|
|
|
} |
484
|
29 |
|
} |
485
|
|
|
|
486
|
29 |
|
if(count($toReplace)){ |
487
|
2 |
|
$toReplace = array_reverse($toReplace); |
488
|
2 |
|
foreach ($toReplace as $i){ |
489
|
2 |
|
list($pos, $v) = $i; |
490
|
2 |
|
$expr = substr($expr, 0, $pos).$v.substr($expr, $pos+1); |
491
|
2 |
|
} |
492
|
2 |
|
$args = $newArgs; |
493
|
2 |
|
} |
494
|
29 |
|
} |
495
|
30 |
|
if($prefix){ |
496
|
30 |
|
$context->appendSql($prefix.' '.$expr); |
497
|
30 |
|
}else{ |
498
|
3 |
|
$context->appendSql($expr); |
499
|
|
|
} |
500
|
|
|
|
501
|
30 |
|
if($args){ |
502
|
29 |
|
$context->appendParams($args); |
503
|
29 |
|
} |
504
|
30 |
|
} |
505
|
30 |
|
} |
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
class GroupByImpl{ |
509
|
5 |
|
static public function groupBy(Context $context, $column){ |
510
|
5 |
|
$column = DB::wrap($column); |
511
|
5 |
|
$context->appendSql("GROUP BY $column"); |
512
|
5 |
|
} |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
class ExecImpl |
516
|
|
|
{ |
517
|
|
|
/** |
518
|
|
|
* |
519
|
|
|
* @param Context $context |
520
|
|
|
* @param $exceOnError boolean whether throw exceptions |
521
|
|
|
* @return ExecResult |
522
|
|
|
* @throws DBException|\Exception |
523
|
|
|
*/ |
524
|
28 |
|
static public function exec($context) { |
525
|
|
|
try{ |
526
|
28 |
|
$st = $context->connection->prepare($context->sql); |
527
|
28 |
|
$success = $st->execute($context->params); |
528
|
28 |
|
return new ExecResult($success, $context->connection, $st); |
529
|
|
|
}catch (\Exception $e){ |
530
|
|
|
\PhpBoot\abort(new DBException($context, $e->getMessage(),$e->getCode()), ['sql'=>$context->sql, 'params'=>$context->params] ); |
531
|
|
|
return null; |
532
|
|
|
} |
533
|
|
|
} |
534
|
|
|
/** |
535
|
|
|
* |
536
|
|
|
* @param Context $context |
537
|
|
|
* @param string|false $asDict return as dict or array |
|
|
|
|
538
|
|
|
* @return false|array |
539
|
|
|
* @throws DBException|\Exception |
540
|
|
|
*/ |
541
|
28 |
|
static public function get($context, $dictAs=false){ |
542
|
|
|
|
543
|
|
|
try{ |
544
|
28 |
|
$st = $context->connection->prepare($context->sql); |
545
|
28 |
|
if($st->execute($context->params)){ |
546
|
|
|
$res = $st->fetchAll(\PDO::FETCH_ASSOC); |
547
|
|
|
if ($dictAs){ |
548
|
|
|
$dict= []; |
549
|
|
|
foreach ($res as $i){ |
550
|
|
|
$dict[$i[$dictAs]]=$i; |
551
|
|
|
} |
552
|
|
|
return $context->handleResult($dict); |
553
|
|
|
} |
554
|
|
|
return $context->handleResult($res); |
555
|
|
|
}else{ |
556
|
28 |
|
return false; |
557
|
|
|
} |
558
|
|
|
}catch (\Exception $e){ |
559
|
|
|
\PhpBoot\abort(new DBException($context, $e->getMessage(),$e->getCode()), ['sql'=>$context->sql, 'params'=>$context->params] ); |
560
|
|
|
return false; |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
} |
564
|
|
|
|
565
|
|
|
/** |
566
|
|
|
* @param Context $context |
567
|
|
|
* @return int|false |
568
|
|
|
* @throws DBException|\Exception |
569
|
|
|
*/ |
570
|
|
|
static public function count($context){ |
571
|
|
|
|
572
|
|
|
$found = []; |
573
|
|
|
if(!preg_match('/\bselect\b/i', $context->sql, $found, PREG_OFFSET_CAPTURE) || |
574
|
|
|
count($found)==0){ |
575
|
|
|
\PhpBoot\abort(new \PDOException("can not use count(*) without select")); |
576
|
|
|
} |
577
|
|
|
list($chars, $columnBegin) = $found[0]; |
|
|
|
|
578
|
|
|
$columnBegin = $columnBegin + strlen('select')+1; |
579
|
|
|
|
580
|
|
|
$columnEnd = 0; |
581
|
|
|
$found = []; |
582
|
|
|
try{ |
583
|
|
|
if(!preg_match('/\bfrom\b/i', $context->sql, $found, PREG_OFFSET_CAPTURE) || |
584
|
|
|
count($found)==0){ |
585
|
|
|
$columnEnd = strlen($context->sql); |
586
|
|
|
}else{ |
587
|
|
|
list($chars, $columnEnd) = $found[0]; |
|
|
|
|
588
|
|
|
} |
589
|
|
|
$sql = substr($context->sql, 0, $columnBegin); |
590
|
|
|
$sql .= ' COUNT(*) as `count` '; |
591
|
|
|
$sql .= substr($context->sql, $columnEnd); |
592
|
|
|
|
593
|
|
|
$st = $context->connection->prepare($sql); |
594
|
|
|
if($st->execute($context->params)){ |
595
|
|
|
$res = $st->fetchAll(\PDO::FETCH_ASSOC); |
596
|
|
|
return intval($res[0]['count']); |
597
|
|
|
}else{ |
598
|
|
|
return false; |
599
|
|
|
} |
600
|
|
|
}catch (\Exception $e){ |
601
|
|
|
\PhpBoot\abort(new DBException($context, $e->getMessage(),$e->getCode()), ['sql'=>$context->sql, 'params'=>$context->params] ); |
602
|
|
|
return false; |
603
|
|
|
} |
604
|
|
|
} |
605
|
|
|
} |
606
|
|
|
class OnDuplicateKeyUpdateImpl |
607
|
|
|
{ |
608
|
1 |
|
public function set($context, $column, $value){ |
609
|
1 |
|
if(is_string($column)){ |
610
|
1 |
|
$this->setExpr($context, $column, $value); |
611
|
1 |
|
}else{ |
612
|
1 |
|
$this->setArgs($context, $column); |
613
|
|
|
} |
614
|
1 |
|
} |
615
|
|
|
|
616
|
1 |
View Code Duplication |
public function setExpr($context, $expr, $args){ |
|
|
|
|
617
|
1 |
|
$prefix = ''; |
|
|
|
|
618
|
1 |
|
if($this->first){ |
619
|
1 |
|
$this->first = false; |
620
|
1 |
|
$prefix = 'ON DUPLICATE KEY UPDATE '; |
621
|
1 |
|
}else{ |
622
|
|
|
$prefix = ','; |
623
|
|
|
} |
624
|
|
|
|
625
|
1 |
|
$context->appendSql("$prefix$expr",$prefix == 'ON DUPLICATE KEY UPDATE '); |
626
|
1 |
|
$context->appendParams($args); |
627
|
|
|
|
628
|
1 |
|
} |
629
|
1 |
View Code Duplication |
public function setArgs($context, $values){ |
|
|
|
|
630
|
1 |
|
$set = []; |
631
|
1 |
|
$params = []; |
632
|
1 |
|
foreach ($values as $k=>$v){ |
633
|
1 |
|
$k = DB::wrap($k); |
634
|
1 |
|
if(is_a($v, Raw::class)){//直接拼接sql,不需要转义 |
635
|
1 |
|
$set[]= "$k=".$v->get(); |
636
|
1 |
|
}else{ |
637
|
|
|
$set[]= "$k=?"; |
638
|
|
|
$params[]=$v; |
639
|
|
|
} |
640
|
1 |
|
} |
641
|
1 |
|
if($this->first){ |
642
|
1 |
|
$this->first = false; |
643
|
1 |
|
$context->appendSql('ON DUPLICATE KEY UPDATE '.implode(',', $set)); |
644
|
1 |
|
$context->appendParams($params); |
645
|
1 |
|
}else{ |
646
|
|
|
$context->appendSql(','.implode(',', $set),false); |
647
|
|
|
$context->appendParams($params); |
648
|
|
|
} |
649
|
1 |
|
} |
650
|
|
|
private $first=true; |
651
|
|
|
} |
652
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.