SqlContentEntityStorageSchema.php 92 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289
  1. <?php
  2. namespace Drupal\Core\Entity\Sql;
  3. use Drupal\Core\Database\Connection;
  4. use Drupal\Core\Database\DatabaseExceptionWrapper;
  5. use Drupal\Core\DependencyInjection\DependencySerializationTrait;
  6. use Drupal\Core\Entity\ContentEntityTypeInterface;
  7. use Drupal\Core\Entity\EntityManagerInterface;
  8. use Drupal\Core\Entity\EntityPublishedInterface;
  9. use Drupal\Core\Entity\EntityStorageException;
  10. use Drupal\Core\Entity\EntityTypeInterface;
  11. use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
  12. use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
  13. use Drupal\Core\Field\BaseFieldDefinition;
  14. use Drupal\Core\Field\FieldException;
  15. use Drupal\Core\Field\FieldStorageDefinitionInterface;
  16. use Drupal\Core\Language\LanguageInterface;
  17. /**
  18. * Defines a schema handler that supports revisionable, translatable entities.
  19. *
  20. * Entity types may extend this class and optimize the generated schema for all
  21. * entity base tables by overriding getEntitySchema() for cross-field
  22. * optimizations and getSharedTableFieldSchema() for optimizations applying to
  23. * a single field.
  24. */
  25. class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorageSchemaInterface {
  26. use DependencySerializationTrait;
  27. /**
  28. * The entity manager.
  29. *
  30. * @var \Drupal\Core\Entity\EntityManagerInterface
  31. */
  32. protected $entityManager;
  33. /**
  34. * The entity type this schema builder is responsible for.
  35. *
  36. * @var \Drupal\Core\Entity\ContentEntityTypeInterface
  37. */
  38. protected $entityType;
  39. /**
  40. * The storage field definitions for this entity type.
  41. *
  42. * @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
  43. */
  44. protected $fieldStorageDefinitions;
  45. /**
  46. * The original storage field definitions for this entity type. Used during
  47. * field schema updates.
  48. *
  49. * @var \Drupal\Core\Field\FieldDefinitionInterface[]
  50. */
  51. protected $originalDefinitions;
  52. /**
  53. * The storage object for the given entity type.
  54. *
  55. * @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage
  56. */
  57. protected $storage;
  58. /**
  59. * A static cache of the generated schema array.
  60. *
  61. * @var array
  62. */
  63. protected $schema;
  64. /**
  65. * The database connection to be used.
  66. *
  67. * @var \Drupal\Core\Database\Connection
  68. */
  69. protected $database;
  70. /**
  71. * The key-value collection for tracking installed storage schema.
  72. *
  73. * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
  74. */
  75. protected $installedStorageSchema;
  76. /**
  77. * The deleted fields repository.
  78. *
  79. * @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface
  80. */
  81. protected $deletedFieldsRepository;
  82. /**
  83. * Constructs a SqlContentEntityStorageSchema.
  84. *
  85. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
  86. * The entity manager.
  87. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  88. * The entity type.
  89. * @param \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage
  90. * The storage of the entity type. This must be an SQL-based storage.
  91. * @param \Drupal\Core\Database\Connection $database
  92. * The database connection to be used.
  93. */
  94. public function __construct(EntityManagerInterface $entity_manager, ContentEntityTypeInterface $entity_type, SqlContentEntityStorage $storage, Connection $database) {
  95. $this->entityManager = $entity_manager;
  96. $this->entityType = $entity_type;
  97. $this->fieldStorageDefinitions = $entity_manager->getFieldStorageDefinitions($entity_type->id());
  98. $this->storage = $storage;
  99. $this->database = $database;
  100. }
  101. /**
  102. * Gets the keyvalue collection for tracking the installed schema.
  103. *
  104. * @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
  105. *
  106. * @todo Inject this dependency in the constructor once this class can be
  107. * instantiated as a regular entity handler:
  108. * https://www.drupal.org/node/2332857.
  109. */
  110. protected function installedStorageSchema() {
  111. if (!isset($this->installedStorageSchema)) {
  112. $this->installedStorageSchema = \Drupal::keyValue('entity.storage_schema.sql');
  113. }
  114. return $this->installedStorageSchema;
  115. }
  116. /**
  117. * Gets the deleted fields repository.
  118. *
  119. * @return \Drupal\Core\Field\DeletedFieldsRepositoryInterface
  120. * The deleted fields repository.
  121. *
  122. * @todo Inject this dependency in the constructor once this class can be
  123. * instantiated as a regular entity handler:
  124. * https://www.drupal.org/node/2332857.
  125. */
  126. protected function deletedFieldsRepository() {
  127. if (!isset($this->deletedFieldsRepository)) {
  128. $this->deletedFieldsRepository = \Drupal::service('entity_field.deleted_fields_repository');
  129. }
  130. return $this->deletedFieldsRepository;
  131. }
  132. /**
  133. * {@inheritdoc}
  134. */
  135. public function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
  136. return
  137. $this->hasSharedTableStructureChange($entity_type, $original) ||
  138. // Detect changes in key or index definitions.
  139. $this->getEntitySchemaData($entity_type, $this->getEntitySchema($entity_type, TRUE)) != $this->loadEntitySchemaData($original);
  140. }
  141. /**
  142. * Detects whether there is a change in the shared table structure.
  143. *
  144. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  145. * The new entity type.
  146. * @param \Drupal\Core\Entity\EntityTypeInterface $original
  147. * The origin entity type.
  148. *
  149. * @return bool
  150. * Returns TRUE if either the revisionable or translatable flag changes or
  151. * a table has been renamed.
  152. */
  153. protected function hasSharedTableStructureChange(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
  154. return
  155. $entity_type->isRevisionable() != $original->isRevisionable() ||
  156. $entity_type->isTranslatable() != $original->isTranslatable() ||
  157. $this->hasSharedTableNameChanges($entity_type, $original);
  158. }
  159. /**
  160. * Detects whether any table name got renamed in an entity type update.
  161. *
  162. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  163. * The new entity type.
  164. * @param \Drupal\Core\Entity\EntityTypeInterface $original
  165. * The origin entity type.
  166. *
  167. * @return bool
  168. * Returns TRUE if there have been changes.
  169. */
  170. protected function hasSharedTableNameChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
  171. $base_table = $this->database->schema()->tableExists($entity_type->getBaseTable());
  172. $data_table = $this->database->schema()->tableExists($entity_type->getDataTable());
  173. $revision_table = $this->database->schema()->tableExists($entity_type->getRevisionTable());
  174. $revision_data_table = $this->database->schema()->tableExists($entity_type->getRevisionDataTable());
  175. // We first check if the new table already exists because the storage might
  176. // have created it even though it wasn't specified in the entity type
  177. // definition.
  178. return
  179. (!$base_table && $entity_type->getBaseTable() != $original->getBaseTable()) ||
  180. (!$data_table && $entity_type->getDataTable() != $original->getDataTable()) ||
  181. (!$revision_table && $entity_type->getRevisionTable() != $original->getRevisionTable()) ||
  182. (!$revision_data_table && $entity_type->getRevisionDataTable() != $original->getRevisionDataTable());
  183. }
  184. /**
  185. * {@inheritdoc}
  186. */
  187. public function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
  188. $table_mapping = $this->storage->getTableMapping();
  189. if (
  190. $storage_definition->hasCustomStorage() != $original->hasCustomStorage() ||
  191. $storage_definition->getSchema() != $original->getSchema() ||
  192. $storage_definition->isRevisionable() != $original->isRevisionable() ||
  193. $table_mapping->allowsSharedTableStorage($storage_definition) != $table_mapping->allowsSharedTableStorage($original) ||
  194. $table_mapping->requiresDedicatedTableStorage($storage_definition) != $table_mapping->requiresDedicatedTableStorage($original)
  195. ) {
  196. return TRUE;
  197. }
  198. if ($storage_definition->hasCustomStorage()) {
  199. // The field has custom storage, so we don't know if a schema change is
  200. // needed or not, but since per the initial checks earlier in this
  201. // function, nothing about the definition changed that we manage, we
  202. // return FALSE.
  203. return FALSE;
  204. }
  205. $current_schema = $this->getSchemaFromStorageDefinition($storage_definition);
  206. $this->processFieldStorageSchema($current_schema);
  207. $installed_schema = $this->loadFieldSchemaData($original);
  208. $this->processFieldStorageSchema($installed_schema);
  209. return $current_schema != $installed_schema;
  210. }
  211. /**
  212. * Gets the schema data for the given field storage definition.
  213. *
  214. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  215. * The field storage definition. The field that must not have custom
  216. * storage, i.e. the storage must take care of storing the field.
  217. *
  218. * @return array
  219. * The schema data.
  220. */
  221. protected function getSchemaFromStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
  222. assert(!$storage_definition->hasCustomStorage());
  223. $table_mapping = $this->storage->getTableMapping();
  224. $schema = [];
  225. if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
  226. $schema = $this->getDedicatedTableSchema($storage_definition);
  227. }
  228. elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) {
  229. $field_name = $storage_definition->getName();
  230. foreach (array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames()) as $table_name) {
  231. if (in_array($field_name, $table_mapping->getFieldNames($table_name))) {
  232. $column_names = $table_mapping->getColumnNames($storage_definition->getName());
  233. $schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
  234. }
  235. }
  236. }
  237. return $schema;
  238. }
  239. /**
  240. * {@inheritdoc}
  241. */
  242. public function requiresEntityDataMigration(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
  243. // Check if the entity type specifies that data migration is being handled
  244. // elsewhere.
  245. if ($entity_type->get('requires_data_migration') === FALSE) {
  246. return FALSE;
  247. }
  248. // If the original storage has existing entities, or it is impossible to
  249. // determine if that is the case, require entity data to be migrated.
  250. $original_storage_class = $original->getStorageClass();
  251. if (!class_exists($original_storage_class)) {
  252. return TRUE;
  253. }
  254. // Data migration is not needed when only indexes changed, as they can be
  255. // applied if there is data.
  256. if (!$this->hasSharedTableStructureChange($entity_type, $original)) {
  257. return FALSE;
  258. }
  259. // Use the original entity type since the storage has not been updated.
  260. $original_storage = $this->entityManager->createHandlerInstance($original_storage_class, $original);
  261. return $original_storage->hasData();
  262. }
  263. /**
  264. * {@inheritdoc}
  265. */
  266. public function requiresFieldDataMigration(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
  267. return !$this->storage->countFieldData($original, TRUE);
  268. }
  269. /**
  270. * {@inheritdoc}
  271. */
  272. public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
  273. $this->checkEntityType($entity_type);
  274. $schema_handler = $this->database->schema();
  275. // Create entity tables.
  276. $schema = $this->getEntitySchema($entity_type, TRUE);
  277. foreach ($schema as $table_name => $table_schema) {
  278. if (!$schema_handler->tableExists($table_name)) {
  279. $schema_handler->createTable($table_name, $table_schema);
  280. }
  281. }
  282. // Create dedicated field tables.
  283. $table_mapping = $this->storage->getTableMapping($this->fieldStorageDefinitions);
  284. foreach ($this->fieldStorageDefinitions as $field_storage_definition) {
  285. if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
  286. $this->createDedicatedTableSchema($field_storage_definition);
  287. }
  288. elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
  289. // The shared tables are already fully created, but we need to save the
  290. // per-field schema definitions for later use.
  291. $this->createSharedTableSchema($field_storage_definition, TRUE);
  292. }
  293. }
  294. // Save data about entity indexes and keys.
  295. $this->saveEntitySchemaData($entity_type, $schema);
  296. }
  297. /**
  298. * {@inheritdoc}
  299. */
  300. public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
  301. $this->checkEntityType($entity_type);
  302. $this->checkEntityType($original);
  303. // If no schema changes are needed, we don't need to do anything.
  304. if (!$this->requiresEntityStorageSchemaChanges($entity_type, $original)) {
  305. return;
  306. }
  307. // If a migration is required, we can't proceed.
  308. if ($this->requiresEntityDataMigration($entity_type, $original)) {
  309. throw new EntityStorageException('The SQL storage cannot change the schema for an existing entity type (' . $entity_type->id() . ') with data.');
  310. }
  311. // If we have no data just recreate the entity schema from scratch.
  312. if ($this->isTableEmpty($this->storage->getBaseTable())) {
  313. if ($this->database->supportsTransactionalDDL()) {
  314. // If the database supports transactional DDL, we can go ahead and rely
  315. // on it. If not, we will have to rollback manually if something fails.
  316. $transaction = $this->database->startTransaction();
  317. }
  318. try {
  319. $this->onEntityTypeDelete($original);
  320. $this->onEntityTypeCreate($entity_type);
  321. }
  322. catch (\Exception $e) {
  323. if ($this->database->supportsTransactionalDDL()) {
  324. $transaction->rollBack();
  325. }
  326. else {
  327. // Recreate original schema.
  328. $this->onEntityTypeCreate($original);
  329. }
  330. throw $e;
  331. }
  332. }
  333. else {
  334. // Drop original indexes and unique keys.
  335. $this->deleteEntitySchemaIndexes($this->loadEntitySchemaData($entity_type));
  336. // Create new indexes and unique keys.
  337. $entity_schema = $this->getEntitySchema($entity_type, TRUE);
  338. $this->createEntitySchemaIndexes($entity_schema);
  339. // Store the updated entity schema.
  340. $this->saveEntitySchemaData($entity_type, $entity_schema);
  341. }
  342. }
  343. /**
  344. * {@inheritdoc}
  345. */
  346. public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
  347. $this->checkEntityType($entity_type);
  348. $schema_handler = $this->database->schema();
  349. $actual_definition = $this->entityManager->getDefinition($entity_type->id());
  350. // @todo Instead of switching the wrapped entity type, we should be able to
  351. // instantiate a new table mapping for each entity type definition. See
  352. // https://www.drupal.org/node/2274017.
  353. $this->storage->setEntityType($entity_type);
  354. // Delete entity tables.
  355. foreach ($this->getEntitySchemaTables() as $table_name) {
  356. if ($schema_handler->tableExists($table_name)) {
  357. $schema_handler->dropTable($table_name);
  358. }
  359. }
  360. // Delete dedicated field tables.
  361. $field_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type->id());
  362. $this->originalDefinitions = $field_storage_definitions;
  363. $table_mapping = $this->storage->getTableMapping($field_storage_definitions);
  364. foreach ($field_storage_definitions as $field_storage_definition) {
  365. // If we have a field having dedicated storage we need to drop it,
  366. // otherwise we just remove the related schema data.
  367. if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
  368. $this->deleteDedicatedTableSchema($field_storage_definition);
  369. }
  370. elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
  371. $this->deleteFieldSchemaData($field_storage_definition);
  372. }
  373. }
  374. $this->originalDefinitions = NULL;
  375. $this->storage->setEntityType($actual_definition);
  376. // Delete the entity schema.
  377. $this->deleteEntitySchemaData($entity_type);
  378. }
  379. /**
  380. * {@inheritdoc}
  381. */
  382. public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
  383. $this->performFieldSchemaOperation('create', $storage_definition);
  384. }
  385. /**
  386. * {@inheritdoc}
  387. */
  388. public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
  389. // Store original definitions so that switching between shared and dedicated
  390. // field table layout works.
  391. $this->performFieldSchemaOperation('update', $storage_definition, $original);
  392. }
  393. /**
  394. * {@inheritdoc}
  395. */
  396. public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
  397. try {
  398. $has_data = $this->storage->countFieldData($storage_definition, TRUE);
  399. }
  400. catch (DatabaseExceptionWrapper $e) {
  401. // This may happen when changing field storage schema, since we are not
  402. // able to use a table mapping matching the passed storage definition.
  403. // @todo Revisit this once we are able to instantiate the table mapping
  404. // properly. See https://www.drupal.org/node/2274017.
  405. return;
  406. }
  407. // If the field storage does not have any data, we can safely delete its
  408. // schema.
  409. if (!$has_data) {
  410. $this->performFieldSchemaOperation('delete', $storage_definition);
  411. return;
  412. }
  413. // There's nothing else we can do if the field storage has a custom storage.
  414. if ($storage_definition->hasCustomStorage()) {
  415. return;
  416. }
  417. // Retrieve a table mapping which contains the deleted field still.
  418. $storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id());
  419. $table_mapping = $this->storage->getTableMapping($storage_definitions);
  420. $field_table_name = $table_mapping->getFieldTableName($storage_definition->getName());
  421. if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
  422. // Move the table to a unique name while the table contents are being
  423. // deleted.
  424. $table = $table_mapping->getDedicatedDataTableName($storage_definition);
  425. $new_table = $table_mapping->getDedicatedDataTableName($storage_definition, TRUE);
  426. $this->database->schema()->renameTable($table, $new_table);
  427. if ($this->entityType->isRevisionable()) {
  428. $revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
  429. $revision_new_table = $table_mapping->getDedicatedRevisionTableName($storage_definition, TRUE);
  430. $this->database->schema()->renameTable($revision_table, $revision_new_table);
  431. }
  432. }
  433. else {
  434. // Move the field data from the shared table to a dedicated one in order
  435. // to allow it to be purged like any other field.
  436. $shared_table_field_columns = $table_mapping->getColumnNames($storage_definition->getName());
  437. // Refresh the table mapping to use the deleted storage definition.
  438. $deleted_storage_definition = $this->deletedFieldsRepository()->getFieldStorageDefinitions()[$storage_definition->getUniqueStorageIdentifier()];
  439. $original_storage_definitions = [$storage_definition->getName() => $deleted_storage_definition] + $storage_definitions;
  440. $table_mapping = $this->storage->getTableMapping($original_storage_definitions);
  441. $dedicated_table_field_schema = $this->getDedicatedTableSchema($deleted_storage_definition);
  442. $dedicated_table_field_columns = $table_mapping->getColumnNames($deleted_storage_definition->getName());
  443. $dedicated_table_name = $table_mapping->getDedicatedDataTableName($deleted_storage_definition, TRUE);
  444. $dedicated_table_name_mapping[$table_mapping->getDedicatedDataTableName($deleted_storage_definition)] = $dedicated_table_name;
  445. if ($this->entityType->isRevisionable()) {
  446. $dedicated_revision_table_name = $table_mapping->getDedicatedRevisionTableName($deleted_storage_definition, TRUE);
  447. $dedicated_table_name_mapping[$table_mapping->getDedicatedRevisionTableName($deleted_storage_definition)] = $dedicated_revision_table_name;
  448. }
  449. // Create the dedicated field tables using "deleted" table names.
  450. foreach ($dedicated_table_field_schema as $name => $table) {
  451. if (!$this->database->schema()->tableExists($dedicated_table_name_mapping[$name])) {
  452. $this->database->schema()->createTable($dedicated_table_name_mapping[$name], $table);
  453. }
  454. else {
  455. throw new EntityStorageException('The field ' . $storage_definition->getName() . ' has already been deleted and it is in the process of being purged.');
  456. }
  457. }
  458. if ($this->database->supportsTransactionalDDL()) {
  459. // If the database supports transactional DDL, we can go ahead and rely
  460. // on it. If not, we will have to rollback manually if something fails.
  461. $transaction = $this->database->startTransaction();
  462. }
  463. try {
  464. // Copy the data from the base table.
  465. $this->database->insert($dedicated_table_name)
  466. ->from($this->getSelectQueryForFieldStorageDeletion($field_table_name, $shared_table_field_columns, $dedicated_table_field_columns))
  467. ->execute();
  468. // Copy the data from the revision table.
  469. if (isset($dedicated_revision_table_name)) {
  470. if ($this->entityType->isTranslatable()) {
  471. $revision_table = $storage_definition->isRevisionable() ? $this->storage->getRevisionDataTable() : $this->storage->getDataTable();
  472. }
  473. else {
  474. $revision_table = $storage_definition->isRevisionable() ? $this->storage->getRevisionTable() : $this->storage->getBaseTable();
  475. }
  476. $this->database->insert($dedicated_revision_table_name)
  477. ->from($this->getSelectQueryForFieldStorageDeletion($revision_table, $shared_table_field_columns, $dedicated_table_field_columns, $field_table_name))
  478. ->execute();
  479. }
  480. }
  481. catch (\Exception $e) {
  482. if (isset($transaction)) {
  483. $transaction->rollBack();
  484. }
  485. else {
  486. // Delete the dedicated tables.
  487. foreach ($dedicated_table_field_schema as $name => $table) {
  488. $this->database->schema()->dropTable($dedicated_table_name_mapping[$name]);
  489. }
  490. }
  491. throw $e;
  492. }
  493. // Delete the field from the shared tables.
  494. $this->deleteSharedTableSchema($storage_definition);
  495. }
  496. }
  497. /**
  498. * {@inheritdoc}
  499. */
  500. public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) {
  501. $this->performFieldSchemaOperation('delete', $storage_definition);
  502. }
  503. /**
  504. * Returns a SELECT query suitable for inserting data into a dedicated table.
  505. *
  506. * @param string $table_name
  507. * The entity table name to select from.
  508. * @param array $shared_table_field_columns
  509. * An array of field column names for a shared table schema.
  510. * @param array $dedicated_table_field_columns
  511. * An array of field column names for a dedicated table schema.
  512. * @param string $base_table
  513. * (optional) The name of the base entity table. Defaults to NULL.
  514. *
  515. * @return \Drupal\Core\Database\Query\SelectInterface
  516. * A database select query.
  517. */
  518. protected function getSelectQueryForFieldStorageDeletion($table_name, array $shared_table_field_columns, array $dedicated_table_field_columns, $base_table = NULL) {
  519. // Create a SELECT query that generates a result suitable for writing into
  520. // a dedicated field table.
  521. $select = $this->database->select($table_name, 'entity_table');
  522. // Add the bundle column.
  523. if ($bundle = $this->entityType->getKey('bundle')) {
  524. if ($base_table) {
  525. $select->join($base_table, 'base_table', "entity_table.{$this->entityType->getKey('id')} = %alias.{$this->entityType->getKey('id')}");
  526. $select->addField('base_table', $bundle, 'bundle');
  527. }
  528. else {
  529. $select->addField('entity_table', $bundle, 'bundle');
  530. }
  531. }
  532. else {
  533. $select->addExpression(':bundle', 'bundle', [':bundle' => $this->entityType->id()]);
  534. }
  535. // Add the deleted column.
  536. $select->addExpression(':deleted', 'deleted', [':deleted' => 1]);
  537. // Add the entity_id column.
  538. $select->addField('entity_table', $this->entityType->getKey('id'), 'entity_id');
  539. // Add the revision_id column.
  540. if ($this->entityType->isRevisionable()) {
  541. $select->addField('entity_table', $this->entityType->getKey('revision'), 'revision_id');
  542. }
  543. else {
  544. $select->addField('entity_table', $this->entityType->getKey('id'), 'revision_id');
  545. }
  546. // Add the langcode column.
  547. if ($langcode = $this->entityType->getKey('langcode')) {
  548. $select->addField('entity_table', $langcode, 'langcode');
  549. }
  550. else {
  551. $select->addExpression(':langcode', 'langcode', [':langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED]);
  552. }
  553. // Add the delta column and set it to 0 because we are only dealing with
  554. // single cardinality fields.
  555. $select->addExpression(':delta', 'delta', [':delta' => 0]);
  556. // Add all the dynamic field columns.
  557. $or = $select->orConditionGroup();
  558. foreach ($shared_table_field_columns as $field_column_name => $schema_column_name) {
  559. $select->addField('entity_table', $schema_column_name, $dedicated_table_field_columns[$field_column_name]);
  560. $or->isNotNull('entity_table.' . $schema_column_name);
  561. }
  562. $select->condition($or);
  563. // Lock the table rows.
  564. $select->forUpdate(TRUE);
  565. return $select;
  566. }
  567. /**
  568. * Checks that we are dealing with the correct entity type.
  569. *
  570. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  571. * The entity type to be checked.
  572. *
  573. * @return bool
  574. * TRUE if the entity type matches the current one.
  575. *
  576. * @throws \Drupal\Core\Entity\EntityStorageException
  577. */
  578. protected function checkEntityType(EntityTypeInterface $entity_type) {
  579. if ($entity_type->id() != $this->entityType->id()) {
  580. throw new EntityStorageException("Unsupported entity type {$entity_type->id()}");
  581. }
  582. return TRUE;
  583. }
  584. /**
  585. * Gets the entity schema for the specified entity type.
  586. *
  587. * Entity types may override this method in order to optimize the generated
  588. * schema of the entity tables. However, only cross-field optimizations should
  589. * be added here; e.g., an index spanning multiple fields. Optimizations that
  590. * apply to a single field have to be added via
  591. * SqlContentEntityStorageSchema::getSharedTableFieldSchema() instead.
  592. *
  593. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  594. * The entity type definition.
  595. * @param bool $reset
  596. * (optional) If set to TRUE static cache will be ignored and a new schema
  597. * array generation will be performed. Defaults to FALSE.
  598. *
  599. * @return array
  600. * A Schema API array describing the entity schema, excluding dedicated
  601. * field tables.
  602. *
  603. * @throws \Drupal\Core\Field\FieldException
  604. */
  605. protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
  606. $this->checkEntityType($entity_type);
  607. $entity_type_id = $entity_type->id();
  608. if (!isset($this->schema[$entity_type_id]) || $reset) {
  609. // Back up the storage definition and replace it with the passed one.
  610. // @todo Instead of switching the wrapped entity type, we should be able
  611. // to instantiate a new table mapping for each entity type definition.
  612. // See https://www.drupal.org/node/2274017.
  613. $actual_definition = $this->entityManager->getDefinition($entity_type_id);
  614. $this->storage->setEntityType($entity_type);
  615. // Prepare basic information about the entity type.
  616. $tables = $this->getEntitySchemaTables();
  617. // Initialize the table schema.
  618. $schema[$tables['base_table']] = $this->initializeBaseTable($entity_type);
  619. if (isset($tables['revision_table'])) {
  620. $schema[$tables['revision_table']] = $this->initializeRevisionTable($entity_type);
  621. }
  622. if (isset($tables['data_table'])) {
  623. $schema[$tables['data_table']] = $this->initializeDataTable($entity_type);
  624. }
  625. if (isset($tables['revision_data_table'])) {
  626. $schema[$tables['revision_data_table']] = $this->initializeRevisionDataTable($entity_type);
  627. }
  628. // We need to act only on shared entity schema tables.
  629. $table_mapping = $this->storage->getTableMapping();
  630. $table_names = array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames());
  631. foreach ($table_names as $table_name) {
  632. if (!isset($schema[$table_name])) {
  633. $schema[$table_name] = [];
  634. }
  635. foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
  636. if (!isset($this->fieldStorageDefinitions[$field_name])) {
  637. throw new FieldException("Field storage definition for '$field_name' could not be found.");
  638. }
  639. // Add the schema for base field definitions.
  640. elseif ($table_mapping->allowsSharedTableStorage($this->fieldStorageDefinitions[$field_name])) {
  641. $column_names = $table_mapping->getColumnNames($field_name);
  642. $storage_definition = $this->fieldStorageDefinitions[$field_name];
  643. $schema[$table_name] = array_merge_recursive($schema[$table_name], $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names));
  644. }
  645. }
  646. }
  647. // Process tables after having gathered field information.
  648. $this->processBaseTable($entity_type, $schema[$tables['base_table']]);
  649. if (isset($tables['revision_table'])) {
  650. $this->processRevisionTable($entity_type, $schema[$tables['revision_table']]);
  651. }
  652. if (isset($tables['data_table'])) {
  653. $this->processDataTable($entity_type, $schema[$tables['data_table']]);
  654. }
  655. if (isset($tables['revision_data_table'])) {
  656. $this->processRevisionDataTable($entity_type, $schema[$tables['revision_data_table']]);
  657. }
  658. // Add an index for the 'published' entity key.
  659. if (is_subclass_of($entity_type->getClass(), EntityPublishedInterface::class)) {
  660. $published_key = $entity_type->getKey('published');
  661. if ($published_key && !$this->fieldStorageDefinitions[$published_key]->hasCustomStorage()) {
  662. $published_field_table = $table_mapping->getFieldTableName($published_key);
  663. $id_key = $entity_type->getKey('id');
  664. if ($bundle_key = $entity_type->getKey('bundle')) {
  665. $key = "{$published_key}_{$bundle_key}";
  666. $columns = [$published_key, $bundle_key, $id_key];
  667. }
  668. else {
  669. $key = $published_key;
  670. $columns = [$published_key, $id_key];
  671. }
  672. $schema[$published_field_table]['indexes'][$this->getEntityIndexName($entity_type, $key)] = $columns;
  673. }
  674. }
  675. $this->schema[$entity_type_id] = $schema;
  676. // Restore the actual definition.
  677. $this->storage->setEntityType($actual_definition);
  678. }
  679. return $this->schema[$entity_type_id];
  680. }
  681. /**
  682. * Gets a list of entity type tables.
  683. *
  684. * @return array
  685. * A list of entity type tables, keyed by table key.
  686. */
  687. protected function getEntitySchemaTables() {
  688. return array_filter([
  689. 'base_table' => $this->storage->getBaseTable(),
  690. 'revision_table' => $this->storage->getRevisionTable(),
  691. 'data_table' => $this->storage->getDataTable(),
  692. 'revision_data_table' => $this->storage->getRevisionDataTable(),
  693. ]);
  694. }
  695. /**
  696. * Gets entity schema definitions for index and key definitions.
  697. *
  698. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  699. * The entity type definition.
  700. * @param array $schema
  701. * The entity schema array.
  702. *
  703. * @return array
  704. * A stripped down version of the $schema Schema API array containing, for
  705. * each table, only the key and index definitions not derived from field
  706. * storage definitions.
  707. */
  708. protected function getEntitySchemaData(ContentEntityTypeInterface $entity_type, array $schema) {
  709. $entity_type_id = $entity_type->id();
  710. // Collect all possible field schema identifiers for shared table fields.
  711. // These will be used to detect entity schema data in the subsequent loop.
  712. $field_schema_identifiers = [];
  713. $table_mapping = $this->storage->getTableMapping($this->fieldStorageDefinitions);
  714. foreach ($this->fieldStorageDefinitions as $field_name => $storage_definition) {
  715. if ($table_mapping->allowsSharedTableStorage($storage_definition)) {
  716. // Make sure both base identifier names and suffixed names are listed.
  717. $name = $this->getFieldSchemaIdentifierName($entity_type_id, $field_name);
  718. $field_schema_identifiers[$name] = $name;
  719. foreach ($storage_definition->getColumns() as $key => $columns) {
  720. $name = $this->getFieldSchemaIdentifierName($entity_type_id, $field_name, $key);
  721. $field_schema_identifiers[$name] = $name;
  722. }
  723. }
  724. }
  725. // Extract entity schema data from the Schema API definition.
  726. $schema_data = [];
  727. $keys = ['indexes', 'unique keys'];
  728. $unused_keys = array_flip(['description', 'fields', 'foreign keys']);
  729. foreach ($schema as $table_name => $table_schema) {
  730. $table_schema = array_diff_key($table_schema, $unused_keys);
  731. foreach ($keys as $key) {
  732. // Exclude data generated from field storage definitions, we will check
  733. // that separately.
  734. if ($field_schema_identifiers && !empty($table_schema[$key])) {
  735. $table_schema[$key] = array_diff_key($table_schema[$key], $field_schema_identifiers);
  736. }
  737. }
  738. $schema_data[$table_name] = array_filter($table_schema);
  739. }
  740. return $schema_data;
  741. }
  742. /**
  743. * Gets an index schema array for a given field.
  744. *
  745. * @param string $field_name
  746. * The name of the field.
  747. * @param array $field_schema
  748. * The schema of the field.
  749. * @param string[] $column_mapping
  750. * A mapping of field column names to database column names.
  751. *
  752. * @return array
  753. * The schema definition for the indexes.
  754. */
  755. protected function getFieldIndexes($field_name, array $field_schema, array $column_mapping) {
  756. return $this->getFieldSchemaData($field_name, $field_schema, $column_mapping, 'indexes');
  757. }
  758. /**
  759. * Gets a unique key schema array for a given field.
  760. *
  761. * @param string $field_name
  762. * The name of the field.
  763. * @param array $field_schema
  764. * The schema of the field.
  765. * @param string[] $column_mapping
  766. * A mapping of field column names to database column names.
  767. *
  768. * @return array
  769. * The schema definition for the unique keys.
  770. */
  771. protected function getFieldUniqueKeys($field_name, array $field_schema, array $column_mapping) {
  772. return $this->getFieldSchemaData($field_name, $field_schema, $column_mapping, 'unique keys');
  773. }
  774. /**
  775. * Gets field schema data for the given key.
  776. *
  777. * @param string $field_name
  778. * The name of the field.
  779. * @param array $field_schema
  780. * The schema of the field.
  781. * @param string[] $column_mapping
  782. * A mapping of field column names to database column names.
  783. * @param string $schema_key
  784. * The type of schema data. Either 'indexes' or 'unique keys'.
  785. *
  786. * @return array
  787. * The schema definition for the specified key.
  788. */
  789. protected function getFieldSchemaData($field_name, array $field_schema, array $column_mapping, $schema_key) {
  790. $data = [];
  791. $entity_type_id = $this->entityType->id();
  792. foreach ($field_schema[$schema_key] as $key => $columns) {
  793. // To avoid clashes with entity-level indexes or unique keys we use
  794. // "{$entity_type_id}_field__" as a prefix instead of just
  795. // "{$entity_type_id}__". We additionally namespace the specifier by the
  796. // field name to avoid clashes when multiple fields of the same type are
  797. // added to an entity type.
  798. $real_key = $this->getFieldSchemaIdentifierName($entity_type_id, $field_name, $key);
  799. foreach ($columns as $column) {
  800. // Allow for indexes and unique keys to specified as an array of column
  801. // name and length.
  802. if (is_array($column)) {
  803. list($column_name, $length) = $column;
  804. $data[$real_key][] = [$column_mapping[$column_name], $length];
  805. }
  806. else {
  807. $data[$real_key][] = $column_mapping[$column];
  808. }
  809. }
  810. }
  811. return $data;
  812. }
  813. /**
  814. * Generates a safe schema identifier (name of an index, column name etc.).
  815. *
  816. * @param string $entity_type_id
  817. * The ID of the entity type.
  818. * @param string $field_name
  819. * The name of the field.
  820. * @param string|null $key
  821. * (optional) A further key to append to the name.
  822. *
  823. * @return string
  824. * The field identifier name.
  825. */
  826. protected function getFieldSchemaIdentifierName($entity_type_id, $field_name, $key = NULL) {
  827. $real_key = isset($key) ? "{$entity_type_id}_field__{$field_name}__{$key}" : "{$entity_type_id}_field__{$field_name}";
  828. // Limit the string to 48 characters, keeping a 16 characters margin for db
  829. // prefixes.
  830. if (strlen($real_key) > 48) {
  831. // Use a shorter separator, a truncated entity_type, and a hash of the
  832. // field name.
  833. // Truncate to the same length for the current and revision tables.
  834. $entity_type = substr($entity_type_id, 0, 36);
  835. $field_hash = substr(hash('sha256', $real_key), 0, 10);
  836. $real_key = $entity_type . '__' . $field_hash;
  837. }
  838. return $real_key;
  839. }
  840. /**
  841. * Gets field foreign keys.
  842. *
  843. * @param string $field_name
  844. * The name of the field.
  845. * @param array $field_schema
  846. * The schema of the field.
  847. * @param string[] $column_mapping
  848. * A mapping of field column names to database column names.
  849. *
  850. * @return array
  851. * The schema definition for the foreign keys.
  852. */
  853. protected function getFieldForeignKeys($field_name, array $field_schema, array $column_mapping) {
  854. $foreign_keys = [];
  855. foreach ($field_schema['foreign keys'] as $specifier => $specification) {
  856. // To avoid clashes with entity-level foreign keys we use
  857. // "{$entity_type_id}_field__" as a prefix instead of just
  858. // "{$entity_type_id}__". We additionally namespace the specifier by the
  859. // field name to avoid clashes when multiple fields of the same type are
  860. // added to an entity type.
  861. $entity_type_id = $this->entityType->id();
  862. $real_specifier = "{$entity_type_id}_field__{$field_name}__{$specifier}";
  863. $foreign_keys[$real_specifier]['table'] = $specification['table'];
  864. foreach ($specification['columns'] as $column => $referenced) {
  865. $foreign_keys[$real_specifier]['columns'][$column_mapping[$column]] = $referenced;
  866. }
  867. }
  868. return $foreign_keys;
  869. }
  870. /**
  871. * Loads stored schema data for the given entity type definition.
  872. *
  873. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  874. * The entity type definition.
  875. *
  876. * @return array
  877. * The entity schema data array.
  878. */
  879. protected function loadEntitySchemaData(EntityTypeInterface $entity_type) {
  880. return $this->installedStorageSchema()->get($entity_type->id() . '.entity_schema_data', []);
  881. }
  882. /**
  883. * Stores schema data for the given entity type definition.
  884. *
  885. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  886. * The entity type definition.
  887. * @param array $schema
  888. * The entity schema data array.
  889. */
  890. protected function saveEntitySchemaData(EntityTypeInterface $entity_type, $schema) {
  891. $data = $this->getEntitySchemaData($entity_type, $schema);
  892. $this->installedStorageSchema()->set($entity_type->id() . '.entity_schema_data', $data);
  893. }
  894. /**
  895. * Deletes schema data for the given entity type definition.
  896. *
  897. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  898. * The entity type definition.
  899. */
  900. protected function deleteEntitySchemaData(EntityTypeInterface $entity_type) {
  901. $this->installedStorageSchema()->delete($entity_type->id() . '.entity_schema_data');
  902. }
  903. /**
  904. * Loads stored schema data for the given field storage definition.
  905. *
  906. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  907. * The field storage definition.
  908. *
  909. * @return array
  910. * The field schema data array.
  911. */
  912. protected function loadFieldSchemaData(FieldStorageDefinitionInterface $storage_definition) {
  913. return $this->installedStorageSchema()->get($storage_definition->getTargetEntityTypeId() . '.field_schema_data.' . $storage_definition->getName(), []);
  914. }
  915. /**
  916. * Stores schema data for the given field storage definition.
  917. *
  918. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  919. * The field storage definition.
  920. * @param array $schema
  921. * The field schema data array.
  922. */
  923. protected function saveFieldSchemaData(FieldStorageDefinitionInterface $storage_definition, $schema) {
  924. $this->processFieldStorageSchema($schema);
  925. $this->installedStorageSchema()->set($storage_definition->getTargetEntityTypeId() . '.field_schema_data.' . $storage_definition->getName(), $schema);
  926. }
  927. /**
  928. * Deletes schema data for the given field storage definition.
  929. *
  930. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  931. * The field storage definition.
  932. */
  933. protected function deleteFieldSchemaData(FieldStorageDefinitionInterface $storage_definition) {
  934. $this->installedStorageSchema()->delete($storage_definition->getTargetEntityTypeId() . '.field_schema_data.' . $storage_definition->getName());
  935. }
  936. /**
  937. * Initializes common information for a base table.
  938. *
  939. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  940. * The entity type.
  941. *
  942. * @return array
  943. * A partial schema array for the base table.
  944. */
  945. protected function initializeBaseTable(ContentEntityTypeInterface $entity_type) {
  946. $entity_type_id = $entity_type->id();
  947. $schema = [
  948. 'description' => "The base table for $entity_type_id entities.",
  949. 'primary key' => [$entity_type->getKey('id')],
  950. 'indexes' => [],
  951. 'foreign keys' => [],
  952. ];
  953. if ($entity_type->hasKey('revision')) {
  954. $revision_key = $entity_type->getKey('revision');
  955. $key_name = $this->getEntityIndexName($entity_type, $revision_key);
  956. $schema['unique keys'][$key_name] = [$revision_key];
  957. $schema['foreign keys'][$entity_type_id . '__revision'] = [
  958. 'table' => $this->storage->getRevisionTable(),
  959. 'columns' => [$revision_key => $revision_key],
  960. ];
  961. }
  962. $this->addTableDefaults($schema);
  963. return $schema;
  964. }
  965. /**
  966. * Initializes common information for a revision table.
  967. *
  968. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  969. * The entity type.
  970. *
  971. * @return array
  972. * A partial schema array for the revision table.
  973. */
  974. protected function initializeRevisionTable(ContentEntityTypeInterface $entity_type) {
  975. $entity_type_id = $entity_type->id();
  976. $id_key = $entity_type->getKey('id');
  977. $revision_key = $entity_type->getKey('revision');
  978. $schema = [
  979. 'description' => "The revision table for $entity_type_id entities.",
  980. 'primary key' => [$revision_key],
  981. 'indexes' => [],
  982. 'foreign keys' => [
  983. $entity_type_id . '__revisioned' => [
  984. 'table' => $this->storage->getBaseTable(),
  985. 'columns' => [$id_key => $id_key],
  986. ],
  987. ],
  988. ];
  989. $schema['indexes'][$this->getEntityIndexName($entity_type, $id_key)] = [$id_key];
  990. $this->addTableDefaults($schema);
  991. return $schema;
  992. }
  993. /**
  994. * Initializes common information for a data table.
  995. *
  996. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  997. * The entity type.
  998. *
  999. * @return array
  1000. * A partial schema array for the data table.
  1001. */
  1002. protected function initializeDataTable(ContentEntityTypeInterface $entity_type) {
  1003. $entity_type_id = $entity_type->id();
  1004. $id_key = $entity_type->getKey('id');
  1005. $schema = [
  1006. 'description' => "The data table for $entity_type_id entities.",
  1007. 'primary key' => [$id_key, $entity_type->getKey('langcode')],
  1008. 'indexes' => [
  1009. $entity_type_id . '__id__default_langcode__langcode' => [$id_key, $entity_type->getKey('default_langcode'), $entity_type->getKey('langcode')],
  1010. ],
  1011. 'foreign keys' => [
  1012. $entity_type_id => [
  1013. 'table' => $this->storage->getBaseTable(),
  1014. 'columns' => [$id_key => $id_key],
  1015. ],
  1016. ],
  1017. ];
  1018. if ($entity_type->hasKey('revision')) {
  1019. $key = $entity_type->getKey('revision');
  1020. $schema['indexes'][$this->getEntityIndexName($entity_type, $key)] = [$key];
  1021. }
  1022. $this->addTableDefaults($schema);
  1023. return $schema;
  1024. }
  1025. /**
  1026. * Initializes common information for a revision data table.
  1027. *
  1028. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  1029. * The entity type.
  1030. *
  1031. * @return array
  1032. * A partial schema array for the revision data table.
  1033. */
  1034. protected function initializeRevisionDataTable(ContentEntityTypeInterface $entity_type) {
  1035. $entity_type_id = $entity_type->id();
  1036. $id_key = $entity_type->getKey('id');
  1037. $revision_key = $entity_type->getKey('revision');
  1038. $schema = [
  1039. 'description' => "The revision data table for $entity_type_id entities.",
  1040. 'primary key' => [$revision_key, $entity_type->getKey('langcode')],
  1041. 'indexes' => [
  1042. $entity_type_id . '__id__default_langcode__langcode' => [$id_key, $entity_type->getKey('default_langcode'), $entity_type->getKey('langcode')],
  1043. ],
  1044. 'foreign keys' => [
  1045. $entity_type_id => [
  1046. 'table' => $this->storage->getBaseTable(),
  1047. 'columns' => [$id_key => $id_key],
  1048. ],
  1049. $entity_type_id . '__revision' => [
  1050. 'table' => $this->storage->getRevisionTable(),
  1051. 'columns' => [$revision_key => $revision_key],
  1052. ]
  1053. ],
  1054. ];
  1055. $this->addTableDefaults($schema);
  1056. return $schema;
  1057. }
  1058. /**
  1059. * Adds defaults to a table schema definition.
  1060. *
  1061. * @param $schema
  1062. * The schema definition array for a single table, passed by reference.
  1063. */
  1064. protected function addTableDefaults(&$schema) {
  1065. $schema += [
  1066. 'fields' => [],
  1067. 'unique keys' => [],
  1068. 'indexes' => [],
  1069. 'foreign keys' => [],
  1070. ];
  1071. }
  1072. /**
  1073. * Processes the gathered schema for a base table.
  1074. *
  1075. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  1076. * The entity type.
  1077. * @param array $schema
  1078. * The table schema, passed by reference.
  1079. *
  1080. * @return array
  1081. * A partial schema array for the base table.
  1082. */
  1083. protected function processBaseTable(ContentEntityTypeInterface $entity_type, array &$schema) {
  1084. $this->processIdentifierSchema($schema, $entity_type->getKey('id'));
  1085. }
  1086. /**
  1087. * Processes the gathered schema for a base table.
  1088. *
  1089. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  1090. * The entity type.
  1091. * @param array $schema
  1092. * The table schema, passed by reference.
  1093. *
  1094. * @return array
  1095. * A partial schema array for the base table.
  1096. */
  1097. protected function processRevisionTable(ContentEntityTypeInterface $entity_type, array &$schema) {
  1098. $this->processIdentifierSchema($schema, $entity_type->getKey('revision'));
  1099. }
  1100. /**
  1101. * Processes the gathered schema for a base table.
  1102. *
  1103. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  1104. * The entity type.
  1105. * @param array $schema
  1106. * The table schema, passed by reference.
  1107. *
  1108. * @return array
  1109. * A partial schema array for the base table.
  1110. */
  1111. protected function processDataTable(ContentEntityTypeInterface $entity_type, array &$schema) {
  1112. // Marking the respective fields as NOT NULL makes the indexes more
  1113. // performant.
  1114. $schema['fields'][$entity_type->getKey('default_langcode')]['not null'] = TRUE;
  1115. }
  1116. /**
  1117. * Processes the gathered schema for a base table.
  1118. *
  1119. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  1120. * The entity type.
  1121. * @param array $schema
  1122. * The table schema, passed by reference.
  1123. *
  1124. * @return array
  1125. * A partial schema array for the base table.
  1126. */
  1127. protected function processRevisionDataTable(ContentEntityTypeInterface $entity_type, array &$schema) {
  1128. // Marking the respective fields as NOT NULL makes the indexes more
  1129. // performant.
  1130. $schema['fields'][$entity_type->getKey('default_langcode')]['not null'] = TRUE;
  1131. }
  1132. /**
  1133. * Processes the specified entity key.
  1134. *
  1135. * @param array $schema
  1136. * The table schema, passed by reference.
  1137. * @param string $key
  1138. * The entity key name.
  1139. */
  1140. protected function processIdentifierSchema(&$schema, $key) {
  1141. if ($schema['fields'][$key]['type'] == 'int') {
  1142. $schema['fields'][$key]['type'] = 'serial';
  1143. }
  1144. $schema['fields'][$key]['not null'] = TRUE;
  1145. unset($schema['fields'][$key]['default']);
  1146. }
  1147. /**
  1148. * Processes the schema for a field storage definition.
  1149. *
  1150. * @param array &$field_storage_schema
  1151. * An array that contains the schema data for a field storage definition.
  1152. */
  1153. protected function processFieldStorageSchema(array &$field_storage_schema) {
  1154. // Clean up some schema properties that should not be taken into account
  1155. // after a field storage has been created.
  1156. foreach ($field_storage_schema as $table_name => $table_schema) {
  1157. foreach ($table_schema['fields'] as $key => $schema) {
  1158. unset($field_storage_schema[$table_name]['fields'][$key]['initial']);
  1159. unset($field_storage_schema[$table_name]['fields'][$key]['initial_from_field']);
  1160. }
  1161. }
  1162. }
  1163. /**
  1164. * Performs the specified operation on a field.
  1165. *
  1166. * This figures out whether the field is stored in a dedicated or shared table
  1167. * and forwards the call to the proper handler.
  1168. *
  1169. * @param string $operation
  1170. * The name of the operation to be performed.
  1171. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1172. * The field storage definition.
  1173. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
  1174. * (optional) The original field storage definition. This is relevant (and
  1175. * required) only for updates. Defaults to NULL.
  1176. */
  1177. protected function performFieldSchemaOperation($operation, FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original = NULL) {
  1178. $table_mapping = $this->storage->getTableMapping();
  1179. if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
  1180. $this->{$operation . 'DedicatedTableSchema'}($storage_definition, $original);
  1181. }
  1182. elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) {
  1183. $this->{$operation . 'SharedTableSchema'}($storage_definition, $original);
  1184. }
  1185. }
  1186. /**
  1187. * Creates the schema for a field stored in a dedicated table.
  1188. *
  1189. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1190. * The storage definition of the field being created.
  1191. */
  1192. protected function createDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition) {
  1193. $schema = $this->getDedicatedTableSchema($storage_definition);
  1194. foreach ($schema as $name => $table) {
  1195. // Check if the table exists because it might already have been
  1196. // created as part of the earlier entity type update event.
  1197. if (!$this->database->schema()->tableExists($name)) {
  1198. $this->database->schema()->createTable($name, $table);
  1199. }
  1200. }
  1201. $this->saveFieldSchemaData($storage_definition, $schema);
  1202. }
  1203. /**
  1204. * Creates the schema for a field stored in a shared table.
  1205. *
  1206. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1207. * The storage definition of the field being created.
  1208. * @param bool $only_save
  1209. * (optional) Whether to skip modification of database tables and only save
  1210. * the schema data for future comparison. For internal use only. This is
  1211. * used by onEntityTypeCreate() after it has already fully created the
  1212. * shared tables.
  1213. */
  1214. protected function createSharedTableSchema(FieldStorageDefinitionInterface $storage_definition, $only_save = FALSE) {
  1215. $created_field_name = $storage_definition->getName();
  1216. $table_mapping = $this->storage->getTableMapping();
  1217. $column_names = $table_mapping->getColumnNames($created_field_name);
  1218. $schema_handler = $this->database->schema();
  1219. $shared_table_names = array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames());
  1220. // Iterate over the mapped table to find the ones that will host the created
  1221. // field schema.
  1222. $schema = [];
  1223. foreach ($shared_table_names as $table_name) {
  1224. foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
  1225. if ($field_name == $created_field_name) {
  1226. // Create field columns.
  1227. $schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
  1228. if (!$only_save) {
  1229. foreach ($schema[$table_name]['fields'] as $name => $specifier) {
  1230. // Check if the field exists because it might already have been
  1231. // created as part of the earlier entity type update event.
  1232. if (!$schema_handler->fieldExists($table_name, $name)) {
  1233. $schema_handler->addField($table_name, $name, $specifier);
  1234. }
  1235. }
  1236. if (!empty($schema[$table_name]['indexes'])) {
  1237. foreach ($schema[$table_name]['indexes'] as $name => $specifier) {
  1238. // Check if the index exists because it might already have been
  1239. // created as part of the earlier entity type update event.
  1240. $this->addIndex($table_name, $name, $specifier, $schema[$table_name]);
  1241. }
  1242. }
  1243. if (!empty($schema[$table_name]['unique keys'])) {
  1244. foreach ($schema[$table_name]['unique keys'] as $name => $specifier) {
  1245. $schema_handler->addUniqueKey($table_name, $name, $specifier);
  1246. }
  1247. }
  1248. }
  1249. // After creating the field schema skip to the next table.
  1250. break;
  1251. }
  1252. }
  1253. }
  1254. $this->saveFieldSchemaData($storage_definition, $schema);
  1255. if (!$only_save) {
  1256. // Make sure any entity index involving this field is re-created if
  1257. // needed.
  1258. $this->createEntitySchemaIndexes($this->getEntitySchema($this->entityType), $storage_definition);
  1259. }
  1260. }
  1261. /**
  1262. * Deletes the schema for a field stored in a dedicated table.
  1263. *
  1264. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1265. * The storage definition of the field being deleted.
  1266. */
  1267. protected function deleteDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition) {
  1268. $table_mapping = $this->storage->getTableMapping();
  1269. $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $storage_definition->isDeleted());
  1270. if ($this->database->schema()->tableExists($table_name)) {
  1271. $this->database->schema()->dropTable($table_name);
  1272. }
  1273. if ($this->entityType->isRevisionable()) {
  1274. $revision_table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $storage_definition->isDeleted());
  1275. if ($this->database->schema()->tableExists($revision_table_name)) {
  1276. $this->database->schema()->dropTable($revision_table_name);
  1277. }
  1278. }
  1279. $this->deleteFieldSchemaData($storage_definition);
  1280. }
  1281. /**
  1282. * Deletes the schema for a field stored in a shared table.
  1283. *
  1284. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1285. * The storage definition of the field being deleted.
  1286. */
  1287. protected function deleteSharedTableSchema(FieldStorageDefinitionInterface $storage_definition) {
  1288. // Make sure any entity index involving this field is dropped.
  1289. $this->deleteEntitySchemaIndexes($this->loadEntitySchemaData($this->entityType), $storage_definition);
  1290. $deleted_field_name = $storage_definition->getName();
  1291. $table_mapping = $this->storage->getTableMapping(
  1292. $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id())
  1293. );
  1294. $column_names = $table_mapping->getColumnNames($deleted_field_name);
  1295. $schema_handler = $this->database->schema();
  1296. $shared_table_names = array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames());
  1297. // Iterate over the mapped table to find the ones that host the deleted
  1298. // field schema.
  1299. foreach ($shared_table_names as $table_name) {
  1300. foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
  1301. if ($field_name == $deleted_field_name) {
  1302. $schema = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
  1303. // Drop indexes and unique keys first.
  1304. if (!empty($schema['indexes'])) {
  1305. foreach ($schema['indexes'] as $name => $specifier) {
  1306. $schema_handler->dropIndex($table_name, $name);
  1307. }
  1308. }
  1309. if (!empty($schema['unique keys'])) {
  1310. foreach ($schema['unique keys'] as $name => $specifier) {
  1311. $schema_handler->dropUniqueKey($table_name, $name);
  1312. }
  1313. }
  1314. // Drop columns.
  1315. foreach ($column_names as $column_name) {
  1316. $schema_handler->dropField($table_name, $column_name);
  1317. }
  1318. // After deleting the field schema skip to the next table.
  1319. break;
  1320. }
  1321. }
  1322. }
  1323. $this->deleteFieldSchemaData($storage_definition);
  1324. }
  1325. /**
  1326. * Updates the schema for a field stored in a shared table.
  1327. *
  1328. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1329. * The storage definition of the field being updated.
  1330. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
  1331. * The original storage definition; i.e., the definition before the update.
  1332. *
  1333. * @throws \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException
  1334. * Thrown when the update to the field is forbidden.
  1335. * @throws \Exception
  1336. * Rethrown exception if the table recreation fails.
  1337. */
  1338. protected function updateDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
  1339. if (!$this->storage->countFieldData($original, TRUE)) {
  1340. // There is no data. Re-create the tables completely.
  1341. if ($this->database->supportsTransactionalDDL()) {
  1342. // If the database supports transactional DDL, we can go ahead and rely
  1343. // on it. If not, we will have to rollback manually if something fails.
  1344. $transaction = $this->database->startTransaction();
  1345. }
  1346. try {
  1347. // Since there is no data we may be switching from a shared table schema
  1348. // to a dedicated table schema, hence we should use the proper API.
  1349. $this->performFieldSchemaOperation('delete', $original);
  1350. $this->performFieldSchemaOperation('create', $storage_definition);
  1351. }
  1352. catch (\Exception $e) {
  1353. if ($this->database->supportsTransactionalDDL()) {
  1354. $transaction->rollBack();
  1355. }
  1356. else {
  1357. // Recreate tables.
  1358. $this->performFieldSchemaOperation('create', $original);
  1359. }
  1360. throw $e;
  1361. }
  1362. }
  1363. else {
  1364. if ($this->hasColumnChanges($storage_definition, $original)) {
  1365. throw new FieldStorageDefinitionUpdateForbiddenException('The SQL storage cannot change the schema for an existing field (' . $storage_definition->getName() . ' in ' . $storage_definition->getTargetEntityTypeId() . ' entity) with data.');
  1366. }
  1367. // There is data, so there are no column changes. Drop all the prior
  1368. // indexes and create all the new ones, except for all the priors that
  1369. // exist unchanged.
  1370. $table_mapping = $this->storage->getTableMapping();
  1371. $table = $table_mapping->getDedicatedDataTableName($original);
  1372. $revision_table = $table_mapping->getDedicatedRevisionTableName($original);
  1373. // Get the field schemas.
  1374. $schema = $storage_definition->getSchema();
  1375. $original_schema = $original->getSchema();
  1376. // Gets the SQL schema for a dedicated tables.
  1377. $actual_schema = $this->getDedicatedTableSchema($storage_definition);
  1378. foreach ($original_schema['indexes'] as $name => $columns) {
  1379. if (!isset($schema['indexes'][$name]) || $columns != $schema['indexes'][$name]) {
  1380. $real_name = $this->getFieldIndexName($storage_definition, $name);
  1381. $this->database->schema()->dropIndex($table, $real_name);
  1382. $this->database->schema()->dropIndex($revision_table, $real_name);
  1383. }
  1384. }
  1385. $table = $table_mapping->getDedicatedDataTableName($storage_definition);
  1386. $revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
  1387. foreach ($schema['indexes'] as $name => $columns) {
  1388. if (!isset($original_schema['indexes'][$name]) || $columns != $original_schema['indexes'][$name]) {
  1389. $real_name = $this->getFieldIndexName($storage_definition, $name);
  1390. $real_columns = [];
  1391. foreach ($columns as $column_name) {
  1392. // Indexes can be specified as either a column name or an array with
  1393. // column name and length. Allow for either case.
  1394. if (is_array($column_name)) {
  1395. $real_columns[] = [
  1396. $table_mapping->getFieldColumnName($storage_definition, $column_name[0]),
  1397. $column_name[1],
  1398. ];
  1399. }
  1400. else {
  1401. $real_columns[] = $table_mapping->getFieldColumnName($storage_definition, $column_name);
  1402. }
  1403. }
  1404. // Check if the index exists because it might already have been
  1405. // created as part of the earlier entity type update event.
  1406. $this->addIndex($table, $real_name, $real_columns, $actual_schema[$table]);
  1407. $this->addIndex($revision_table, $real_name, $real_columns, $actual_schema[$revision_table]);
  1408. }
  1409. }
  1410. $this->saveFieldSchemaData($storage_definition, $this->getDedicatedTableSchema($storage_definition));
  1411. }
  1412. }
  1413. /**
  1414. * Updates the schema for a field stored in a shared table.
  1415. *
  1416. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1417. * The storage definition of the field being updated.
  1418. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
  1419. * The original storage definition; i.e., the definition before the update.
  1420. *
  1421. * @throws \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException
  1422. * Thrown when the update to the field is forbidden.
  1423. * @throws \Exception
  1424. * Rethrown exception if the table recreation fails.
  1425. */
  1426. protected function updateSharedTableSchema(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
  1427. if (!$this->storage->countFieldData($original, TRUE)) {
  1428. if ($this->database->supportsTransactionalDDL()) {
  1429. // If the database supports transactional DDL, we can go ahead and rely
  1430. // on it. If not, we will have to rollback manually if something fails.
  1431. $transaction = $this->database->startTransaction();
  1432. }
  1433. try {
  1434. // Since there is no data we may be switching from a dedicated table
  1435. // to a schema table schema, hence we should use the proper API.
  1436. $this->performFieldSchemaOperation('delete', $original);
  1437. $this->performFieldSchemaOperation('create', $storage_definition);
  1438. }
  1439. catch (\Exception $e) {
  1440. if ($this->database->supportsTransactionalDDL()) {
  1441. $transaction->rollBack();
  1442. }
  1443. else {
  1444. // Recreate original schema.
  1445. $this->createSharedTableSchema($original);
  1446. }
  1447. throw $e;
  1448. }
  1449. }
  1450. else {
  1451. if ($this->hasColumnChanges($storage_definition, $original)) {
  1452. throw new FieldStorageDefinitionUpdateForbiddenException('The SQL storage cannot change the schema for an existing field (' . $storage_definition->getName() . ' in ' . $storage_definition->getTargetEntityTypeId() . ' entity) with data.');
  1453. }
  1454. $updated_field_name = $storage_definition->getName();
  1455. $table_mapping = $this->storage->getTableMapping();
  1456. $column_names = $table_mapping->getColumnNames($updated_field_name);
  1457. $schema_handler = $this->database->schema();
  1458. // Iterate over the mapped table to find the ones that host the deleted
  1459. // field schema.
  1460. $original_schema = $this->loadFieldSchemaData($original);
  1461. $schema = [];
  1462. foreach ($table_mapping->getTableNames() as $table_name) {
  1463. foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
  1464. if ($field_name == $updated_field_name) {
  1465. $schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
  1466. // Handle NOT NULL constraints.
  1467. foreach ($schema[$table_name]['fields'] as $column_name => $specifier) {
  1468. $not_null = !empty($specifier['not null']);
  1469. $original_not_null = !empty($original_schema[$table_name]['fields'][$column_name]['not null']);
  1470. if ($not_null !== $original_not_null) {
  1471. if ($not_null && $this->hasNullFieldPropertyData($table_name, $column_name)) {
  1472. throw new EntityStorageException("The $column_name column cannot have NOT NULL constraints as it holds NULL values.");
  1473. }
  1474. $column_schema = $original_schema[$table_name]['fields'][$column_name];
  1475. $column_schema['not null'] = $not_null;
  1476. $schema_handler->changeField($table_name, $field_name, $field_name, $column_schema);
  1477. }
  1478. }
  1479. // Drop original indexes and unique keys.
  1480. if (!empty($original_schema[$table_name]['indexes'])) {
  1481. foreach ($original_schema[$table_name]['indexes'] as $name => $specifier) {
  1482. $schema_handler->dropIndex($table_name, $name);
  1483. }
  1484. }
  1485. if (!empty($original_schema[$table_name]['unique keys'])) {
  1486. foreach ($original_schema[$table_name]['unique keys'] as $name => $specifier) {
  1487. $schema_handler->dropUniqueKey($table_name, $name);
  1488. }
  1489. }
  1490. // Create new indexes and unique keys.
  1491. if (!empty($schema[$table_name]['indexes'])) {
  1492. foreach ($schema[$table_name]['indexes'] as $name => $specifier) {
  1493. // Check if the index exists because it might already have been
  1494. // created as part of the earlier entity type update event.
  1495. $this->addIndex($table_name, $name, $specifier, $schema[$table_name]);
  1496. }
  1497. }
  1498. if (!empty($schema[$table_name]['unique keys'])) {
  1499. foreach ($schema[$table_name]['unique keys'] as $name => $specifier) {
  1500. $schema_handler->addUniqueKey($table_name, $name, $specifier);
  1501. }
  1502. }
  1503. // After deleting the field schema skip to the next table.
  1504. break;
  1505. }
  1506. }
  1507. }
  1508. $this->saveFieldSchemaData($storage_definition, $schema);
  1509. }
  1510. }
  1511. /**
  1512. * Creates the specified entity schema indexes and keys.
  1513. *
  1514. * @param array $entity_schema
  1515. * The entity schema definition.
  1516. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface|null $storage_definition
  1517. * (optional) If a field storage definition is specified, only indexes and
  1518. * keys involving its columns will be processed. Otherwise all defined
  1519. * entity indexes and keys will be processed.
  1520. */
  1521. protected function createEntitySchemaIndexes(array $entity_schema, FieldStorageDefinitionInterface $storage_definition = NULL) {
  1522. $schema_handler = $this->database->schema();
  1523. if ($storage_definition) {
  1524. $table_mapping = $this->storage->getTableMapping();
  1525. $column_names = $table_mapping->getColumnNames($storage_definition->getName());
  1526. }
  1527. $index_keys = [
  1528. 'indexes' => 'addIndex',
  1529. 'unique keys' => 'addUniqueKey',
  1530. ];
  1531. foreach ($this->getEntitySchemaData($this->entityType, $entity_schema) as $table_name => $schema) {
  1532. // Add fields schema because database driver may depend on this data to
  1533. // perform index normalization.
  1534. $schema['fields'] = $entity_schema[$table_name]['fields'];
  1535. foreach ($index_keys as $key => $add_method) {
  1536. if (!empty($schema[$key])) {
  1537. foreach ($schema[$key] as $name => $specifier) {
  1538. // If a set of field columns were specified we process only indexes
  1539. // involving them. Only indexes for which all columns exist are
  1540. // actually created.
  1541. $create = FALSE;
  1542. $specifier_columns = array_map(function ($item) {
  1543. return is_string($item) ? $item : reset($item);
  1544. }, $specifier);
  1545. if (!isset($column_names) || array_intersect($specifier_columns, $column_names)) {
  1546. $create = TRUE;
  1547. foreach ($specifier_columns as $specifier_column_name) {
  1548. // This may happen when adding more than one field in the same
  1549. // update run. Eventually when all field columns have been
  1550. // created the index will be created too.
  1551. if (!$schema_handler->fieldExists($table_name, $specifier_column_name)) {
  1552. $create = FALSE;
  1553. break;
  1554. }
  1555. }
  1556. }
  1557. if ($create) {
  1558. $this->{$add_method}($table_name, $name, $specifier, $schema);
  1559. }
  1560. }
  1561. }
  1562. }
  1563. }
  1564. }
  1565. /**
  1566. * Deletes the specified entity schema indexes and keys.
  1567. *
  1568. * @param array $entity_schema_data
  1569. * The entity schema data definition.
  1570. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface|null $storage_definition
  1571. * (optional) If a field storage definition is specified, only indexes and
  1572. * keys involving its columns will be processed. Otherwise all defined
  1573. * entity indexes and keys will be processed.
  1574. */
  1575. protected function deleteEntitySchemaIndexes(array $entity_schema_data, FieldStorageDefinitionInterface $storage_definition = NULL) {
  1576. $schema_handler = $this->database->schema();
  1577. if ($storage_definition) {
  1578. $table_mapping = $this->storage->getTableMapping();
  1579. $column_names = $table_mapping->getColumnNames($storage_definition->getName());
  1580. }
  1581. $index_keys = [
  1582. 'indexes' => 'dropIndex',
  1583. 'unique keys' => 'dropUniqueKey',
  1584. ];
  1585. foreach ($entity_schema_data as $table_name => $schema) {
  1586. foreach ($index_keys as $key => $drop_method) {
  1587. if (!empty($schema[$key])) {
  1588. foreach ($schema[$key] as $name => $specifier) {
  1589. $specifier_columns = array_map(function ($item) {
  1590. return is_string($item) ? $item : reset($item);
  1591. }, $specifier);
  1592. if (!isset($column_names) || array_intersect($specifier_columns, $column_names)) {
  1593. $schema_handler->{$drop_method}($table_name, $name);
  1594. }
  1595. }
  1596. }
  1597. }
  1598. }
  1599. }
  1600. /**
  1601. * Checks whether a field property has NULL values.
  1602. *
  1603. * @param string $table_name
  1604. * The name of the table to inspect.
  1605. * @param string $column_name
  1606. * The name of the column holding the field property data.
  1607. *
  1608. * @return bool
  1609. * TRUE if NULL data is found, FALSE otherwise.
  1610. */
  1611. protected function hasNullFieldPropertyData($table_name, $column_name) {
  1612. $query = $this->database->select($table_name, 't')
  1613. ->fields('t', [$column_name])
  1614. ->range(0, 1);
  1615. $query->isNull('t.' . $column_name);
  1616. $result = $query->execute()->fetchAssoc();
  1617. return (bool) $result;
  1618. }
  1619. /**
  1620. * Gets the schema for a single field definition.
  1621. *
  1622. * Entity types may override this method in order to optimize the generated
  1623. * schema for given field. While all optimizations that apply to a single
  1624. * field have to be added here, all cross-field optimizations should be via
  1625. * SqlContentEntityStorageSchema::getEntitySchema() instead; e.g.,
  1626. * an index spanning multiple fields.
  1627. *
  1628. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1629. * The storage definition of the field whose schema has to be returned.
  1630. * @param string $table_name
  1631. * The name of the table columns will be added to.
  1632. * @param string[] $column_mapping
  1633. * A mapping of field column names to database column names.
  1634. *
  1635. * @return array
  1636. * The schema definition for the table with the following keys:
  1637. * - fields: The schema definition for the each field columns.
  1638. * - indexes: The schema definition for the indexes.
  1639. * - unique keys: The schema definition for the unique keys.
  1640. * - foreign keys: The schema definition for the foreign keys.
  1641. *
  1642. * @throws \Drupal\Core\Field\FieldException
  1643. * Exception thrown if the schema contains reserved column names or if the
  1644. * initial values definition is invalid.
  1645. */
  1646. protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
  1647. $schema = [];
  1648. $table_mapping = $this->storage->getTableMapping();
  1649. $field_schema = $storage_definition->getSchema();
  1650. // Check that the schema does not include forbidden column names.
  1651. if (array_intersect(array_keys($field_schema['columns']), $table_mapping->getReservedColumns())) {
  1652. throw new FieldException("Illegal field column names on {$storage_definition->getName()}");
  1653. }
  1654. $field_name = $storage_definition->getName();
  1655. $base_table = $this->storage->getBaseTable();
  1656. // Define the initial values, if any.
  1657. $initial_value = $initial_value_from_field = [];
  1658. $storage_definition_is_new = empty($this->loadFieldSchemaData($storage_definition));
  1659. if ($storage_definition_is_new && $storage_definition instanceof BaseFieldDefinition && $table_mapping->allowsSharedTableStorage($storage_definition)) {
  1660. if (($initial_storage_value = $storage_definition->getInitialValue()) && !empty($initial_storage_value)) {
  1661. // We only support initial values for fields that are stored in shared
  1662. // tables (i.e. single-value fields).
  1663. // @todo Implement initial value support for multi-value fields in
  1664. // https://www.drupal.org/node/2883851.
  1665. $initial_value = reset($initial_storage_value);
  1666. }
  1667. if ($initial_value_field_name = $storage_definition->getInitialValueFromField()) {
  1668. // Check that the field used for populating initial values is valid. We
  1669. // must use the last installed version of that, as the new field might
  1670. // be created in an update function and the storage definition of the
  1671. // "from" field might get changed later.
  1672. $last_installed_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id());
  1673. if (!isset($last_installed_storage_definitions[$initial_value_field_name])) {
  1674. throw new FieldException("Illegal initial value definition on {$storage_definition->getName()}: The field $initial_value_field_name does not exist.");
  1675. }
  1676. if ($storage_definition->getType() !== $last_installed_storage_definitions[$initial_value_field_name]->getType()) {
  1677. throw new FieldException("Illegal initial value definition on {$storage_definition->getName()}: The field types do not match.");
  1678. }
  1679. if (!$table_mapping->allowsSharedTableStorage($last_installed_storage_definitions[$initial_value_field_name])) {
  1680. throw new FieldException("Illegal initial value definition on {$storage_definition->getName()}: Both fields have to be stored in the shared entity tables.");
  1681. }
  1682. $initial_value_from_field = $table_mapping->getColumnNames($initial_value_field_name);
  1683. }
  1684. }
  1685. // A shared table contains rows for entities where the field is empty
  1686. // (since other fields stored in the same table might not be empty), thus
  1687. // the only columns that can be 'not null' are those for required
  1688. // properties of required fields. For now, we only hardcode 'not null' to a
  1689. // few "entity keys", in order to keep their indexes optimized.
  1690. // @todo Fix this in https://www.drupal.org/node/2841291.
  1691. $not_null_keys = $this->entityType->getKeys();
  1692. // Label and the 'revision_translation_affected' fields are not necessarily
  1693. // required.
  1694. unset($not_null_keys['label'], $not_null_keys['revision_translation_affected']);
  1695. // Because entity ID and revision ID are both serial fields in the base and
  1696. // revision table respectively, the revision ID is not known yet, when
  1697. // inserting data into the base table. Instead the revision ID in the base
  1698. // table is updated after the data has been inserted into the revision
  1699. // table. For this reason the revision ID field cannot be marked as NOT
  1700. // NULL.
  1701. if ($table_name == $base_table) {
  1702. unset($not_null_keys['revision']);
  1703. }
  1704. foreach ($column_mapping as $field_column_name => $schema_field_name) {
  1705. $column_schema = $field_schema['columns'][$field_column_name];
  1706. $schema['fields'][$schema_field_name] = $column_schema;
  1707. $schema['fields'][$schema_field_name]['not null'] = in_array($field_name, $not_null_keys);
  1708. // Use the initial value of the field storage, if available.
  1709. if ($initial_value && isset($initial_value[$field_column_name])) {
  1710. $schema['fields'][$schema_field_name]['initial'] = drupal_schema_get_field_value($column_schema, $initial_value[$field_column_name]);
  1711. }
  1712. elseif (!empty($initial_value_from_field)) {
  1713. $schema['fields'][$schema_field_name]['initial_from_field'] = $initial_value_from_field[$field_column_name];
  1714. }
  1715. }
  1716. if (!empty($field_schema['indexes'])) {
  1717. $schema['indexes'] = $this->getFieldIndexes($field_name, $field_schema, $column_mapping);
  1718. }
  1719. if (!empty($field_schema['unique keys'])) {
  1720. $schema['unique keys'] = $this->getFieldUniqueKeys($field_name, $field_schema, $column_mapping);
  1721. }
  1722. if (!empty($field_schema['foreign keys'])) {
  1723. $schema['foreign keys'] = $this->getFieldForeignKeys($field_name, $field_schema, $column_mapping);
  1724. }
  1725. return $schema;
  1726. }
  1727. /**
  1728. * Adds an index for the specified field to the given schema definition.
  1729. *
  1730. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1731. * The storage definition of the field for which an index should be added.
  1732. * @param array $schema
  1733. * A reference to the schema array to be updated.
  1734. * @param bool $not_null
  1735. * (optional) Whether to also add a 'not null' constraint to the column
  1736. * being indexed. Doing so improves index performance. Defaults to FALSE,
  1737. * in case the field needs to support NULL values.
  1738. * @param int $size
  1739. * (optional) The index size. Defaults to no limit.
  1740. */
  1741. protected function addSharedTableFieldIndex(FieldStorageDefinitionInterface $storage_definition, &$schema, $not_null = FALSE, $size = NULL) {
  1742. $name = $storage_definition->getName();
  1743. $real_key = $this->getFieldSchemaIdentifierName($storage_definition->getTargetEntityTypeId(), $name);
  1744. $schema['indexes'][$real_key] = [$size ? [$name, $size] : $name];
  1745. if ($not_null) {
  1746. $schema['fields'][$name]['not null'] = TRUE;
  1747. }
  1748. }
  1749. /**
  1750. * Adds a unique key for the specified field to the given schema definition.
  1751. *
  1752. * Also adds a 'not null' constraint, because many databases do not reliably
  1753. * support unique keys on null columns.
  1754. *
  1755. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1756. * The storage definition of the field to which to add a unique key.
  1757. * @param array $schema
  1758. * A reference to the schema array to be updated.
  1759. */
  1760. protected function addSharedTableFieldUniqueKey(FieldStorageDefinitionInterface $storage_definition, &$schema) {
  1761. $name = $storage_definition->getName();
  1762. $real_key = $this->getFieldSchemaIdentifierName($storage_definition->getTargetEntityTypeId(), $name);
  1763. $schema['unique keys'][$real_key] = [$name];
  1764. $schema['fields'][$name]['not null'] = TRUE;
  1765. }
  1766. /**
  1767. * Adds a foreign key for the specified field to the given schema definition.
  1768. *
  1769. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1770. * The storage definition of the field to which to add a foreign key.
  1771. * @param array $schema
  1772. * A reference to the schema array to be updated.
  1773. * @param string $foreign_table
  1774. * The foreign table.
  1775. * @param string $foreign_column
  1776. * The foreign column.
  1777. */
  1778. protected function addSharedTableFieldForeignKey(FieldStorageDefinitionInterface $storage_definition, &$schema, $foreign_table, $foreign_column) {
  1779. $name = $storage_definition->getName();
  1780. $real_key = $this->getFieldSchemaIdentifierName($storage_definition->getTargetEntityTypeId(), $name);
  1781. $schema['foreign keys'][$real_key] = [
  1782. 'table' => $foreign_table,
  1783. 'columns' => [$name => $foreign_column],
  1784. ];
  1785. }
  1786. /**
  1787. * Gets the SQL schema for a dedicated table.
  1788. *
  1789. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1790. * The field storage definition.
  1791. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  1792. * (optional) The entity type definition. Defaults to the one returned by
  1793. * the entity manager.
  1794. *
  1795. * @return array
  1796. * The schema definition for the table with the following keys:
  1797. * - fields: The schema definition for the each field columns.
  1798. * - indexes: The schema definition for the indexes.
  1799. * - unique keys: The schema definition for the unique keys.
  1800. * - foreign keys: The schema definition for the foreign keys.
  1801. *
  1802. * @throws \Drupal\Core\Field\FieldException
  1803. * Exception thrown if the schema contains reserved column names.
  1804. *
  1805. * @see hook_schema()
  1806. */
  1807. protected function getDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition, ContentEntityTypeInterface $entity_type = NULL) {
  1808. $description_current = "Data storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}.";
  1809. $description_revision = "Revision archive storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}.";
  1810. $id_definition = $this->fieldStorageDefinitions[$this->entityType->getKey('id')];
  1811. if ($id_definition->getType() == 'integer') {
  1812. $id_schema = [
  1813. 'type' => 'int',
  1814. 'unsigned' => TRUE,
  1815. 'not null' => TRUE,
  1816. 'description' => 'The entity id this data is attached to',
  1817. ];
  1818. }
  1819. else {
  1820. $id_schema = [
  1821. 'type' => 'varchar_ascii',
  1822. 'length' => 128,
  1823. 'not null' => TRUE,
  1824. 'description' => 'The entity id this data is attached to',
  1825. ];
  1826. }
  1827. // Define the revision ID schema.
  1828. if (!$this->entityType->isRevisionable()) {
  1829. $revision_id_schema = $id_schema;
  1830. $revision_id_schema['description'] = 'The entity revision id this data is attached to, which for an unversioned entity type is the same as the entity id';
  1831. }
  1832. elseif ($this->fieldStorageDefinitions[$this->entityType->getKey('revision')]->getType() == 'integer') {
  1833. $revision_id_schema = [
  1834. 'type' => 'int',
  1835. 'unsigned' => TRUE,
  1836. 'not null' => TRUE,
  1837. 'description' => 'The entity revision id this data is attached to',
  1838. ];
  1839. }
  1840. else {
  1841. $revision_id_schema = [
  1842. 'type' => 'varchar',
  1843. 'length' => 128,
  1844. 'not null' => TRUE,
  1845. 'description' => 'The entity revision id this data is attached to',
  1846. ];
  1847. }
  1848. $data_schema = [
  1849. 'description' => $description_current,
  1850. 'fields' => [
  1851. 'bundle' => [
  1852. 'type' => 'varchar_ascii',
  1853. 'length' => 128,
  1854. 'not null' => TRUE,
  1855. 'default' => '',
  1856. 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
  1857. ],
  1858. 'deleted' => [
  1859. 'type' => 'int',
  1860. 'size' => 'tiny',
  1861. 'not null' => TRUE,
  1862. 'default' => 0,
  1863. 'description' => 'A boolean indicating whether this data item has been deleted'
  1864. ],
  1865. 'entity_id' => $id_schema,
  1866. 'revision_id' => $revision_id_schema,
  1867. 'langcode' => [
  1868. 'type' => 'varchar_ascii',
  1869. 'length' => 32,
  1870. 'not null' => TRUE,
  1871. 'default' => '',
  1872. 'description' => 'The language code for this data item.',
  1873. ],
  1874. 'delta' => [
  1875. 'type' => 'int',
  1876. 'unsigned' => TRUE,
  1877. 'not null' => TRUE,
  1878. 'description' => 'The sequence number for this data item, used for multi-value fields',
  1879. ],
  1880. ],
  1881. 'primary key' => ['entity_id', 'deleted', 'delta', 'langcode'],
  1882. 'indexes' => [
  1883. 'bundle' => ['bundle'],
  1884. 'revision_id' => ['revision_id'],
  1885. ],
  1886. ];
  1887. // Check that the schema does not include forbidden column names.
  1888. $schema = $storage_definition->getSchema();
  1889. $properties = $storage_definition->getPropertyDefinitions();
  1890. $table_mapping = $this->storage->getTableMapping();
  1891. if (array_intersect(array_keys($schema['columns']), $table_mapping->getReservedColumns())) {
  1892. throw new FieldException("Illegal field column names on {$storage_definition->getName()}");
  1893. }
  1894. // Add field columns.
  1895. foreach ($schema['columns'] as $column_name => $attributes) {
  1896. $real_name = $table_mapping->getFieldColumnName($storage_definition, $column_name);
  1897. $data_schema['fields'][$real_name] = $attributes;
  1898. // A dedicated table only contain rows for actual field values, and no
  1899. // rows for entities where the field is empty. Thus, we can safely
  1900. // enforce 'not null' on the columns for the field's required properties.
  1901. $data_schema['fields'][$real_name]['not null'] = $properties[$column_name]->isRequired();
  1902. }
  1903. // Add indexes.
  1904. foreach ($schema['indexes'] as $index_name => $columns) {
  1905. $real_name = $this->getFieldIndexName($storage_definition, $index_name);
  1906. foreach ($columns as $column_name) {
  1907. // Indexes can be specified as either a column name or an array with
  1908. // column name and length. Allow for either case.
  1909. if (is_array($column_name)) {
  1910. $data_schema['indexes'][$real_name][] = [
  1911. $table_mapping->getFieldColumnName($storage_definition, $column_name[0]),
  1912. $column_name[1],
  1913. ];
  1914. }
  1915. else {
  1916. $data_schema['indexes'][$real_name][] = $table_mapping->getFieldColumnName($storage_definition, $column_name);
  1917. }
  1918. }
  1919. }
  1920. // Add unique keys.
  1921. foreach ($schema['unique keys'] as $index_name => $columns) {
  1922. $real_name = $this->getFieldIndexName($storage_definition, $index_name);
  1923. foreach ($columns as $column_name) {
  1924. // Unique keys can be specified as either a column name or an array with
  1925. // column name and length. Allow for either case.
  1926. if (is_array($column_name)) {
  1927. $data_schema['unique keys'][$real_name][] = [
  1928. $table_mapping->getFieldColumnName($storage_definition, $column_name[0]),
  1929. $column_name[1],
  1930. ];
  1931. }
  1932. else {
  1933. $data_schema['unique keys'][$real_name][] = $table_mapping->getFieldColumnName($storage_definition, $column_name);
  1934. }
  1935. }
  1936. }
  1937. // Add foreign keys.
  1938. foreach ($schema['foreign keys'] as $specifier => $specification) {
  1939. $real_name = $this->getFieldIndexName($storage_definition, $specifier);
  1940. $data_schema['foreign keys'][$real_name]['table'] = $specification['table'];
  1941. foreach ($specification['columns'] as $column_name => $referenced) {
  1942. $sql_storage_column = $table_mapping->getFieldColumnName($storage_definition, $column_name);
  1943. $data_schema['foreign keys'][$real_name]['columns'][$sql_storage_column] = $referenced;
  1944. }
  1945. }
  1946. $dedicated_table_schema = [$table_mapping->getDedicatedDataTableName($storage_definition) => $data_schema];
  1947. // If the entity type is revisionable, construct the revision table.
  1948. $entity_type = $entity_type ?: $this->entityType;
  1949. if ($entity_type->isRevisionable()) {
  1950. $revision_schema = $data_schema;
  1951. $revision_schema['description'] = $description_revision;
  1952. $revision_schema['primary key'] = ['entity_id', 'revision_id', 'deleted', 'delta', 'langcode'];
  1953. $revision_schema['fields']['revision_id']['not null'] = TRUE;
  1954. $revision_schema['fields']['revision_id']['description'] = 'The entity revision id this data is attached to';
  1955. $dedicated_table_schema += [$table_mapping->getDedicatedRevisionTableName($storage_definition) => $revision_schema];
  1956. }
  1957. return $dedicated_table_schema;
  1958. }
  1959. /**
  1960. * Gets the name to be used for the given entity index.
  1961. *
  1962. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  1963. * The entity type.
  1964. * @param string $index
  1965. * The index column name.
  1966. *
  1967. * @return string
  1968. * The index name.
  1969. */
  1970. protected function getEntityIndexName(ContentEntityTypeInterface $entity_type, $index) {
  1971. return $entity_type->id() . '__' . $index;
  1972. }
  1973. /**
  1974. * Generates an index name for a field data table.
  1975. *
  1976. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1977. * The field storage definition.
  1978. * @param string $index
  1979. * The name of the index.
  1980. *
  1981. * @return string
  1982. * A string containing a generated index name for a field data table that is
  1983. * unique among all other fields.
  1984. */
  1985. protected function getFieldIndexName(FieldStorageDefinitionInterface $storage_definition, $index) {
  1986. return $storage_definition->getName() . '_' . $index;
  1987. }
  1988. /**
  1989. * Checks whether a database table is non-existent or empty.
  1990. *
  1991. * Empty tables can be dropped and recreated without data loss.
  1992. *
  1993. * @param string $table_name
  1994. * The database table to check.
  1995. *
  1996. * @return bool
  1997. * TRUE if the table is empty, FALSE otherwise.
  1998. */
  1999. protected function isTableEmpty($table_name) {
  2000. return !$this->database->schema()->tableExists($table_name) ||
  2001. !$this->database->select($table_name)
  2002. ->countQuery()
  2003. ->range(0, 1)
  2004. ->execute()
  2005. ->fetchField();
  2006. }
  2007. /**
  2008. * Compares schemas to check for changes in the column definitions.
  2009. *
  2010. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  2011. * Current field storage definition.
  2012. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
  2013. * The original field storage definition.
  2014. *
  2015. * @return bool
  2016. * Returns TRUE if there are schema changes in the column definitions.
  2017. */
  2018. protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
  2019. if ($storage_definition->getColumns() != $original->getColumns()) {
  2020. // Base field definitions have schema data stored in the original
  2021. // definition.
  2022. return TRUE;
  2023. }
  2024. if (!$storage_definition->hasCustomStorage()) {
  2025. $keys = array_flip($this->getColumnSchemaRelevantKeys());
  2026. $definition_schema = $this->getSchemaFromStorageDefinition($storage_definition);
  2027. foreach ($this->loadFieldSchemaData($original) as $table => $table_schema) {
  2028. foreach ($table_schema['fields'] as $name => $spec) {
  2029. $definition_spec = array_intersect_key($definition_schema[$table]['fields'][$name], $keys);
  2030. $stored_spec = array_intersect_key($spec, $keys);
  2031. if ($definition_spec != $stored_spec) {
  2032. return TRUE;
  2033. }
  2034. }
  2035. }
  2036. }
  2037. return FALSE;
  2038. }
  2039. /**
  2040. * Returns a list of column schema keys affecting data storage.
  2041. *
  2042. * When comparing schema definitions, only changes in certain properties
  2043. * actually affect how data is stored and thus, if applied, may imply data
  2044. * manipulation.
  2045. *
  2046. * @return string[]
  2047. * An array of key names.
  2048. */
  2049. protected function getColumnSchemaRelevantKeys() {
  2050. return ['type', 'size', 'length', 'unsigned'];
  2051. }
  2052. /**
  2053. * Creates an index, dropping it if already existing.
  2054. *
  2055. * @param string $table
  2056. * The table name.
  2057. * @param string $name
  2058. * The index name.
  2059. * @param array $specifier
  2060. * The fields to index.
  2061. * @param array $schema
  2062. * The table specification.
  2063. *
  2064. * @see \Drupal\Core\Database\Schema::addIndex()
  2065. */
  2066. protected function addIndex($table, $name, array $specifier, array $schema) {
  2067. $schema_handler = $this->database->schema();
  2068. $schema_handler->dropIndex($table, $name);
  2069. $schema_handler->addIndex($table, $name, $specifier, $schema);
  2070. }
  2071. /**
  2072. * Creates a unique key, dropping it if already existing.
  2073. *
  2074. * @param string $table
  2075. * The table name.
  2076. * @param string $name
  2077. * The index name.
  2078. * @param array $specifier
  2079. * The unique fields.
  2080. *
  2081. * @see \Drupal\Core\Database\Schema::addUniqueKey()
  2082. */
  2083. protected function addUniqueKey($table, $name, array $specifier) {
  2084. $schema_handler = $this->database->schema();
  2085. $schema_handler->dropUniqueKey($table, $name);
  2086. $schema_handler->addUniqueKey($table, $name, $specifier);
  2087. }
  2088. }