diff -u b/dynamic_entity_reference.services.yml b/dynamic_entity_reference.services.yml --- b/dynamic_entity_reference.services.yml +++ b/dynamic_entity_reference.services.yml @@ -15,6 +15,9 @@ mysql.dynamic_entity_reference.storage.create_column: class: Drupal\dynamic_entity_reference\Storage\IntColumnHandlerMySQL arguments: ['@database'] + pgsql.dynamic_entity_reference.storage.create_column: + class: Drupal\dynamic_entity_reference\Storage\IntColumnHandlerPostgreSQL + arguments: ['@database'] sqlite.dynamic_entity_reference.storage.create_column: class: Drupal\dynamic_entity_reference\Storage\IntColumnHandlerSQLite arguments: ['@database'] diff -u b/src/EventSubscriber/FieldStorageSubscriber.php b/src/EventSubscriber/FieldStorageSubscriber.php --- b/src/EventSubscriber/FieldStorageSubscriber.php +++ b/src/EventSubscriber/FieldStorageSubscriber.php @@ -7,11 +7,10 @@ use Drupal\Core\Entity\EntityTypeEvents; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\Sql\SqlEntityStorageInterface; -use Drupal\Core\Entity\Sql\TableMappingInterface; use Drupal\Core\Field\FieldStorageDefinitionEvent; use Drupal\Core\Field\FieldStorageDefinitionEvents; use Drupal\Core\Field\FieldStorageDefinitionInterface; -use Drupal\dynamic_entity_reference\Storage\IntColumnHandler; +use Drupal\dynamic_entity_reference\Storage\IntColumnHandlerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -44,7 +43,7 @@ * @param \Drupal\dynamic_entity_reference\Storage\IntColumnHandler $int_column_handler * The integer column handler. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, IntColumnHandler $int_column_handler) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, IntColumnHandlerInterface $int_column_handler) { $this->entityTypeManager = $entity_type_manager; $this->entityFieldManager = $entity_field_manager; $this->intColumnHandler = $int_column_handler; diff -u b/src/Storage/IntColumnHandler.php b/src/Storage/IntColumnHandler.php --- b/src/Storage/IntColumnHandler.php +++ b/src/Storage/IntColumnHandler.php @@ -7,7 +7,7 @@ /** * Per database implementation of denormalizing into integer columns. */ -class IntColumnHandler { +class IntColumnHandler implements IntColumnHandlerInterface { /** * @var \Drupal\Core\Database\Connection @@ -29,7 +29,6 @@ * The DER target_id columns. */ public function create($table, array $columns) { - $this->createStart($table); $schema = $this->connection->schema(); // The integer column specification. $spec = [ @@ -62,11 +61,6 @@ $this->createTrigger($trigger, $op, $prefixed_name, $body); } } - $this->createEnd(); - } - - protected function createStart($table) { - } /** @@ -82,7 +76,6 @@ throw new \LogicException('Not implemented'); } - /** * Actually create the trigger. * @@ -97,14 +90,6 @@ } /** - * We are done. - */ - protected function createEnd() { - - } - - - /** * @TODO * * @param $table diff -u b/src/Storage/IntColumnHandlerMySQL.php b/src/Storage/IntColumnHandlerMySQL.php --- b/src/Storage/IntColumnHandlerMySQL.php +++ b/src/Storage/IntColumnHandlerMySQL.php @@ -4,20 +4,10 @@ class IntColumnHandlerMySQL extends IntColumnHandler { - protected function createStart($table) { - // MySQL does not have transactional DDL so before executing many DDL - // statements it is safest to lock the table. - $this->connection->query('LOCK TABLES {' . $table . '} WRITE'); - } - protected function createBody($column_int, $column) { return "NEW.$column_int = CAST(NEW.$column AS UNSIGNED)"; } - protected function createEnd() { - $this->connection->query('UNLOCK TABLES'); - } - protected function createTrigger($trigger, $op, $prefixed_name, $body) { $this->connection->query("CREATE TRIGGER $trigger BEFORE $op ON $prefixed_name FOR EACH ROW SET $body"); } diff -u b/src/Storage/IntColumnHandlerSQLite.php b/src/Storage/IntColumnHandlerSQLite.php --- b/src/Storage/IntColumnHandlerSQLite.php +++ b/src/Storage/IntColumnHandlerSQLite.php @@ -23,14 +23,18 @@ */ protected function createTrigger($trigger, $op, $prefixed_name, $body) { $parts = explode('.', $prefixed_name); + // Simpletest for example prefixes with a database name but SQLite does + // not support referencing databases in the body of the trigger (even if it + // is the same database the triggering table is in). $table_name = array_pop($parts); $query = " - CREATE TRIGGER $trigger - AFTER $op - ON $prefixed_name - FOR EACH ROW - BEGIN + CREATE TRIGGER $trigger AFTER $op ON $prefixed_name + FOR EACH ROW + BEGIN UPDATE $table_name SET $body WHERE ROWID=NEW.ROWID"; + // SQLite requires a ; in the query which requires bypassing Drupal's built + // in single statement only protection. Although this method is not + // supposed to be called by user submitted data, if (strpos($query, ';') !== FALSE) { throw new \InvalidArgumentException('; is not supported in SQL strings. Use only one statement at a time.'); } only in patch2: unchanged: --- /dev/null +++ b/dynamic_entity_reference.install @@ -0,0 +1,13 @@ +getFieldMapByFieldType('dynamic_entity_reference') as $entity_type_id => $map) { + array_map([$update_manager, 'updateFieldStorageDefinition'], array_intersect_key($entity_field_manager->getFieldStorageDefinitions($entity_type_id), $map)); + } +} only in patch2: unchanged: --- /dev/null +++ b/src/Storage/IntColumnHandlerInterface.php @@ -0,0 +1,18 @@ +connection = $connection; + } + + /** + * Create the _int columns and the triggers for them. + * + * This is common for SQLite and MySQL. + * + * @param $table + * The non-prefix table to operate on. + * @param array $columns + * The DER target_id columns. + */ + public function create($table, array $columns) { + $schema = $this->connection->schema(); + // The integer column specification. + $spec = [ + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + ]; + foreach ($columns as $column) { + $column_int = $column . '_int'; + // Make sure the integer columns exist. + if (!$schema->fieldExists($table, $column_int)) { + $this->createTriggerFunction($table, $column, $column_int); + $this->createTrigger($table, $column, $column_int); + $schema->addField($table, $column_int, $spec); + } + } + } + + protected function createTriggerFunction($table, $column, $column_int) { + $function_name = $this->getFunctionName($table, $column_int); + $query = "CREATE FUNCTION $function_name() RETURNS trigger AS $$ + BEGIN + NEW.$column_int = (CASE WHEN NEW.$column ~ '^[0-9]+$' THEN NEW.$column ELSE '0' END)::integer"; + if (strpos($query, ';') !== FALSE) { + throw new \InvalidArgumentException('; is not supported in SQL strings. Use only one statement at a time.'); + } + $this->connection->query("$query; RETURN NEW; END; $$ LANGUAGE plpgsql IMMUTABLE RETURNS NULL ON NULL INPUT", [], ['allow_delimiter_in_query' => TRUE]); + } + + protected function createTrigger($table, $column, $column_int) { + $function_name = $this->getFunctionName($table, $column_int); + $this->connection->query(" + CREATE TRIGGER $function_name + BEFORE INSERT OR UPDATE + ON {" . "$table} + FOR EACH ROW + EXECUTE PROCEDURE $function_name(); + "); + } + + /** + * @param $table + * @param $column_int + * @return string + */ + protected function getFunctionName($table, $column_int) { + return implode('_', [$this->connection->prefixTables('{' . $table . '}'), $column_int]); + } + +}