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 @@ -18,3 +18,3 @@ sqlite.dynamic_entity_reference.storage.create_column: - class: Drupal\dynamic_entity_reference\Storage\IntColumnHandlerSqlite + 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 @@ -95,20 +95,12 @@ $storage = $this->entityTypeManager->getStorage($entity_type_id); if ($storage instanceof SqlEntityStorageInterface) { $storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id); - if ($field_name) { - $current_definition = [$field_name => $field_storage_definition]; - // DefaultMapping is buggy so we can't just pass in the single storage - // definition we want. - $storage_definitions += $current_definition; - } - $mapping = $storage->getTableMapping($storage_definitions); + // If a field is given then only work with that. + $current_definitions = $field_name ? [$field_name => $field_storage_definition] : $storage_definitions; + // DefaultMapping is buggy and requires all the field definitions. + $mapping = $storage->getTableMapping($current_definitions + $storage_definitions); $tables = []; - // From this point on, however, we can work with the current definition - // only if there is one. - if ($field_name) { - $storage_definitions = $current_definition; - } - foreach ($storage_definitions as $storage_definition) { + foreach ($current_definitions as $storage_definition) { if ($storage_definition->getType() == 'dynamic_entity_reference') { $table = $mapping->getFieldTableName($storage_definition->getName()); $column = $mapping->getFieldColumnName($storage_definition, 'target_id'); diff -u b/src/Storage/IntColumnHandler.php b/src/Storage/IntColumnHandler.php --- b/src/Storage/IntColumnHandler.php +++ b/src/Storage/IntColumnHandler.php @@ -23,14 +23,95 @@ * + * 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) { + $this->createStart($table); + $schema = $this->connection->schema(); + // The integer column specification. + $spec = [ + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + ]; + // Before MySQL 5.7.2, there cannot be multiple triggers for a given table + // that have the same trigger event and action time so set all involved + // columns in one go. See + // https://dev.mysql.com/doc/refman/5.7/en/trigger-syntax.html for more. + // In SQLite, it's cheaper to run one query instead on per column. + $body = []; + foreach ($columns as $column) { + $column_int = $column . '_int'; + // Make sure the integer columns exist. + if (!$schema->fieldExists($table, $column_int)) { + $schema->addField($table, $column_int, $spec); + } + // This is the heart of this function: before an UPDATE/INSERT, set the + // value of the integer column to the integer value of the string column. + $body[] = $this->createBody($column_int, $column); + } + $body = implode(', ', $body); + $prefixed_name = $this->connection->prefixTables('{' . $table . '}'); + foreach (['update', 'insert'] as $op) { + $trigger = $prefixed_name . '_der_' . $op; + $this->connection->query("DROP TRIGGER IF EXISTS $trigger"); + if ($body) { + $this->createTrigger($trigger, $op, $prefixed_name, $body); + } + } + $this->createEnd(); + } + + protected function createStart($table) { + + } + + /** + * Create the body of the trigger. + * + * Create a part of the statement to set the value of the integer column to + * the integer value of the string column. + * + * @param $column_int + * @param $column + */ + protected function createBody($column_int, $column) { throw new \LogicException('Not implemented'); } + + /** + * Actually create the trigger. + * + * @param $trigger + * @param $op + * @param $prefixed_name + * @param $body + * @internal param null $table_name + */ + protected function createTrigger($trigger, $op, $prefixed_name, $body) { + + } + + /** + * We are done. + */ + protected function createEnd() { + + } + + + /** + * @TODO + * + * @param $table + * @param $column + */ public function delete($table, $column) { } + } diff -u b/src/Storage/IntColumnHandlerMySQL.php b/src/Storage/IntColumnHandlerMySQL.php --- b/src/Storage/IntColumnHandlerMySQL.php +++ b/src/Storage/IntColumnHandlerMySQL.php @@ -6,45 +6,20 @@ - /** - * @inheritdoc} - */ - public function create($table, array $columns) { + 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'); - $schema = $this->connection->schema(); - // The integer column specification. - $spec = [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => FALSE, - ]; - // Before MySQL 5.7.2, there cannot be multiple triggers for a given table - // that have the same trigger event and action time so set all involved - // columns in one go. See - // https://dev.mysql.com/doc/refman/5.7/en/trigger-syntax.html for more. - $body = []; - foreach ($columns as $column) { - $column_int = $column . '_int'; - // Make sure the integer columns exist. - if (!$schema->fieldExists($table, $column_int)) { - $schema->addField($table, $column_int, $spec); - } - // This is the heart of this function: before an UPDATE/INSERT, set the - // value of the integer column to the integer value of the string column. - $body[] = "NEW.$column_int = CAST(NEW.$column AS UNSIGNED)"; - } - $body = implode(', ', $body); - // For the sake of readability, get the prefixed table name. - $prefixed_name = $this->connection->prefixTables('{' . $table . '}'); - foreach (['update', 'insert'] as $op) { - $trigger = $prefixed_name . '_der_' . $op; - $this->connection->query("DROP TRIGGER IF EXISTS $trigger"); - if ($body) { - $this->connection->query("CREATE TRIGGER $trigger BEFORE $op ON $prefixed_name FOR EACH ROW SET $body"); - } - } - // We are done. + } + + 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 @@ -7,2 +7,34 @@ + /** + * @param $column_int + * @param $column + * @return string + */ + protected function createBody($column_int, $column) { + return "$column_int = CAST($column AS INTEGER)"; + } + + /** + * @param $trigger + * @param $op + * @param $prefixed_name + * @param $body + * @internal param null $table_name + */ + protected function createTrigger($trigger, $op, $prefixed_name, $body) { + $parts = explode('.', $prefixed_name); + $table_name = array_pop($parts); + $query = " + CREATE TRIGGER $trigger + AFTER $op + ON $prefixed_name + FOR EACH ROW + BEGIN + UPDATE $table_name SET $body WHERE ROWID=NEW.ROWID"; + if (strpos($query, ';') !== FALSE) { + throw new \InvalidArgumentException('; is not supported in SQL strings. Use only one statement at a time.'); + } + $this->connection->query("$query; END", [], ['allow_delimiter_in_query' => TRUE]); + } + }