You are here

public function SelectQuery_sqlsrv::preExecute in Drupal driver for SQL Server and SQL Azure 7.2

Same name and namespace in other branches
  1. 7.3 sqlsrv/select.inc \SelectQuery_sqlsrv::preExecute()
  2. 7 sqlsrv/select.inc \SelectQuery_sqlsrv::preExecute()

Override for SelectQuery::preExecute().

Ensure that all the fields in ORDER BY and GROUP BY are part of the main query.

Overrides SelectQuery::preExecute

File

sqlsrv/select.inc, line 37

Class

SelectQuery_sqlsrv

Code

public function preExecute(SelectQueryInterface $query = NULL) {

  // If no query object is passed in, use $this.
  if (!isset($query)) {
    $query = $this;
  }

  // Only execute this once.
  if ($this
    ->isPrepared()) {
    return TRUE;
  }

  // Execute standard pre-execution first.
  parent::preExecute($query);
  if ($this->distinct || $this->group) {

    // When the query is DISTINCT or contains GROUP BY fields, all the fields
    // in the GROUP BY and ORDER BY clauses must appear in the returned
    // columns.
    $columns = $this->order + array_flip($this->group);
    $counter = 0;
    foreach ($columns as $field => $dummy) {
      $found = FALSE;
      foreach ($this->fields as $f) {
        if (!isset($f['table']) || !isset($f['field'])) {
          continue;
        }
        $alias = "{$f['table']}.{$f['field']}";
        if ($alias == $field) {
          $found = TRUE;
          break;
        }
      }
      if (!isset($this->fields[$field]) && !isset($this->expressions[$field]) && !$found) {
        $alias = '_field_' . $counter++;
        $this
          ->addExpression($field, $alias, array(), FALSE, FALSE);
        $this->queryOptions['sqlsrv_drop_columns'][] = $alias;
      }
    }

    // The other way round is also true, if using aggregates, all the fields in the SELECT
    // must be present in the GROUP BY.
    if (!empty($this->group)) {
      foreach ($this->fields as $field) {
        $spec = $field['table'] . '.' . $field['field'];
        $alias = $field['alias'];
        if (!isset($this->group[$spec]) && !isset($this->group[$alias])) {
          $this->group[$spec] = $spec;
        }
      }
    }

    // More over, GROUP BY columns cannot use aliases, so expand them to
    // their full expressions.
    foreach ($this->group as $key => &$group_field) {

      // Expand an alias on a field.
      if (isset($this->fields[$group_field])) {
        $field = $this->fields[$group_field];
        $group_field = (isset($field['table']) ? $this->connection
          ->escapeTable($field['table']) . '.' : '') . $this->connection
          ->escapeField($field['field']);
      }
      else {
        if (isset($this->expressions[$group_field])) {
          $expression = $this->expressions[$group_field];
          $group_field = $expression['expression'];

          // If the expression has arguments, we now
          // have duplicate placeholders. Run as insecure.
          if (is_array($expression['arguments'])) {
            $this->queryOptions['insecure'] = TRUE;
          }
        }
      }
    }
  }

  // Verify type bindings in the conditions, and throw the Exception
  // now to prevent a bug in MSSQLPDO where transactions are f**** UP
  // when the driver throws a PDOException().
  // @see https://github.com/Azure/msphpsql/issues/50
  //
  // TODO: Remove when the issue is fixed in the PDO driver.
  if (DatabaseUtils::GetConfigBoolean('MSSQL_VERIFY_NUMERIC_BINDINGS')) {
    foreach ($this->where
      ->conditions() as $condition) {
      if (!isset($condition['field']) || !is_string($condition['field'])) {
        continue;
      }

      // Make sure we have a valid $table.$field format.
      $parts = explode('.', $condition['field']);
      if (count($parts) !== 2) {
        continue;
      }
      list($table, $field_alias) = $parts;

      // Fin the real field name if this was an alias.
      $fields = $this
        ->getFields();
      $field = $field_alias;
      if (isset($fields[$field_alias])) {
        $field = $fields[$field_alias]['field'];
      }

      // Get the real table name.
      $tables = $this
        ->getTables();
      if (!isset($tables[$table])) {
        continue;
      }
      $real_table = $tables[$table]['table'];

      /** @var DatabaseSchema_sqlsrv **/
      $schema = $this->connection
        ->schema();
      $spec = $schema
        ->queryColumnInformation($real_table);
      if (!isset($spec['columns'][$field])) {
        continue;
      }

      // If no operator defined SKIP
      if (!isset($condition['operator'])) {
        continue;
      }

      // Only do this validation for very specific operators (note that IS NULL is an operator itself)
      if (!in_array($condition['operator'], array(
        '=',
        '<',
        '>',
        'IN',
        '<>',
        '>=',
        '<=',
      ))) {
        continue;
      }
      $col_spec = $spec['columns'][$field];
      $values = $condition['value'];
      if (!is_array($values)) {
        $values = array(
          $values,
        );
      }
      foreach ($values as $value) {
        if (!is_numeric($value) && !is_bool($value)) {
          if (in_array($col_spec['type'], array(
            'int',
            'bigint',
          ))) {

            // This is anyways going to throw an exception when running the query against the PDO driver.
            // Only trigger the exception if inside a transaction, to maximize backwards compatibility.
            if ($this->connection
              ->inTransaction()) {
              $type = gettype($value);
              $exception = new \PDOException("Invalid type binding for column ({$col_spec['type']}){$real_table}.{$col_spec['name']} and value ({$type})[{$value}]");
              throw $exception;
            }
          }
        }
      }
    }
  }
  return $this->prepared;
}