query.inc 53.9 KB
Newer Older
1 2 3 4 5 6 7
<?php

/**
 * @ingroup database
 * @{
 */

8 9 10 11 12
/**
 * @file
 * Non-specific Database query code. Used by all engines.
 */

13 14 15 16 17 18
/**
 * Interface for a conditional clause in a query.
 */
interface QueryConditionInterface {

  /**
19
   * Helper function: builds the most common conditional clauses.
20
   *
21
   * This method can take a variable number of parameters. If called with two
22 23
   * parameters, they are taken as $field and $value with $operator having a
   * value of IN if $value is an array and = otherwise.
24 25
   *
   * @param $field
26 27
   *   The name of the field to check. If you would like to add a more complex
   *   condition involving operators or functions, use where().
28
   * @param $value
29 30 31
   *   The value to test the field against. In most cases, this is a scalar.
   *   For more complex options, it is an array. The meaning of each element in
   *   the array is dependent on the $operator.
32
   * @param $operator
33 34 35 36
   *   The comparison operator, such as =, <, or >=. It also accepts more
   *   complex options such as IN, LIKE, or BETWEEN. Defaults to IN if $value is
   *   an array, and = otherwise.
   *
37
   * @return QueryConditionInterface
38 39 40 41 42
   *   The called object.
   */
  public function condition($field, $value = NULL, $operator = NULL);

  /**
43
   * Adds an arbitrary WHERE clause to the query.
44 45
   *
   * @param $snippet
46 47
   *   A portion of a WHERE clause as a prepared statement. It must use named
   *   placeholders, not ? placeholders.
48 49
   * @param $args
   *   An associative array of arguments.
50
   *
51
   * @return QueryConditionInterface
52 53 54 55
   *   The called object.
   */
  public function where($snippet, $args = array());

56
  /**
57
   * Sets a condition that the specified field be NULL.
58 59 60
   *
   * @param $field
   *   The name of the field to check.
61
   *
62
   * @return QueryConditionInterface
63 64 65 66 67
   *   The called object.
   */
  public function isNull($field);

  /**
68
   * Sets a condition that the specified field be NOT NULL.
69 70 71
   *
   * @param $field
   *   The name of the field to check.
72
   *
73
   * @return QueryConditionInterface
74 75 76 77
   *   The called object.
   */
  public function isNotNull($field);

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
  /**
   * Sets a condition that the specified subquery returns values.
   * 
   * @param SelectQueryInterface $select
   *   The subquery that must contain results.
   *
   * @return QueryConditionInterface
   *   The called object.
   */
  public function exists(SelectQueryInterface $select);
  
  /**
   * Sets a condition that the specified subquery returns no values.
   * 
   * @param SelectQueryInterface $select
   *   The subquery that must not contain results.
   *
   * @return QueryConditionInterface
   *   The called object.
   */
  public function notExists(SelectQueryInterface $select);
  
100 101 102
  /**
   * Gets a complete list of all conditions in this conditional clause.
   *
103
   * This method returns by reference. That allows alter hooks to access the
104
   * data structure directly and manipulate it before it gets compiled.
105
   *
106 107
   * The data structure that is returned is an indexed array of entries, where
   * each entry looks like the following:
108
   * @code
109 110 111 112 113
   * array(
   *   'field' => $field,
   *   'value' => $value,
   *   'operator' => $operator,
   * );
114
   * @endcode
115
   *
116
   * In the special case that $operator is NULL, the $field is taken as a raw
117
   * SQL snippet (possibly containing a function) and $value is an associative
118
   * array of placeholders for the snippet.
119 120
   *
   * There will also be a single array entry of #conjunction, which is the
121 122 123 124 125 126 127
   * conjunction that will be applied to the array, such as AND.
   */
  public function &conditions();

  /**
   * Gets a complete list of all values to insert into the prepared statement.
   *
128
   * @return
129 130 131
   *   An associative array of placeholders and values.
   */
  public function arguments();
132

133 134 135 136 137 138 139 140
  /**
   * Compiles the saved conditions for later retrieval.
   *
   * This method does not return anything, but simply prepares data to be
   * retrieved via __toString() and arguments().
   *
   * @param $connection
   *   The database connection for which to compile the conditionals.
141
   * @param $queryPlaceholder
142 143
   *   The query this condition belongs to. If not given, the current query is
   *   used.
144
   */
145
  public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder = NULL);
146 147 148 149 150 151 152
}


/**
 * Interface for a query that can be manipulated via an alter hook.
 */
interface QueryAlterableInterface {
153

154 155 156
  /**
   * Adds a tag to a query.
   *
157 158
   * Tags are strings that identify a query. A query may have any number of
   * tags. Tags are used to mark a query so that alter hooks may decide if they
159 160 161
   * wish to take action. Tags should be all lower-case and contain only
   * letters, numbers, and underscore, and start with a letter. That is, they
   * should follow the same rules as PHP identifiers in general.
162 163 164
   *
   * @param $tag
   *   The tag to add.
165
   *
166
   * @return QueryAlterableInterface
167
   *   The called object.
168 169
   */
  public function addTag($tag);
170

171 172 173 174 175
  /**
   * Determines if a given query has a given tag.
   *
   * @param $tag
   *   The tag to check.
176
   *
177 178 179 180
   * @return
   *   TRUE if this query has been marked with this tag, FALSE otherwise.
   */
  public function hasTag($tag);
181

182 183 184 185 186
  /**
   * Determines if a given query has all specified tags.
   *
   * @param $tags
   *   A variable number of arguments, one for each tag to check.
187
   *
188
   * @return
189 190
   *   TRUE if this query has been marked with all specified tags, FALSE
   *   otherwise.
191 192 193 194 195 196 197 198
   */
  public function hasAllTags();

  /**
   * Determines if a given query has any specified tag.
   *
   * @param $tags
   *   A variable number of arguments, one for each tag to check.
199
   *
200 201 202 203 204
   * @return
   *   TRUE if this query has been marked with at least one of the specified
   *   tags, FALSE otherwise.
   */
  public function hasAnyTag();
205

206 207 208 209
  /**
   * Adds additional metadata to the query.
   *
   * Often, a query may need to provide additional contextual data to alter
210
   * hooks. Alter hooks may then use that information to decide if and how
211 212 213
   * to take action.
   *
   * @param $key
214
   *   The unique identifier for this piece of metadata. Must be a string that
215 216
   *   follows the same rules as any other PHP identifier.
   * @param $object
217
   *   The additional data to add to the query. May be any valid PHP variable.
218
   *
219
   * @return QueryAlterableInterface
220
   *   The called object.
221 222
   */
  public function addMetaData($key, $object);
223

224 225 226 227 228
  /**
   * Retrieves a given piece of metadata.
   *
   * @param $key
   *   The unique identifier for the piece of metadata to retrieve.
229
   *
230 231 232 233 234 235
   * @return
   *   The previously attached metadata object, or NULL if one doesn't exist.
   */
  public function getMetaData($key);
}

236 237 238 239
/**
 * Interface for a query that accepts placeholders.
 */
interface QueryPlaceholderInterface {
240

241
  /**
242 243 244 245
   * Returns the next placeholder ID for the query.
   *
   * @return
   *   The next available placeholder ID as an integer.
246 247 248 249
   */
  function nextPlaceholder();
}

250
/**
251
 * Base class for query builders.
252
 *
253 254
 * Note that query builders use PHP's magic __toString() method to compile the
 * query object into a prepared statement.
255
 */
256
abstract class Query implements QueryPlaceholderInterface {
257 258 259 260 261 262 263 264

  /**
   * The connection object on which to run this query.
   *
   * @var DatabaseConnection
   */
  protected $connection;

265 266 267 268 269 270 271 272 273 274 275 276 277 278
  /**
   * The target of the connection object.
   * 
   * @var string
   */
  protected $connectionTarget;

  /**
   * The key of the connection object.
   * 
   * @var string
   */
  protected $connectionKey;

279 280 281 282 283 284 285
  /**
   * The query options to pass on to the connection object.
   *
   * @var array
   */
  protected $queryOptions;

286 287 288 289 290
  /**
   * The placeholder counter.
   */
  protected $nextPlaceholder = 0;

291 292 293 294 295 296 297
  /**
   * An array of comments that can be prepended to a query.
   *
   * @var array
   */
  protected $comments = array();

298 299 300 301 302 303 304 305
  /**
   * Constructs a Query object.
   *
   * @param DatabaseConnection $connection
   *   Database connection object.
   * @param array $options
   *   Array of query options.
   */
306 307
  public function __construct(DatabaseConnection $connection, $options) {
    $this->connection = $connection;
308 309 310
    $this->connectionKey = $this->connection->getKey();
    $this->connectionTarget = $this->connection->getTarget();

311 312 313
    $this->queryOptions = $options;
  }

314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
  /**
   * Implements the magic __sleep function to disconnect from the database.
   */
  public function __sleep() {
    $keys = get_object_vars($this);
    unset($keys['connection']);
    return array_keys($keys);
  }

  /**
  * Implements the magic __wakeup function to reconnect to the database.
   */
  public function __wakeup() {
    $this->connection = Database::getConnection($this->connectionTarget, $this->connectionKey);
  }

330
  /**
331
   * Runs the query against the database.
332 333 334 335
   */
  abstract protected function execute();

  /**
336
   * Implements PHP magic __toString method to convert the query to a string.
337
   *
338 339
   * The toString operation is how we compile a query object to a prepared
   * statement.
340 341 342
   *
   * @return
   *   A prepared statement query string for this object.
343
   */
344
  abstract public function __toString();
345

346 347 348 349 350 351
  /**
   * Gets the next placeholder value for this query object.
   *
   * @return int
   *   Next placeholder value.
   */
352 353 354
  public function nextPlaceholder() {
    return $this->nextPlaceholder++;
  }
355 356 357 358 359

  /**
   * Adds a comment to the query.
   *
   * By adding a comment to a query, you can more easily find it in your
360
   * query log or the list of active queries on an SQL server. This allows
361 362 363 364 365
   * for easier debugging and allows you to more easily find where a query
   * with a performance problem is being generated.
   *
   * @param $comment
   *   The comment string to be inserted into the query.
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
   * @return Query
   *   The called object.
   */
  public function comment($comment) {
    $this->comments[] = $comment;
    return $this;
  }

  /**
   * Returns a reference to the comments array for the query.
   *
   * Because this method returns by reference, alter hooks may edit the comments
   * array directly to make their changes. If just adding comments, however, the
   * use of comment() is preferred.
   *
   * Note that this method must be called by reference as well:
   * @code
   * $comments =& $query->getComments();
   * @endcode
   *
   * @return
   *   A reference to the comments array structure.
   */
  public function &getComments() {
    return $this->comments;
  }
393 394 395
}

/**
396
 * General class for an abstracted INSERT query.
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
 */
class InsertQuery extends Query {

  /**
   * The table on which to insert.
   *
   * @var string
   */
  protected $table;

  /**
   * An array of fields on which to insert.
   *
   * @var array
   */
  protected $insertFields = array();

  /**
415
   * An array of fields that should be set to their database-defined defaults.
416 417 418 419 420 421 422 423
   *
   * @var array
   */
  protected $defaultFields = array();

  /**
   * A nested array of values to insert.
   *
424 425 426 427
   * $insertValues is an array of arrays. Each sub-array is either an
   * associative array whose keys are field names and whose values are field
   * values to insert, or a non-associative array of values in the same order
   * as $insertFields.
428
   *
429 430 431 432 433 434
   * Whether multiple insert sets will be run in a single query or multiple
   * queries is left to individual drivers to implement in whatever manner is
   * most appropriate. The order of values in each sub-array must match the
   * order of fields in $insertFields.
   *
   * @var array
435 436 437
   */
  protected $insertValues = array();

438 439
  /**
   * A SelectQuery object to fetch the rows that should be inserted.
440 441
   *
   * @var SelectQueryInterface
442 443 444
   */
  protected $fromQuery;

445 446 447 448 449 450 451 452 453 454
  /**
   * Constructs an InsertQuery object.
   *
   * @param DatabaseConnection $connection
   *   A DatabaseConnection object.
   * @param string $table
   *   Name of the table to associate with this query.
   * @param array $options
   *   Array of database options.
   */
455
  public function __construct($connection, $table, array $options = array()) {
456 457 458
    if (!isset($options['return'])) {
      $options['return'] = Database::RETURN_INSERT_ID;
    }
459 460 461 462 463
    parent::__construct($connection, $options);
    $this->table = $table;
  }

  /**
464
   * Adds a set of field->value pairs to be inserted.
465
   *
466 467
   * This method may only be called once. Calling it a second time will be
   * ignored. To queue up multiple sets of values to be inserted at once,
468 469 470
   * use the values() method.
   *
   * @param $fields
471 472
   *   An array of fields on which to insert. This array may be indexed or
   *   associative. If indexed, the array is taken to be the list of fields.
473
   *   If associative, the keys of the array are taken to be the fields and
474
   *   the values are taken to be corresponding values to insert. If a
475 476
   *   $values argument is provided, $fields must be indexed.
   * @param $values
477
   *   An array of fields to insert into the database. The values must be
478
   *   specified in the same order as the $fields array.
479
   *
480
   * @return InsertQuery
481 482
   *   The called object.
   */
483
  public function fields(array $fields, array $values = array()) {
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
    if (empty($this->insertFields)) {
      if (empty($values)) {
        if (!is_numeric(key($fields))) {
          $values = array_values($fields);
          $fields = array_keys($fields);
        }
      }
      $this->insertFields = $fields;
      if (!empty($values)) {
        $this->insertValues[] = $values;
      }
    }

    return $this;
  }

  /**
501
   * Adds another set of values to the query to be inserted.
502
   *
503
   * If $values is a numeric-keyed array, it will be assumed to be in the same
504
   * order as the original fields() call. If it is associative, it may be
505 506 507 508 509
   * in any order as long as the keys of the array match the names of the
   * fields.
   *
   * @param $values
   *   An array of values to add to the query.
510
   *
511
   * @return InsertQuery
512 513
   *   The called object.
   */
514
  public function values(array $values) {
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
    if (is_numeric(key($values))) {
      $this->insertValues[] = $values;
    }
    else {
      // Reorder the submitted values to match the fields array.
      foreach ($this->insertFields as $key) {
        $insert_values[$key] = $values[$key];
      }
      // For consistency, the values array is always numerically indexed.
      $this->insertValues[] = array_values($insert_values);
    }
    return $this;
  }

  /**
530
   * Specifies fields for which the database defaults should be used.
531
   *
532
   * If you want to force a given field to use the database-defined default,
533
   * not NULL or undefined, use this method to instruct the database to use
534
   * default values explicitly. In most cases this will not be necessary
535 536
   * unless you are inserting a row that is all default values, as you cannot
   * specify no values in an INSERT query.
537
   *
538 539
   * Specifying a field both in fields() and in useDefaults() is an error
   * and will not execute.
540
   *
541 542 543
   * @param $fields
   *   An array of values for which to use the default values
   *   specified in the table definition.
544
   *
545
   * @return InsertQuery
546 547
   *   The called object.
   */
548
  public function useDefaults(array $fields) {
549 550 551
    $this->defaultFields = $fields;
    return $this;
  }
552

553 554 555 556 557 558 559 560 561
  /**
   * Sets the fromQuery on this InsertQuery object.
   *
   * @param SelectQueryInterface $query
   *   The query to fetch the rows that should be inserted.
   *
   * @return InsertQuery
   *   The called object.
   */
562 563 564 565 566
  public function from(SelectQueryInterface $query) {
    $this->fromQuery = $query;
    return $this;
  }

567 568 569 570
  /**
   * Executes the insert query.
   *
   * @return
571
   *   The last insert ID of the query, if one exists. If the query
572
   *   was given multiple sets of values to insert, the return value is
573 574
   *   undefined. If no fields are specified, this method will do nothing and
   *   return NULL. That makes it safe to use in multi-insert loops.
575 576
   */
  public function execute() {
577 578
    // If validation fails, simply return NULL. Note that validation routines
    // in preExecute() may throw exceptions instead.
579
    if (!$this->preExecute()) {
580 581 582
      return NULL;
    }

583 584 585
    // If we're selecting from a SelectQuery, finish building the query and
    // pass it back, as any remaining options are irrelevant.
    if (!empty($this->fromQuery)) {
586
      $sql = (string) $this;
587 588
      // The SelectQuery may contain arguments, load and pass them through.
      return $this->connection->query($sql, $this->fromQuery->getArguments(), $this->queryOptions);
589 590
    }

591 592
    $last_insert_id = 0;

593 594
    // Each insert happens in its own query in the degenerate case. However,
    // we wrap it in a transaction so that it is atomic where possible. On many
595 596
    // databases, such as SQLite, this is also a notable performance boost.
    $transaction = $this->connection->startTransaction();
597 598 599 600 601 602 603 604 605 606 607 608

    try {
      $sql = (string) $this;
      foreach ($this->insertValues as $insert_values) {
        $last_insert_id = $this->connection->query($sql, $insert_values, $this->queryOptions);
      }
    }
    catch (Exception $e) {
      // One of the INSERTs failed, rollback the whole batch.
      $transaction->rollback();
      // Rethrow the exception for the calling code.
      throw $e;
609 610 611 612 613
    }

    // Re-initialize the values array so that we can re-use this query.
    $this->insertValues = array();

614 615
    // Transaction commits here where $transaction looses scope.

616 617 618
    return $last_insert_id;
  }

619 620 621 622 623 624
  /**
   * Implements PHP magic __toString method to convert the query to a string.
   *
   * @return string
   *   The prepared statement.
   */
625
  public function __toString() {
626

627 628 629
    // Create a comments string to prepend to the query.
    $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';

630 631
    // Default fields are always placed first for consistency.
    $insert_fields = array_merge($this->defaultFields, $this->insertFields);
632

633
    if (!empty($this->fromQuery)) {
634
      return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery;
635 636
    }

637
    // For simplicity, we will use the $placeholders array to inject
638 639 640 641 642 643
    // default keywords even though they are not, strictly speaking,
    // placeholders for prepared statements.
    $placeholders = array();
    $placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
    $placeholders = array_pad($placeholders, count($this->insertFields), '?');

644
    return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES (' . implode(', ', $placeholders) . ')';
645
  }
646 647

  /**
648
   * Preprocesses and validates the query.
649 650 651
   *
   * @return
   *   TRUE if the validation was successful, FALSE if not.
652 653 654
   *
   * @throws FieldsOverlapException
   * @throws NoFieldsException
655
   */
656
  public function preExecute() {
657 658 659 660 661 662 663 664 665 666
    // Confirm that the user did not try to specify an identical
    // field and default field.
    if (array_intersect($this->insertFields, $this->defaultFields)) {
      throw new FieldsOverlapException('You may not specify the same field to have a value and a schema-default value.');
    }

    if (!empty($this->fromQuery)) {
      // We have to assume that the used aliases match the insert fields.
      // Regular fields are added to the query before expressions, maintain the
      // same order for the insert fields.
667
      // This behavior can be overridden by calling fields() manually as only the
668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
      // first call to fields() does have an effect.
      $this->fields(array_merge(array_keys($this->fromQuery->getFields()), array_keys($this->fromQuery->getExpressions())));
    }

    // Don't execute query without fields.
    if (count($this->insertFields) + count($this->defaultFields) == 0) {
      throw new NoFieldsException('There are no fields available to insert with.');
    }

    // If no values have been added, silently ignore this query. This can happen
    // if values are added conditionally, so we don't want to throw an
    // exception.
    if (!isset($this->insertValues[0]) && count($this->insertFields) > 0 && empty($this->fromQuery)) {
      return FALSE;
    }
    return TRUE;
  }
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
}

/**
 * General class for an abstracted DELETE operation.
 */
class DeleteQuery extends Query implements QueryConditionInterface {

  /**
   * The table from which to delete.
   *
   * @var string
   */
  protected $table;

  /**
700 701 702
   * The condition object for this query.
   *
   * Condition handling is handled via composition.
703 704 705 706 707
   *
   * @var DatabaseCondition
   */
  protected $condition;

708 709 710 711 712 713 714 715 716 717
  /**
   * Constructs a DeleteQuery object.
   *
   * @param DatabaseConnection $connection
   *   A DatabaseConnection object.
   * @param string $table
   *   Name of the table to associate with this query.
   * @param array $options
   *   Array of database options.
   */
718
  public function __construct(DatabaseConnection $connection, $table, array $options = array()) {
719 720 721
    $options['return'] = Database::RETURN_AFFECTED;
    parent::__construct($connection, $options);
    $this->table = $table;
722

723 724 725
    $this->condition = new DatabaseCondition('AND');
  }

726 727 728
  /**
   * Implements QueryConditionInterface::condition().
   */
729 730
  public function condition($field, $value = NULL, $operator = NULL) {
    $this->condition->condition($field, $value, $operator);
731 732 733
    return $this;
  }

734 735 736
  /**
   * Implements QueryConditionInterface::isNull().
   */
737 738 739 740 741
  public function isNull($field) {
    $this->condition->isNull($field);
    return $this;
  }

742 743 744
  /**
   * Implements QueryConditionInterface::isNotNull().
   */
745 746 747 748 749
  public function isNotNull($field) {
    $this->condition->isNotNull($field);
    return $this;
  }

750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765
  /**
   * Implements QueryConditionInterface::exists().
   */
  public function exists(SelectQueryInterface $select) {
    $this->condition->exists($select);
    return $this;
  }

  /**
   * Implements QueryConditionInterface::notExists().
   */
  public function notExists(SelectQueryInterface $select) {
    $this->condition->notExists($select);
    return $this;
  }
  
766 767 768
  /**
   * Implements QueryConditionInterface::conditions().
   */
769 770 771 772
  public function &conditions() {
    return $this->condition->conditions();
  }

773 774 775
  /**
   * Implements QueryConditionInterface::arguments().
   */
776 777 778 779
  public function arguments() {
    return $this->condition->arguments();
  }

780 781 782
  /**
   * Implements QueryConditionInterface::where().
   */
783 784 785 786
  public function where($snippet, $args = array()) {
    $this->condition->where($snippet, $args);
    return $this;
  }
787

788 789 790
  /**
   * Implements QueryConditionInterface::compile().
   */
791 792
  public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder = NULL) {
    return $this->condition->compile($connection, isset($queryPlaceholder) ? $queryPlaceholder : $this);
793 794
  }

795 796 797 798 799 800
  /**
   * Executes the DELETE query.
   *
   * @return
   *   The return value is dependant on the database connection.
   */
801 802 803
  public function execute() {
    $values = array();
    if (count($this->condition)) {
804
      $this->condition->compile($this->connection, $this);
805 806 807
      $values = $this->condition->arguments();
    }

808
    return $this->connection->query((string) $this, $values, $this->queryOptions);
809 810
  }

811 812 813 814 815 816
  /**
   * Implements PHP magic __toString method to convert the query to a string.
   *
   * @return string
   *   The prepared statement.
   */
817
  public function __toString() {
818 819 820 821 822

    // Create a comments string to prepend to the query.
    $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';

    $query = $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} ';
823 824

    if (count($this->condition)) {
825

826
      $this->condition->compile($this->connection, $this);
827 828 829 830 831 832 833
      $query .= "\nWHERE " . $this->condition;
    }

    return $query;
  }
}

834 835 836 837 838 839 840

/**
 * General class for an abstracted TRUNCATE operation.
 */
class TruncateQuery extends Query {

  /**
841
   * The table to truncate.
842 843 844 845 846
   *
   * @var string
   */
  protected $table;

847 848 849 850 851 852 853 854 855 856
  /**
   * Constructs a TruncateQuery object.
   *
   * @param DatabaseConnection $connection
   *   A DatabaseConnection object.
   * @param string $table
   *   Name of the table to associate with this query.
   * @param array $options
   *   Array of database options.
   */
857 858 859 860 861 862
  public function __construct(DatabaseConnection $connection, $table, array $options = array()) {
    $options['return'] = Database::RETURN_AFFECTED;
    parent::__construct($connection, $options);
    $this->table = $table;
  }

863 864 865
  /**
   * Implements QueryConditionInterface::compile().
   */
866 867
  public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder = NULL) {
    return $this->condition->compile($connection, isset($queryPlaceholder) ? $queryPlaceholder : $this);
868 869
  }

870 871 872 873 874 875
  /**
   * Executes the TRUNCATE query.
   *
   * @return
   *   Return value is dependent on the database type.
   */
876
  public function execute() {
877
    return $this->connection->query((string) $this, array(), $this->queryOptions);
878 879
  }

880 881 882 883 884 885
  /**
   * Implements PHP magic __toString method to convert the query to a string.
   *
   * @return string
   *   The prepared statement.
   */
886
  public function __toString() {
887 888 889 890
    // Create a comments string to prepend to the query.
    $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';

    return $comments . 'TRUNCATE {' . $this->connection->escapeTable($this->table) . '} ';
891 892 893
  }
}

894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910
/**
 * General class for an abstracted UPDATE operation.
 */
class UpdateQuery extends Query implements QueryConditionInterface {

  /**
   * The table to update.
   *
   * @var string
   */
  protected $table;

  /**
   * An array of fields that will be updated.
   *
   * @var array
   */
911
  protected $fields = array();
912 913 914 915 916 917 918 919 920

  /**
   * An array of values to update to.
   *
   * @var array
   */
  protected $arguments = array();

  /**
921 922 923
   * The condition object for this query.
   *
   * Condition handling is handled via composition.
924 925 926 927 928 929
   *
   * @var DatabaseCondition
   */
  protected $condition;

  /**
930
   * Array of fields to update to an expression in case of a duplicate record.
931 932
   *
   * This variable is a nested array in the following format:
933
   * @code
934
   * <some field> => array(
935 936
   *  'condition' => <condition to execute, as a string>,
   *  'arguments' => <array of arguments for condition, or NULL for none>,
937
   * );
938
   * @endcode
939 940 941 942 943
   *
   * @var array
   */
  protected $expressionFields = array();

944 945 946 947 948 949 950 951 952 953
  /**
   * Constructs an UpdateQuery object.
   *
   * @param DatabaseConnection $connection
   *   A DatabaseConnection object.
   * @param string $table
   *   Name of the table to associate with this query.
   * @param array $options
   *   Array of database options.
   */
954
  public function __construct(DatabaseConnection $connection, $table, array $options = array()) {
955 956 957
    $options['return'] = Database::RETURN_AFFECTED;
    parent::__construct($connection, $options);
    $this->table = $table;
958

959 960 961
    $this->condition = new DatabaseCondition('AND');
  }

962 963 964
  /**
   * Implements QueryConditionInterface::condition().
   */
965 966
  public function condition($field, $value = NULL, $operator = NULL) {
    $this->condition->condition($field, $value, $operator);
967 968 969
    return $this;
  }

970 971 972
  /**
   * Implements QueryConditionInterface::isNull().
   */
973 974 975 976 977
  public function isNull($field) {
    $this->condition->isNull($field);
    return $this;
  }

978 979 980
  /**
   * Implements QueryConditionInterface::isNotNull().
   */
981 982 983 984 985
  public function isNotNull($field) {
    $this->condition->isNotNull($field);
    return $this;
  }

986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001
  /**
   * Implements QueryConditionInterface::exists().
   */
  public function exists(SelectQueryInterface $select) {
    $this->condition->exists($select);
    return $this;
  }

  /**
   * Implements QueryConditionInterface::notExists().
   */
  public function notExists(SelectQueryInterface $select) {
    $this->condition->notExists($select);
    return $this;
  }
  
1002 1003 1004
  /**
   * Implements QueryConditionInterface::conditions().
   */
1005 1006 1007 1008
  public function &conditions() {
    return $this->condition->conditions();
  }

1009 1010 1011
  /**
   * Implements QueryConditionInterface::arguments().
   */
1012 1013 1014 1015
  public function arguments() {
    return $this->condition->arguments();
  }

1016 1017 1018
  /**
   * Implements QueryConditionInterface::where().
   */
1019 1020 1021 1022
  public function where($snippet, $args = array()) {
    $this->condition->where($snippet, $args);
    return $this;
  }
1023

1024 1025 1026
  /**
   * Implements QueryConditionInterface::compile().
   */
1027 1028
  public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder = NULL) {
    return $this->condition->compile($connection, isset($queryPlaceholder) ? $queryPlaceholder : $this);
1029 1030 1031
  }

  /**
1032
   * Adds a set of field->value pairs to be updated.
1033 1034
   *
   * @param $fields
1035
   *   An associative array of fields to write into the database. The array keys
1036 1037
   *   are the field names and the values are the values to which to set them.
   *
1038
   * @return UpdateQuery
1039 1040
   *   The called object.
   */
1041
  public function fields(array $fields) {
1042 1043 1044 1045 1046
    $this->fields = $fields;
    return $this;
  }

  /**
1047
   * Specifies fields to be updated as an expression.
1048
   *
1049
   * Expression fields are cases such as counter=counter+1. This method takes
1050 1051 1052 1053 1054
   * precedence over fields().
   *
   * @param $field
   *   The field to set.
   * @param $expression
1055
   *   The field will be set to the value of this expression. This parameter
1056 1057 1058 1059
   *   may include named placeholders.
   * @param $arguments
   *   If specified, this is an array of key/value pairs for named placeholders
   *   corresponding to the expression.
1060
   *
1061
   * @return UpdateQuery
1062 1063
   *   The called object.
   */
1064
  public function expression($field, $expression, array $arguments = NULL) {
1065 1066 1067 1068
    $this->expressionFields[$field] = array(
      'expression' => $expression,
      'arguments' => $arguments,
    );
1069

1070 1071 1072
    return $this;
  }

1073 1074 1075 1076 1077 1078
  /**
   * Executes the UPDATE query.
   *
   * @return
   *   The number of rows affected by the update.
   */
1079
  public function execute() {
1080

1081 1082 1083 1084 1085 1086 1087 1088 1089 1090
    // Expressions take priority over literal fields, so we process those first
    // and remove any literal fields that conflict.
    $fields = $this->fields;
    $update_values = array();
    foreach ($this->expressionFields as $field => $data) {
      if (!empty($data['arguments'])) {
        $update_values += $data['arguments'];
      }
      unset($fields[$field]);
    }
1091

1092 1093 1094 1095 1096 1097
    // Because we filter $fields the same way here and in __toString(), the
    // placeholders will all match up properly.
    $max_placeholder = 0;
    foreach ($fields as $field => $value) {
      $update_values[':db_update_placeholder_' . ($max_placeholder++)] = $value;
    }
1098

1099
    if (count($this->condition)) {
1100
      $this->condition->compile($this->connection, $this);
1101 1102 1103
      $update_values = array_merge($update_values, $this->condition->arguments());
    }

1104
    return $this->connection->query((string) $this, $update_values, $this->queryOptions);
1105 1106
  }

1107 1108 1109 1110 1111 1112
  /**
   * Implements PHP magic __toString method to convert the query to a string.
   *
   * @return string
   *   The prepared statement.
   */
1113
  public function __toString() {
1114 1115 1116 1117

    // Create a comments string to prepend to the query.
    $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';

1118 1119 1120 1121 1122 1123 1124 1125
    // Expressions take priority over literal fields, so we process those first
    // and remove any literal fields that conflict.
    $fields = $this->fields;
    $update_fields = array();
    foreach ($this->expressionFields as $field => $data) {
      $update_fields[] = $field . '=' . $data['expression'];
      unset($fields[$field]);
    }
1126

1127 1128 1129 1130 1131
    $max_placeholder = 0;
    foreach ($fields as $field => $value) {
      $update_fields[] = $field . '=:db_update_placeholder_' . ($max_placeholder++);
    }

1132
    $query = $comments . 'UPDATE {' . $this->connection->escapeTable($this->table) . '} SET ' . implode(', ', $update_fields);
1133 1134

    if (count($this->condition)) {
1135
      $this->condition->compile($this->connection, $this);
1136 1137 1138 1139 1140