| 1 |
<?php |
<?php |
| 2 |
|
|
| 3 |
/** |
/** |
| 4 |
|
* @file |
| 5 |
|
* Per-bundle storage implementation using the Field Storage API "pre" |
| 6 |
|
* hooks and other Field API hooks. |
| 7 |
|
*/ |
| 8 |
|
|
| 9 |
|
/** |
| 10 |
|
* @defgroup field_pbs Field API Per-Bundle Storage |
| 11 |
|
* @{ |
| 12 |
|
* Store multiple Field API data fields in a wide table so that they |
| 13 |
|
* can be loaded for a single object in a single query instead of |
| 14 |
|
* requiring multiple queries or joins. |
| 15 |
|
*/ |
| 16 |
|
/** |
| 17 |
|
* @} End of "defgroup field_pbs" |
| 18 |
|
*/ |
| 19 |
|
|
| 20 |
|
/** |
| 21 |
|
* If TRUE, shared field data will be stored both in the per-bundle |
| 22 |
|
* table for all of the field's bundles plus the default field storage |
| 23 |
|
* engine. If FALSE, shared fields are only stored in per-bundle |
| 24 |
|
* tables, just as non-shared fields are. |
| 25 |
|
*/ |
| 26 |
|
define('PBS_DUPLICATE_SHARED_FIELDS', FALSE); |
| 27 |
|
|
| 28 |
|
/** |
| 29 |
* Generate a table name for a bundle data table. |
* Generate a table name for a bundle data table. |
| 30 |
* |
* |
| 31 |
* @param $name |
* @param $name |
| 133 |
* Pre-compute the mapping of bundle table column name to field, item, |
* Pre-compute the mapping of bundle table column name to field, item, |
| 134 |
* and delta. This must be called any time the set of field instances |
* and delta. This must be called any time the set of field instances |
| 135 |
* changes in any way. |
* changes in any way. |
| 136 |
|
* |
| 137 |
|
* Suppose that a field F with columns C1 and C2 and with cardinality |
| 138 |
|
* 2 has instances for bundles B1 and B2. For field F, the map will |
| 139 |
|
* contain: |
| 140 |
|
* |
| 141 |
|
* $map = array( |
| 142 |
|
* 'B1' => array( |
| 143 |
|
* 'F_C1_1' => array('F', 'C1', 1), |
| 144 |
|
* 'F_C1_2' => array('F', 'C1', 2), |
| 145 |
|
* 'F_C2_1' => array('F', 'C2', 1), |
| 146 |
|
* 'F_C2_2' => array('F', 'C2', 2), |
| 147 |
|
* // other entries for B1 |
| 148 |
|
* ), |
| 149 |
|
* 'B2' => array( |
| 150 |
|
* 'F_C1_1' => array('F', 'C1', 1), |
| 151 |
|
* 'F_C1_2' => array('F', 'C1', 2), |
| 152 |
|
* 'F_C2_1' => array('F', 'C2', 1), |
| 153 |
|
* 'F_C2_2' => array('F', 'C2', 2), |
| 154 |
|
* // other entries for B2 |
| 155 |
|
* ), |
| 156 |
|
* ); |
| 157 |
*/ |
*/ |
| 158 |
function pbs_precompute_bundle_map() { |
function pbs_precompute_bundle_map() { |
| 159 |
$map = array(); |
$map = array(); |
| 316 |
* |
* |
| 317 |
* When an object's fields are saved, update its bundle table and |
* When an object's fields are saved, update its bundle table and |
| 318 |
* add saved field names as keys to $skip_fields. |
* add saved field names as keys to $skip_fields. |
| 319 |
|
* |
| 320 |
|
* If the field is not shared, tell the field storage not to store it |
| 321 |
|
* normally. If the field is shared, let the field storage system |
| 322 |
|
* store it. The idea here is that we want to allow querying the |
| 323 |
|
* field to require querying only a single table. If we stored a |
| 324 |
|
* shared field in multiple bundle tables and prevented per-field |
| 325 |
|
* storage, querying would have to query multiple per-bundle tables. |
| 326 |
*/ |
*/ |
| 327 |
function pbs_field_attach_pre_update($obj_type, $object, &$skip_fields) { |
function pbs_field_attach_pre_update($obj_type, $object, &$skip_fields) { |
| 328 |
$etid = _field_sql_storage_etid($obj_type); |
$etid = _field_sql_storage_etid($obj_type); |
| 354 |
} |
} |
| 355 |
|
|
| 356 |
// Whether we left it untouched, emptied it, or stored it, we |
// Whether we left it untouched, emptied it, or stored it, we |
| 357 |
// still handled it. |
// still handled it. Tell the field storage module to skip it, |
| 358 |
if (count($field['bundles']) == 1) { |
// but only if it is a non-shared field. |
| 359 |
|
if (!PBS_DUPLICATE_SHARED_FIELDS || count($field['bundles']) == 1) { |
| 360 |
$all_fields[$field_name] = 1; |
$all_fields[$field_name] = 1; |
| 361 |
} |
} |
| 362 |
} |
} |
| 383 |
} |
} |
| 384 |
|
|
| 385 |
/** |
/** |
| 386 |
|
* Implement hook_field_storage_query(). |
| 387 |
|
* |
| 388 |
|
* To query a single field, query all the per-bundle tables in which |
| 389 |
|
* that field appears. Also, if the field's cardinality is greater |
| 390 |
|
* than 1, query all the columnname_N columns for each condition. |
| 391 |
|
*/ |
| 392 |
|
function pbs_field_attach_pre_query($field_name, $conditions, $result_format, $age, &$skip_field) { |
| 393 |
|
$field = field_info_field($field_name); |
| 394 |
|
// We do not store unlimited cardinality fields. |
| 395 |
|
if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) { |
| 396 |
|
return; |
| 397 |
|
} |
| 398 |
|
// We store shared fields, but the base storage module does too, and |
| 399 |
|
// it will be able to make only one query instead of the multiple |
| 400 |
|
// queries we would need. |
| 401 |
|
if (PBS_DUPLICATE_SHARED_FIELDS && count($field['bundles']) == 1) { |
| 402 |
|
return; |
| 403 |
|
} |
| 404 |
|
|
| 405 |
|
$load_values = $result_format == FIELD_QUERY_RETURN_VALUES; |
| 406 |
|
$load_current = $age == FIELD_LOAD_CURRENT; |
| 407 |
|
$field_columns = array_keys($field['columns']); |
| 408 |
|
$return = array(); |
| 409 |
|
|
| 410 |
|
// Get the fields to load for each bundle. |
| 411 |
|
$bundle_fields = variable_get('pbs_field_map', array()); |
| 412 |
|
|
| 413 |
|
// We know there is only one bundle, but this code will work if we |
| 414 |
|
// ever decide not to store shared fields in per-field storage and |
| 415 |
|
// instead require multiple queries into per-bundle storage. |
| 416 |
|
foreach ($field['bundles'] as $bundle) { |
| 417 |
|
$table = $load_current ? pbs_tablename($bundle) : pbs_revision_tablename($bundle); |
| 418 |
|
|
| 419 |
|
// Build the query. |
| 420 |
|
$query = db_select($table, 't'); |
| 421 |
|
$query->join('field_config_entity_type', 'e', 't.etid = e.etid'); |
| 422 |
|
$query |
| 423 |
|
->fields('e', array('type')); |
| 424 |
|
// TODO ->condition('deleted', 0) |
| 425 |
|
// Add fields, depending on the return format. |
| 426 |
|
if ($load_values) { |
| 427 |
|
$query->fields('t'); |
| 428 |
|
} |
| 429 |
|
else { |
| 430 |
|
$query->fields('t', array('entity_id', 'revision_id')); |
| 431 |
|
} |
| 432 |
|
// Add conditions. |
| 433 |
|
foreach ($conditions as $condition) { |
| 434 |
|
// A condition is either a (column, value, operator) triple, or a |
| 435 |
|
// (column, value) pair with implied operator. |
| 436 |
|
@list($column, $value, $operator) = $condition; |
| 437 |
|
|
| 438 |
|
// TODO |
| 439 |
|
if ($column == 'bundle') { |
| 440 |
|
continue; |
| 441 |
|
} |
| 442 |
|
|
| 443 |
|
// Translate operator. |
| 444 |
|
switch ($operator) { |
| 445 |
|
case NULL: |
| 446 |
|
$operator = is_array($value) ? 'IN' : '='; |
| 447 |
|
break; |
| 448 |
|
|
| 449 |
|
case 'starts_with': |
| 450 |
|
$operator = 'LIKE'; |
| 451 |
|
$value .= '%'; |
| 452 |
|
break; |
| 453 |
|
|
| 454 |
|
case 'contains': |
| 455 |
|
$operator = 'LIKE'; |
| 456 |
|
$value = "%$value%"; |
| 457 |
|
break; |
| 458 |
|
} |
| 459 |
|
// Translate field columns into prefixed db columns. |
| 460 |
|
if (in_array($column, $field_columns)) { |
| 461 |
|
$or = db_or(); |
| 462 |
|
for ($delta = 0; $delta < $field['cardinality']; ++$delta) { |
| 463 |
|
$columnname = _field_sql_storage_columnname($field_name, $column) .'_'. $delta; |
| 464 |
|
$or->condition($columnname, $value, $operator); |
| 465 |
|
} |
| 466 |
|
$query->condition($or); |
| 467 |
|
} |
| 468 |
|
else { |
| 469 |
|
$query->condition($column, $value, $operator); |
| 470 |
|
} |
| 471 |
|
} |
| 472 |
|
|
| 473 |
|
$results = $query->execute(); |
| 474 |
|
|
| 475 |
|
// Build results |
| 476 |
|
foreach ($results as $row) { |
| 477 |
|
// If querying all revisions and the entity type has revisions, we need to |
| 478 |
|
// key the results by revision_ids. |
| 479 |
|
$entity_type = field_info_fieldable_types($row->type); |
| 480 |
|
$id = ($load_current || empty($entity_type['revision key'])) ? $row->entity_id : $row->revision_id; |
| 481 |
|
|
| 482 |
|
if ($load_values) { |
| 483 |
|
$item = array(); |
| 484 |
|
|
| 485 |
|
// Initialize the 'pseudo object' if needed. |
| 486 |
|
if (!isset($return[$row->type][$id])) { |
| 487 |
|
$return[$row->type][$id] = field_attach_create_stub_object($row->type, array($row->entity_id, $row->revision_id, $bundle)); |
| 488 |
|
} |
| 489 |
|
|
| 490 |
|
// TODO: refactor with load |
| 491 |
|
foreach ($bundle_fields[$bundle] as $column_name => $tuple) { |
| 492 |
|
list($field_name, $item_name, $delta) = $tuple; |
| 493 |
|
if (!is_null($row->{$column_name})) { |
| 494 |
|
// We must explicitly include $delta in the assignment |
| 495 |
|
// below. Consider a field 'foo' with two columns, e.g. value |
| 496 |
|
// and format. Without using $delta, the row containing |
| 497 |
|
// foo_value_0 and foo_format_0 would end up stored as |
| 498 |
|
// $obj->foo[0]['value'] and $obj->foo[1/*BZZZ!*/]['format']. |
| 499 |
|
$return[$row->type][$id]->{$field_name}[$delta][$item_name] = $row->{$column_name}; |
| 500 |
|
} |
| 501 |
|
else if (!isset($return[$row->type][$id]->{$field_name})) { |
| 502 |
|
$return[$row->type][$id]->{$field_name} = array(); |
| 503 |
|
} |
| 504 |
|
} |
| 505 |
|
} |
| 506 |
|
else { |
| 507 |
|
// Simply return the list of selected ids. |
| 508 |
|
$return[$row->type][$id] = $row->entity_id; |
| 509 |
|
} |
| 510 |
|
} |
| 511 |
|
} |
| 512 |
|
|
| 513 |
|
$skip_field = TRUE; |
| 514 |
|
|
| 515 |
|
return $return; |
| 516 |
|
} |
| 517 |
|
|
| 518 |
|
/** |
| 519 |
* Implementation of hook_field_attach_pre_delete. |
* Implementation of hook_field_attach_pre_delete. |
| 520 |
* |
* |
| 521 |
* When an object's fields are deleted, update its bundle table. |
* When an object's fields are deleted, update its bundle table. |