1: <?php
2: 3: 4: 5: 6: 7: 8: 9:
10:
11: namespace Kotchasan\Database;
12:
13: use Kotchasan\ArrayTool;
14:
15: 16: 17: 18: 19: 20: 21:
22: abstract class Query extends \Kotchasan\Database\Db
23: {
24: 25: 26: 27: 28:
29: protected $debugger = false;
30: 31: 32: 33: 34:
35: protected $sqls;
36:
37: 38: 39: 40:
41: public function debug()
42: {
43: $this->debugger = true;
44:
45: return $this;
46: }
47:
48: 49: 50: 51: 52: 53: 54: 55:
56: public function getFullTableName($table)
57: {
58: $dbname = empty($this->db->settings->dbname) ? '' : '`'.$this->db->settings->dbname.'`.';
59:
60: return $dbname.'`'.$this->getTableName($table).'`';
61: }
62:
63: 64: 65: 66: 67: 68: 69: 70:
71: public function getTableName($table)
72: {
73: $prefix = empty($this->db->settings->prefix) ? '' : $this->db->settings->prefix.'_';
74:
75: return $prefix.(isset($this->db->tables->$table) ? $this->db->tables->$table : $table);
76: }
77:
78: 79: 80: 81: 82:
83: public function text()
84: {
85: $sql = '';
86: if (!empty($this->sqls)) {
87: $sql = $this->db->makeQuery($this->sqls);
88: foreach (array_reverse($this->getValues()) as $key => $value) {
89: $sql = str_replace($key, (is_string($value) ? "'$value'" : $value), $sql);
90: }
91: }
92:
93: return $sql;
94: }
95:
96: 97: 98: 99: 100: 101: 102: 103:
104: protected function aliasName($name, $prefix = '')
105: {
106: return ':'.$prefix.trim(preg_replace('/[`\._\-]/', '', $name));
107: }
108:
109: 110: 111: 112: 113: 114: 115:
116: protected function buildGroup($fields)
117: {
118: $sqls = array();
119: foreach ((array) $fields as $item) {
120: $sqls[] = $this->fieldName($item);
121: }
122:
123: return empty($sqls) ? '' : implode(', ', $sqls);
124: }
125:
126: 127: 128: 129: 130: 131: 132: 133: 134: 135:
136: protected function buildJoin($table, $type, $on)
137: {
138: $ret = $this->buildWhere($on);
139: $sql = is_array($ret) ? $ret[0] : $ret;
140: if (is_array($table)) {
141: $sql = ' '.$type.' JOIN ('.$table[0]->text().') AS '.$table[1].' ON '.$sql;
142: } elseif (preg_match('/^([a-zA-Z0-9_]+)([\s]+(as|AS))?[\s]+([A-Z0-9]{1,2})$/', $table, $match)) {
143: $sql = ' '.$type.' JOIN '.$this->getFullTableName($match[1]).' AS '.$match[4].' ON '.$sql;
144: } elseif (preg_match('/^([a-z0-9_]+)([\s]+(as|AS))?[\s]+([a-z0-9_]+)$/', $table, $match)) {
145: $sql = ' '.$type.' JOIN '.$this->getFullTableName($match[1]).' AS `'.$match[4].'` ON '.$sql;
146: } else {
147: $sql = ' '.$type.' JOIN '.$table.' ON '.$sql;
148: }
149: if (is_array($ret)) {
150: return array($sql, $ret[1]);
151: } else {
152: return $sql;
153: }
154: }
155:
156: 157: 158: 159: 160: 161: 162:
163: protected function buildOrder($fields)
164: {
165: $sqls = array();
166: foreach ((array) $fields as $item) {
167: if (preg_match('/^([A-Z]{1,1}[0-9]{0,1}\.)([a-zA-Z0-9_]+)([\s]{1,}(ASC|DESC|asc|desc))?$/', $item, $match)) {
168:
169: $sqls[] = $match[1].'`'.$match[2].'`'.(isset($match[4]) ? " $match[4]" : '');
170: } elseif (preg_match('/^([a-zA-Z0-9_]+)(\.([a-zA-Z0-9_]+))?(([\s]+)?(ASC|DESC|asc|desc))?$/', $item, $match)) {
171:
172: $sqls[] = '`'.$match[1].'`'.(empty($match[3]) ? '' : '.`'.$match[3].'`').(isset($match[6]) ? " $match[6]" : '');
173: } elseif (strtoupper($item) === 'RAND()') {
174:
175: $sqls[] = 'RAND()';
176: }
177: }
178:
179: return implode(', ', $sqls);
180: }
181:
182: 183: 184: 185: 186: 187: 188:
189: protected function buildSelect($fields)
190: {
191: if (is_array($fields)) {
192: if ($fields[0] instanceof QueryBuilder) {
193:
194: $ret = '('.$fields[0]->text().') AS `'.$fields[1].'`';
195: } elseif (is_string($fields[0]) && preg_match('/^([a-zA-Z0-9\\\]+)::([a-zA-Z0-9]+)$/', $fields[0], $match)) {
196:
197: $ret = '\''.addslashes($fields[0]).'\' AS `'.$fields[1].'`';
198: } else {
199:
200: $rets = array();
201: foreach ($fields as $item) {
202: $rets[] = $this->buildSelect($item);
203: }
204: $ret = implode(',', $rets);
205: }
206: } elseif ($fields instanceof QueryBuilder) {
207:
208: $ret = '('.$fields->text().')';
209: } elseif ($fields instanceof Sql) {
210:
211: $ret = $fields->text();
212: } elseif ($fields == '*') {
213: $ret = '*';
214: } elseif (preg_match('/^(NULL|[0-9]+)([\s]+as)?[\s]+`?([^`]+)`?$/i', $fields, $match)) {
215:
216: $ret = $match[1].' AS `'.$match[3].'`';
217: } elseif (preg_match('/^([\'"])(.*)\\1([\s]+as)?[\s]+`?([^`]+)`?$/i', $fields, $match)) {
218:
219: $ret = "'$match[2]' AS `$match[4]`";
220: } elseif (preg_match('/^([A-Z]{1,1}[0-9]{0,1})\.`?([\*a-zA-Z0-9_]+)`?(([\s]+(as|AS))?[\s]+`?([^`]+)`?)?$/', $fields, $match)) {
221:
222: $ret = $match[1].'.'.($match[2] == '*' ? '*' : '`'.$match[2].'`').(isset($match[6]) ? ' AS `'.$match[6].'`' : '');
223: } elseif (preg_match('/^`?([a-z0-9_]+)`?\.`?([\*a-z0-9_]+)`?(([\s]+as)?[\s]+`?([^`]+)`?)?$/i', $fields, $match)) {
224:
225: $ret = '`'.$match[1].'`.'.($match[2] == '*' ? '*' : '`'.$match[2].'`').(isset($match[5]) ? ' AS `'.$match[5].'`' : '');
226: } elseif (preg_match('/^`?([a-z0-9_]+)`?([\s]+as)?[\s]+`?([^`]+)`?$/i', $fields, $match)) {
227:
228: $ret = '`'.$match[1].'` AS `'.$match[3].'`';
229: } elseif (preg_match('/([a-z0-9_]+)/i', $fields, $match)) {
230:
231: $ret = '`'.$fields.'`';
232: }
233:
234: return isset($ret) ? $ret : '';
235: }
236:
237: 238: 239: 240: 241: 242: 243: 244: 245:
246: protected function buildValue($params)
247: {
248: if (is_array($params)) {
249: if (count($params) == 2) {
250: $params = array($params[0], '=', $params[1]);
251: } else {
252: $params = array($params[0], trim($params[1]), $params[2]);
253: }
254: $key = $this->fieldName($params[0]);
255: if (is_numeric($params[2]) || is_bool($params[2])) {
256:
257: $value = $params[2];
258: } elseif (is_array($params[2])) {
259:
260: if ($params[1] == '=') {
261: $params[1] = 'IN';
262: }
263: $qs = array();
264: foreach ($params[2] as $item) {
265: if (is_numeric($item) || is_bool($item)) {
266: $qs[] = $item;
267: } else {
268: $qs[] = "'$item'";
269: }
270: }
271: $value = '('.implode(', ', $qs).')';
272: } elseif (preg_match('/^\((.*)\)([\s]+as)?[\s]+([a-z0-9_]+)$/i', $params[2], $match)) {
273:
274: $value = "($match[1]) AS `$match[3]`";
275: } elseif (preg_match('/^([A-Z]{1,1}[0-9]{0,1})\.([a-zA-Z0-9_]+)$/', $params[2], $match)) {
276:
277: $value = $match[1].'.`'.$match[2].'`';
278: } elseif (preg_match('/^([a-z0-9_]+)\.([a-z0-9_]+)$/i', $params[2], $match)) {
279:
280: $value = '`'.$match[1].'`.`'.$match[2].'`';
281: } else {
282:
283: $value = "'".$params[2]."'";
284: }
285: $params = $key.' '.$params[1].' '.$value;
286: }
287:
288: return $params;
289: }
290:
291: 292: 293: 294: 295: 296: 297: 298: 299: 300:
301: protected function buildWhere($condition, $operator = 'AND', $id = 'id')
302: {
303: if (is_array($condition)) {
304: if (is_array($condition[0])) {
305: $qs = array();
306: $ps = array();
307: foreach ($condition as $i => $item) {
308: $ret = $this->whereValue($item, $i);
309: if (is_array($ret)) {
310: $qs[] = $ret[0];
311: $ps += $ret[1];
312: } else {
313: $qs[] = $ret;
314: }
315: }
316: $ret = implode(' '.$operator.' ', $qs);
317: if (!empty($ps)) {
318: $ret = array($ret, $ps);
319: }
320: } elseif ($condition[0] instanceof Sql) {
321: $qs = array();
322: $ps = array();
323: foreach ($condition as $i => $item) {
324: $qs[] = $item->text();
325: $ps += $item->getValues();
326: }
327: $ret = implode(' '.$operator.' ', $qs);
328: if (!empty($ps)) {
329: $ret = array($ret, $ps);
330: }
331: } else {
332: $ret = $this->whereValue($condition);
333: }
334: } elseif ($condition instanceof Sql) {
335: $values = $condition->getValues();
336: if (empty($values)) {
337: $ret = $condition->text();
338: } else {
339: $ret = array($condition->text(), $values);
340: }
341: } elseif (preg_match('/^[0-9]+$/', $condition)) {
342:
343: $ret = $this->fieldName($id).' = '.$condition;
344: } else {
345:
346: trigger_error('Invalid arguments in buildWhere('.var_export($condition, true).')', E_USER_ERROR);
347: }
348:
349: return $ret;
350: }
351:
352: 353: 354: 355: 356: 357: 358: 359: 360: 361:
362: protected function buildWhereValues($condition, $operator = 'AND', $id = 'id')
363: {
364: if (is_array($condition)) {
365: $values = array();
366: $qs = array();
367: if (is_array($condition[0])) {
368: foreach ($condition as $item) {
369: $ret = $this->buildWhereValues($item, $operator, $id);
370: $qs[] = $ret[0];
371: $values = ArrayTool::replace($values, $ret[1]);
372: }
373: $condition = implode(' '.$operator.' ', $qs);
374: } elseif (strpos($condition[0], '(') !== false) {
375: $condition = $condition[0];
376: } else {
377: if (count($condition) == 2) {
378: $condition = array($condition[0], '=', $condition[1]);
379: } else {
380: $condition[1] = strtoupper(trim($condition[1]));
381: }
382: if (is_array($condition[2])) {
383: $operator = $condition[1] == '=' ? 'IN' : $condition[1];
384: $qs = array();
385: foreach ($condition[2] as $k => $v) {
386: $qs[] = ":$condition[0]$k";
387: $values[":$condition[0]$k"] = $v;
388: }
389: $condition = $this->fieldName($condition[0]).' '.$operator.' ('.implode(',', $qs).')';
390: } else {
391: $values[":$condition[0]"] = $condition[2];
392: $condition = $this->fieldName($condition[0]).' '.$condition[1].' :'.$condition[0];
393: }
394: }
395: } elseif (is_numeric($condition)) {
396:
397: $values = array(":$id" => $condition);
398: $condition = "`$id` = :$id";
399: } else {
400: $values = array();
401: }
402:
403: return array($condition, $values);
404: }
405:
406: 407: 408: 409: 410: 411: 412:
413: protected function fieldName($name)
414: {
415: if (is_array($name)) {
416: if ($name[0] instanceof QueryBuilder) {
417: $ret = '('.$name[0]->text().') AS `'.$name[1].'`';
418: } else {
419: $rets = array();
420: foreach ($name as $item) {
421: $rets[] = $this->fieldName($item);
422: }
423: $ret = implode(', ', $rets);
424: }
425: } elseif (is_numeric($name)) {
426: $ret = $name;
427: } elseif (is_string($name)) {
428: $name = trim($name);
429: if (strpos($name, '(') !== false && preg_match('/^(.*?)(\s{0,}(as)?\s{0,}`?([a-z0-9_]+)`?)?$/i', $name, $match)) {
430:
431: $ret = $match[1].(isset($match[4]) ? " AS `$match[4]`" : '');
432: } elseif (preg_match('/^([A-Z]{1,1}[0-9]{0,1})\.([\*a-zA-Z0-9_]+)((\s+(as|AS))?\s+([a-zA-Z0-9_]+))?$/', $name, $match)) {
433:
434: $ret = $match[1].'.'.($match[2] == '*' ? '*' : '`'.$match[2].'`').(isset($match[6]) ? ' AS `'.$match[6].'`' : '');
435: } elseif (preg_match('/^`?([a-z0-9_]+)`?\.([\*a-z0-9_]+)(([\s]+as)?[\s]+([a-z0-9_]+))?$/i', $name, $match)) {
436:
437: $ret = '`'.$match[1].'`.'.($match[2] == '*' ? '*' : '`'.$match[2].'`').(isset($match[5]) ? ' AS `'.$match[5].'`' : '');
438: } elseif (preg_match('/^([a-z0-9_]+)(([\s]+as)?[\s]+([a-z0-9_]+))?$/i', $name, $match)) {
439:
440: $ret = '`'.$match[1].'`'.(isset($match[4]) ? ' AS `'.$match[4].'`' : '');
441: } else {
442: $ret = $name == '*' ? '*' : '`'.$name.'`';
443: }
444: } elseif ($name instanceof Sql) {
445: $ret = $name->text();
446: } elseif ($name instanceof QueryBuilder) {
447: $ret = '('.$name->text().')';
448: } else {
449:
450: trigger_error('Invalid arguments in fieldName('.var_export($name, true).')', E_USER_ERROR);
451: }
452:
453: return $ret;
454: }
455:
456: 457: 458: 459: 460: 461: 462:
463: protected function fieldValue($value)
464: {
465: if (is_array($value)) {
466: $rets = array();
467: foreach ($value as $item) {
468: $rets[] = $this->fieldValue($item);
469: }
470: $ret = '('.implode(', ', $rets).')';
471: } elseif (is_numeric($value)) {
472: $ret = $value;
473: } elseif (preg_match('/^([a-z0-9_]+)\.([a-z0-9_]+)(([\s]+as)?[\s]+([a-z0-9]+))?$/i', $value, $match)) {
474: $ret = "`$match[1]`.`$match[2]`".(isset($match[5]) ? ' AS `'.$match[5].'`' : '');
475: } else {
476: $ret = '\''.$value.'\'';
477: }
478:
479: return $ret;
480: }
481:
482: 483: 484: 485: 486: 487: 488:
489: protected function groupAnd($params)
490: {
491: if (func_num_args() > 1) {
492: $params = func_get_args();
493: }
494: $sqls = array();
495: foreach ($params as $i => $item) {
496: $sqls[] = $this->buildValue($item);
497: }
498:
499: return Sql::create('('.implode(' AND ', $sqls).')');
500: }
501:
502: 503: 504: 505: 506: 507: 508:
509: protected function groupOr($params)
510: {
511: if (func_num_args() > 1) {
512: $params = func_get_args();
513: }
514: $sqls = array();
515: foreach ($params as $i => $item) {
516: $sqls[] = $this->buildValue($item);
517: }
518:
519: return Sql::create('('.implode(' OR ', $sqls).')');
520: }
521:
522: 523: 524: 525: 526: 527: 528:
529: protected function quoteTableName($table)
530: {
531: if (is_array($table)) {
532: if ($table[0] instanceof QueryBuilder) {
533: $table = '('.$table[0]->text().') AS '.$table[1];
534: } else {
535: $table = '('.$table[0].') AS '.$table[1];
536: }
537: } elseif (preg_match('/^([a-zA-Z0-9_]+)(\s+(as|AS))?[\s]+([A-Z0-9]{1,2})$/', $table, $match)) {
538: $table = $this->getFullTableName($match[1]).' AS '.$match[4];
539: } elseif (preg_match('/^([a-zA-Z0-9_]+)(\s+(as|AS))?[\s]+([a-zA-Z0-9]+)$/', $table, $match)) {
540: $table = $this->getFullTableName($match[1]).' AS `'.$match[4].'`';
541: } else {
542: $table = $this->getFullTableName($table);
543: }
544:
545: return $table;
546: }
547:
548: 549: 550: 551: 552: 553: 554: 555:
556: private function whereValue($params, $i = null)
557: {
558: $result = array();
559: if (is_array($params)) {
560: if (count($params) == 2) {
561: $operator = '=';
562: $value = $params[1];
563: } else {
564: $operator = trim($params[1]);
565: $value = $params[2];
566: }
567: $key = $this->fieldName($params[0]);
568: if ($value instanceof QueryBuilder) {
569: $values = $value->getValues();
570: if (empty($values)) {
571: $result = $key.' '.$operator.' ('.$value->text().')';
572: } else {
573: $result = array($key.' '.$operator.' ('.$value->text().')', $values);
574: }
575: } elseif ($value instanceof Sql) {
576: $values = $value->getValues();
577: if (empty($values)) {
578: $result = $key.' '.$operator.' '.$value->text();
579: } else {
580: $result = array($key.' '.$operator.' '.$value->text(), $values);
581: }
582: } elseif (is_array($value)) {
583: if ($operator == '=') {
584: $operator = 'IN';
585: }
586: $q = $this->aliasName($key);
587: $qs = array();
588: $vs = array();
589: foreach ($value as $a => $item) {
590: if (empty($item)) {
591: $qs[] = is_string($item) ? "'$item'" : $item;
592: } elseif (is_string($item)) {
593: if (preg_match('/^([A-Z]{1,1}[0-9]{0,1})\.`?([a-zA-Z0-9_\-]+)`?$/', $item, $match)) {
594: $qs[] = "$match[1].`$match[2]`";
595: } elseif (preg_match('/^`([a-zA-Z0-9_\-]+)`$/', $item, $match)) {
596: $qs[] = "`$match[1]`";
597: } else {
598: $k = $q.($i === null ? '' : $i).$a;
599: $qs[] = $k;
600: $vs[$k] = $item;
601: }
602: } else {
603: $k = $q.($i === null ? '' : $i).$a;
604: $qs[] = $k;
605: $vs[$k] = $item;
606: }
607: }
608: $result = array($key.' '.$operator.' ('.implode(', ', $qs).')', $vs);
609: } elseif (empty($value)) {
610:
611: $result = $key.' '.$operator.' '.(is_string($value) ? "'$value'" : $value);
612: } elseif (preg_match('/^(\-?[0-9\s\.]+|true|false)$/i', $value)) {
613:
614:
615: $result = "$key $operator ".(is_string($value) ? "'$value'" : $value);
616: } elseif (preg_match('/^[0-9\s\-:]+$/', $value)) {
617:
618: $result = "$key $operator '$value'";
619: } elseif (preg_match('/^([A-Z]{1,1}[0-9]{0,1})\.([a-zA-Z0-9_\-]+)$/', $value, $match)) {
620:
621: if ($operator == 'IN' || $operator == 'NOT IN') {
622: $result = "$key $operator ($match[1].`$match[2]`)";
623: } else {
624: $result = "$key $operator $match[1].`$match[2]`";
625: }
626: } elseif (preg_match('/^`([a-zA-Z0-9_\-]+)`$/', $value, $match)) {
627:
628: if ($operator == 'IN' || $operator == 'NOT IN') {
629: $result = "$key $operator (`$match[1]`)";
630: } else {
631: $result = "$key $operator `$match[1]`";
632: }
633: } else {
634:
635: $q = ':'.preg_replace('/[\.`]/', '', strtolower($key)).($i === null ? '' : $i);
636: $result = array($key.' '.$operator.' '.$q, array($q => $value));
637: }
638: } elseif ($params instanceof QueryBuilder) {
639: $values = $params->getValues();
640: if (empty($values)) {
641: $result = $key.' '.$operator.' ('.$params->text().')';
642: } else {
643: $result = array($key.' '.$operator.' ('.$params->text().')', $values);
644: }
645: } elseif ($params instanceof Sql) {
646: $result = $params->text();
647: } else {
648: $result = $params;
649: }
650:
651: return $result;
652: }
653: }
654: