/** * Additional columns and views need to be added to existing fields. * Below is an example using the addtional column and view defined above. * my_new_view_additional_data() is schema defined in * hook_recline_field_columns. */ function hook_update_N(&$sandbox) { $ret = array(); $fields = field_info_fields(); foreach ($fields as $field_name => $field) { if ($field['type'] == 'recline_field' && $field['storage']['type'] == 'field_sql_storage') { foreach ($field['storage']['details']['sql'] as $type => $table_info) { foreach ($table_info as $table_name => $columns) { $column_name = _field_sql_storage_columnname($field_name, 'my_new_view_additional_data'); // Adding my_new_view_additional_data. if (!db_field_exists($table_name, $column_name)) { // Calling schema defined in hook_recline_field_column(). $schema = my_new_view_additional_data(); db_add_field($table_name, $column_name, $schema); } // Adding my_new_view. $column_name = _field_sql_storage_columnname($field_name, 'my_new_view'); $schema = recline_field_schema(); if (!db_field_exists($table_name, $column_name)) { db_add_field($table_name, $column_name, $schema['columns']['my_new_view']); } field_cache_clear(); } } } } return $ret; }
/** * Execute an EntityFieldQuery. * * This hook is called to find the entities having certain entity and field * conditions and sort them in the given field order. If the field storage * engine also handles property sorts and orders, it should unset those * properties in the called object to signal that those have been handled. * * @param EntityFieldQuery $query * An EntityFieldQuery. * * @return * See EntityFieldQuery::execute() for the return values. */ function hook_field_storage_query($query) { $groups = array(); if ($query->age == FIELD_LOAD_CURRENT) { $tablename_function = '_field_sql_storage_tablename'; $id_key = 'entity_id'; } else { $tablename_function = '_field_sql_storage_revision_tablename'; $id_key = 'revision_id'; } $table_aliases = array(); // Add tables for the fields used. foreach ($query->fields as $key => $field) { $tablename = $tablename_function($field); // Every field needs a new table. $table_alias = $tablename . $key; $table_aliases[$key] = $table_alias; if ($key) { $select_query->join($tablename, $table_alias, "{$table_alias}.entity_type = {$field_base_table}.entity_type AND {$table_alias}.{$id_key} = {$field_base_table}.{$id_key}"); } else { $select_query = db_select($tablename, $table_alias); $select_query->addTag('entity_field_access'); $select_query->addMetaData('base_table', $tablename); $select_query->fields($table_alias, array('entity_type', 'entity_id', 'revision_id', 'bundle')); $field_base_table = $table_alias; } if ($field['cardinality'] != 1) { $select_query->distinct(); } } // Add field conditions. foreach ($query->fieldConditions as $key => $condition) { $table_alias = $table_aliases[$key]; $field = $condition['field']; // Add the specified condition. $sql_field = "{$table_alias}." . _field_sql_storage_columnname($field['field_name'], $condition['column']); $query->addCondition($select_query, $sql_field, $condition); // Add delta / language group conditions. foreach (array('delta', 'language') as $column) { if (isset($condition[$column . '_group'])) { $group_name = $condition[$column . '_group']; if (!isset($groups[$column][$group_name])) { $groups[$column][$group_name] = $table_alias; } else { $select_query->where("{$table_alias}.{$column} = " . $groups[$column][$group_name] . ".{$column}"); } } } } if (isset($query->deleted)) { $select_query->condition("{$field_base_table}.deleted", (int) $query->deleted); } // Is there a need to sort the query by property? $has_property_order = FALSE; foreach ($query->order as $order) { if ($order['type'] == 'property') { $has_property_order = TRUE; } } if ($query->propertyConditions || $has_property_order) { if (empty($query->entityConditions['entity_type']['value'])) { throw new EntityFieldQueryException('Property conditions and orders must have an entity type defined.'); } $entity_type = $query->entityConditions['entity_type']['value']; $entity_base_table = _field_sql_storage_query_join_entity($select_query, $entity_type, $field_base_table); $query->entityConditions['entity_type']['operator'] = '='; foreach ($query->propertyConditions as $property_condition) { $query->addCondition($select_query, "{$entity_base_table}." . $property_condition['column'], $property_condition); } } foreach ($query->entityConditions as $key => $condition) { $query->addCondition($select_query, "{$field_base_table}.{$key}", $condition); } // Order the query. foreach ($query->order as $order) { if ($order['type'] == 'entity') { $key = $order['specifier']; $select_query->orderBy("{$field_base_table}.{$key}", $order['direction']); } elseif ($order['type'] == 'field') { $specifier = $order['specifier']; $field = $specifier['field']; $table_alias = $table_aliases[$specifier['index']]; $sql_field = "{$table_alias}." . _field_sql_storage_columnname($field['field_name'], $specifier['column']); $select_query->orderBy($sql_field, $order['direction']); } elseif ($order['type'] == 'property') { $select_query->orderBy("{$entity_base_table}." . $order['specifier'], $order['direction']); } } return $query->finishQuery($select_query, $id_key); }
/** * Called to determine what to tell the clicksorter. */ function click_sort($order) { // No column selected, can't continue. if (empty($this->options['click_sort_column'])) { return; } $this->ensure_my_table(); $column = _field_sql_storage_columnname($this->definition['field_name'], _field_sql_storage_columnname($this->definition['subfield_name'], $this->options['click_sort_column'])); if (!isset($this->aliases[$column])) { // Column is not in query; add a sort on it (without adding the column). $this->aliases[$column] = $this->table_alias . '.' . $column; } $this->query->add_orderby(NULL, NULL, $order, $this->aliases[$column]); }
public static function changeSchema(array &$field, array $column_renames = array()) { // Update the field schema $old_schema = array_intersect_key($field, array('columns' => '', 'indexes' => '', 'foreign keys' => '')); module_load_install($field['module']); $new_schema = (array) module_invoke($field['module'], 'field_schema', $field); $new_schema += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array()); $field['data']['columns'] = $new_schema['columns']; $field['data']['indexes'] = $new_schema['indexes']; $field['data']['foreign keys'] = $new_schema['foreign keys']; $data_table = _field_sql_storage_tablename($field); $revision_table = _field_sql_storage_revision_tablename($field); // Validate that all the columns described in the existing schema actually exist. foreach (array_keys($old_schema['columns']) as $old_column) { $old_column_name = _field_sql_storage_columnname($field['field_name'], $old_column); if (!db_field_exists($data_table, $old_column_name)) { throw new Exception(); } if (!db_field_exists($revision_table, $old_column_name)) { throw new Exception(); } // Attempt to re-use any columns that have the same name. // This can be skipped by setting $column_renames['column-name'] = FALSE; if (!empty($new_schema['columns'][$old_column]) && !isset($column_renames[$old_column])) { $column_renames[$old_column] = $old_column; } } // Validate that any columns to be renamed actually exist. foreach ($column_renames as $old_column => $new_column) { if (!isset($old_schema['columns'][$old_column])) { throw new Exception("Cannot rename field {$field['field_name']} column {$old_column} because it does not exist in the old schema."); } if (!isset($new_schema['columns'][$new_column])) { throw new Exception("Cannot rename field {$field['field_name']} column {$old_column} to {$new_column} because it does not exist in the new schema."); } } // Remove all existing indexes. foreach ($old_schema['indexes'] as $index => $index_fields) { $index_name = _field_sql_storage_indexname($field['field_name'], $index); if (db_index_exists($data_table, $index_name)) { watchdog('helper', "Dropped index {$data_table}.{$index_name}"); db_drop_index($data_table, $index_name); } if (db_index_exists($revision_table, $index_name)) { watchdog('helper', "Dropped index {$revision_table}.{$index_name}"); db_drop_index($revision_table, $index_name); } } // Rename any columns. foreach ($column_renames as $old_column => $new_column) { $old_column_name = _field_sql_storage_columnname($field['field_name'], $old_column); if ($new_column === FALSE) { db_drop_field($data_table, $old_column_name); watchdog('helper', "Dropped column {$data_table}.{$old_column_name}"); db_drop_field($revision_table, $old_column_name); watchdog('helper', "Dropped column {$revision_table}.{$old_column_name}"); unset($old_schema['columns'][$old_column]); } else { $new_column_name = _field_sql_storage_columnname($field['field_name'], $new_column); db_change_field($data_table, $old_column_name, $new_column_name, $new_schema['columns'][$new_column]); watchdog('helper', "Changed column {$data_table}.{$old_column_name}<br/><pre>" . print_r($new_schema['columns'][$new_column], TRUE) . '</pre>'); db_change_field($revision_table, $old_column_name, $new_column_name, $new_schema['columns'][$new_column]); watchdog('helper', "Changed column {$revision_table}.{$old_column_name}<br/><pre>" . print_r($new_schema['columns'][$new_column], TRUE) . '</pre>'); // Remove these fields so they aren't removed or added in the code below. unset($new_schema['columns'][$new_column]); unset($old_schema['columns'][$old_column]); } } // Remove any old columns. $old_columns = array_diff_key($old_schema['columns'], $new_schema['columns']); foreach (array_keys($old_columns) as $old_column) { $old_column_name = _field_sql_storage_columnname($field['field_name'], $old_column); db_drop_field($data_table, $old_column_name); watchdog('helper', "Dropped column {$data_table}.{$old_column_name}"); db_drop_field($revision_table, $old_column_name); watchdog('helper', "Dropped column {$revision_table}.{$old_column_name}"); } // Add any new columns. $new_columns = array_diff_key($new_schema['columns'], $old_schema['columns']); foreach (array_keys($new_columns) as $new_column) { $new_column_name = _field_sql_storage_columnname($field['field_name'], $new_column); db_add_field($data_table, $new_column_name, $new_schema['columns'][$new_column]); watchdog('helper', "Added column {$data_table}.{$new_column_name}"); db_add_field($revision_table, $new_column_name, $new_schema['columns'][$new_column]); watchdog('helper', "Added column {$revision_table}.{$new_column_name}"); } // Re-add indexes. foreach ($new_schema['indexes'] as $index => $index_fields) { foreach ($index_fields as &$index_field) { if (is_array($index_field)) { $index_field[0] = _field_sql_storage_columnname($field['field_name'], $index_field[0]); } else { $index_field = _field_sql_storage_columnname($field['field_name'], $index_field); } } $index_name = _field_sql_storage_indexname($field['field_name'], $index); db_add_index($data_table, $index_name, $index_fields); watchdog('helper', "Added index {$data_table}.{$index_name}<br/><pre>" . print_r($index_fields, TRUE) . '</pre>'); db_add_index($revision_table, $index_name, $index_fields); watchdog('helper', "Added index {$revision_table}.{$index_name}<br/><pre>" . print_r($index_fields, TRUE) . '</pre>'); } }
/** * Execute an EntityFieldQuery. * * This hook is called to find the entities having certain entity and field * conditions and sort them in the given field order. If the field storage * engine also handles property sorts and orders, it should unset those * properties in the called object to signal that those have been handled. * * @param EntityFieldQuery $query * An EntityFieldQuery. * * @return * See EntityFieldQuery::execute() for the return values. */ function hook_field_storage_query($query) { $load_current = $options['age'] == FIELD_LOAD_CURRENT; $field = field_info_field_by_id($field_id); $field_name = $field['field_name']; $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); $field_columns = array_keys($field['columns']); // Build the query. $query = db_select($table, 't'); $query->join('field_config_entity_type', 'e', 't.etid = e.etid'); // Add conditions. foreach ($conditions as $condition) { // A condition is either a (column, value, operator) triple, or a // (column, value) pair with implied operator. @(list($column, $value, $operator) = $condition); // Translate operator and value if needed. switch ($operator) { case 'STARTS_WITH': $operator = 'LIKE'; $value = db_like($value) . '%'; break; case 'ENDS_WITH': $operator = 'LIKE'; $value = '%' . db_like($value); break; case 'CONTAINS': $operator = 'LIKE'; $value = '%' . db_like($value) . '%'; break; } // Translate field columns into prefixed db columns. if (in_array($column, $field_columns)) { $column = _field_sql_storage_columnname($field_name, $column); } // Translate entity types into numeric ids. Expressing the condition on the // local 'etid' column rather than the JOINed 'type' column avoids a // filesort. if ($column == 'type') { $column = 't.etid'; if (is_array($value)) { foreach (array_keys($value) as $key) { $value[$key] = _field_sql_storage_etid($value[$key]); } } else { $value = _field_sql_storage_etid($value); } } // Track condition on 'deleted'. if ($column == 'deleted') { $condition_deleted = TRUE; } $query->condition($column, $value, $operator); } // Exclude deleted data unless we have a condition on it. if (!isset($condition_deleted)) { $query->condition('deleted', 0); } // For a count query, return the count now. if ($options['count']) { return $query->fields('t', array('etid', 'entity_id', 'revision_id'))->distinct()->countQuery()->execute()->fetchField(); } // For a data query, add fields. $query->fields('t', array('bundle', 'entity_id', 'revision_id'))->fields('e', array('type'))->orderBy('t.etid')->orderBy('t.entity_id'); // Initialize results array $return = array(); // Getting $count entities possibly requires reading more than $count rows // since fields with multiple values span over several rows. We query for // batches of $count rows until we've either read $count entities or received // less rows than asked for. $entity_count = 0; do { if ($options['limit'] != FIELD_QUERY_NO_LIMIT) { $query->range($options['cursor'], $options['limit']); } $results = $query->execute(); $row_count = 0; foreach ($results as $row) { $row_count++; $options['cursor']++; // If querying all revisions and the entity type has revisions, we need // to key the results by revision_ids. $entity_type = entity_get_info($row->type); $id = $load_current || empty($entity_type['entity keys']['revision']) ? $row->entity_id : $row->revision_id; if (!isset($return[$row->type][$id])) { $return[$row->type][$id] = entity_create_stub_entity($row->type, array($row->entity_id, $row->revision_id, $row->bundle)); $entity_count++; } } } while ($options['limit'] != FIELD_QUERY_NO_LIMIT && $row_count == $options['limit'] && $entity_count < $options['limit']); // The query is complete when the last batch returns less rows than asked // for. if ($row_count < $options['limit']) { $options['cursor'] = FIELD_QUERY_COMPLETE; } return $return; }
/** * Copies field_sql_storage_field_storage_query() using left joins some times. * * @see field_sql_storage_field_storage_query() */ protected function fieldStorageQuery(SelectQuery $select_query) { if ($this->age == FIELD_LOAD_CURRENT) { $tablename_function = '_field_sql_storage_tablename'; $id_key = 'entity_id'; } else { $tablename_function = '_field_sql_storage_revision_tablename'; $id_key = 'revision_id'; } $table_aliases = array(); $query_tables = NULL; $base_table = $this->metaData['base_table']; // Add tables for the fields used. $field_base_table = NULL; foreach ($this->fields as $key => $field) { $tablename = $tablename_function($field); $table_alias = _field_sql_storage_tablealias($tablename, $key, $this); $table_aliases[$key] = $table_alias; $select_query->addMetaData('base_table', $base_table); $entity_id_key = $this->metaData['entity_id_key']; if ($field_base_table) { if (!isset($query_tables[$table_alias])) { $this->addFieldJoin($select_query, $field['field_name'], $tablename, $table_alias, "{$table_alias}.entity_type = {$field_base_table}.entity_type AND {$table_alias}.{$id_key} = {$field_base_table}.{$id_key}"); } } else { // By executing prePropertyQuery() we made sure that the base table is // the entity table. $this->addFieldJoin($select_query, $field['field_name'], $tablename, $table_alias, "{$base_table}.{$entity_id_key} = {$table_alias}.{$id_key}"); // Store a reference to the list of joined tables. $query_tables =& $select_query->getTables(); // Allow queries internal to the Field API to opt out of the access // check, for situations where the query's results should not depend on // the access grants for the current user. if (!isset($this->tags['DANGEROUS_ACCESS_CHECK_OPT_OUT'])) { $select_query->addTag('entity_field_access'); } if (!$this->containsLeftJoinOperator($this->fields[$key]['field_name'])) { $field_base_table = $table_alias; } } if ($field['cardinality'] != 1 || $field['translatable']) { $select_query->distinct(); } } // Add field conditions. We need a fresh grouping cache. drupal_static_reset('_field_sql_storage_query_field_conditions'); _field_sql_storage_query_field_conditions($this, $select_query, $this->fieldConditions, $table_aliases, '_field_sql_storage_columnname'); // Add field meta conditions. _field_sql_storage_query_field_conditions($this, $select_query, $this->fieldMetaConditions, $table_aliases, '_field_sql_storage_query_columnname'); // If there was no field condition that created an INNER JOIN, that means // that additional JOINs need to carry the OR condition. For the base table // we'll use the table for the first field. $needs_or = FALSE; if (!isset($field_base_table)) { $needs_or = TRUE; // Get the table name for the first field. $field_table_name = key($this->fields[0]['storage']['details']['sql'][$this->age]); $field_base_table = _field_sql_storage_tablealias($field_table_name, 0, $this); } if (isset($this->deleted)) { $delete_condition = array('value' => (int) $this->deleted, 'operator' => '=', 'or' => $needs_or); $this->addCondition($select_query, "{$field_base_table}.deleted", $delete_condition); } foreach ($this->entityConditions as $key => $condition) { $condition['or'] = $needs_or; $this->addCondition($select_query, "{$field_base_table}.{$key}", $condition); } // Order the query. foreach ($this->order as $order) { if ($order['type'] == 'entity') { $key = $order['specifier']; $select_query->orderBy("{$field_base_table}.{$key}", $order['direction']); } elseif ($order['type'] == 'field') { $specifier = $order['specifier']; $field = $specifier['field']; $table_alias = $table_aliases[$specifier['index']]; $sql_field = "{$table_alias}." . _field_sql_storage_columnname($field['field_name'], $specifier['column']); $select_query->orderBy($sql_field, $order['direction']); } elseif ($order['type'] == 'property') { $select_query->orderBy("{$base_table}." . $order['specifier'], $order['direction']); } } return array($select_query, $id_key); }