Problem/Motivation

In the testbot results from https://git.drupalcode.org/project/drupalsqlsrvci/-/jobs/8393, there are a lot of errors like:

PDOException: SQLSTATE[23000]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Violation of PRIMARY KEY constraint 'test67130852users_pkey'. Cannot insert duplicate key in object 'dbo.test67130852users'. The duplicate key value is (1).

They are the result of the issue: #838992: Change the uid field from integer to serial by leveraging NO_AUTO_VALUE_ON_ZERO on MySQL. The problem is that we need to insert the user with uid "0" into the users table. For PostgreSQL and SQLite this is not a problem. For MySQL it was. MySQL does not like it when you try to insert the number zero into a serial field. MySQL likes to change the value zero to the first free auto increment value. Usually that will be the number 1. When Drupal then tries to insert the admin user with uid 1, the database response with an error for duplicate key. We had to add the option NO_AUTO_VALUE_ON_ZERO to the SQL mode. We need to find out how to fix this in MS SQL Server.

Proposed resolution

From https://stackoverflow.com/questions/5427142/sql-primary-key-can-accept-0:

set Identity_insert dbo.table1 ON
insert dbo.table1 (id, myfield)
Values (0, 'test')
set Identity_insert dbo.table1 OFF

Remaining tasks

TBD

User interface changes

None

API changes

None

Data model changes

None

Release notes snippet

TBD

CommentFileSizeAuthor
#8 3231206-8.patch1.77 KBdaffie

Comments

daffie created an issue. See original summary.

daffie’s picture

Issue summary: View changes
beakerboy’s picture

The driver is already capable of doing this. Migration scripts specify the key values on insertion, so I check any insertions to see if a serial field is included. If it is, the “ set Identity_insert dbo.table1 ON;” is prepended to the statement and allow_delimiter_in_query option is set to true. I wonder if I need to run one of these test and echo each query to the screen to make sure it’s happening like it should.

beakerboy’s picture

These errors do not happen in Drupal 9.3. Were there changes in the way 9.3 inserts user ids that would affect SQL Server in a good way?

daffie’s picture

Status: Active » Closed (works as designed)

I opened this issue some time ago and after testing the problem myself I found that the problem does not exists on SQL Server.

beakerboy’s picture

The thing is there IS a problem in 4.2.x. I don't know if it is a "testing only" problem, or an actual problem with the code. There are failures. The fact that the failures do not happen in 4.3.x means something changed in either 4.3.x or in Drupal 9.3, and I'm curious what that change was.

beakerboy’s picture

Status: Closed (works as designed) » Active

Moving back to active because the module does throw an exception during the test. If the problem is in the way the test code is written, can we patch the test to allow it to do what it is supposed to do in sqlsrv?

In both 9.2 and 9.2 EntityAutocompleteElementFormTest.php manually creates a user with uid=1, and then creates another user. In 9.2, sqlsrv tries to create the new one with an id of 1, while in 9.3 it must not. Why is sqlsrv not inserting the second user with the correct value? Is it because the field in not serial and Drupal for some reason does not know the next value? is it because Insert::execute() is using the deprecated fetchColumn() method in 9.2 and $stmt->fetchField() in 9.3?

daffie’s picture

Status: Active » Needs review
StatusFileSize
new1.77 KB

I found out why the test EntityAutocompleteElementFormTest is failing. The method Connection::nextId($existing) does not return the next value. If you set $existing to 1 the method returns the value 1 and not 2 as you should expect.

I have created a patch to fix the method, it now also passes the test Drupal\KernelTests\Core\Database\NextIdTest.

beakerboy’s picture

Thanks for this patch. It works, but I have no idea why. I think I need to run some extra tests and add some comments. To be honest, the code that is currently in the driver, which I did not write, should work given everything I know about SQL. The output section must return the value before the statement is executed or something like that.

beakerboy’s picture

Status: Needs review » Needs work

I added some extra tests and one fails with your patch.
https://git.drupalcode.org/project/sqlsrv/-/blob/4.2.x-nextId/tests/src/...

beakerboy’s picture

Status: Needs work » Reviewed & tested by the community

I guess if we accept that sometimes values will be skipped and incremented by two instead of one than this is okay. The requirements just say that the returned value will be larger than the parameter passed in. I’ll make a few more tests but I think this is good.

  • Beakerboy committed e232bd6 on 4.2.x
    Issue #3231206 by daffie: The uid field from the table users has changed...
beakerboy’s picture

Status: Reviewed & tested by the community » Needs work

Nope, this causes a lot more failures. There are many tests that are expecting different ID values than they are receiving. The number of errors has decreased, but the number of failures has significantly increased.

Drupal\Tests\media\Kernel\MediaEmbedFilterTest::testFilterIntegration

beakerboy’s picture

I think I figured out the root cause on why the original algorithm was not working anymore. When Connection::prepare_statement() and Connection::query() were refactored, the check on if there was a delimiter in the query was moved. Connection::queryDirect() used to not care if there was a semicolon or not because it trusted that the query was safe. Now options['allow_delimiter_in_query'] has to be set. The driver was throwing an exception because the statement contained a semicolon, but it was caught and not rethrown. The insert was not ever actually occurring, which was why a value of 3 was returning instead of 1001.

In your code, if the insert causes a problem, the "SET IDENTITY_INSERT OFF" would not actually happen if the insert value was already in the database, and the next insert would throw an exception because the DB is expecting a primary key since IDENTITY_INSERT is still ON.

A few possible solutions. If we separate this into multiple statements, there probably needs to be a "finally" section where we set IDENTITY_INSERT to OFF, or this needs to happen in the catch section. We should probably also check the type of exception, and if it is not the exception we expect, it needs to be rethrown.

  • Beakerboy committed 241ed71 on 4.2.x
    Issue #3231206 by daffie, Beakerboy: The uid field from the table users...
beakerboy’s picture

Version: 4.2.x-dev » 4.3.x-dev

Testing on 4.3.

beakerboy’s picture

Status: Needs work » Reviewed & tested by the community
beakerboy’s picture

Status: Reviewed & tested by the community » Fixed

  • Beakerboy committed 93e0cba on 4.3.x
    Issue #3231206 by daffie, Beakerboy: The uid field from the table users...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.