Problem/Motivation

TL;DR The problem occurs when the mapping table name has either a hyphen in the name or a name that has only digits or a name that has a digit at the start and an "e".

@joachim noticed this problem 2.5 years ago in #2986452-79: Database reserved keywords need to be quoted as per the ANSI standard.

He explained it in more detail in subsequent comments.

It was suggested in #2986452-83: Database reserved keywords need to be quoted as per the ANSI standard that the problem was the hyphen in the database name, for which #2767595: Support "-" in database table names existed. But that's not the case. The problem is simply that the necessary quoting of identifiers is missing.

I traced the problem further over at #3218978-11: MySQL driver allows settings.php to remove ANSI_QUOTES from sql_mode, but doesn't work when it is, quoting in full here:

Seems like this problem is even worse when database or tables names consists solely of digits:

Certain objects within MySQL, including database, table, index, column, alias, view, stored procedure, partition, tablespace, resource group and other object names are known as identifiers. This section describes the permissible syntax for identifiers in MySQL.

[…]

Identifiers may begin with a digit but unless quoted may not consist solely of digits.

[…]

The identifier quote character is the backtick (`):

[…]

If the ANSI_QUOTES SQL mode is enabled, it is also permissible to quote identifiers within double quotation marks:

— https://dev.mysql.com/doc/refman/8.0/en/identifiers.html

IOW: On MySQL, you should always quote identifiers, either using " when ANSI_QUOTES is on, otherwise using `.

That makes this at least major IMHO, bordering on critical. Any site that turns off ANSI_QUOTES which uses a fully numerical database name will get a SQL error, and fail to work.


Worse, counter to the docs, I can confirm that even database names that just start with a digit reproduce this behavior:

  1. SELECT * FROM d8.users u INNER JOIN 101.foo f ON f.id = u.uid
    

    ⇒ You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '101.foo f ON f.id = u.uid' at line 1

  2. SELECT * FROM d8.users u INNER JOIN 73829e0c4090444f85fbae2e92f8925a.foo f ON f.id = u.uid
    

    ⇒ You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '73829e0c4090444f85fbae2e92f8925a.foo f ON f.id = u.uid' at line 1

  3. Turns out this is because there's an e (the letter "e") in the database name, which makes MySQL interpret it as a number in exponential notation! 🤪🤯🤯🤯🤯🤯🤯🤯🤯🤯🤯 Check yourself:
    SELECT * FROM d8.users u INNER JOIN 1e0.foo f ON f.id = u.uid
    

    ⇒ You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '1e0.foo f ON f.id = u.uid' at line 1
    … yet 1a0 works fine!


To reproduce:

  1. create three database names above
  2. create an empty foo table like so in each:
    CREATE TABLE `foo` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    

⚠️ Databases named using only digits (unlikely) or named with a hash (kinda likely) that happen to contain the letter "e" (unlikely) will fail hard! ⚠️

So this problem is not the hyphen, the problem is much simpler:

  1. all tests in Drupal core only use simple database names that do not use database or table names that need escaping in the first place
  2. the necessary quoting of "identifiers" is obviously missing

This is very clear in @joachim's #2986452-86: Database reserved keywords need to be quoted as per the ANSI standard, where he cites this:

SELECT m.id AS id, m.title AS title, m.changed AS changed, map.sourceid1 AS migrate_map_sourceid1, map.source_row_status AS migrate_map_source_row_status
FROM
{high_water_node} m
LEFT OUTER JOIN drupalaccounts8.test55734627migrate_map_high_water_test map ON id = map.sourceid1
WHERE (map.sourceid1 IS NULL) OR (map.source_row_status = :db_condition_placeholder_0) OR (changed > :db_condition_placeholder_1)
GROUP BY id, title, changed, map.sourceid1, map.source_row_status
ORDER BY changed ASC

(before that patch)
and

SELECT "m"."id" AS "id", "m"."title" AS "title", "m"."changed" AS "changed", "map"."sourceid1" AS "migrate_map_sourceid1", "map"."source_row_status" AS "migrate_map_source_row_status"
FROM
{high_water_node} "m"
LEFT OUTER JOIN drupalaccounts8.test77520843migrate_map_high_water_test "map" ON id = map.sourceid1
WHERE ("map"."sourceid1" IS NULL) OR ("map"."source_row_status" = :db_condition_placeholder_0) OR ("changed" > :db_condition_placeholder_1)
GROUP BY id, title, changed, map.sourceid1, map.source_row_status
ORDER BY "changed" ASC

A proper follow-up was never filed. This is then that follow-up.

Any migration that stores the mapping table in a database with either a hyphen in the name or a name that has only digits or a name that has a digit at the start and an "e" somewhere will encounter this problem, both before and after #2986452. But since #2986452, the ANSI mode being turned on causes it to fail 100% of the time.

Steps to reproduce

See above. Easier to see:

Do the following on a new DB called d9-destination (dash: not allowed):

-- Simulate users 0 and 1 have been created as part of the default Drupal 9 installation.
CREATE TABLE `users` (
  `uid` int(10) unsigned NOT NULL,
  `uuid` varchar(128) CHARACTER SET ascii NOT NULL,
  `langcode` varchar(12) CHARACTER SET ascii NOT NULL,
  PRIMARY KEY (`uid`),
  UNIQUE KEY `user_field__uuid__value` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='The base table for user entities.';
INSERT INTO `users` (`uid`, `uuid`, `langcode`)
VALUES
	(0,'f214b7ad-94fb-4202-bd68-a6d4d1119c27','en'),
	(1,'a500cd13-6aac-485d-8454-14e035b4b61e','en');

-- Simulate the migration system being active; but no migrations having run yet.
CREATE TABLE `migrate_map_d7_users` (
  `source_ids_hash` varchar(64) NOT NULL COMMENT 'Hash of source ids. Used as primary key',
  `sourceid1` int(11) NOT NULL,
  `destid1` int(10) unsigned DEFAULT NULL,
  `source_row_status` tinyint(3) unsigned NOT NULL DEFAULT 0 COMMENT 'Indicates current status of the source row',
  `rollback_action` tinyint(3) unsigned NOT NULL DEFAULT 0 COMMENT 'Flag indicating what to do for this item on rollback',
  `last_imported` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'UNIX timestamp of the last time this row was imported',
  `hash` varchar(64) DEFAULT NULL COMMENT 'Hash of source row data, for detecting changes',
  PRIMARY KEY (`source_ids_hash`),
  KEY `source` (`sourceid1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Mappings from source identifier value(s) to destination…';

-- Match \Drupal\Core\Database\Driver\mysql\Connection::open()
SET SQL_MODE='ANSI,TRADITIONAL';

-- Use the DB query Drupal 9 HEAD generates (but simplified slightly, to focus on the problem at hand).
SELECT "m"."uid" AS "id", "map"."sourceid1" AS "migrate_map_sourceid1", "map"."source_row_status" AS "migrate_map_source_row_status"
FROM
users "m"
LEFT OUTER JOIN d9-destination.migrate_map_d7_users "map" ON m.uid = map.sourceid1
WHERE ("map"."sourceid1" IS NULL) OR ("map"."source_row_status" = 1);

-- Result: ❌ You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '-destination.migrate_map_d7_users "map" ON m.uid = map.sourceid1
WHERE ("map"."s' at line 4

-- Use the DB query that is generated with the patch in #3 applied
SELECT "m"."uid" AS "id", "map"."sourceid1" AS "migrate_map_sourceid1", "map"."source_row_status" AS "migrate_map_source_row_status"
FROM
users "m"
LEFT OUTER JOIN "d9-destination"."migrate_map_d7_users" "map" ON "m"."uid" = "map"."sourceid1"
WHERE ("map"."sourceid1" IS NULL) OR ("map"."source_row_status" = 1)

-- Result: `1 NULL NULL` and `0 NULL NULL`, which is expected ✅

Rename the database from d9-destination to 1a1 and update the queries. Both queries will now work.

Rename the database from 1a1 to 1e1 and update the queries. Now once again only the second query will work.

Proposed resolution

Ensure cross-database joins are correctly escaped.

.

Remaining tasks

Review

User interface changes

None.

API changes

TBD

Data model changes

None.

Release notes snippet

TBD

Issue fork drupal-3227361

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Issue tags: +Needs tests
Related issues: +#2125717: Migrate in core: patch #1

While #2986452: Database reserved keywords need to be quoted as per the ANSI standard made the problem worse, the real problems are:

  1. \Drupal\migrate\Plugin\migrate\source\SqlBase::initializeIterator() has been doing this incorrectly since it was committed to Drupal core on November 11, 2013, in #2125717: Migrate in core: patch #1.
  2. There's no proper test coverage for this. (If there was, this would've failed a long time ago, and most certainly in #2986452: Database reserved keywords need to be quoted as per the ANSI standard.)
wim leers’s picture

Status: Active » Needs review
StatusFileSize
new1.69 KB

Wim Leers credited joachim.

wim leers’s picture

Crediting @joachim for his super valuable comments in #2986452: Database reserved keywords need to be quoted as per the ANSI standard.

wim leers’s picture

Issue summary: View changes

Massive revamp for the issue summary to make the problem very easy to reproduce locally. Still needs tests here though.

wim leers’s picture

Issue summary: View changes

Clarify STR further.

wim leers’s picture

+++ b/core/lib/Drupal/Core/Database/Query/Select.php
@@ -846,6 +846,11 @@ public function __toString() {
+        // But do ensure identifiers (databases and table names) are escaped.
+        else {
+          [$db_name, $table_name] = explode('.', $table_string, 2);
+          $table_string = "[$db_name].[$table_name]";
+        }

Note that we do not need prefixing anymore because
\Drupal\Core\Database\Connection::getFullQualifiedTableName() already returns the prefixed table name:

  public function getFullQualifiedTableName($table) {
    $options = $this->getConnectionOptions();
    $prefix = $this->tablePrefix($table);
    return $options['database'] . '.' . $prefix . $table;
  }

(If that weren't the case, we would need to iterate over all known connections, try to find the matching database name, verify it is unique, and then automatically associate that prefix. But that would not be reliable, because the same database could have multiple connections with a different prefix each. Hence the migration system's use of \Drupal\Core\Database\Connection::getFullQualifiedTableName() is correct and necessary. And hence the simplicity of this patch.

wim leers’s picture

Title: Follow-up for #2986452: fix \Drupal\migrate\Plugin\migrate\source\SqlBase::initializeIterator()'s cross-database joining broken when using ANSI mode (the new default) » Fix \Drupal\migrate\Plugin\migrate\source\SqlBase::initializeIterator()'s cross-database joining broken when using particular DB/table names

I'm actually not sure this is true:

But since #2986452, the ANSI mode being turned on causes it to fail 100% of the time.

… it would seem that this has actually been a problem since day 1 in all of these cases. 😨 The ANSI mode was a red herring it seems.

With ANSI mode being enabled, the problem just becomes much more obvious. 👍

Status: Needs review » Needs work

The last submitted patch, 3: 3227361-3.patch, failed testing. View results

wim leers’s picture

I think we might be able to copy the explicit test coverage pattern in \Drupal\KernelTests\Core\Entity\EntityQueryTest::testToString().

Although obviously this is currently causing lots of failures 🤓

mcdruid’s picture

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new911 bytes
new1.68 KB

Got slightly over eager 😅

wim leers’s picture

#12: Yes, this is basically the same root cause. It is that very bug in the Drupal 7 migrate system that was committed to Drupal 8 too in #2125717: Migrate in core: patch #1…

wim leers’s picture

Tested in production. Works great 👍

wim leers’s picture

So, let's try to figure out how/why this bug was introduced 🕵️‍♂️

+++ b/core/lib/Drupal/Core/Database/Query/Select.php
@@ -846,6 +846,11 @@ public function __toString() {
         if (strpos($table_string, '.') === FALSE) {
           $table_string = '{' . $table_string . '}';
         }
+        // But do ensure identifiers (databases and table names) are escaped.
+        else {
+          [$db_name, $table_name] = explode('.', $table_string, 2);
+          $table_string = "[$db_name].[$table_name]";
+        }

The need for this addition was introduced in #2180539: Do not attempt prefixing cross database / schema queries., which added that if-statement above it.

That issue was actually opened specifically based on the migration system, i.e. specifically for the problem we're facing here! We're going in circles 🤪

The difference is that back then, correct escaping (quoting) was less important than it is now (hold on, you'll see why).

And so, that explains this decision/proposed resolution:

Trust the caller to prefix the table name as necessary for that database.

Which is still true after this patch: we cannot prefix another database's tables: we don't know enough about it. Only database connections defined in Drupal's settings.php can allow us to know the prefix. Drupal could not ever automatically do this.

Never trust the caller with security.

Always a good idea! 👍

And this is fine: escapeTable is already keeping the dots.

This is the problematic part. At least in a world where #2986452: Database reserved keywords need to be quoted as per the ANSI standard was introduced.

Yes, the dots are kept. But since #2986452, all identifiers need to be quoted. And a database name and table name are both distinct identifiers. That's how #2180539 + #2986452 caused this together AFAICT 🤯

👉 This also means we really do need both changes in this patch AFAICT:

  1. first we need to ensure the migration system marks identifiers in the query appropriately
  2. next we need to ensure that cross-database-tables have the necessary identifiers. Even if we were to update \Drupal\Core\Database\Connection::getFullQualifiedTableName() to add the necessary square brackets, then \Drupal\Core\Database\Query\Select::__toString()'s indiscriminate call to ::escapeTable() would strip those square brackets away … which is why this patch is explicitly restoring them in cross-database case.
wim leers’s picture

daffie’s picture

Status: Needs review » Needs work
+++ b/core/lib/Drupal/Core/Database/Query/Select.php
@@ -846,6 +846,11 @@ public function __toString() {
+          [$db_name, $table_name] = explode('.', $table_string, 2);
+          $table_string = "[$db_name].[$table_name]";

With this construction the table name will no longer be prefixed. As is done in the if part with $table_string = '{' . $table_string . '}';.

daffie’s picture

  1. +++ b/core/lib/Drupal/Core/Database/Query/Select.php
    @@ -846,6 +846,11 @@ public function __toString() {
    +          $table_string = "[$db_name].[$table_name]";
    

    Why do we not do:

    $table_string = $this->connection->escapeDatabase($db_name) . '.{' . $table_name . '}';
    
  2. +++ b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
    @@ -288,9 +288,12 @@ protected function initializeIterator() {
    +            $field_name = '[' . $field_schema['alias'] . '].' . $this->query->escapeField($field_name);
    

    Why do we not do:

    $field_name = $this->query->escapeAlias($field_schema['alias']) . '.' . $this->query->escapeField($field_name);
    
  3. +++ b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
    @@ -288,9 +288,12 @@ protected function initializeIterator() {
    +            $field_name = "[$field_name]";
    

    Why do we not do:

    $field_name = $this->query->escapeField($field_name);
    
  4. +++ b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
    @@ -288,9 +288,12 @@ protected function initializeIterator() {
    +          $map_join .= "$delimiter$field_name = [map].[sourceid" . $count++ . ']';
    

    Why do we not do:

    $map_join .= "$delimiter$field_name = " . $this->query->escapeAlias(map) . '.' . $this->connection->escapeField(sourceid" . $count++);
    
  5. +++ b/core/lib/Drupal/Core/Database/Query/Select.php
    @@ -846,6 +846,11 @@ public function __toString() {
    +          [$db_name, $table_name] = explode('.', $table_string, 2);
    

    This can for PostgreSQL databases be 3 parts: database name, schema name and table name.

wim leers’s picture

Thanks for the review, @daffie! 😊

#18 + #19.1: {…} applies the prefix for the current DB connection, not the prefix in use on the other database you're joining against.

#19.2+3+4: sure, these are possible. But almost everything else in the migration system uses explicit […], so I figured I'd match that. Does this solve a problem or is this just a matter of preference?

#19.5: So … that'd mean we need to make it possible to have driver-specific behavior in this generic logic, since I do not think we want PostgreSQL-specific checks here. Thoughts on how we should do that?

daffie’s picture

The testbot is failing for PostgreSQL, because of #19.5. Maybe we could do something like:

  // But do ensure identifiers (databases and table names) are escaped.
  else {
    $table_string_parts = explode('.', $table_string);
    $table_name = array_pop($table_string_parts);
    $table_string = '';
    foreach($table_string_parts as $table_string_part) {
      $table_string .= $this->connection->escapeDatabase($table_string_part) . '.';
    }
    $table_string .= "[$table_name]";
  }

Not sure about the part: $table_string .= "[$table_name]";
This way it works for all relational databases, including PostgreSQL.

#18 + #19.1: {…} applies the prefix for the current DB connection, not the prefix in use on the other database you're joining against.

I would very much to like have testing for that.

#19.2-4: ok, lets wait with that for the moment. Lets fix the important parts first.

ghost of drupal past’s picture

It is becoming crystal clear that the slapdash join we have in dbtng is just not enough.

I have been thinking on that for a long, long time. And Views has a join builder, for a very different reason, of course.

Rule of thumb when writing DBTNG was: never parse a query. It will always fail in unexpected ways and if I gather correctly, that's what happens here.

We need an expression builder for DBTNG which we partially have but not fully. like ->condition('foo', 1) translates to foo = 1 but if you wanted foo.bar = 1 properly escaped? sucks to be you. And for any complex expression we have ->expression() and ->where().

That's the full solution to this. And then you don't escape anything because the builder does.

It's quite a bit of work -- but perhaps ... we just need to build a tree. The leaves can have value objects based on escaping strategy necessary (variable/fieldname/tablename/dbname) and then one level up, what do we have? we need an infix operator class and a function call.

$e = \Drupal::database()->getConnection()->expression();
$left = $e->infix('.', $e->table($field_schema['alias']), $e->field($field_name));
$right = $e->infix('.', $e->table('map'), $e->field('sourceId'  . $ocunt++);
$piece = $e->Infix('=', $left, $right);
if (!isset($return)) {
  $return = $piece;
}
else {
  $return = $e->infix(' AND ', $return, $piece);
}

That was just an example. Make the join method accept n expression besides a string and then the objects can deal with printing themselves via __toString(). Everywhere where migrate uses the database name, use an expression. Do not attempt to solve this. Make it smart and make it solve itself.

Bonus: the builder is instantly usable for where and expression.

Here's a start: https://gist.github.com/chx/e74ac22799ba6cd4a7beebad92d1301a

wim leers’s picture

#22:

It is becoming crystal clear that the slapdash join we have in dbtng is just not enough.

💯

Rule of thumb when writing DBTNG was: never parse a query. It will always fail in unexpected ways and if I gather correctly, that's what happens here.

Oh … very interesting! :)

And for any complex expression we have ->expression() and ->where().

After all these years I still get really really really confused by ->condition() vs ->where()! 🙈

It's quite a bit of work -- but perhaps ... we just need to build a tree.

That sounds like DBTNG v1.5? 🤓

I totally agree that we should have a more elegant fundamental solution, but I think expanding the scope to that is making it likely this will not land in the next year 😅

So … can you live with the general approach in here? (I'm happy to create a new issue for #22!)

ghost of drupal past’s picture

The expression builder I suggested would be very limited -- just enough for writing the join conditions and such. I will write it over the weekend.

Edit: however, while writing it, I found a bug, I think: Connection::getFullQualifiedTableName does not escape $options['database']. I think we should and that would fix this?

ghost of drupal past’s picture

Status: Needs work » Needs review
StatusFileSize
new2.31 KB

This seems a simple but better route. If the problem is funny database names, just .... escape those? Especially since we already have the escape method for those?

I have changed the [] in the plugin base to explicit escapes. We already have an escapeField there and half explicit half magic beans is not a tasty soup.

Status: Needs review » Needs work

The last submitted patch, 25: 3227361_25.patch, failed testing. View results

ghost of drupal past’s picture

Status: Needs work » Needs review

It had a JavaScript fail, that was unrelated.

daffie’s picture

  1. +++ b/core/lib/Drupal/Core/Database/Connection.php
    @@ -531,7 +531,7 @@ public function getUnprefixedTablesMap() {
    -    return $options['database'] . '.' . $prefix . $table;
    +    return $this->escapeDatabase($options['database']) . '.' . $prefix . $table;
    
    +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
    @@ -319,7 +319,7 @@ public function getFullQualifiedTableName($table) {
    -    return $options['database'] . '.public.' . $prefix . $table;
    +    return $this->escapeDatabase($options['database']) . '.public.' . $prefix . $table;
    

    I think these changes are wrong. The method getFullQualifiedTableName() returns it unquoted. With this change, only the database name gets quoted.

  2. I think we should add a new method to the class Drupal\Core\Database\Select. In this method we should be able to add all parameters that belong to the join. It is then up to the query builder to add the quotes as it sees fit.
    The code comes from Drupal\Core\Database\Query\SelectInterface and modules/views/src/Plugin/views/join/JoinPluginBase.php
      /**
       * Join against another table in the database with an inner join.
       *
       * This method does the "hard" work of queuing up a table to be joined against.
       * In some cases, that may include dipping into the Schema API to find the necessary
       * fields on which to join.
       *
       * @param string $table
       *   The table against which to join. May be a string or another SelectQuery
       *   object. If a query object is passed, it will be used as a subselect.
       *   Unless the table name starts with the database / schema name and a dot
       *   it will be prefixed.
       * @param string $field
       *   The field of the table $table to use in the join.
       * @param string $left_table
       *   The table to which the table $table joins to.
       * @param string $left_field
       *   The field of the table $left_table to use in the join.
       * @param string $alias
       *   The alias for the table. In most cases this should be the first letter
       *   of the table, or the first letter of each "word" in the table.
       * @param string $operator
       *   (optional) The operator to use in the join. Defaults to '='.
       * @param array $extra
       *   An array of extra conditions on the join.
       *
       *   Each condition is either a string that's directly added, or an array of
       *   items:
       *     - table(optional): If not set, current table; if NULL, no table.
       *     - field(optional): Field or expression. In expressions we can reference the
       *       right table by using %alias.
       *     - left_field(optional): Field or expression. In expression we can reference
       *       the left table by using %alias.
       *     - operator(optional): The operator used, Defaults to "=".
       *     - value: Must be set. If an array, operator will be defaulted to IN.
       *     - numeric: If true, the value will not be surrounded in quotes.
       *
       * @return $this
       *   The called object.
       */
      public function addInnerJoin($table, $field, $left_table, $left_field, $alias, $operator = '=', $extra = NULL) {
    

    We should also add addLeftJoin() for a left join.

quietone’s picture

Title: Fix \Drupal\migrate\Plugin\migrate\source\SqlBase::initializeIterator()'s cross-database joining broken when using particular DB/table names » Fix SqlBase::initializeIterator()'s cross-database joining when using particular DB/table names

Long issue titles hurt my brain. Since this in migration system I don't think it will be confused with \Drupal\views\Plugin\views\pager\SqlBase.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

quietone’s picture

Issue summary: View changes
Status: Needs review » Needs work
Issue tags: +Bug Smash Initiative

Updated IS to start with the database name formats that cause the error.

Changing status to NW for the tests and the comments in #28.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

joseph.olstad’s picture

StatusFileSize
new2.32 KB

Patch #25 no longer applies, I rerolled patch #25 however then I read the comments from @daffie , @daffie says this is wrong. I'd like this issue to be fixed because it's not only affecting Symfony Drupal it's also affecting Drupal 7.94 which has modeled it's db abstraction layer changes from changes made to symfony Drupals abstraction layer.

For now, handing this off to someone else, good luck!

I am pushing the reroll of patch 25 to see if it still passes testing just for curiosity sake.

Apparently this patch needs changes as per #28 @daffie comment and #31 @quietone

joseph.olstad’s picture

hmm, checking between branches, 9.5.x, 10.0.x, 10.1.x, the patch #34 conflicts with 10.1.x core/lib/Drupal/Core/Database/Connection.php

Perhaps part/or/all of this has been fixed already in 10.1.x ?

Probably not, but ya, hopefully someone with more experience working on the db abstraction layer can make the expected changes.

joachim’s picture

I tried applying the latest patch to 10.0 and it doesn't fix the problem -- running a migration from D7 in the UI shows lots of:

> Migration failed with source plugin exception: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'drupalfirecore10.migrate_map_d7_action' doesn't exist: SELECT "a".*, "map"."sourceid1" AS "migrate_map_sourceid1", "map"."source_row_status" AS "migrate_map_source_row_status" FROM "actions" "a" LEFT OUTER JOIN drupalfirecore10.migrate_map_d7_action "map" ON aid = map.sourceid1 WHERE ("map"."sourceid1" IS NULL) OR ("map"."source_row_status" = :db_condition_placeholder_0); Array ( [:db_condition_placeholder_0] => 1 ) in /Users/joachim/Sites/firecore/web/core/modules/mysql/src/Driver/Database/mysql/ExceptionHandler.php line 46

My DB is called drupal-firecore-10, not drupalfirecore10!

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

joachim’s picture

I've received this suggestion as a fix:

  public function escapeTable($table) {
    if (!isset($this->escapedTables[$table])) {
      $database = $this->getConnectionOptions()['database'];
      if (str_starts_with($table, "$database.")) {
        $to_escape = substr($table, strlen($database) + 1);
        $prefix = $this->quoteIdentifiers("[$database].");
      }
      else {
        $to_escape = $table;
        $prefix = '';
      }
      $this->escapedTables[$table] = $prefix . preg_replace('/[^A-Za-z0-9_.]+/', '', $to_escape);
    }
    return $this->escapedTables[$table];
  }
alex.skrypnyk’s picture

StatusFileSize
new967 bytes

#38 worked for me. thank you @joachim

Patch for 11.2.5 attached

meeni_dhobale made their first commit to this issue’s fork.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.