Problem/Motivation

When altering a table, the MySQL Schema class needs to check for existing indexes and recreate them if
1. You alter a field and the new spec is varchar
2. There's already an index on this field

When a table is created a field or an index createKeySql fires getNormalizedIndexes which will in turn fire shortenIndex and shorten the index but if you just alter then it will never fire because the index is already there.

This issue came up in #2769603: Syntax error or access violation: 1071 Specified key was too long.

Proposed resolution

Use SHOW INDEX to check for existing indexes on fields being altered into a varchar.

Remaining tasks

User interface changes

None

API changes

Data model changes

Comments

jibran created an issue. See original summary.

jibran’s picture

Status: Active » Needs review
StatusFileSize
new1.2 KB

Here is a test only patch courtesy @chx

dawehner’s picture

This gonna be fun to solve ... because in order to do that we would need the full schema of the table, which is not something we have.

chx’s picture

http://dev.mysql.com/doc/refman/5.7/en/show-index.html

return db_query('SHOW INDEX FROM {tablename} WHERE column_name = :new_column_name')->fetch(); or somesuch. If there are results, check Sub_part.

jibran’s picture

chx’s picture

Re-roll it with the SET GLOBAL commands by three db_query and we will see.

jibran’s picture

StatusFileSize
new1.43 KB

Here we go.

Status: Needs review » Needs work

The last submitted patch, 7: mysql_schema_class-2770321-7.patch, failed testing.

chx’s picture

Note: the testbot my.cnf is http://cgit.drupalcode.org/drupalci_testbot/tree/containers/database/mys... this.

I do not understand anything. Not only does this fail when we supposedly switch on large index support, it fails just before row format alter, same for jibran. But for me, it fails *after* the alter, on line 706, not on line 703. To recap:

    db_create_table('test_table', $table_spec);
    db_change_field('test_table', 'f1', 'f1', ['type' => 'varchar', 'length' => 191]);
    db_change_field('test_table', 'f1', 'f1', ['type' => 'varchar', 'length' => 192]); // bot and jibran env fails here. After those three settings at least but not before.
    db_query('ALTER TABLE {test_table} ROW_FORMAT = COMPACT');
    db_change_field('test_table', 'f1', 'f1', ['type' => 'varchar', 'length' => 191]);
    db_change_field('test_table', 'f1', 'f1', ['type' => 'varchar', 'length' => 192]); // it gets here for me but not further. This is the only thing that makes sense in this issue, compact doesn't support large prefixes so this must fail. How on earth does this pass in #2? The only logical explanation would be the table not support utf8mb4 somehow but NodeViewTest::testMultiByteUtf8 has a 4 byte UTF-8 character stored...
morgantocker’s picture

Issue summary: View changes

Updating description based on 767 not being a limitation but a default.

morgantocker’s picture

@chx: I believe above fails because the ROW_FORMAT=COMPACT instead of DYNAMIC.

chx’s picture

There are two questions here, really: a) why does #2 pass? b) why does #7 fail before the row format change.

morgantocker’s picture

I apologize - I am not quite sure what you mean by #2 and #7 (not a drupal developer). But could this be what you are describing:
http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_inn...

(It now defaults strict, but it won't be in the version CI is using. It makes sense to turn it to strict for CI, since you typically want to catch these errors.)

chx’s picture

Oh I meant #2, a patch and #7, another patch , it's the comment numbers of this issue. I apologize for not being clear.

morgantocker’s picture

So I have an answer for you. The setting innodb_large_prefix is escalating a warning to an error. Here are the test cases in MySQL-only form, which is a little easier to follow:

# first test (comment #2)

DROP TABLE IF EXISTS test_table;

CREATE TABLE test_table (f1 INT, INDEX(f1)) character set utf8mb4;
ALTER TABLE test_table CHANGE f1 f1 varchar(191);
ALTER TABLE test_table CHANGE f1 f1 varchar(192); # results in 2 warnings

ALTER TABLE test_table ROW_FORMAT = COMPACT; # does nothing, compact is already the default in 5.5 (testcase probably meant to switch this to DYNAMIC?)

ALTER TABLE test_table CHANGE f1 f1 varchar(191);
ALTER TABLE test_table CHANGE f1 f1 varchar(192);  # results in 2 warnings

# second test (comment #7)

SET GLOBAL innodb_large_prefix = ON; # This line changes the warning to an error
SET GLOBAL innodb_file_format=Barracuda;
SET GLOBAL innodb_file_per_table=ON;

DROP TABLE IF EXISTS test_table;

CREATE TABLE test_table (f1 INT, INDEX(f1)) character set utf8mb4;
ALTER TABLE test_table CHANGE f1 f1 varchar(191);
ALTER TABLE test_table CHANGE f1 f1 varchar(192); # results in an error

ALTER TABLE test_table ROW_FORMAT = COMPACT; # does nothing, this is the default for 5.5

ALTER TABLE test_table CHANGE f1 f1 varchar(191);
ALTER TABLE test_table CHANGE f1 f1 varchar(192);  # results in an error
chx’s picture

Issue summary: View changes
StatusFileSize
new73.41 KB

OK, we now understand what is happening. My local environment is MySQL 5.7.

So to recap: innodb_large_prefix got introduced in 5.5. This allows creating longer than 767 byte indexes if all of the following is in place:

  1. Barracuda file format
  2. Per file storage
  3. Dynamic row format

In 5.5 the default for innodb_large_prefix was OFF but in 5.7 it is ON. Soon you won't be able to switch it off at all as it is deprecated. Besides potentially allowing for larger indexes it also causes MySQL to stop truncating indexes with a warning and throw an error instead. This is visible for example in this https://bugs.mysql.com/bug.php?id=68453 bug report. This is the reason the bot doesn't fail in #2 but fails in #7. Note: it should fail and the bot needs to be reconfigured as in the sister issue, we are hiding a warning and if in a future MySQL release when innodb_large_prefix gets removed and hardwired to ON we are SOL.

As for the ROW_FORMAT again the difference is in 5.7 vs 5.5: 5.7 has a new setting called innodb_default_row_format which is DYNAMIC by default while previously the row format defaulted to COMPACT and you needed to change this table by table. This is why bot / jibran fails before the ROW_FORMAT change while my environment goes to the last error as visible in the screenshot attached. It also means that for complete testing this is necessary because people will have both COMPACT and DYNAMIC tables lying around.

chx’s picture

And , before I forget: thank you Morgan for your patient guidance, it has been invaluable help to understand this problem.

jibran’s picture

Thanks @morgantocker this is a perfect explanation.

morgantocker’s picture

@chx: one quick comment in response to future changes -
The concept of file formats itself is really just backwards compatibility (the newer being the super-set to the previous, and there are not typically pros/cons of each. See my answer on StackExchange here). I think you should expect both innodb_large_prefix + innodb_file_format to disappear at some point.

jibran’s picture

Status: Needs work » Needs review
StatusFileSize
new2.42 KB
new2.32 KB

Here is new patch after the discussion courtesy @chx.

Status: Needs review » Needs work

The last submitted patch, 20: mysql_schema_class-2770321-20.patch, failed testing.

chx’s picture

+    db_change_field('test_table', 'f1', 'f1', ['type' => 'varchar', 'length' => 192]);
+    db_query('ALTER TABLE {test_table} ROW_FORMAT = COMPACT');

In fact... the alters after this is not needed because when we switch to COMPACT then the system dies if the index is 192 at this point. So just switch to COMPACT and assert the index is indeed 191.

jibran’s picture

You mean remove line 731,735 and 736?

stefan.r’s picture

+++ b/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php
@@ -696,6 +697,47 @@ function testSchemaChangeField() {
+    db_change_field('test_table', 'f1', 'f1', ['type' => 'varchar', 'length' => 192]);

Doesn't Schema::addIndex() silently set this to be 191 anyway? See Schema::getNormalizedIndexes().

jibran’s picture

Doesn't Schema::addIndex() silently set this to be 191 anyway? See Schema::getNormalizedIndexes().

The problem is not with Schema::addIndex(). When you call Schema::changeField() and don't add new key and index, it doesn't call Schema::createKeysSql() which calls Schema::getNormalizedIndexes().
Quoting the issue summary

When altering a table, the MySQL Schema class needs to check for existing indexes and recreate them if
1. You alter a field and the new spec is varchar
2. There's already an index on this field

From core/lib/Drupal/Core/Database/Driver/mysql/Schema.php


  public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
    if (!$this->fieldExists($table, $field)) {
      throw new SchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
    }
    if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
      throw new SchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
    }

    $sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec));
    if ($keys_sql = $this->createKeysSql($keys_new)) {
      $sql .= ', ADD ' . implode(', ADD ', $keys_sql);
    }
    $this->connection->query($sql);
  }

See if ($keys_sql = $this->createKeysSql($keys_new)) { only adds new keys and index. We should also check existing index and specs of the new column.

stefan.r’s picture

Yes, I mean in the proposed fix, not the current situation -- when altering a field to be a varchar of 191+ characters, we would shorten the index to be 191 characters. So #22 would not apply (the system wouldn't die -- it would just silently shorten the index (or the field length, I don't remember).

chx’s picture

Exactly, my comment might have been too terse but I said "and assert the index is indeed 191" . That's what I meant.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

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

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

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should 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.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should 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.

Version: 9.5.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. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Curious if this one is still an issue?

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.