Merge pull request #12430 from JMAConsulting/dev-core-239
[civicrm-core.git] / CRM / Utils / SQL / Select.php
CommitLineData
e2b5e5b1 1<?php
d1d3c04a
CW
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
d1d3c04a 5 +--------------------------------------------------------------------+
8c9251b3 6 | Copyright CiviCRM LLC (c) 2004-2018 |
d1d3c04a
CW
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
e2b5e5b1
TO
27
28/**
29 * Dear God Why Do I Have To Write This (Dumb SQL Builder)
30 *
31 * Usage:
c5458931
TO
32 * @code
33 * $select = CRM_Utils_SQL_Select::from('civicrm_activity act')
e2b5e5b1 34 * ->join('absence', 'inner join civicrm_activity absence on absence.id = act.source_record_id')
c5458931
TO
35 * ->where('activity_type_id = #type', array('type' => 234))
36 * ->where('status_id IN (#statuses)', array('statuses' => array(1,2,3))
37 * ->where('subject like @subj', array('subj' => '%hello%'))
38 * ->where('!dynamicColumn = 1', array('dynamicColumn' => 'coalesce(is_active,0)'))
e2b5e5b1 39 * ->where('!column = @value', array(
c5458931
TO
40 * 'column' => $customField->column_name,
41 * 'value' => $form['foo']
e2b5e5b1
TO
42 * ))
43 * echo $select->toSQL();
c5458931 44 * @endcode
e2b5e5b1
TO
45 *
46 * Design principles:
47 * - Portable
48 * - No knowledge of the underlying SQL API (except for escaping -- CRM_Core_DAO::escapeString)
49 * - No knowledge of the underlying data model
50 * - Single file
51 * - SQL clauses correspond to PHP functions ($select->where("foo_id=123"))
52 * - Variable escaping is concise and controllable based on prefixes, eg
53 * - similar to Drupal's t()
54 * - use "@varname" to insert the escaped value
55 * - use "!varname" to insert raw (unescaped) values
56 * - use "#varname" to insert a numerical value (these are validated but not escaped)
57 * - to disable any preprocessing, simply omit the variable list
c5458931 58 * - control characters (@!#) are mandatory in expressions but optional in arg-keys
e2b5e5b1
TO
59 * - Variables may be individual values or arrays; arrays are imploded with commas
60 * - Conditionals are AND'd; if you need OR's, do it yourself
c3a8e3e5 61 * - Use classes/functions with documentation (rather than undocumented array-trees)
c5458931
TO
62 * - For any given string, interpolation is only performed once. After an interpolation,
63 * a string may never again be subjected to interpolation.
64 *
65 * The "interpolate-once" principle can be enforced by either interpolating on input
66 * xor output. The notations for input and output interpolation are a bit different,
67 * and they may not be mixed.
68 *
69 * @code
70 * // Interpolate on input. Set params when using them.
71 * $select->where('activity_type_id = #type', array(
72 * 'type' => 234,
73 * ));
74 *
75 * // Interpolate on output. Set params independently.
76 * $select
77 * ->where('activity_type_id = #type')
78 * ->param('type', 234),
79 * @endcode
d1d3c04a
CW
80 *
81 * @package CRM
8c9251b3 82 * @copyright CiviCRM LLC (c) 2004-2018
e2b5e5b1 83 */
c5458931
TO
84class CRM_Utils_SQL_Select implements ArrayAccess {
85
86 /**
87 * Interpolate values as soon as they are passed in (where(), join(), etc).
88 *
89 * Default.
90 *
91 * Pro: Every clause has its own unique namespace for parameters.
92 * Con: Probably slower.
93 * Advice: Use this when aggregating SQL fragments from agents who
94 * maintained by different parties.
95 */
96 const INTERPOLATE_INPUT = 'in';
97
98 /**
99 * Interpolate values when rendering SQL output (toSQL()).
100 *
101 * Pro: Probably faster.
102 * Con: Must maintain an aggregated list of all parameters.
103 * Advice: Use this when you have control over the entire query.
104 */
105 const INTERPOLATE_OUTPUT = 'out';
106
107 /**
108 * Determine mode automatically. When the first attempt is made
109 * to use input-interpolation (eg `where(..., array(...))`) or
110 * output-interpolation (eg `param(...)`), the mode will be
111 * set. Subsequent calls will be validated using the same mode.
112 */
113 const INTERPOLATE_AUTO = 'auto';
114
115 private $mode = NULL;
116 private $insertInto = NULL;
6dd717a6 117 private $insertVerb = 'INSERT INTO ';
c5458931 118 private $insertIntoFields = array();
e2b5e5b1
TO
119 private $selects = array();
120 private $from;
121 private $joins = array();
122 private $wheres = array();
123 private $groupBys = array();
124 private $havings = array();
125 private $orderBys = array();
c3a8e3e5
TO
126 private $limit = NULL;
127 private $offset = NULL;
c5458931 128 private $params = array();
f8da3b93 129 private $distinct = NULL;
c5458931
TO
130
131 // Public to work-around PHP 5.3 limit.
132 public $strict = NULL;
e2b5e5b1
TO
133
134 /**
fe482240 135 * Create a new SELECT query.
e2b5e5b1 136 *
77855840
TO
137 * @param string $from
138 * Table-name and optional alias.
c5458931
TO
139 * @param array $options
140 * @return CRM_Utils_SQL_Select
141 */
142 public static function from($from, $options = array()) {
143 return new self($from, $options);
144 }
145
146 /**
147 * Create a partial SELECT query.
148 *
149 * @param array $options
e2b5e5b1
TO
150 * @return CRM_Utils_SQL_Select
151 */
c5458931
TO
152 public static function fragment($options = array()) {
153 return new self(NULL, $options);
e2b5e5b1
TO
154 }
155
c3a8e3e5 156 /**
fe482240 157 * Create a new SELECT query.
c3a8e3e5 158 *
77855840
TO
159 * @param string $from
160 * Table-name and optional alias.
c5458931 161 * @param array $options
c3a8e3e5 162 */
c5458931 163 public function __construct($from, $options = array()) {
e2b5e5b1 164 $this->from = $from;
c5458931
TO
165 $this->mode = isset($options['mode']) ? $options['mode'] : self::INTERPOLATE_AUTO;
166 }
167
168 /**
169 * Make a new copy of this query.
170 *
171 * @return CRM_Utils_SQL_Select
172 */
173 public function copy() {
174 return clone $this;
175 }
176
177 /**
3d469574 178 * Merge something or other.
179 *
c5458931
TO
180 * @param CRM_Utils_SQL_Select $other
181 * @param array|NULL $parts
182 * ex: 'joins', 'wheres'
14069c56 183 * @return CRM_Utils_SQL_Select
c5458931
TO
184 */
185 public function merge($other, $parts = NULL) {
186 if ($other === NULL) {
187 return $this;
188 }
189
190 if ($this->mode === self::INTERPOLATE_AUTO) {
191 $this->mode = $other->mode;
192 }
193 elseif ($other->mode === self::INTERPOLATE_AUTO) {
194 // Noop.
195 }
196 elseif ($this->mode !== $other->mode) {
197 // Mixing modes will lead to someone getting an expected substitution.
198 throw new RuntimeException("Cannot merge queries that use different interpolation modes ({$this->mode} vs {$other->mode}).");
199 }
200
201 $arrayFields = array('insertIntoFields', 'selects', 'joins', 'wheres', 'groupBys', 'havings', 'orderBys', 'params');
202 foreach ($arrayFields as $f) {
203 if ($parts === NULL || in_array($f, $parts)) {
204 $this->{$f} = array_merge($this->{$f}, $other->{$f});
205 }
206 }
207
208 $flatFields = array('insertInto', 'from', 'limit', 'offset');
209 foreach ($flatFields as $f) {
210 if ($parts === NULL || in_array($f, $parts)) {
211 if ($other->{$f} !== NULL) {
212 $this->{$f} = $other->{$f};
213 }
214 }
215 }
216
217 return $this;
e2b5e5b1
TO
218 }
219
c3a8e3e5 220 /**
fe482240 221 * Add a new JOIN clause.
c3a8e3e5 222 *
c5458931
TO
223 * Note: To add multiple JOINs at once, use $name===NULL and
224 * pass an array of $exprs.
225 *
226 * @param string|NULL $name
77855840 227 * The effective alias of the joined table.
c5458931 228 * @param string|array $exprs
77855840 229 * The complete join expression (eg "INNER JOIN mytable myalias ON mytable.id = maintable.foo_id").
c3a8e3e5
TO
230 * @param array|null $args
231 * @return CRM_Utils_SQL_Select
232 */
c5458931
TO
233 public function join($name, $exprs, $args = NULL) {
234 if ($name !== NULL) {
235 $this->joins[$name] = $this->interpolate($exprs, $args);
236 }
237 else {
238 foreach ($exprs as $name => $expr) {
239 $this->joins[$name] = $this->interpolate($expr, $args);
240 }
241 return $this;
242 }
e2b5e5b1
TO
243 return $this;
244 }
245
246 /**
c3a8e3e5
TO
247 * Specify the column(s)/value(s) to return by adding to the SELECT clause
248 *
e2b5e5b1
TO
249 * @param string|array $exprs list of SQL expressions
250 * @param null|array $args use NULL to disable interpolation; use an array of variables to enable
251 * @return CRM_Utils_SQL_Select
252 */
253 public function select($exprs, $args = NULL) {
254 $exprs = (array) $exprs;
255 foreach ($exprs as $expr) {
6e6685dc 256 $this->selects[] = $this->interpolate($expr, $args);
e2b5e5b1
TO
257 }
258 return $this;
259 }
260
f8da3b93 261 /**
262 * Return only distinct values
263 *
264 * @param bool $isDistinct allow DISTINCT select or not
265 * @return CRM_Utils_SQL_Select
266 */
267 public function distinct($isDistinct = TRUE) {
268 if ($isDistinct) {
269 $this->distinct = 'DISTINCT ';
270 }
271 return $this;
272 }
273
e2b5e5b1 274 /**
c3a8e3e5
TO
275 * Limit results by adding extra condition(s) to the WHERE clause
276 *
e2b5e5b1
TO
277 * @param string|array $exprs list of SQL expressions
278 * @param null|array $args use NULL to disable interpolation; use an array of variables to enable
279 * @return CRM_Utils_SQL_Select
280 */
281 public function where($exprs, $args = NULL) {
282 $exprs = (array) $exprs;
283 foreach ($exprs as $expr) {
fa7c2979
TO
284 $evaluatedExpr = $this->interpolate($expr, $args);
285 $this->wheres[$evaluatedExpr] = $evaluatedExpr;
e2b5e5b1
TO
286 }
287 return $this;
288 }
289
290 /**
fe482240 291 * Group results by adding extra items to the GROUP BY clause.
c3a8e3e5 292 *
e2b5e5b1
TO
293 * @param string|array $exprs list of SQL expressions
294 * @param null|array $args use NULL to disable interpolation; use an array of variables to enable
295 * @return CRM_Utils_SQL_Select
296 */
297 public function groupBy($exprs, $args = NULL) {
298 $exprs = (array) $exprs;
299 foreach ($exprs as $expr) {
6e6685dc
C
300 $evaluatedExpr = $this->interpolate($expr, $args);
301 $this->groupBys[$evaluatedExpr] = $evaluatedExpr;
e2b5e5b1
TO
302 }
303 return $this;
304 }
305
306 /**
c3a8e3e5
TO
307 * Limit results by adding extra condition(s) to the HAVING clause
308 *
e2b5e5b1
TO
309 * @param string|array $exprs list of SQL expressions
310 * @param null|array $args use NULL to disable interpolation; use an array of variables to enable
311 * @return CRM_Utils_SQL_Select
312 */
313 public function having($exprs, $args = NULL) {
314 $exprs = (array) $exprs;
315 foreach ($exprs as $expr) {
6e6685dc
C
316 $evaluatedExpr = $this->interpolate($expr, $args);
317 $this->havings[$evaluatedExpr] = $evaluatedExpr;
e2b5e5b1
TO
318 }
319 return $this;
320 }
321
322 /**
fe482240 323 * Sort results by adding extra items to the ORDER BY clause.
c3a8e3e5 324 *
e2b5e5b1
TO
325 * @param string|array $exprs list of SQL expressions
326 * @param null|array $args use NULL to disable interpolation; use an array of variables to enable
4c6cc364
CW
327 * @param int $weight
328 * @return \CRM_Utils_SQL_Select
e2b5e5b1 329 */
6db70618
TO
330 public function orderBy($exprs, $args = NULL, $weight = 0) {
331 static $guid = 0;
e2b5e5b1
TO
332 $exprs = (array) $exprs;
333 foreach ($exprs as $expr) {
6e6685dc 334 $evaluatedExpr = $this->interpolate($expr, $args);
6db70618 335 $this->orderBys[$evaluatedExpr] = array('value' => $evaluatedExpr, 'weight' => $weight, 'guid' => $guid++);
e2b5e5b1
TO
336 }
337 return $this;
338 }
339
c5458931
TO
340 /**
341 * Set one (or multiple) parameters to interpolate into the query.
342 *
343 * @param array|string $keys
344 * Key name, or an array of key-value pairs.
345 * @param null|mixed $value
1f7d270d
TO
346 * The new value of the parameter.
347 * Values may be strings, ints, or arrays thereof -- provided that the
348 * SQL query uses appropriate prefix (e.g. "@", "!", "#").
14069c56 349 * @return \CRM_Utils_SQL_Select
c5458931
TO
350 */
351 public function param($keys, $value = NULL) {
352 if ($this->mode === self::INTERPOLATE_AUTO) {
353 $this->mode = self::INTERPOLATE_OUTPUT;
354 }
355 elseif ($this->mode !== self::INTERPOLATE_OUTPUT) {
356 throw new RuntimeException("Select::param() only makes sense when interpolating on output.");
357 }
358
359 if (is_array($keys)) {
360 foreach ($keys as $k => $v) {
361 $this->params[$k] = $v;
362 }
363 }
364 else {
365 $this->params[$keys] = $value;
366 }
367 return $this;
368 }
369
c3a8e3e5 370 /**
fe482240 371 * Set a limit on the number of records to return.
c3a8e3e5
TO
372 *
373 * @param int $limit
374 * @param int $offset
375 * @return CRM_Utils_SQL_Select
376 * @throws CRM_Core_Exception
377 */
378 public function limit($limit, $offset = 0) {
379 if ($limit !== NULL && !is_numeric($limit)) {
380 throw new CRM_Core_Exception("Illegal limit");
381 }
382 if ($offset !== NULL && !is_numeric($offset)) {
383 throw new CRM_Core_Exception("Illegal offset");
384 }
385 $this->limit = $limit;
386 $this->offset = $offset;
387 return $this;
388 }
389
c5458931
TO
390 /**
391 * Insert the results of the SELECT query into another
392 * table.
393 *
394 * @param string $table
395 * The name of the other table (which receives new data).
396 * @param array $fields
397 * The fields to fill in the other table (in order).
14069c56 398 * @return CRM_Utils_SQL_Select
c5458931
TO
399 * @see insertIntoField
400 */
401 public function insertInto($table, $fields = array()) {
402 $this->insertInto = $table;
403 $this->insertIntoField($fields);
404 return $this;
405 }
406
6dd717a6 407 /**
408 * Wrapper function of insertInto fn but sets insertVerb = "INSERT IGNORE INTO "
409 *
410 * @param string $table
411 * The name of the other table (which receives new data).
412 * @param array $fields
413 * The fields to fill in the other table (in order).
414 * @return CRM_Utils_SQL_Select
415 */
416 public function insertIgnoreInto($table, $fields = array()) {
417 $this->insertVerb = "INSERT IGNORE INTO ";
418 return $this->insertInto($table, $fields);
419 }
420
421 /**
422 * Wrapper function of insertInto fn but sets insertVerb = "REPLACE INTO "
423 *
424 * @param string $table
425 * The name of the other table (which receives new data).
426 * @param array $fields
427 * The fields to fill in the other table (in order).
428 */
429 public function replaceInto($table, $fields = array()) {
430 $this->insertVerb = "REPLACE INTO ";
431 return $this->insertInto($table, $fields);
432 }
433
434
c5458931
TO
435 /**
436 * @param array $fields
437 * The fields to fill in the other table (in order).
14069c56 438 * @return CRM_Utils_SQL_Select
c5458931
TO
439 */
440 public function insertIntoField($fields) {
441 $fields = (array) $fields;
442 foreach ($fields as $field) {
443 $this->insertIntoFields[] = $field;
444 }
445 return $this;
446 }
447
448 /**
449 * @param array|NULL $parts
450 * List of fields to check (e.g. 'selects', 'joins').
451 * Defaults to all.
452 * @return bool
453 */
454 public function isEmpty($parts = NULL) {
455 $empty = TRUE;
456 $fields = array(
457 'insertInto',
458 'insertIntoFields',
459 'selects',
460 'from',
461 'joins',
462 'wheres',
463 'groupBys',
464 'havings',
465 'orderBys',
466 'limit',
467 'offset',
468 );
469 if ($parts !== NULL) {
470 $fields = array_intersect($fields, $parts);
471 }
472 foreach ($fields as $field) {
473 if (!empty($this->{$field})) {
474 $empty = FALSE;
475 }
476 }
477 return $empty;
478 }
479
480 /**
481 * Enable (or disable) strict mode.
482 *
483 * In strict mode, unknown variables will generate exceptions.
484 *
485 * @param bool $strict
14069c56 486 * @return CRM_Utils_SQL_Select
c5458931
TO
487 */
488 public function strict($strict = TRUE) {
489 $this->strict = $strict;
490 return $this;
491 }
492
e2b5e5b1
TO
493 /**
494 * Given a string like "field_name = @value", replace "@value" with an escaped SQL string
495 *
1f7d270d 496 * @param string $expr SQL expression
e2b5e5b1
TO
497 * @param null|array $args a list of values to insert into the SQL expression; keys are prefix-coded:
498 * prefix '@' => escape SQL
499 * prefix '#' => literal number, skip escaping but do validation
500 * prefix '!' => literal, skip escaping and validation
501 * if a value is an array, then it will be imploded
502 *
503 * PHP NULL's will be treated as SQL NULL's. The PHP string "null" will be treated as a string.
504 *
ad37ac8e 505 * @param string $activeMode
506 *
a6c01b45 507 * @return string
e2b5e5b1 508 */
c5458931 509 public function interpolate($expr, $args, $activeMode = self::INTERPOLATE_INPUT) {
e2b5e5b1
TO
510 if ($args === NULL) {
511 return $expr;
512 }
513 else {
c5458931
TO
514 if ($this->mode === self::INTERPOLATE_AUTO) {
515 $this->mode = $activeMode;
516 }
517 elseif ($activeMode !== $this->mode) {
518 throw new RuntimeException("Cannot mix interpolation modes.");
519 }
520
521 $select = $this;
522 return preg_replace_callback('/([#!@])([a-zA-Z0-9_]+)/', function($m) use ($select, $args) {
523 if (isset($args[$m[2]])) {
524 $values = $args[$m[2]];
e2b5e5b1 525 }
c5458931
TO
526 elseif (isset($args[$m[1] . $m[2]])) {
527 // Backward compat. Keys in $args look like "#myNumber" or "@myString".
528 $values = $args[$m[1] . $m[2]];
e2b5e5b1 529 }
c5458931
TO
530 elseif ($select->strict) {
531 throw new CRM_Core_Exception('Cannot build query. Variable "' . $m[1] . $m[2] . '" is unknown.');
e2b5e5b1
TO
532 }
533 else {
c5458931
TO
534 // Unrecognized variables are ignored. Mitigate risk of accidents.
535 return $m[0];
e2b5e5b1 536 }
c5458931
TO
537 $values = is_array($values) ? $values : array($values);
538 switch ($m[1]) {
539 case '@':
540 $parts = array_map(array($select, 'escapeString'), $values);
541 return implode(', ', $parts);
542
577a782f 543 // TODO: ensure all uses of this un-escaped literal are safe
c5458931
TO
544 case '!':
545 return implode(', ', $values);
546
547 case '#':
548 foreach ($values as $valueKey => $value) {
549 if ($value === NULL) {
550 $values[$valueKey] = 'NULL';
551 }
552 elseif (!is_numeric($value)) {
b98af2ca
TO
553 //throw new API_Exception("Failed encoding non-numeric value" . var_export(array($m[0] => $values), TRUE));
554 throw new CRM_Core_Exception("Failed encoding non-numeric value (" . $m[0] . ")");
c5458931
TO
555 }
556 }
557 return implode(', ', $values);
558
559 default:
560 throw new CRM_Core_Exception("Unrecognized prefix");
561 }
562 }, $expr);
e2b5e5b1
TO
563 }
564 }
565
c3a8e3e5
TO
566 /**
567 * @param string|NULL $value
a6c01b45
CW
568 * @return string
569 * SQL expression, e.g. "it\'s great" (with-quotes) or NULL (without-quotes)
c3a8e3e5 570 */
c5458931 571 public function escapeString($value) {
e2b5e5b1
TO
572 return $value === NULL ? 'NULL' : '"' . CRM_Core_DAO::escapeString($value) . '"';
573 }
574
575 /**
a6c01b45
CW
576 * @return string
577 * SQL statement
e2b5e5b1
TO
578 */
579 public function toSQL() {
c5458931
TO
580 $sql = '';
581 if ($this->insertInto) {
6dd717a6 582 $sql .= $this->insertVerb . $this->insertInto . ' (';
c5458931
TO
583 $sql .= implode(', ', $this->insertIntoFields);
584 $sql .= ")\n";
585 }
6dd717a6 586
e2b5e5b1 587 if ($this->selects) {
f8da3b93 588 $sql .= 'SELECT ' . $this->distinct . implode(', ', $this->selects) . "\n";
e2b5e5b1
TO
589 }
590 else {
c5458931
TO
591 $sql .= 'SELECT *' . "\n";
592 }
593 if ($this->from !== NULL) {
594 $sql .= 'FROM ' . $this->from . "\n";
e2b5e5b1 595 }
e2b5e5b1
TO
596 foreach ($this->joins as $join) {
597 $sql .= $join . "\n";
598 }
599 if ($this->wheres) {
600 $sql .= 'WHERE (' . implode(') AND (', $this->wheres) . ")\n";
601 }
602 if ($this->groupBys) {
603 $sql .= 'GROUP BY ' . implode(', ', $this->groupBys) . "\n";
604 }
605 if ($this->havings) {
606 $sql .= 'HAVING (' . implode(') AND (', $this->havings) . ")\n";
607 }
608 if ($this->orderBys) {
6db70618
TO
609 $orderBys = CRM_Utils_Array::crmArraySortByField($this->orderBys,
610 array('weight', 'guid'));
4c6cc364
CW
611 $orderBys = CRM_Utils_Array::collect('value', $orderBys);
612 $sql .= 'ORDER BY ' . implode(', ', $orderBys) . "\n";
e2b5e5b1 613 }
c3a8e3e5
TO
614 if ($this->limit !== NULL) {
615 $sql .= 'LIMIT ' . $this->limit . "\n";
616 if ($this->offset !== NULL) {
617 $sql .= 'OFFSET ' . $this->offset . "\n";
618 }
619 }
c5458931
TO
620 if ($this->mode === self::INTERPOLATE_OUTPUT) {
621 $sql = $this->interpolate($sql, $this->params, self::INTERPOLATE_OUTPUT);
622 }
e2b5e5b1
TO
623 return $sql;
624 }
96025800 625
77e74ae1 626 /**
c4dcc9cf
TO
627 * Execute the query.
628 *
629 * To examine the results, use a function like `fetch()`, `fetchAll()`,
630 * `fetchValue()`, or `fetchMap()`.
631 *
632 * @param string|NULL $daoName
633 * The return object should be an instance of this class.
634 * Ex: 'CRM_Contact_BAO_Contact'.
635 * @param bool $i18nRewrite
636 * If the system has multilingual features, should the field/table
637 * names be rewritten?
77e74ae1 638 * @return CRM_Core_DAO
c4dcc9cf
TO
639 * @see CRM_Core_DAO::executeQuery
640 * @see CRM_Core_I18n_Schema::rewriteQuery
77e74ae1
TO
641 */
642 public function execute($daoName = NULL, $i18nRewrite = TRUE) {
643 // Don't pass through $params. toSQL() handles interpolation.
644 $params = array();
645
646 // Don't pass through $abort, $trapException. Just use straight-up exceptions.
647 $abort = TRUE;
648 $trapException = FALSE;
649 $errorScope = CRM_Core_TemporaryErrorScope::useException();
650
651 // Don't pass through freeDAO. You can do it yourself.
652 $freeDAO = FALSE;
653
654 return CRM_Core_DAO::executeQuery($this->toSQL(), $params, $abort, $daoName,
655 $freeDAO, $i18nRewrite, $trapException);
656 }
657
bc854509 658 /**
659 * Has an offset been set.
660 *
661 * @param string $offset
662 *
663 * @return bool
664 */
c5458931
TO
665 public function offsetExists($offset) {
666 return isset($this->params[$offset]);
667 }
668
1f7d270d
TO
669 /**
670 * Get the value of a SQL parameter.
671 *
672 * @code
673 * $select['cid'] = 123;
674 * $select->where('contact.id = #cid');
675 * echo $select['cid'];
676 * @endCode
677 *
678 * @param string $offset
679 * @return mixed
680 * @see param()
681 * @see ArrayAccess::offsetGet
682 */
c5458931
TO
683 public function offsetGet($offset) {
684 return $this->params[$offset];
685 }
686
1f7d270d
TO
687 /**
688 * Set the value of a SQL parameter.
689 *
690 * @code
691 * $select['cid'] = 123;
692 * $select->where('contact.id = #cid');
693 * echo $select['cid'];
694 * @endCode
695 *
696 * @param string $offset
697 * @param mixed $value
698 * The new value of the parameter.
699 * Values may be strings, ints, or arrays thereof -- provided that the
700 * SQL query uses appropriate prefix (e.g. "@", "!", "#").
701 * @see param()
702 * @see ArrayAccess::offsetSet
703 */
c5458931
TO
704 public function offsetSet($offset, $value) {
705 $this->param($offset, $value);
706 }
707
1f7d270d
TO
708 /**
709 * Unset the value of a SQL parameter.
710 *
711 * @param string $offset
712 * @see param()
713 * @see ArrayAccess::offsetUnset
714 */
c5458931
TO
715 public function offsetUnset($offset) {
716 unset($this->params[$offset]);
717 }
718
e2b5e5b1 719}