🇮🇹
Account created on 7 May 2011, over 13 years ago
#

Merge Requests

More

Recent comments

🇮🇹Italy mondrake 🇮🇹

The more I look at this, the more I think we should build on top of 📌 Move the on-demand-table creation into the database API Needs work , and introduce a generic method that takes care of making db calls 'safe' from different perspectives.

For example, what we have in that issue at the moment is a ::executeEnsuringSchemaOnFailure that in case of failure of a db operation, tries to build missing tables and retries the db operation:

    $execution = $this->connection->executeEnsuringSchemaOnFailure(
      execute: function (): int {
        return do_something();
      },
      schema: $schemaDefinition,
      retryAfterSchemaEnsured: TRUE,
    );

we could make that more generic:

    $execution = $this->connection->safelyExecute(
      execute: function (): int {
        return do_something();
      },
      withSchema: $schemaDefinition,
      withReconnection: TRUE,
      retryOnFailure: TRUE,
    );
🇮🇹Italy mondrake 🇮🇹

The motivation is clear and sensible, +1.

However, pinging with a query anytime the client collection is fetched means a lot of db roundtrips, and as such kill performance - if I am not mistaken, getClientConnection() is called anytime a transaction is opened. BTW, that would also mean that non-transactional statement executions will not be covered.

I think it would be preferable to store the client connection state in the Connection object, being 'active' at opening, then through exception handling change its state to 'connection lost' when an issue occurs. Code executing calls to the db could then check that state before executing and ping/close/reconnect in case. A challenge for sure would be how to determine a clear connection lost exception vs other exceptions.

Another aspect to take care of is understanding the implications of a reconnect occurring while a db transaction is open - is the db transaction rolled back, how will the db failure reflect in the transaction manager, etc.

On the positive side, we could think of building on the experience from 📌 Move the on-demand-table creation into the database API Needs work and think of a method that could embed a retry of executing a statement, i.e. in case of failure for disconnect, reconnect and retry.

Finally - MySql (and possibly PgSql) would lose the connection, but what should be the behavior for Sqlite? It also can fail if the file is not in the same filesystem of the web server.

🇮🇹Italy mondrake 🇮🇹

@nicxvan thanks, I like where MR 10607 is heading, especially in the part where the early subscribers to be registered with the early dispatcher are moved to the database API.

However, this is creating an (early) event dispatcher that is attached to the Connection class instead of being unique across the request. Not a big deal for now, right, but what if in the future more code would need to dispatch events early? Cache for instance may want to dispatch an event to be responded by a database listener (again, not the case know, but to prepare for possible future). I'd rather keep a singleton somewhere outside of the Connection class, so that we do not end up with vertical dispatchers not talking to each other. That's an architecture topic, tagging so we can have a review and guidance.

Then, I think we should use a closure also to register the early subscribers in place of the const array - so to allow passing instantiated objects that may require parameters (atm in both MRs the assumption is that the subscribers have no parameters in the construction).

🇮🇹Italy mondrake 🇮🇹

Tests are failing.

🇮🇹Italy mondrake 🇮🇹

Thanks all!

Please do not post patch files: this project only accepts MRs.

Automated tests are failing, so this issue needs work to solve the failures.

🇮🇹Italy mondrake 🇮🇹

Green on all three core supported databases! Ready for review.

I am bumping to Major as it's known there are problems with the current handling of autocommit in HEAD.

🇮🇹Italy mondrake 🇮🇹

The PHPStan job is failing, because the new return type introduced makes the added entry on the PHPStan baseline no longer needed. Needs to remove that entry (or regenerate the baseline).

🇮🇹Italy mondrake 🇮🇹

@daffie thanks, if you do not mind I'd leave this to NR a bit longer to see if more reviews/input come. I will definitely do the CR and IS updates when we have settled on the final solution.

@nicxvan I am not sure I understand; feel free to open another issue branch and MR if you want to propose an alternative!

🇮🇹Italy mondrake 🇮🇹

Thanks for review @daffie!

#22 would be nice, but I do not see it as a blocker.

Addressed the comments.

🇮🇹Italy mondrake 🇮🇹
🇮🇹Italy mondrake 🇮🇹

Looks great to me, thanks for hearing my question. I triggered Sqlite and PgSql pipelines for safety, even though I do not think they are relevant here. RTBC

🇮🇹Italy mondrake 🇮🇹

Looking at the motivation, I am not sure about this. The file must have a size, but we cannot get it so we hide the problem. BTW possibly the image cannot be loaded by GD either. IMHO the right solution here would be to download the file from S3 to local storage, get its stats and use it for manipulation. IIRC the ImageMagick toolkit does something similar since it does not support stream wrappers.

🇮🇹Italy mondrake 🇮🇹

mondrake created an issue.

🇮🇹Italy mondrake 🇮🇹

Question: can't we use something from Drupal\core_fake\Driver\Database\CoreFakeWithAllCustomClasses instead of adding a completely new dummy db and all of its minimal implementations?

Anyway, the above should not hold this. RTBC

🇮🇹Italy mondrake 🇮🇹

📌 Allow parsing and writing PHP constants and enums in YAML files Needs work would help, we could pass an enum parameter in the service definition file, which we cannot presently.

🇮🇹Italy mondrake 🇮🇹

Added 2 CR stubs, one [#3494043] for the generic availability of the event dispatcher before container bootstrap, and one [#3494044] for its usage in Database::Connection. Will complete at later stage, doing at this stage has an high risk of getting stale information in it while reviews and tuning still needs to happen.

🇮🇹Italy mondrake 🇮🇹

Just noticed the CR links redirect to this issue and not to a CR. A CR for this is actually missing.

🇮🇹Italy mondrake 🇮🇹

Let’s see if we can make one more step and make the factory a decorator of the event dispatcher.

🇮🇹Italy mondrake 🇮🇹

@nicxvan re #5 this is about the event dispatcher itself, not about the subscribers.

🇮🇹Italy mondrake 🇮🇹

My 2 cents:

- minor is important for knowing exactly since when new code should be used, for the reasons explained ^^. Patch is not relevant here.
- major is enough for removal

so I'd suggest

@deprecated in drupal:11.1 and is removed from drupal:12. %extra-info%.
@see %cr-link%
🇮🇹Italy mondrake 🇮🇹

mondrake changed the visibility of the branch 3492391-explore-injecting-the to hidden.

🇮🇹Italy mondrake 🇮🇹

Ooops no, there's no 4.0.x branch... :)

🇮🇹Italy mondrake 🇮🇹

Thanks! Does this need to be applied to 4.0.x too?

🇮🇹Italy mondrake 🇮🇹

Thank you! We need to fix the phpdocs, too.

🇮🇹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.

🇮🇹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 🇮🇹

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.

🇮🇹Italy mondrake 🇮🇹

#24 is above me, but to me it sounds like it can be a follow up anyway.

🇮🇹Italy mondrake 🇮🇹

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

🇮🇹Italy mondrake 🇮🇹

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

🇮🇹Italy mondrake 🇮🇹

I think this should go in the upcoming point releases - it fixes a bug introduced by a MR not released yet in 10.4 and 11.1, and the bug leads to data corruption.

🇮🇹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.

🇮🇹Italy mondrake 🇮🇹

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

🇮🇹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 🇮🇹

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();
  }

🇮🇹Italy mondrake 🇮🇹

This is very nice regardless of MongoDb in its enforcing proper dependencies of actions.

Is it possible to add a test for the new subscriber? More comments inline in the MR.

🇮🇹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;
  }
🇮🇹Italy mondrake 🇮🇹

Let's add deprecation for StatementPrefetchIterator::fetchColumn. On it.

🇮🇹Italy mondrake 🇮🇹

Rebased after commit of 🐛 StatementPrefetchIterator::fetchField fails silently if the column index is out of range Active . I swapped responsability between ::fetchColumn() and ::fetchField() in StatementPrefetchIterator, very minor but I think this requires to be set back to NR. Sorry.

🇮🇹Italy mondrake 🇮🇹

rebased, still green.

@daffie this test will work on a vanilla install; if you have custom/contrib modules, it could very well be that there are tests that PHPUnit cannot discover (see the IS point 2). In any case, could you rather share PHPUnit's output than the dump?

🇮🇹Italy mondrake 🇮🇹

Added 📌 Check StatementPrefetchIterator::fetchCol after strenghtening of ::fetchField Active as a followup for #16, and replied inline referencing existing issues to deprecate StatementPrefetchIterator::fetchField.

🇮🇹Italy mondrake 🇮🇹

@daffie have you checked your local phpunit.xml vs the one in the MR?

🇮🇹Italy mondrake 🇮🇹

Ok, so empty resultset prevails on missing column index in PDO, and prefetch works the same. I think we are covered now.

🇮🇹Italy mondrake 🇮🇹

Interestingly, trying to insert FALSE in a nullable database field behaves differently between MySql and SQLite - MySql fails hard at insert, SQLite inserts something that is returned as an empty string when reading back.

Working on a test case that can be db abstract.

🇮🇹Italy mondrake 🇮🇹

TL;DR

  • returning a string -> valid value
  • returning FALSE -> no more rows in resultset
  • throw exception -> invalid column index

Full story:

fetchField() is historically an alias of \PDOStatement::fetchColumn() - it is so since #225450: Database Layer: The Next Generation .

So IMHO we should make a non-PDO implementation behave as closely as possible to its original.

Now, \PDOStatement::fetchColumn() documentation says (emphasis mine)

Returns a single column from the next row of a result set or false if there are no more rows.

returning false is btw a potential issue, as the same documentation makes clear:

... fetchColumn() should not be used to retrieve boolean columns, as it is impossible to distinguish a value of false from there being no more rows to retrieve

However, this is mitigated in Drupal by the fact that all values returned from the statement are strings, by setting \PDO::ATTR_STRINGIFY_FETCHES in the connection options.

So, the fetch will return a PHP string if a value is available (independently from its db representation), and a PHP bool === false if no rows are available.

If the required column index is out of range, \PDOStatement::fetchColumn() throws a \ValueError (it's not documented, but the test here proves that). This makes sense because if we were returning a bool false instead, we would be again unable to distinguish between no more rows and no column defined, which are very different situations - no more rows is a legitimate runtime state, whereas a wrong column index means most likely the query was built wrong. I think in the emulated fetch of the prefetched data we should do the same.

🇮🇹Italy mondrake 🇮🇹

@daffie MySql and PostgresSql are throwing \ValueError today, so the 3 core drivers behave differently from each other ATM.

It seemed to me that aligning SQLite would be the best option given that anyway it's more unlikely to be used in production.

🇮🇹Italy mondrake 🇮🇹

By the time of this, PHPUnit will likely be on its version 13, and close to 14 - if they keep the cycle of 1 major per year.

🇮🇹Italy mondrake 🇮🇹

#27 when there will be a PHP 8,5 docker image available, we can easily add it to the job matrix here, allowing failures. That will help showing deprecations for 8.5 while keeping the entire pipeline green.

🇮🇹Italy mondrake 🇮🇹

Found a glitch while working on the follow up.

Production build 0.71.5 2024