As part of #2721313: Upgrade path between revisionable / non-revisionable entities I am looking for a scalable and reusable way to update entity definitions, and in this case make them revisionable, this includes adding a "revision_id" field.
Take the example:
$revision_id = BaseFieldDefinition::create('integer')
->setLabel(new TranslatableMarkup('Revision ID'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
\Drupal::entityDefinitionUpdateManager()
->installFieldStorageDefinition($entity_type->getKey('revision'), $entity_type->id(), $entity_type->id(), $revision_id);
This eventually ends up in \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::getSharedTableFieldSchema
via \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::getEntitySchema
. Here all entity keys are set as NOT NULL
apart from in the base table where the revision entity key is no set to NOT NULL
.
Therefore when adding new fields, which are also entity keys, when there is already content in the table, we get the issue that the fields can't be empty.
One possible fix could be:
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php b/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php
index 107495b..6caf20b 100644
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php
+++ b/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php
@@ -413,7 +413,10 @@ public function addField($table, $field, $spec, $keys_new = array()) {
}
if ($fixnull) {
$spec['not null'] = TRUE;
- $this->changeField($table, $field, $field, $spec);
+ $count = $this->connection->query('SELECT COUNT(*) AS count FROM {' . $table . '}')->fetchAssoc();
+ if ($count['count'] == 0) {
+ $this->changeField($table, $field, $field, $spec);
+ };
}
}
But as @dawehner suggests, this may be better further up the stack, maybe in \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::getSharedTableFieldSchema
, or could we pass a callable into installFieldStorageDefinition
to populate the fields with a default value before adding the NOT NULL
? This seems like a good option, but the callable would have to be passed along for quite a while.
Comment | File | Size | Author |
---|---|---|---|
#13 | interdiff.txt | 1.58 KB | timmillwood |
#13 | adding_not_null_fields-2746279-13.patch | 6.01 KB | timmillwood |
Comments
Comment #2
timmillwoodComment #3
timmillwoodComment #4
timmillwoodComment #5
timmillwoodComment #6
dawehnerThank you @timmillwood for creating the issue.
Comment #8
timmillwoodok, looks like I am hitting too early, before the table has even been created.
Back to the drawing board.
Comment #9
timmillwoodWondering if we could pass a callable as part of the FieldStorageDefinition to get the initial values for fields. To
\Drupal\Core\Field\FieldStorageDefinitionInterface
we can addsetInitialValueCallable
to set the callable,getInitialValueCallable
to get the callable (not sure we need this really),getInitialValues
to run the callable and get the values.The callable can be passed into
\Drupal\Core\Entity\EntityDefinitionUpdateManager::installFieldStorageDefinition
andsetInitialValueCallable
can be used in there.Comment #10
timmillwoodIt looks as though there is already a way to pass in an initial value http://cgit.drupalcode.org/drupal/tree/core/lib/Drupal/Core/Database/Dri.... This comes from the field schema, so we'd need to add this in somewhere along the line, the only issue is each row ends up with the same value, this would cause issues when fields need to be "not null" and unique.
Comment #11
timmillwoodThis patch adds a test which exposes this issue.
Comment #13
timmillwoodNot yet a working patch, but should give an idea of what I'm trying to do.
Comment #15
daffie CreditAttribution: daffie commentedA quick review:
I really do not like this change. It feels completely wrong to me.
Is this not doing it twice? Or am I missing something.
Should the test not be equal to 'NO'.
Comment #16
timmillwoodClosing in favour of #2753475: Support using the values of another field as initial values when adding a new field.