Move the on-demand-table creation into the database API

Created on 8 November 2014, about 10 years ago
Updated 21 March 2023, almost 2 years ago

Problem/Motivation

Menu tree storage, config and cache uses the same pattern: execute a query, catch an exception, try to create the table, if succeeded rerun the query. This is already wasteful and there's already demand #2338747: Move {router} out of system.install and create the table lazy → for it elsewhere.

Proposed resolution

  1. We add a schemaProvider interface with a single getSchema method that takes an optional table name argument.
  2. We add a per driver isTableMissingException method that checks an exception whether it means table is missing. Sucks PDO doesn't do that.
  3. Queries can specify $options = ['schema_provider' => $this] which is then used in order to create the table on the fly.
  4. query will catch the exceptions and call the isTableMissingException method (this can be disabled by an option). For selects it returns new FakeStatement([]);. insert and merge passes in a table name. For insert/update/merge ... it will create the passed in tables.

Note: This concept has limits, so for example tables which are used in entity queries/views, so ones which contain joins, are not supported yet.

Remaining tasks

User interface changes

API changes

  • Add Schema::ensureTableExists()
  • Queries, like db_select() etc. get $options = ['schema_provider' => $this]/li>

This is an API addition: schema gets ensureTableExists and query gets executeEnsuringTable -- so it should be good to go.

📌 Task
Status

Needs work

Version

10.1 ✨

Component
Database  →

Last updated 2 days ago

  • Maintained by
  • 🇳🇱Netherlands @daffie
Created by

🇨🇦Canada chx

Live updates comments and jobs are added and updated live.
  • Needs reroll

    The patch will have to be re-rolled with new suggestions/changes described in the comments in the issue.

Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • First commit to issue fork.
  • last update over 1 year ago
    Custom Commands Failed
  • @bhanu951 opened merge request.
  • 🇮🇳India bhanu951

    Tried to Re-roll patch from #120 📌 Move the on-demand-table creation into the database API Needs work to11. Branch.

  • Status changed to Needs review over 1 year ago
  • last update over 1 year ago
    Patch Failed to Apply
  • last update over 1 year ago
    Build Successful
  • Status changed to Needs work over 1 year ago
  • 🇺🇸United States smustgrave

    Reroll seemed to have test failures.

  • Pipeline finished with Failed
    30 days ago
    Total: 139s
    #346791
  • Pipeline finished with Failed
    30 days ago
    Total: 143s
    #346796
  • Pipeline finished with Failed
    30 days ago
    Total: 140s
    #346798
  • Pipeline finished with Failed
    30 days ago
    Total: 218s
    #346806
  • Pipeline finished with Failed
    30 days ago
    Total: 477s
    #346812
  • Pipeline finished with Failed
    30 days ago
    Total: 231s
    #346832
  • Pipeline finished with Failed
    30 days ago
    Total: 165s
    #346841
  • Pipeline finished with Failed
    30 days ago
    Total: 797s
    #346848
  • Pipeline finished with Failed
    30 days ago
    Total: 798s
    #346859
  • Pipeline finished with Success
    30 days ago
    Total: 549s
    #346875
  • Pipeline finished with Success
    30 days ago
    Total: 555s
    #346884
  • Pipeline finished with Success
    30 days ago
    Total: 1186s
    #346918
  • Pipeline finished with Success
    30 days ago
    Total: 1003s
    #346961
  • 🇳🇱Netherlands daffie

    I have updated the IS and created a CR.

  • 🇳🇱Netherlands daffie

    daffie → changed the visibility of the branch 2371709-move-the-on-demand-table to hidden.

  • 🇳🇱Netherlands daffie

    The Mr is ready for a review.

  • 🇺🇸United States nicxvan

    Oh I've had my eye on this it will make hook schema deprecation much nicer.

  • 🇫🇷France andypost

    added suggestion

  • Pipeline finished with Failed
    29 days ago
    Total: 211s
    #347745
  • Pipeline finished with Success
    29 days ago
    Total: 1325s
    #347763
  • 🇳🇱Netherlands daffie

    @andypost: Thank you for the review.

    Changing the class variable to a readonly variable and setting its value is not allowed in PHP. From the PHP documentation:

    Specifying an explicit default value on readonly properties is not allowed, because a readonly property with a default value is essentially the same as a constant, and thus not particularly useful.

    See: https://www.php.net/manual/en/language.oop5.properties.php

    I have changed the variables to be of the string type.

  • 🇬🇧United Kingdom catch

    Out of interest why could we not use constants for this case? The classes should be able to override those.

  • 🇳🇱Netherlands daffie

    @catch: I some cases it can be a constant in other cases it cannot. Like with Drupal\Core\Menu\MenuTreeStorage, Drupal\Core\Routing\MatcherDumper and Drupal\Core\KeyValueStore\DatabaseStorage.

  • 🇮🇹Italy mondrake 🇮🇹

    A bit late to the party... but why not to use events instead of a trait? ✨ Use events in Database Schema operations Active

    For example (conceptual, not tested):

    from

      /**
       * Returns a new batch id.
       *
       * @return int
       *   A batch id.
       */
      public function getId(): int {
        $try_again = FALSE;
        try {
          // The batch table might not yet exist.
          return $this->doInsertBatchRecord();
        }
        catch (\Exception $e) {
          // If there was an exception, try to create the table.
          if (!$try_again = $this->ensureTableExists()) {
            // If the exception happened for other reason than the missing table,
            // propagate the exception.
            throw $e;
          }
        }
        // Now that the table has been created, try again if necessary.
        if ($try_again) {
          return $this->doInsertBatchRecord();
        }
      }
    

    to

      /**
       * Returns a new batch id.
       *
       * @return int
       *   A batch id.
       */
      public function getId(): int {
        $event = new EnsureTableExistEvent(
          callable: [$this, 'doInsertBatchRecord'],
          table: static::TABLE_NAME,
          schema: $this->schemaDefinition(),
        );
        $this->connection->dispatchEvent($event);
        return $event->result();
      }
    

    OR

    from

      public function load($id) {
        // Ensure that a session is started before using the CSRF token generator.
        $this->session->start();
        try {
          $batch = $this->connection->select('batch', 'b')
            ->fields('b', ['batch'])
            ->condition('bid', $id)
            ->condition('token', $this->csrfToken->get($id))
            ->execute()
            ->fetchField();
        }
        catch (\Exception $e) {
          $this->catchException($e);
          $batch = FALSE;
        }
        if ($batch) {
          return unserialize($batch);
        }
        return FALSE;
      }
    

    to

      public function load($id) {
        // Ensure that a session is started before using the CSRF token generator.
        $this->session->start();
        try {
          $batch = $this->connection->select('batch', 'b')
            ->fields('b', ['batch'])
            ->condition('bid', $id)
            ->condition('token', $this->csrfToken->get($id))
            ->execute()
            ->fetchField();
        }
        catch (\Exception $e) {
          $event = new TablePossiblyMissingEvent(
            exception: $e,
            table: static::TABLE_NAME,
            schema: $this->schemaDefinition(),
          );
          $this->connection->dispatchEvent($event);
          $batch = FALSE;
        }
        if ($batch) {
          return unserialize($batch);
        }
        return FALSE;
      }
    
  • Merge request !10397alternative approach → (Open) created by mondrake
  • 🇮🇹Italy mondrake 🇮🇹

    A PoC for #163 in MR!10397.

  • Pipeline finished with Failed
    23 days ago
    Total: 142s
    #354187
  • Pipeline finished with Success
    23 days ago
    Total: 597s
    #354190
  • Pipeline finished with Success
    22 days ago
    Total: 551s
    #354308
  • 🇮🇹Italy mondrake 🇮🇹

    Hm... looking at the MR now, I start figuring out we could simplify further, and let the dynamic query layer handle this:

      public function getId(): int {
            return $this->connection->insert('batch')
              ->fields([
                'timestamp' => $this->time->getRequestTime(),
                'token' => '',
                'batch' => NULL,
              ])
              ->onFailureEnsureSchemaAndRetry([static::TABLE_NAME => $this->schemaDefinition()])
              ->execute();
      }
    
      public function delete($id) {
          $this->connection->delete('batch')
            ->condition('bid', $id)
            ->onFailureEnsureSchema([static::TABLE_NAME => $this->schemaDefinition()])
            ->execute();
      }
    
    
  • 🇬🇧United Kingdom catch

    #163-#166 are incredibly concise so if we can make those work, that is very tempting.

  • 🇮🇹Italy mondrake 🇮🇹

    #166 would have BC concerns (db drivers would have to match the behavior of the onFailureEnsureSchema() method on all classes extending from Query), so maybe for now we can settle on a proxy of that.

  • 🇮🇹Italy mondrake 🇮🇹

    mondrake → changed the visibility of the branch 2371709-use-events-II to hidden.

  • Merge request !10403Closes #2371709 → (Open) created by mondrake
  • Pipeline finished with Failed
    22 days ago
    #354684
  • Pipeline finished with Failed
    22 days ago
    Total: 740s
    #354691
  • Pipeline finished with Canceled
    22 days ago
    Total: 3190s
    #355101
  • Pipeline finished with Canceled
    22 days ago
    #355126
  • Pipeline finished with Failed
    21 days ago
    Total: 663s
    #355140
  • Pipeline finished with Failed
    21 days ago
    Total: 146s
    #355152
  • Pipeline finished with Failed
    21 days ago
    Total: 231s
    #355156
  • Pipeline finished with Failed
    21 days ago
    Total: 217s
    #355158
  • Pipeline finished with Failed
    21 days ago
    Total: 637s
    #355494
  • Pipeline finished with Failed
    21 days ago
    Total: 141s
    #355513
  • Pipeline finished with Failed
    21 days ago
    #355514
  • Pipeline finished with Failed
    21 days ago
    Total: 2023s
    #355565
  • Pipeline finished with Success
    20 days ago
    Total: 692s
    #355701
  • Pipeline finished with Success
    20 days ago
    Total: 710s
    #355728
  • Pipeline finished with Success
    20 days ago
    Total: 714s
    #355777
  • Pipeline finished with Failed
    20 days ago
    Total: 140s
    #356192
  • Pipeline finished with Failed
    20 days ago
    Total: 141s
    #356286
  • Pipeline finished with Failed
    20 days ago
    Total: 186s
    #356295
  • Pipeline finished with Failed
    20 days ago
    Total: 124s
    #356298
  • Pipeline finished with Success
    20 days ago
    Total: 1014s
    #356305
  • Pipeline finished with Failed
    19 days ago
    Total: 198s
    #356697
  • Pipeline finished with Success
    19 days ago
    Total: 1772s
    #356703
  • 🇮🇹Italy mondrake 🇮🇹

    MR!10403 is for review. That includes the framework and some conversion, but not all of them. Once/if the framework is agreed, we can easy complete the remaining ones.

    An interesting next step would be to pass the schema as a value object instead of an array, but there are other issues for that.

  • 🇳🇱Netherlands daffie
  • Pipeline finished with Success
    18 days ago
    Total: 954s
    #357617
  • 🇮🇹Italy mondrake 🇮🇹

    mondrake → changed the visibility of the branch 2371709-lazy-table-creation-trait to hidden.

  • 🇮🇹Italy mondrake 🇮🇹

    Thanks for review @daffie! Let's continue with the new approach then. Before completing the conversions: add tests for Connection::executeEnsuringSchemaOnFailure().

  • Pipeline finished with Failed
    18 days ago
    Total: 122s
    #357806
  • Pipeline finished with Failed
    18 days ago
    Total: 495s
    #357816
  • Pipeline finished with Failed
    18 days ago
    Total: 123s
    #358181
  • Pipeline finished with Success
    18 days ago
    #358213
  • Pipeline finished with Failed
    18 days ago
    Total: 583s
    #358660
  • Pipeline finished with Failed
    18 days ago
    Total: 548s
    #358870
  • Pipeline finished with Failed
    17 days ago
    Total: 149s
    #359572
  • Pipeline finished with Failed
    17 days ago
    Total: 730s
    #359596
  • Pipeline finished with Failed
    17 days ago
    Total: 529s
    #360094
  • Pipeline finished with Failed
    17 days ago
    Total: 314s
    #360152
  • Pipeline finished with Failed
    16 days ago
    Total: 167s
    #360268
  • Pipeline finished with Failed
    16 days ago
    Total: 140s
    #360346
  • Pipeline finished with Canceled
    16 days ago
    Total: 515s
    #360399
  • Pipeline finished with Failed
    16 days ago
    Total: 621s
    #360415
  • Pipeline finished with Failed
    16 days ago
    Total: 528s
    #360445
  • Pipeline finished with Failed
    16 days ago
    Total: 500s
    #360585
  • Pipeline finished with Failed
    16 days ago
    Total: 817s
    #360594
  • Pipeline finished with Failed
    16 days ago
    Total: 615s
    #360625
  • Pipeline finished with Failed
    16 days ago
    Total: 598s
    #361116
  • Pipeline finished with Failed
    16 days ago
    Total: 163s
    #361142
  • Pipeline finished with Failed
    16 days ago
    Total: 1804s
    #361152
  • Pipeline finished with Failed
    15 days ago
    Total: 723s
    #361285
  • Pipeline finished with Failed
    15 days ago
    Total: 743s
    #361292
  • Pipeline finished with Failed
    15 days ago
    Total: 4067s
    #361313
  • Pipeline finished with Failed
    15 days ago
    Total: 142s
    #361403
  • Pipeline finished with Failed
    15 days ago
    Total: 144s
    #361423
  • Pipeline finished with Failed
    15 days ago
    Total: 524s
    #361434
  • Pipeline finished with Success
    15 days ago
    Total: 1089s
    #361436
  • Pipeline finished with Success
    15 days ago
    Total: 3974s
    #361481
  • Pipeline finished with Failed
    15 days ago
    Total: 676s
    #361790
  • Pipeline finished with Success
    15 days ago
    Total: 1092s
    #361842
  • 🇮🇹Italy mondrake 🇮🇹

    MR!10403 is now doing #163, deprecates/converts all uses of ::ensureTableExists() and ::catchException(), and is green on all databases. A review at this stage would be helpful.

    #166 is just a piece of cake away with this underlying layer, so I will push that forward.

  • Pipeline finished with Success
    15 days ago
    Total: 718s
    #361873
  • 🇮🇹Italy mondrake 🇮🇹

    Now Flood\DatabaseBackend implemenrs #166, and if this is OK we need to convert the rest where possible (i.e. calls to dynamic queries extending Query).

    Pausing now to let reviewing happen.

  • 🇮🇹Italy mondrake 🇮🇹

    Filed 📌 Explore injecting the event dispatcher in the database service Active as a possible follow up.

  • 🇳🇱Netherlands daffie

    @mondrake: It looks like a beautiful solution. My problem is that the solution will not work with MongoDB. The solution relies on that the database will throw an exception when the table does not exists. However that is not what MongoDB does. MongoDB just creates a table when one does not exists on an insert. A table in MongoDB is called a collection. It will be a table with no validation added to make sure that it only allows data in the right form being added. See: https://www.mongodb.com/docs/manual/reference/method/db.collection.inser....

    The reason I started to work on this issue was to add a hook to allow the database driver to change the table schema. The database driver for MongoDB stores timestamps as MongoDB\BSON\UTCDateTime instead of an integer value as is done by the by core supported databases.

    If we could make it to work for MongoDB that would be great. I will need some time to think how I can make it to work with MongoDB. If you have some ideas or suggestions, then please speak up.

  • 🇳🇱Netherlands daffie

    Wrongly assigned to issue to @acbramley

  • 🇮🇹Italy mondrake 🇮🇹

    @daffie well, that's why I suggest using events, that gives us the possibility to extend/override how they are processed. At least conceptually, the MongoDB driver could implement a subscriber/listener for the ExecuteMethodEnsuringSchemaEvent, make it so that it processes the event before core's SchemaRequestSubscriber does, do its alternative processing of the schema request, and stop propagation of the event so that core no longer processes it afterwards. Of course, needs to be proven in practice.

  • 🇳🇱Netherlands daffie
  • 🇮🇹Italy mondrake 🇮🇹
  • Pipeline finished with Failed
    6 days ago
    Total: 137s
    #369190
  • Pipeline finished with Failed
    6 days ago
    Total: 531s
    #369199
Production build 0.71.5 2024