1: | <?php
|
2: | |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | |
9: | |
10: | |
11: | |
12: | |
13: | |
14: |
|
15: |
|
16: | namespace LucidFrame\Core;
|
17: |
|
18: | |
19: | |
20: |
|
21: | class QueryBuilder
|
22: | {
|
23: | const EXP_CONDITION = '__QueryBuilder::condition__';
|
24: | const EXP_RAW = '__QueryBuilder::raw__';
|
25: |
|
26: |
|
27: | protected $table;
|
28: |
|
29: | protected $alias;
|
30: |
|
31: | protected $joins;
|
32: |
|
33: | protected $fields;
|
34: |
|
35: | protected $where;
|
36: |
|
37: | protected $exist = array();
|
38: |
|
39: | protected $notExist = array();
|
40: |
|
41: | protected $orderBy;
|
42: |
|
43: | protected $groupBy;
|
44: |
|
45: | protected $having;
|
46: |
|
47: | protected $offset;
|
48: |
|
49: | protected $limit;
|
50: |
|
51: | protected $sql;
|
52: |
|
53: | protected $aggregates = array();
|
54: |
|
55: | private $result;
|
56: |
|
57: | private $whereType = 'AND';
|
58: |
|
59: | protected static $bindValues = array();
|
60: |
|
61: | private static $operators = array(
|
62: | '=', '>=', '<=', '>', '<', '!=', '<>',
|
63: | 'not', 'in',
|
64: | 'between', 'nbetween',
|
65: | 'like', 'like%%', 'like%~', 'like~%',
|
66: | 'nlike', 'nlike%%', 'nlike%~', 'nlike~%'
|
67: | );
|
68: | private static $eqs = array(
|
69: | 'eq' => '=',
|
70: | 'neq' => '!=',
|
71: | 'lt' => '<',
|
72: | 'lte' => '<=',
|
73: | 'gt' => '>',
|
74: | 'gte' => '>=',
|
75: | );
|
76: |
|
77: | private static $likes = array(
|
78: | 'like' => 'LIKE CONCAT("%", :placeholder, "%")',
|
79: | 'like%~' => 'LIKE CONCAT("%", :placeholder)',
|
80: | 'like~%' => 'LIKE CONCAT(:placeholder, "%")',
|
81: | 'nlike' => 'NOT LIKE CONCAT("%", :placeholder, "%")',
|
82: | 'nlike%~' => 'NOT LIKE CONCAT("%", :placeholder)',
|
83: | 'nlike~%' => 'NOT LIKE CONCAT(:placeholder, "%")',
|
84: | );
|
85: |
|
86: | private static $betweens = array(
|
87: | 'between' => 'BETWEEN',
|
88: | 'nbetween' => 'NOT BETWEEN',
|
89: | );
|
90: |
|
91: | private static $joinTypes = array('INNER', 'LEFT', 'RIGHT', 'OUTER');
|
92: |
|
93: | private static $functions = array(
|
94: | 'ABS', 'ADDDATE', 'ADDTIME', 'AVG',
|
95: | 'CONCAT', 'COUNT', 'CUR_DATE', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
|
96: | 'DATE', 'DATE_ADD', 'DATE_FORMAT', 'DATE_SUB', 'DATEDIFF',
|
97: | 'DAY', 'DAYNAME', 'DAYOFMONTH', 'DAYOFWEEK', 'DAYOFYEAR',
|
98: | 'LEFT', 'LENGTH', 'LOCATE', 'LOWER', 'LPAD', 'LTRIM', 'MAX', 'MIN', 'MOD', 'MONTH', 'MONTHNAME', 'NOW',
|
99: | 'RIGHT', 'RPAD', 'RTRIM', 'SIZE', 'SQRT', 'SUBDATE', 'SUBSTR', 'SUBSTRING', 'SUBTIME', 'SUM',
|
100: | 'TRIM', 'TIME', 'TIMEDIFF', 'TIMESTAMP', 'TIMESTAMPADD', 'TIMESTAMPDIFF',
|
101: | 'UPPER', 'WEEK', 'WEEKDAY', 'WEEKOFYEAR', 'YEAR'
|
102: | );
|
103: |
|
104: | |
105: | |
106: | |
107: | |
108: | |
109: | |
110: |
|
111: | public function __construct($table = null, $alias = null)
|
112: | {
|
113: | self::clearBindValues();
|
114: |
|
115: | $this->from($table, $alias);
|
116: | }
|
117: |
|
118: | |
119: | |
120: | |
121: | |
122: | |
123: | |
124: | |
125: |
|
126: | public function from($table, $alias = null)
|
127: | {
|
128: | if (self::validateName($table) === false) {
|
129: | return $this;
|
130: | }
|
131: |
|
132: | if ($this->alias && self::validateName($alias) === false) {
|
133: | $alias = $table;
|
134: | }
|
135: |
|
136: | if ($alias === null) {
|
137: | $alias = $table;
|
138: | }
|
139: |
|
140: | $this->table = db_table($table);
|
141: | $this->alias = $alias;
|
142: |
|
143: | return $this;
|
144: | }
|
145: |
|
146: | |
147: | |
148: | |
149: | |
150: | |
151: | |
152: | |
153: |
|
154: | public function fields($alias, array $fields = array())
|
155: | {
|
156: | if (!$fields || count($fields) === 0) {
|
157: | $fields = array('*');
|
158: | }
|
159: | $this->fields[$alias] = $fields;
|
160: |
|
161: | return $this;
|
162: | }
|
163: |
|
164: | |
165: | |
166: | |
167: | |
168: | |
169: | |
170: | |
171: |
|
172: | public function field($field, $alias = null)
|
173: | {
|
174: | $this->fields['*'][] = $alias ? array($field, $alias) : $field;
|
175: |
|
176: | return $this;
|
177: | }
|
178: |
|
179: | |
180: | |
181: | |
182: | |
183: | |
184: | |
185: | |
186: |
|
187: | private function prepareField($table, $field)
|
188: | {
|
189: | if ($table === '*') {
|
190: | return is_array($field) ? $field[0] . ' ' . $field[1] : $field;
|
191: | }
|
192: |
|
193: | if ($field === '*') {
|
194: | return self::quote($table) . '.' . $field;
|
195: | } else {
|
196: | if (is_array($field)) {
|
197: | if (count($field) != 2) {
|
198: | return null;
|
199: | }
|
200: |
|
201: |
|
202: | list($fieldName, $alias) = $field;
|
203: |
|
204: | $f = self::quote($fieldName);
|
205: | if (substr($f, 0, 1) !== '`') {
|
206: | if (self::isRawExp($f)) {
|
207: | $f = self::parseFromRawExp($f);
|
208: | }
|
209: |
|
210: | return $f . ' AS ' . $alias;
|
211: | } else {
|
212: | if (self::isRawExp($f)) {
|
213: | return self::parseFromRawExp($f) . ' AS ' . self::quote($alias);
|
214: | }
|
215: |
|
216: | return self::quote($table) . '.' . $f . ' AS ' . self::quote($alias);
|
217: | }
|
218: | } else {
|
219: |
|
220: | $f = self::quote($field);
|
221: | if (substr($f, 0, 1) !== '`') {
|
222: | return self::isRawExp($f) ? self::parseFromRawExp($f) : $f;
|
223: | } else {
|
224: | if (self::isRawExp($f)) {
|
225: | return self::parseFromRawExp($f);
|
226: | }
|
227: |
|
228: | return self::quote($table) . '.' . $f;
|
229: | }
|
230: | }
|
231: | }
|
232: | }
|
233: |
|
234: | |
235: | |
236: | |
237: | |
238: | |
239: | |
240: | |
241: | |
242: | |
243: |
|
244: | public function join($table, $alias, $condition, $type = 'INNER')
|
245: | {
|
246: | if (self::validateName($table) === false || self::validateName($alias) === false) {
|
247: | return $this;
|
248: | }
|
249: |
|
250: | $type = strtoupper($type);
|
251: |
|
252: | if (!in_array($type, self::$joinTypes)) {
|
253: | $type = 'INNER';
|
254: | }
|
255: |
|
256: | $this->joins[] = array(
|
257: | 'table' => db_table($table),
|
258: | 'alias' => $alias === null ? $table : $alias,
|
259: | 'condition' => $condition,
|
260: | 'type' => $type
|
261: | );
|
262: |
|
263: | return $this;
|
264: | }
|
265: |
|
266: | |
267: | |
268: | |
269: | |
270: | |
271: | |
272: | |
273: | |
274: |
|
275: | public function leftJoin($table, $alias, $condition)
|
276: | {
|
277: | $this->join($table, $alias, $condition, 'left');
|
278: |
|
279: | return $this;
|
280: | }
|
281: |
|
282: | |
283: | |
284: | |
285: | |
286: | |
287: | |
288: | |
289: | |
290: |
|
291: | public function rightJoin($table, $alias, $condition)
|
292: | {
|
293: | $this->join($table, $alias, $condition, 'right');
|
294: |
|
295: | return $this;
|
296: | }
|
297: |
|
298: | |
299: | |
300: | |
301: | |
302: | |
303: | |
304: | |
305: | |
306: |
|
307: | public function outerJoin($table, $alias, $condition)
|
308: | {
|
309: | $this->join($table, $alias, $condition, 'outer');
|
310: |
|
311: | return $this;
|
312: | }
|
313: |
|
314: | |
315: | |
316: | |
317: | |
318: | |
319: | |
320: |
|
321: | public function where($condition = null)
|
322: | {
|
323: | return $this->andWhere($condition);
|
324: | }
|
325: |
|
326: | |
327: | |
328: | |
329: | |
330: | |
331: | |
332: | |
333: | |
334: | |
335: | |
336: | |
337: | |
338: | |
339: | |
340: | |
341: | |
342: | |
343: | |
344: | |
345: | |
346: | |
347: | |
348: | |
349: | |
350: |
|
351: | public function andWhere($condition = null)
|
352: | {
|
353: | if (is_array($condition) && count($condition) == 0) {
|
354: | return $this;
|
355: | }
|
356: |
|
357: | if ($condition === null) {
|
358: | $this->where['AND'] = array();
|
359: | } else {
|
360: | $this->where['AND'][] = self::buildCondition($condition, 'AND');
|
361: | }
|
362: | $this->whereType = 'AND';
|
363: |
|
364: | return $this;
|
365: | }
|
366: |
|
367: | |
368: | |
369: | |
370: | |
371: | |
372: | |
373: | |
374: | |
375: | |
376: | |
377: | |
378: | |
379: | |
380: | |
381: | |
382: | |
383: | |
384: | |
385: | |
386: | |
387: | |
388: | |
389: | |
390: | |
391: |
|
392: | public function orWhere($condition = null)
|
393: | {
|
394: | if (is_array($condition) && count($condition) == 0) {
|
395: | return $this;
|
396: | }
|
397: |
|
398: | if ($condition === null) {
|
399: | $this->where['OR'] = array();
|
400: | } else {
|
401: | $this->where['OR'][] = self::buildCondition($condition, 'OR');
|
402: | }
|
403: | $this->whereType = 'OR';
|
404: |
|
405: | return $this;
|
406: | }
|
407: |
|
408: | |
409: | |
410: | |
411: | |
412: | |
413: | |
414: | |
415: | |
416: | |
417: | |
418: | |
419: | |
420: |
|
421: | public function condition($field, $value)
|
422: | {
|
423: | if (isset($this->where[$this->whereType][$field])) {
|
424: | $field .= uniqid('__' . trim(__METHOD__, 'LucidFrame\Core') . '__');
|
425: | }
|
426: | $this->where[$this->whereType][$field] = $value;
|
427: |
|
428: | return $this;
|
429: | }
|
430: |
|
431: | |
432: | |
433: | |
434: | |
435: | |
436: |
|
437: | public function exists($subquery, $type = 'AND')
|
438: | {
|
439: | $this->exist[] = array(
|
440: | 'query' => self::raw($subquery),
|
441: | 'type' => strtoupper($type)
|
442: | );
|
443: |
|
444: | return $this;
|
445: | }
|
446: |
|
447: | |
448: | |
449: | |
450: | |
451: | |
452: |
|
453: | public function notExists($subquery, $type = 'AND')
|
454: | {
|
455: | $this->notExist[] = array(
|
456: | 'query' => self::raw($subquery),
|
457: | 'type' => strtoupper($type)
|
458: | );
|
459: |
|
460: | return $this;
|
461: | }
|
462: |
|
463: | |
464: | |
465: | |
466: | |
467: |
|
468: | public function orExists($subquery)
|
469: | {
|
470: | return $this->exists($subquery, 'OR');
|
471: | }
|
472: |
|
473: | |
474: | |
475: | |
476: | |
477: |
|
478: | public function orNotExists($subquery)
|
479: | {
|
480: | return $this->notExists($subquery, 'OR');
|
481: | }
|
482: |
|
483: | |
484: | |
485: | |
486: | |
487: | |
488: | |
489: | |
490: |
|
491: | public function orderBy($field, $sort = 'ASC')
|
492: | {
|
493: | $sort = strtoupper($sort);
|
494: | if (!in_array($sort, array('ASC', 'DESC'))) {
|
495: | $sort = 'ASC';
|
496: | }
|
497: | $this->orderBy[$field] = $sort;
|
498: |
|
499: | return $this;
|
500: | }
|
501: |
|
502: | |
503: | |
504: | |
505: | |
506: | |
507: | |
508: |
|
509: | public function groupBy($field)
|
510: | {
|
511: | $this->groupBy[] = $field;
|
512: | $this->groupBy = array_unique($this->groupBy);
|
513: |
|
514: | return $this;
|
515: | }
|
516: |
|
517: | |
518: | |
519: | |
520: | |
521: | |
522: | |
523: | |
524: | |
525: | |
526: | |
527: | |
528: | |
529: | |
530: | |
531: | |
532: | |
533: | |
534: | |
535: | |
536: | |
537: | |
538: | |
539: | |
540: | |
541: |
|
542: | public function having(array $condition)
|
543: | {
|
544: | return $this->andHaving($condition);
|
545: | }
|
546: |
|
547: | |
548: | |
549: | |
550: | |
551: | |
552: |
|
553: | public function andHaving(array $condition)
|
554: | {
|
555: | return $this->addHaving($condition, 'AND');
|
556: | }
|
557: |
|
558: | |
559: | |
560: | |
561: | |
562: | |
563: |
|
564: | public function orHaving(array $condition = array())
|
565: | {
|
566: | return $this->addHaving($condition, 'OR');
|
567: | }
|
568: |
|
569: | |
570: | |
571: | |
572: | |
573: | |
574: | |
575: |
|
576: | private function addHaving(array $condition, $type)
|
577: | {
|
578: | list($clause, $values) = self::buildCondition($condition, $type);
|
579: |
|
580: | $this->having = $clause;
|
581: | self::addBindValues($values);
|
582: |
|
583: | return $this;
|
584: | }
|
585: |
|
586: | |
587: | |
588: | |
589: | |
590: | |
591: | |
592: | |
593: | |
594: | |
595: |
|
596: | public function limit()
|
597: | {
|
598: | $args = func_get_args();
|
599: | if (count($args) === 2 && is_numeric($args[0]) && is_numeric($args[1])) {
|
600: | $this->offset = $args[0];
|
601: | $this->limit = $args[1];
|
602: | } elseif (count($args) === 1 && is_numeric($args[0])) {
|
603: | $this->limit = $args[0];
|
604: | }
|
605: |
|
606: | return $this;
|
607: | }
|
608: |
|
609: | |
610: | |
611: | |
612: | |
613: | |
614: | |
615: | |
616: |
|
617: | public function count($field = null, $alias = null)
|
618: | {
|
619: | $this->setAggregate('count', $field, $alias);
|
620: |
|
621: | return $this;
|
622: | }
|
623: |
|
624: | |
625: | |
626: | |
627: | |
628: | |
629: | |
630: | |
631: |
|
632: | public function max($field, $alias = null)
|
633: | {
|
634: | $this->setAggregate('max', $field, $alias);
|
635: |
|
636: | return $this;
|
637: | }
|
638: |
|
639: | |
640: | |
641: | |
642: | |
643: | |
644: | |
645: | |
646: |
|
647: | public function min($field, $alias = null)
|
648: | {
|
649: | $this->setAggregate('min', $field, $alias);
|
650: |
|
651: | return $this;
|
652: | }
|
653: |
|
654: | |
655: | |
656: | |
657: | |
658: | |
659: | |
660: | |
661: |
|
662: | public function sum($field, $alias = null)
|
663: | {
|
664: | $this->setAggregate('sum', $field, $alias);
|
665: |
|
666: | return $this;
|
667: | }
|
668: |
|
669: | |
670: | |
671: | |
672: | |
673: | |
674: | |
675: | |
676: |
|
677: | public function avg($field, $alias = null)
|
678: | {
|
679: | $this->setAggregate('avg', $field, $alias);
|
680: |
|
681: | return $this;
|
682: | }
|
683: |
|
684: | |
685: | |
686: | |
687: | |
688: | |
689: | |
690: | |
691: | |
692: |
|
693: | protected function setAggregate($name, $field = null, $alias = null)
|
694: | {
|
695: | if (!isset($this->aggregates[$name])) {
|
696: | $this->aggregates[$name] = array();
|
697: | }
|
698: | $field = ($field === null) ? '*' : $field;
|
699: | $this->aggregates[$name][$field] = ($alias === null) ? $field : array($field, $alias);
|
700: |
|
701: | return $this;
|
702: | }
|
703: |
|
704: | |
705: | |
706: | |
707: | |
708: |
|
709: | protected function buildSQL()
|
710: | {
|
711: | $sql = 'SELECT ';
|
712: |
|
713: | $select = array();
|
714: | if ($this->fields) {
|
715: | foreach ($this->fields as $tableAlias => $field) {
|
716: | foreach ($field as $f) {
|
717: | $readyField = $this->prepareField($tableAlias, $f);
|
718: | if ($readyField) {
|
719: | $select[] = $readyField;
|
720: | }
|
721: | }
|
722: | }
|
723: | }
|
724: |
|
725: | if (count($this->aggregates)) {
|
726: | foreach ($this->aggregates as $func => $fields) {
|
727: | $func = strtoupper($func);
|
728: | foreach ($fields as $field) {
|
729: | if (is_array($field)) {
|
730: | $select[] = $func . '(' . self::quote($field[0]) . ') ' . self::quote($field[1]);
|
731: | } else {
|
732: | $select[] = $func . '(' . self::quote($field) . ')';
|
733: | }
|
734: | }
|
735: | }
|
736: | }
|
737: |
|
738: | if (count($select) === 0) {
|
739: | $select = array(self::quote($this->alias) . '.*');
|
740: | }
|
741: |
|
742: | $sql .= implode(', ', $select);
|
743: |
|
744: |
|
745: | $sql .= ' FROM ' . self::quote($this->table) . ' ' . self::quote($this->alias);
|
746: |
|
747: |
|
748: | if ($this->joins) {
|
749: | $joins = array();
|
750: | foreach ($this->joins as $join) {
|
751: | $join = (object)$join;
|
752: | if (preg_match_all('/([a-z0-9_]+\.[a-z0-9_]+)/i', $join->condition, $matches)) {
|
753: | $matchedFields = array_unique($matches[0]);
|
754: | foreach ($matchedFields as $field) {
|
755: | $join->condition = str_replace($field, self::quote($field), $join->condition);
|
756: | }
|
757: | }
|
758: | $joins[] = $join->type . ' JOIN '
|
759: | . self::quote($join->table) . ' ' . self::quote($join->alias)
|
760: | . ' ON ' . $join->condition;
|
761: | }
|
762: | $sql .= ' ' . implode(' ', $joins);
|
763: | }
|
764: |
|
765: |
|
766: | if (is_array($this->where)) {
|
767: | $sql .= ' WHERE 1 = 1';
|
768: | foreach ($this->where as $key => $where) {
|
769: | if ($key == 'AND') {
|
770: | list($clause, $values) = self::buildCondition($where, 'AND');
|
771: | $sql .= ' AND ' . $clause;
|
772: | self::addBindValues($values);
|
773: | } elseif ($key == 'OR') {
|
774: | list($clause, $values) = self::buildCondition($where, 'OR');
|
775: | $sql .= ' AND ' . $clause;
|
776: | self::addBindValues($values);
|
777: | } elseif ($key == 'NOT') {
|
778: | list($clause, $values) = self::buildCondition($where, 'NOT');
|
779: | $sql .= ' AND ' . $clause;
|
780: | self::addBindValues($values);
|
781: | }
|
782: | }
|
783: | } elseif (is_string($this->where)) {
|
784: | $sql .= ' WHERE ' . $this->where;
|
785: | }
|
786: |
|
787: |
|
788: | $exists = array();
|
789: | if (!empty($this->exist)) {
|
790: | foreach ($this->exist as $exist) {
|
791: | $subquery = self::isRawExp($exist['query']) ? self::parseFromRawExp($exist['query']) : $exist['query'];
|
792: | $exists[] = " $exist[type] EXISTS ($subquery)";
|
793: | }
|
794: | }
|
795: |
|
796: |
|
797: | if (!empty($this->notExist)) {
|
798: | foreach ($this->notExist as $exist) {
|
799: | $subquery = self::isRawExp($exist['query']) ? self::parseFromRawExp($exist['query']) : $exist['query'];
|
800: | $exists[] = " $exist[type] NOT EXISTS ($subquery)";
|
801: | }
|
802: | }
|
803: |
|
804: | $sql = $this->appendExistClauses($exists, $sql);
|
805: |
|
806: |
|
807: | if ($this->groupBy) {
|
808: | $groupBy = array();
|
809: | foreach ($this->groupBy as $field) {
|
810: | if (self::isRawExp($field)) {
|
811: | $groupBy[] = self::parseFromRawExp($field);
|
812: | continue;
|
813: | }
|
814: |
|
815: | $groupBy[] = self::quote($field);
|
816: | }
|
817: | $sql .= ' GROUP BY ' . implode(', ', $groupBy);
|
818: | }
|
819: |
|
820: |
|
821: | if ($this->having) {
|
822: | $sql .= ' HAVING ' . $this->having;
|
823: | }
|
824: |
|
825: |
|
826: | if ($this->orderBy) {
|
827: | $orderBy = array();
|
828: | foreach ($this->orderBy as $field => $sort) {
|
829: | if (self::isRawExp($field)) {
|
830: | $orderBy[] = self::parseFromRawExp($field);
|
831: | continue;
|
832: | }
|
833: |
|
834: | $orderBy[] = self::quote($field) . ' ' . $sort;
|
835: | }
|
836: | $sql .= ' ORDER BY ' . implode(', ', $orderBy);
|
837: | }
|
838: |
|
839: |
|
840: | if ($this->offset !== null && $this->limit) {
|
841: | $sql .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
|
842: | } elseif ($this->limit && $this->offset === null) {
|
843: | $sql .= ' LIMIT ' . $this->limit;
|
844: | }
|
845: |
|
846: | $this->sql = $sql;
|
847: |
|
848: | return $this;
|
849: | }
|
850: |
|
851: | |
852: | |
853: | |
854: | |
855: | |
856: |
|
857: | protected function appendExistClauses(array $exists, $sql)
|
858: | {
|
859: | if (!count($exists)) {
|
860: | return $sql;
|
861: | }
|
862: |
|
863: | $clause = implode('', $exists);
|
864: | if (!empty($this->where)) {
|
865: |
|
866: | $sql .= $clause;
|
867: | } else {
|
868: |
|
869: | $clause = preg_replace('/^(AND|OR)\s+/', '', trim($clause));
|
870: | $sql .= ' WHERE ' . $clause;
|
871: | }
|
872: |
|
873: | return $sql;
|
874: | }
|
875: |
|
876: | |
877: | |
878: | |
879: | |
880: |
|
881: | public function execute()
|
882: | {
|
883: | $this->buildSQL();
|
884: |
|
885: | if ($this->sql) {
|
886: | $this->result = db_query($this->sql, self::$bindValues);
|
887: | }
|
888: |
|
889: | self::clearBindValues();
|
890: |
|
891: | return $this->result;
|
892: | }
|
893: |
|
894: | |
895: | |
896: | |
897: |
|
898: | public function getNumRows()
|
899: | {
|
900: | if ($this->result === null) {
|
901: | $this->execute();
|
902: | }
|
903: |
|
904: | if ($this->result) {
|
905: | return db_numRows($this->result);
|
906: | }
|
907: |
|
908: | return 0;
|
909: | }
|
910: |
|
911: | |
912: | |
913: | |
914: | |
915: | |
916: | |
917: | |
918: | |
919: | |
920: |
|
921: | public function fetchRow($resultType = LC_FETCH_OBJECT)
|
922: | {
|
923: | if ($this->result === null) {
|
924: | $this->execute();
|
925: | }
|
926: |
|
927: | if ($this->result) {
|
928: | if ($row = db_fetchAssoc($this->result)) {
|
929: | if ($resultType === LC_FETCH_ARRAY) {
|
930: | return array_values($row);
|
931: | } elseif ($resultType === LC_FETCH_OBJECT) {
|
932: | return (object)$row;
|
933: | } else {
|
934: | return $row;
|
935: | }
|
936: | }
|
937: | }
|
938: |
|
939: | return null;
|
940: | }
|
941: |
|
942: | |
943: | |
944: | |
945: | |
946: | |
947: |
|
948: | public function getResult()
|
949: | {
|
950: | if ($this->result === null) {
|
951: | $this->execute();
|
952: | }
|
953: |
|
954: | $data = array();
|
955: | if ($this->result) {
|
956: | while ($row = db_fetchObject($this->result)) {
|
957: | $data[] = $row;
|
958: | }
|
959: | }
|
960: |
|
961: | return $data;
|
962: | }
|
963: |
|
964: | |
965: | |
966: | |
967: | |
968: | |
969: |
|
970: | public function getResultArray()
|
971: | {
|
972: | if ($this->result === null) {
|
973: | $this->execute();
|
974: | }
|
975: |
|
976: | $data = array();
|
977: | if ($this->result) {
|
978: | while ($row = db_fetchAssoc($this->result)) {
|
979: | $data[] = $row;
|
980: | }
|
981: | }
|
982: |
|
983: | return $data;
|
984: | }
|
985: |
|
986: | |
987: | |
988: | |
989: | |
990: | |
991: | |
992: |
|
993: | public function getList($keyField = 'id', $valueField = 'name')
|
994: | {
|
995: | if ($this->result === null) {
|
996: | $this->execute();
|
997: | }
|
998: |
|
999: | $data = array();
|
1000: | if ($this->result) {
|
1001: | while ($row = db_fetchObject($this->result)) {
|
1002: | $data[$row->{$keyField}] = $row->{$valueField};
|
1003: | }
|
1004: | }
|
1005: |
|
1006: | return $data;
|
1007: | }
|
1008: |
|
1009: | |
1010: | |
1011: | |
1012: | |
1013: | |
1014: |
|
1015: | public function getSingleResult()
|
1016: | {
|
1017: | $this->limit(1);
|
1018: |
|
1019: | if ($this->result === null) {
|
1020: | $this->execute();
|
1021: | }
|
1022: |
|
1023: | if ($row = db_fetchObject($this->result)) {
|
1024: | return $row;
|
1025: | }
|
1026: |
|
1027: | return null;
|
1028: | }
|
1029: |
|
1030: | |
1031: | |
1032: | |
1033: | |
1034: | |
1035: |
|
1036: | public function fetch()
|
1037: | {
|
1038: | $this->limit(1);
|
1039: |
|
1040: | if ($this->result === null) {
|
1041: | $this->execute();
|
1042: | }
|
1043: |
|
1044: | if ($this->result && $row = db_fetchArray($this->result)) {
|
1045: | return $row[0];
|
1046: | }
|
1047: |
|
1048: | return null;
|
1049: | }
|
1050: |
|
1051: | |
1052: | |
1053: | |
1054: | |
1055: |
|
1056: | public function getSQL()
|
1057: | {
|
1058: | if ($this->sql === null) {
|
1059: | $this->buildSQL();
|
1060: | }
|
1061: |
|
1062: | return $this->sql;
|
1063: | }
|
1064: |
|
1065: | |
1066: | |
1067: | |
1068: |
|
1069: | public function getReadySQL()
|
1070: | {
|
1071: | $sql = $this->getSQL();
|
1072: |
|
1073: | foreach (QueryBuilder::getBindValues() as $key => $value) {
|
1074: | if (is_string($value) && !self::hasQuote($value)) {
|
1075: | $value = '"' . $value . '"';
|
1076: | }
|
1077: |
|
1078: | $sql = preg_replace('/' . $key . '\b/', $value, $sql);
|
1079: | }
|
1080: |
|
1081: | return $sql;
|
1082: | }
|
1083: |
|
1084: | |
1085: | |
1086: | |
1087: | |
1088: | |
1089: |
|
1090: | public static function validateName($name)
|
1091: | {
|
1092: | if (!is_string($name)) {
|
1093: | return false;
|
1094: | }
|
1095: |
|
1096: | return preg_match('/^[A-Za-z0-9_]+$/', $name);
|
1097: | }
|
1098: |
|
1099: | |
1100: | |
1101: | |
1102: | |
1103: | |
1104: |
|
1105: | public static function quote($name)
|
1106: | {
|
1107: | $name = trim($name);
|
1108: |
|
1109: | if ($name === '*' || self::isRawExp($name)) {
|
1110: | return $name;
|
1111: | }
|
1112: |
|
1113: | foreach (self::$functions as $func) {
|
1114: | if (stripos($name, $func) === 0) {
|
1115: | return $name;
|
1116: | }
|
1117: | }
|
1118: |
|
1119: | if (strpos($name, '.') !== false) {
|
1120: | $name = str_replace('.', '`.`', $name);
|
1121: | }
|
1122: |
|
1123: | return '`' . $name . '`';
|
1124: | }
|
1125: |
|
1126: | |
1127: | |
1128: | |
1129: | |
1130: |
|
1131: | public static function hasQuote($value)
|
1132: | {
|
1133: | return preg_match_all('/(`[a-z0-9_-]+`\.`[a-z0-9_-]+`)/i', $value);
|
1134: | }
|
1135: |
|
1136: | |
1137: | |
1138: | |
1139: | |
1140: | |
1141: |
|
1142: | public static function raw($expression, array $values = array())
|
1143: | {
|
1144: | return vsprintf(self::EXP_RAW . $expression, $values);
|
1145: | }
|
1146: |
|
1147: | |
1148: | |
1149: | |
1150: | |
1151: |
|
1152: | private static function isRawExp($field)
|
1153: | {
|
1154: | return is_string($field) && strpos($field, self::EXP_RAW) !== false;
|
1155: | }
|
1156: |
|
1157: | |
1158: | |
1159: | |
1160: | |
1161: |
|
1162: | private static function parseFromRawExp($field)
|
1163: | {
|
1164: | if (self::isRawExp($field)) {
|
1165: | return substr($field, strlen(self::EXP_RAW));
|
1166: | }
|
1167: |
|
1168: | return $field;
|
1169: | }
|
1170: |
|
1171: | |
1172: | |
1173: | |
1174: | |
1175: | |
1176: | |
1177: | |
1178: | |
1179: | |
1180: | |
1181: | |
1182: | |
1183: | |
1184: | |
1185: | |
1186: | |
1187: |
|
1188: | public static function buildCondition($cond = array(), $type = 'AND')
|
1189: | {
|
1190: | if (!is_array($cond)) {
|
1191: | return $cond;
|
1192: | }
|
1193: |
|
1194: | if (empty($cond)) {
|
1195: | return array('', array());
|
1196: | }
|
1197: |
|
1198: | $type = strtoupper($type);
|
1199: | $condition = array();
|
1200: |
|
1201: | foreach ($cond as $field => $value) {
|
1202: | $field = trim($field);
|
1203: | if (self::isRawExp($value)) {
|
1204: | $value = self::quote(self::parseFromRawExp($value));
|
1205: | }
|
1206: |
|
1207: | if (in_array(strtolower($field), array('$and', '$or', '$not'))) {
|
1208: | $field = substr($field, 1);
|
1209: | if (strtoupper($field) == 'NOT') {
|
1210: | list($nestedClause, $values) = self::buildCondition($value, 'AND');
|
1211: | $condition[] = 'NOT (' . $nestedClause . ')';
|
1212: | } else {
|
1213: | list($nestedClause, $values) = self::buildCondition($value, $field);
|
1214: | $condition[] = '(' . $nestedClause . ')';
|
1215: | }
|
1216: | self::addBindValues($values);
|
1217: | continue;
|
1218: | }
|
1219: |
|
1220: | $fieldOpr = explode(' ', $field);
|
1221: | $field = trim($fieldOpr[0]);
|
1222: |
|
1223: | if (strpos($field, self::EXP_CONDITION) !== false) {
|
1224: | $field = substr($field, 0, strpos($field, self::EXP_CONDITION));
|
1225: | }
|
1226: |
|
1227: | $opr = count($fieldOpr) === 2 ? trim($fieldOpr[1]) : '=';
|
1228: | $opr = strtolower($opr);
|
1229: |
|
1230: |
|
1231: | if (!in_array($opr, self::$operators)) {
|
1232: | $opr = '=';
|
1233: | }
|
1234: |
|
1235: | if (is_numeric($field)) {
|
1236: |
|
1237: |
|
1238: | list($nestedClause, $values) = $value;
|
1239: | $condition[] = '( ' . $nestedClause . ' )';
|
1240: | self::addBindValues($values);
|
1241: | } else {
|
1242: |
|
1243: |
|
1244: | if (in_array($opr, array('between', 'nbetween')) && !is_array($value)) {
|
1245: | $opr = '=';
|
1246: | }
|
1247: |
|
1248: | $key = $field;
|
1249: | $placeholder = self::getPlaceholder($key, self::$bindValues);
|
1250: | $field = self::quote($field);
|
1251: |
|
1252: | if ($opr == 'in') {
|
1253: | if (self::isRawExp($value)) {
|
1254: | $condition[] = $field . ' IN (' . self::parseFromRawExp($value) . ')';
|
1255: | } else {
|
1256: | $condition[] = $field . ' IN (' . $placeholder . ')';
|
1257: | self::setBindValue($placeholder, is_array($value) ? implode(', ', $value) : $value);
|
1258: | }
|
1259: | continue;
|
1260: | }
|
1261: |
|
1262: | if (array_key_exists($opr, self::$likes)) {
|
1263: | $condition[] = $field . ' ' . str_replace(':placeholder', $placeholder, self::$likes[$opr]);
|
1264: | self::setBindValue($placeholder, $value);
|
1265: | continue;
|
1266: | }
|
1267: |
|
1268: | if (is_null($value)) {
|
1269: | if (in_array($opr, array('!=', '<>'))) {
|
1270: | $condition[] = $field . ' IS NOT NULL';
|
1271: | } else {
|
1272: | $condition[] = $field . ' IS NULL';
|
1273: | }
|
1274: | continue;
|
1275: | }
|
1276: |
|
1277: | if (is_array($value) && count($value)) {
|
1278: | if ($opr === 'between' || $opr === 'nbetween') {
|
1279: | $condition[] = sprintf(
|
1280: | '(%s %s :%s_from AND :%s_to)',
|
1281: | $field,
|
1282: | self::$betweens[$opr],
|
1283: | $key,
|
1284: | $key
|
1285: | );
|
1286: |
|
1287: | self::setBindValue($placeholder . '_from', $value[0]);
|
1288: | self::setBindValue($placeholder . '_to', $value[1]);
|
1289: | } else {
|
1290: | $inPlaceholders = array();
|
1291: | foreach ($value as $i => $val) {
|
1292: | $holder = preg_replace('/(\d)*/', '', $placeholder);
|
1293: | $holder = $holder . $i;
|
1294: | $inPlaceholders[] = $holder;
|
1295: | self::setBindValue($holder, $val);
|
1296: | }
|
1297: |
|
1298: | $condition[] = sprintf(
|
1299: | '%s%sIN (%s)',
|
1300: | $field,
|
1301: | $opr === '!=' ? ' NOT ' : ' ',
|
1302: | implode(', ', $inPlaceholders)
|
1303: | );
|
1304: | }
|
1305: | continue;
|
1306: | }
|
1307: |
|
1308: | $condition[] = "{$field} {$opr} {$placeholder}";
|
1309: | self::setBindValue($placeholder, $value);
|
1310: | }
|
1311: | }
|
1312: |
|
1313: | if (count($condition)) {
|
1314: | return array(
|
1315: | implode(" {$type} ", $condition),
|
1316: | self::$bindValues,
|
1317: | );
|
1318: | }
|
1319: |
|
1320: | return array('', array());
|
1321: | }
|
1322: |
|
1323: | private static function getPlaceholder($key, $values = array())
|
1324: | {
|
1325: | $specChars = array(
|
1326: | '`','~','!','@','#','$','%','\^','&',
|
1327: | '*','(',')','=','+','{','}','[',']',
|
1328: | ':',';',"'",'"','<','>','\\','|','?','/',',','.'
|
1329: | );
|
1330: |
|
1331: | $key = str_replace($specChars, '_', $key);
|
1332: |
|
1333: | $placeholders = array_filter($values, function ($placeholder) use ($key) {
|
1334: | return stripos($placeholder, $key) === 1;
|
1335: | }, ARRAY_FILTER_USE_KEY);
|
1336: |
|
1337: | if (!count($placeholders)) {
|
1338: | return ':' . $key;
|
1339: | }
|
1340: |
|
1341: | $placeholders = array_keys($placeholders);
|
1342: | rsort($placeholders);
|
1343: |
|
1344: | $index = '';
|
1345: | if (preg_match('/:' . $key . '(\d)*/', $placeholders[0], $matches)) {
|
1346: | $index = isset($matches[1]) ? $matches[1] + 1 : 0;
|
1347: | }
|
1348: |
|
1349: | return ':' . $key . $index;
|
1350: | }
|
1351: |
|
1352: | |
1353: | |
1354: | |
1355: |
|
1356: | private static function addBindValues(array $values)
|
1357: | {
|
1358: | self::$bindValues = array_merge(self::$bindValues, $values);
|
1359: | }
|
1360: |
|
1361: | |
1362: | |
1363: | |
1364: | |
1365: |
|
1366: | private static function setBindValue($key, $value)
|
1367: | {
|
1368: | self::$bindValues[$key] = $value;
|
1369: | }
|
1370: |
|
1371: | |
1372: | |
1373: |
|
1374: | public static function clearBindValues()
|
1375: | {
|
1376: | self::$bindValues = array();
|
1377: | }
|
1378: |
|
1379: | |
1380: | |
1381: | |
1382: |
|
1383: | public static function getBindValues()
|
1384: | {
|
1385: | return self::$bindValues;
|
1386: | }
|
1387: | }
|
1388: | |