TYPO3  7.6
Adodb.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Dbal\Database\SqlCompilers;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
18 use TYPO3\CMS\Dbal\Database\Specifics;
20 
24 class Adodb extends AbstractCompiler
25 {
33  protected function compileINSERT($components)
34  {
35  $values = array();
36  if (isset($components['VALUES_ONLY']) && is_array($components['VALUES_ONLY'])) {
37  $valuesComponents = $components['EXTENDED'] === '1' ? $components['VALUES_ONLY'] : array($components['VALUES_ONLY']);
38  $tableFields = array_keys($this->databaseConnection->cache_fieldType[$components['TABLE']]);
39  } else {
40  $valuesComponents = $components['EXTENDED'] === '1' ? $components['FIELDS'] : array($components['FIELDS']);
41  $tableFields = array_keys($valuesComponents[0]);
42  }
43  foreach ($valuesComponents as $valuesComponent) {
44  $fields = array();
45  $fc = 0;
46  foreach ($valuesComponent as $fV) {
47  $fields[$tableFields[$fc++]] = $fV[0];
48  }
49  $values[] = $fields;
50  }
51  return count($values) === 1 ? $values[0] : $values;
52  }
53 
61  protected function compileCREATETABLE($components)
62  {
63  // Create fields and keys:
64  $fieldsKeys = array();
65  $indexKeys = array();
66  foreach ($components['FIELDS'] as $fN => $fCfg) {
67  $handlerKey = $this->databaseConnection->handler_getFromTableList($components['TABLE']);
68  $fieldsKeys[$fN] = $this->databaseConnection->quoteName($fN, $handlerKey, true) . ' ' . $this->compileFieldCfg($fCfg['definition']);
69  }
70  if (isset($components['KEYS']) && is_array($components['KEYS'])) {
71  foreach ($components['KEYS'] as $kN => $kCfg) {
72  if ($kN === 'PRIMARYKEY') {
73  foreach ($kCfg as $field) {
74  $fieldsKeys[$field] .= ' PRIMARY';
75  }
76  } elseif ($kN === 'UNIQUE') {
77  foreach ($kCfg as $n => $field) {
78  $indexKeys = array_merge($indexKeys, $this->compileCREATEINDEX($n, $components['TABLE'], $field, array('UNIQUE')));
79  }
80  } else {
81  $indexKeys = array_merge($indexKeys, $this->compileCREATEINDEX($kN, $components['TABLE'], $kCfg));
82  }
83  }
84  }
85  // Generally create without OID on PostgreSQL
86  $tableOptions = array('postgres' => 'WITHOUT OIDS');
87  // Fetch table/index generation query:
88  $tableName = $this->databaseConnection->quoteName($components['TABLE'], null, true);
89  $query = array_merge($this->databaseConnection->handlerInstance[$this->databaseConnection->lastHandlerKey]->DataDictionary->CreateTableSQL($tableName, implode(',' . LF, $fieldsKeys), $tableOptions), $indexKeys);
90  return $query;
91  }
92 
100  protected function compileALTERTABLE($components)
101  {
102  $query = '';
103  $tableName = $this->databaseConnection->quoteName($components['TABLE'], null, true);
104  $fieldName = $this->databaseConnection->quoteName($components['FIELD'], null, true);
105  switch (strtoupper(str_replace(array(' ', "\n", "\r", "\t"), '', $components['action']))) {
106  case 'ADD':
107  $query = $this->databaseConnection->handlerInstance[$this->databaseConnection->lastHandlerKey]->DataDictionary->AddColumnSQL($tableName, $fieldName . ' ' . $this->compileFieldCfg($components['definition']));
108  break;
109  case 'CHANGE':
110  $query = $this->databaseConnection->handlerInstance[$this->databaseConnection->lastHandlerKey]->DataDictionary->AlterColumnSQL($tableName, $fieldName . ' ' . $this->compileFieldCfg($components['definition']));
111  break;
112  case 'DROP':
113 
114  case 'DROPKEY':
115  $query = $this->compileDROPINDEX($components['KEY'], $components['TABLE']);
116  break;
117 
118  case 'ADDKEY':
119  $query = $this->compileCREATEINDEX($components['KEY'], $components['TABLE'], $components['fields']);
120  break;
121  case 'ADDUNIQUE':
122  $query = $this->compileCREATEINDEX($components['KEY'], $components['TABLE'], $components['fields'], array('UNIQUE'));
123  break;
124  case 'ADDPRIMARYKEY':
125  // @todo ???
126  break;
127  case 'DEFAULTCHARACTERSET':
128 
129  case 'ENGINE':
130  // @todo ???
131  break;
132  }
133  return $query;
134  }
135 
149  protected function compileCREATEINDEX($indexName, $tableName, $indexFields, $indexOptions = array())
150  {
151  $indexIdentifier = $this->databaseConnection->quoteName(hash('crc32b', $tableName) . '_' . $indexName, null, true);
152  $dbmsSpecifics = $this->databaseConnection->getSpecifics();
153  $keepFieldLengths = $dbmsSpecifics->specificExists(Specifics\AbstractSpecifics::PARTIAL_STRING_INDEX) && $dbmsSpecifics->getSpecific(Specifics\AbstractSpecifics::PARTIAL_STRING_INDEX);
154 
155  foreach ($indexFields as $key => $fieldName) {
156  if (!$keepFieldLengths) {
157  $fieldName = preg_replace('/\A([^\(]+)(\(\d+\))/', '\\1', $fieldName);
158  }
159  // Quote the fieldName in backticks with escaping, ADOdb will replace the backticks with the correct quoting
160  $indexFields[$key] = '`' . str_replace('`', '``', $fieldName) . '`';
161  }
162 
163  return $this->databaseConnection->handlerInstance[$this->databaseConnection->handler_getFromTableList($tableName)]->DataDictionary->CreateIndexSQL(
164  $indexIdentifier, $this->databaseConnection->quoteName($tableName, null, true), $indexFields, $indexOptions
165  );
166  }
167 
179  protected function compileDROPINDEX($indexName, $tableName)
180  {
181  $indexIdentifier = $this->databaseConnection->quoteName(hash('crc32b', $tableName) . '_' . $indexName, null, true);
182 
183  return $this->databaseConnection->handlerInstance[$this->databaseConnection->handler_getFromTableList($tableName)]->DataDictionary->DropIndexSQL(
184  $indexIdentifier, $this->databaseConnection->quoteName($tableName)
185  );
186  }
187 
198  public function compileFieldList($selectFields, $compileComments = true, $functionMapping = true)
199  {
200  $output = '';
201  // Traverse the selectFields if any:
202  if (is_array($selectFields)) {
203  $outputParts = array();
204  foreach ($selectFields as $k => $v) {
205  // Detecting type:
206  switch ($v['type']) {
207  case 'function':
208  $outputParts[$k] = $v['function'] . '(' . $v['func_content'] . ')';
209  break;
210  case 'flow-control':
211  if ($v['flow-control']['type'] === 'CASE') {
212  $outputParts[$k] = $this->compileCaseStatement($v['flow-control'], $functionMapping);
213  }
214  break;
215  case 'field':
216  $outputParts[$k] = ($v['distinct'] ? $v['distinct'] : '') . ($v['table'] ? $v['table'] . '.' : '') . $v['field'];
217  break;
218  }
219  // Alias:
220  if ($v['as']) {
221  $outputParts[$k] .= ' ' . $v['as_keyword'] . ' ' . $v['as'];
222  }
223  // Specifically for ORDER BY and GROUP BY field lists:
224  if ($v['sortDir']) {
225  $outputParts[$k] .= ' ' . $v['sortDir'];
226  }
227  }
228  // @todo Handle SQL hints in comments according to current DBMS
229  if (false && $selectFields[0]['comments']) {
230  $output = $selectFields[0]['comments'] . ' ';
231  }
232  $output .= implode(', ', $outputParts);
233  }
234  return $output;
235  }
236 
245  protected function compileAddslashes($str)
246  {
247  return $str;
248  }
249 
256  protected function compileFieldCfg($fieldCfg)
257  {
258  // Set type:
259  $type = $this->databaseConnection->getSpecifics()->getMetaFieldType($fieldCfg['fieldType']);
260  $cfg = $type;
261  // Add value, if any:
262  if ((string)$fieldCfg['value'] !== '' && in_array($type, array('C', 'C2'))) {
263  $cfg .= ' ' . $fieldCfg['value'];
264  } elseif (!isset($fieldCfg['value']) && in_array($type, array('C', 'C2'))) {
265  $cfg .= ' 255';
266  }
267  // Add additional features:
268  $noQuote = true;
269  if (is_array($fieldCfg['featureIndex'])) {
270  // MySQL assigns DEFAULT value automatically if NOT NULL, fake this here
271  // numeric fields get 0 as default, other fields an empty string
272  if (isset($fieldCfg['featureIndex']['NOTNULL']) && !isset($fieldCfg['featureIndex']['DEFAULT']) && !isset($fieldCfg['featureIndex']['AUTO_INCREMENT'])) {
273  switch ($type) {
274  case 'I8':
275 
276  case 'F':
277 
278  case 'N':
279  $fieldCfg['featureIndex']['DEFAULT'] = array('keyword' => 'DEFAULT', 'value' => array('0', ''));
280  break;
281  default:
282  $fieldCfg['featureIndex']['DEFAULT'] = array('keyword' => 'DEFAULT', 'value' => array('', '\''));
283  }
284  }
285  foreach ($fieldCfg['featureIndex'] as $feature => $featureDef) {
286  switch (true) {
287  case $feature === 'UNSIGNED' && !$this->databaseConnection->runningADOdbDriver('mysql'):
288  case $feature === 'NOTNULL' && $this->databaseConnection->runningADOdbDriver('oci8'):
289  continue;
290  case $feature === 'AUTO_INCREMENT':
291  $cfg .= ' AUTOINCREMENT';
292  break;
293  case $feature === 'NOTNULL':
294  $cfg .= ' NOTNULL';
295  break;
296  default:
297  $cfg .= ' ' . $featureDef['keyword'];
298  }
299  // Add value if found:
300  if (is_array($featureDef['value'])) {
301  if ($featureDef['value'][0] === '') {
302  $cfg .= ' "\'\'"';
303  } else {
304  $cfg .= ' ' . $featureDef['value'][1] . $this->compileAddslashes($featureDef['value'][0]) . $featureDef['value'][1];
305  if (!is_numeric($featureDef['value'][0])) {
306  $noQuote = false;
307  }
308  }
309  }
310  }
311  }
312  if ($noQuote) {
313  $cfg .= ' NOQUOTE';
314  }
315  // Return field definition string:
316  return $cfg;
317  }
318 
335  public function compileWhereClause($clauseArray, $functionMapping = true)
336  {
337  // Prepare buffer variable:
338  $output = '';
339  // Traverse clause array:
340  if (is_array($clauseArray)) {
341  foreach ($clauseArray as $v) {
342  // Set operator:
343  $output .= $v['operator'] ? ' ' . $v['operator'] : '';
344  // Look for sublevel:
345  if (is_array($v['sub'])) {
346  $output .= ' (' . trim($this->compileWhereClause($v['sub'], $functionMapping)) . ')';
347  } elseif (isset($v['func']) && $v['func']['type'] === 'EXISTS') {
348  $output .= ' ' . trim($v['modifier']) . ' EXISTS (' . $this->compileSELECT($v['func']['subquery']) . ')';
349  } else {
350  if (isset($v['func']) && $v['func']['type'] === 'LOCATE') {
351  $output .= ' ' . trim($v['modifier']);
352  switch (true) {
353  case $this->databaseConnection->runningADOdbDriver('mssql') && $functionMapping:
354  $output .= ' CHARINDEX(';
355  $output .= $v['func']['substr'][1] . $v['func']['substr'][0] . $v['func']['substr'][1];
356  $output .= ', ' . ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
357  $output .= isset($v['func']['pos']) ? ', ' . $v['func']['pos'][0] : '';
358  $output .= ')';
359  break;
360  case $this->databaseConnection->runningADOdbDriver('oci8') && $functionMapping:
361  $output .= ' INSTR(';
362  $output .= ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
363  $output .= ', ' . $v['func']['substr'][1] . $v['func']['substr'][0] . $v['func']['substr'][1];
364  $output .= isset($v['func']['pos']) ? ', ' . $v['func']['pos'][0] : '';
365  $output .= ')';
366  break;
367  default:
368  $output .= ' LOCATE(';
369  $output .= $v['func']['substr'][1] . $v['func']['substr'][0] . $v['func']['substr'][1];
370  $output .= ', ' . ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
371  $output .= isset($v['func']['pos']) ? ', ' . $v['func']['pos'][0] : '';
372  $output .= ')';
373  }
374  } elseif (isset($v['func']) && $v['func']['type'] === 'IFNULL') {
375  $output .= ' ' . trim($v['modifier']) . ' ';
376  switch (true) {
377  case $this->databaseConnection->runningADOdbDriver('mssql') && $functionMapping:
378  $output .= 'ISNULL';
379  break;
380  case $this->databaseConnection->runningADOdbDriver('oci8') && $functionMapping:
381  $output .= 'NVL';
382  break;
383  default:
384  $output .= 'IFNULL';
385  }
386  $output .= '(';
387  $output .= ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
388  $output .= ', ' . $v['func']['default'][1] . $this->compileAddslashes($v['func']['default'][0]) . $v['func']['default'][1];
389  $output .= ')';
390  } elseif (isset($v['func']) && $v['func']['type'] === 'FIND_IN_SET') {
391  $output .= ' ' . trim($v['modifier']) . ' ';
392  if ($functionMapping) {
393  switch (true) {
394  case $this->databaseConnection->runningADOdbDriver('mssql'):
395  $field = ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
396  if (!isset($v['func']['str_like'])) {
397  $v['func']['str_like'] = $v['func']['str'][0];
398  }
399  $output .= '\',\'+' . $field . '+\',\' LIKE \'%,' . $v['func']['str_like'] . ',%\'';
400  break;
401  case $this->databaseConnection->runningADOdbDriver('oci8'):
402  $field = ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
403  if (!isset($v['func']['str_like'])) {
404  $v['func']['str_like'] = $v['func']['str'][0];
405  }
406  $output .= '\',\'||' . $field . '||\',\' LIKE \'%,' . $v['func']['str_like'] . ',%\'';
407  break;
408  case $this->databaseConnection->runningADOdbDriver('postgres'):
409  $output .= ' FIND_IN_SET(';
410  $output .= $v['func']['str'][1] . $v['func']['str'][0] . $v['func']['str'][1];
411  $output .= ', ' . ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
412  $output .= ') != 0';
413  break;
414  default:
415  $field = ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
416  if (!isset($v['func']['str_like'])) {
417  $v['func']['str_like'] = $v['func']['str'][0];
418  }
419  $output .= '(' . $field . ' LIKE \'%,' . $v['func']['str_like'] . ',%\'' . ' OR ' . $field . ' LIKE \'' . $v['func']['str_like'] . ',%\'' . ' OR ' . $field . ' LIKE \'%,' . $v['func']['str_like'] . '\'' . ' OR ' . $field . '= ' . $v['func']['str'][1] . $v['func']['str'][0] . $v['func']['str'][1] . ')';
420  }
421  } else {
422  switch (true) {
423  case $this->databaseConnection->runningADOdbDriver('mssql'):
424 
425  case $this->databaseConnection->runningADOdbDriver('oci8'):
426 
427  case $this->databaseConnection->runningADOdbDriver('postgres'):
428  $output .= ' FIND_IN_SET(';
429  $output .= $v['func']['str'][1] . $v['func']['str'][0] . $v['func']['str'][1];
430  $output .= ', ' . ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
431  $output .= ')';
432  break;
433  default:
434  $field = ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
435  if (!isset($v['func']['str_like'])) {
436  $v['func']['str_like'] = $v['func']['str'][0];
437  }
438  $output .= '(' . $field . ' LIKE \'%,' . $v['func']['str_like'] . ',%\'' . ' OR ' . $field . ' LIKE \'' . $v['func']['str_like'] . ',%\'' . ' OR ' . $field . ' LIKE \'%,' . $v['func']['str_like'] . '\'' . ' OR ' . $field . '= ' . $v['func']['str'][1] . $v['func']['str'][0] . $v['func']['str'][1] . ')';
439  }
440  }
441  } else {
442  // Set field/table with modifying prefix if any:
443  $output .= ' ' . trim($v['modifier']) . ' ';
444  // DBAL-specific: Set calculation, if any:
445  if ($v['calc'] === '&' && $functionMapping) {
446  switch (true) {
447  case $this->databaseConnection->runningADOdbDriver('oci8'):
448  // Oracle only knows BITAND(x,y) - sigh
449  $output .= 'BITAND(' . trim(($v['table'] ? $v['table'] . '.' : '') . $v['field']) . ',' . $v['calc_value'][1] . $this->compileAddslashes($v['calc_value'][0]) . $v['calc_value'][1] . ')';
450  break;
451  default:
452  // MySQL, MS SQL Server, PostgreSQL support the &-syntax
453  $output .= trim(($v['table'] ? $v['table'] . '.' : '') . $v['field']) . $v['calc'] . $v['calc_value'][1] . $this->compileAddslashes($v['calc_value'][0]) . $v['calc_value'][1];
454  }
455  } elseif ($v['calc']) {
456  $output .= trim(($v['table'] ? $v['table'] . '.' : '') . $v['field']) . $v['calc'];
457  if (isset($v['calc_table'])) {
458  $output .= trim(($v['calc_table'] ? $v['calc_table'] . '.' : '') . $v['calc_field']);
459  } else {
460  $output .= $v['calc_value'][1] . $this->compileAddslashes($v['calc_value'][0]) . $v['calc_value'][1];
461  }
462  } elseif (!($this->databaseConnection->runningADOdbDriver('oci8') && preg_match('/(NOT )?LIKE( BINARY)?/', $v['comparator']) && $functionMapping)) {
463  $output .= trim(($v['table'] ? $v['table'] . '.' : '') . $v['field']);
464  }
465  }
466  // Set comparator:
467  if ($v['comparator']) {
468  $isLikeOperator = preg_match('/(NOT )?LIKE( BINARY)?/', $v['comparator']);
469  switch (true) {
470  case $this->databaseConnection->runningADOdbDriver('oci8') && $isLikeOperator && $functionMapping:
471  // Oracle cannot handle LIKE on CLOB fields - sigh
472  if (isset($v['value']['operator'])) {
473  $values = array();
474  foreach ($v['value']['args'] as $fieldDef) {
475  $values[] = ($fieldDef['table'] ? $fieldDef['table'] . '.' : '') . $fieldDef['field'];
476  }
477  $compareValue = ' ' . $v['value']['operator'] . '(' . implode(',', $values) . ')';
478  } else {
479  $compareValue = $v['value'][1] . $this->compileAddslashes(trim($v['value'][0], '%')) . $v['value'][1];
480  }
481  if (GeneralUtility::isFirstPartOfStr($v['comparator'], 'NOT')) {
482  $output .= 'NOT ';
483  }
484  // To be on the safe side
485  $isLob = true;
486  if ($v['table']) {
487  // Table and field names are quoted:
488  $tableName = substr($v['table'], 1, strlen($v['table']) - 2);
489  $fieldName = substr($v['field'], 1, strlen($v['field']) - 2);
490  $fieldType = $this->databaseConnection->sql_field_metatype($tableName, $fieldName);
491  $isLob = $fieldType === 'B' || $fieldType === 'XL';
492  }
493  if (strtoupper(substr($v['comparator'], -6)) === 'BINARY') {
494  if ($isLob) {
495  $output .= '(dbms_lob.instr(' . trim(($v['table'] ? $v['table'] . '.' : '') . $v['field']) . ', ' . $compareValue . ',1,1) > 0)';
496  } else {
497  $output .= '(instr(' . trim((($v['table'] ? $v['table'] . '.' : '') . $v['field'])) . ', ' . $compareValue . ',1,1) > 0)';
498  }
499  } else {
500  if ($isLob) {
501  $output .= '(dbms_lob.instr(LOWER(' . trim(($v['table'] ? $v['table'] . '.' : '') . $v['field']) . '), ' . GeneralUtility::strtolower($compareValue) . ',1,1) > 0)';
502  } else {
503  $output .= '(instr(LOWER(' . trim(($v['table'] ? $v['table'] . '.' : '') . $v['field']) . '), ' . GeneralUtility::strtolower($compareValue) . ',1,1) > 0)';
504  }
505  }
506  break;
507  default:
508  if ($isLikeOperator && $functionMapping) {
509  if ($this->databaseConnection->runningADOdbDriver('postgres') || $this->databaseConnection->runningADOdbDriver('postgres64') || $this->databaseConnection->runningADOdbDriver('postgres7') || $this->databaseConnection->runningADOdbDriver('postgres8')) {
510  // Remap (NOT)? LIKE to (NOT)? ILIKE
511  // and (NOT)? LIKE BINARY to (NOT)? LIKE
512  switch ($v['comparator']) {
513  case 'LIKE':
514  $v['comparator'] = 'ILIKE';
515  break;
516  case 'NOT LIKE':
517  $v['comparator'] = 'NOT ILIKE';
518  break;
519  default:
520  $v['comparator'] = str_replace(' BINARY', '', $v['comparator']);
521  }
522  } else {
523  // No more BINARY operator
524  $v['comparator'] = str_replace(' BINARY', '', $v['comparator']);
525  }
526  }
527  $output .= ' ' . $v['comparator'];
528  // Detecting value type; list or plain:
529  $comparator = SqlParser::normalizeKeyword($v['comparator']);
530  if (GeneralUtility::inList('NOTIN,IN', $comparator)) {
531  if (isset($v['subquery'])) {
532  $output .= ' (' . $this->compileSELECT($v['subquery']) . ')';
533  } else {
534  $valueBuffer = array();
535  foreach ($v['value'] as $realValue) {
536  $valueBuffer[] = $realValue[1] . $this->compileAddslashes($realValue[0]) . $realValue[1];
537  }
538 
539  $dbmsSpecifics = $this->databaseConnection->getSpecifics();
540  if ($dbmsSpecifics === null) {
541  $output .= ' (' . trim(implode(',', $valueBuffer)) . ')';
542  } else {
543  $chunkedList = $dbmsSpecifics->splitMaxExpressions($valueBuffer);
544  $chunkCount = count($chunkedList);
545 
546  if ($chunkCount === 1) {
547  $output .= ' (' . trim(implode(',', $valueBuffer)) . ')';
548  } else {
549  $listExpressions = array();
550  $field = trim(($v['table'] ? $v['table'] . '.' : '') . $v['field']);
551 
552  switch ($comparator) {
553  case 'IN':
554  $operator = 'OR';
555  break;
556  case 'NOTIN':
557  $operator = 'AND';
558  break;
559  default:
560  $operator = '';
561  }
562 
563  for ($i = 0; $i < $chunkCount; ++$i) {
564  $listPart = trim(implode(',', $chunkedList[$i]));
565  $listExpressions[] = ' (' . $listPart . ')';
566  }
567 
568  $implodeString = ' ' . $operator . ' ' . $field . ' ' . $v['comparator'];
569 
570  // add opening brace before field
571  $lastFieldPos = strrpos($output, $field);
572  $output = substr_replace($output, '(', $lastFieldPos, 0);
573  $output .= implode($implodeString, $listExpressions) . ')';
574  }
575  }
576  }
577  } elseif (GeneralUtility::inList('BETWEEN,NOT BETWEEN', $v['comparator'])) {
578  $lbound = $v['values'][0];
579  $ubound = $v['values'][1];
580  $output .= ' ' . $lbound[1] . $this->compileAddslashes($lbound[0]) . $lbound[1];
581  $output .= ' AND ';
582  $output .= $ubound[1] . $this->compileAddslashes($ubound[0]) . $ubound[1];
583  } elseif (isset($v['value']['operator'])) {
584  $values = array();
585  foreach ($v['value']['args'] as $fieldDef) {
586  $values[] = ($fieldDef['table'] ? $fieldDef['table'] . '.' : '') . $fieldDef['field'];
587  }
588  $output .= ' ' . $v['value']['operator'] . '(' . implode(',', $values) . ')';
589  } else {
590  $output .= ' ' . $v['value'][1] . $this->compileAddslashes($v['value'][0]) . $v['value'][1];
591  }
592  }
593  }
594  }
595  }
596  }
597  return $output;
598  }
599 }