When upgrading a field / changing it's type using pgsql:changeField, it's impossible to make a field a serial, bigserial, or numeric. This appears to be because of confusion between the typecast that is required to do the data-conversion and the field-type we are creating. As is currently stands, pgsql:changeField() is forcing serial, bigserial, or numeric to be int.

We found this issue because of this contrib issue: #1657910: Field type update fails in PostgreSQL

Related to this, is that there should better logic around when we need to force explicit casting with a USING statement. It's not always required, and we shouldn't use it unless we need to.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

phayes’s picture

Assigned: Unassigned » phayes

Assigning this to myself to create a patch.

tim.plunkett’s picture

Version: 7.x-dev » 8.x-dev
Priority: Critical » Major
Issue tags: +Needs backport to D7

Can you confirm that this is a bug in D8 as well?

Brandonian’s picture

@tim.plunkett, it appears so. Haven't actually tried it, but the same block of code appears in the D8 pgsql database driver, lines 519-521.

http://drupalcode.org/project/drupal.git/blob/refs/heads/8.x:/core/lib/D...

phayes’s picture

There are two problems here

Line 543 shouldn't be using the $typecast variable as the target field type

Lines 519 et al should have better logic on when we need to do any typecasting. Not all these types require a cast. Running the command "\dC" in postgres shell shows the matrix of which combination of field-types we can rely on to have automatic type-casting, and which ones need manual intervention.

phayes’s picture

I'm just posting my research here as I go along, hope you folks don't mind...

If we want to figure out dynamically when we need to do the casting, we can use this query to get a list of all supported casts:

SELECT
  castmethod,
  source.typname as source, 
  target.typname as target
FROM pg_cast
INNER JOIN pg_type as source on source.oid = pg_cast.castsource
INNER JOIN pg_type as target on target.oid = pg_cast.casttarget

See http://www.postgresql.org/docs/9.1/static/catalog-pg-cast.html

chx’s picture

Wow, amazing , someone working on postgresql! I very much like it. Thanks.

phayes’s picture

Status: Active » Needs review
Issue tags: -Needs backport to D7
FileSize
2.08 KB

So, in the end, the problem ended by being far simpler than I had thought it was. Attached is a patch that fixes this. It's still untested (need to figure-out how to run simple-tests using postgres).

The basic problem was two fold:

1. Really confusing inline documentation, there is no need to "cast" anything...

2. What's really happening is that serial and bigserial do not exist when ALTERing a table, only when CREATEing one. So we need to cast them as integers then manually apply their sequence. Somehow numeric was using the same logic, which was what was causing problems.

This fix:
1. Cleans up inline documentation and variable names to be less confusing
2. Removes numerics being treated as integers (only serial and bigserial are not treated as inegeters)

phayes’s picture

Status: Needs work » Needs review

[duplicate posting]

tim.plunkett’s picture

Status: Needs review » Needs work
Issue tags: +Needs backport to D7
+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.phpundefined
@@ -513,21 +513,21 @@ class Schema extends DatabaseSchema {
-    // We need to typecast the new column to best be able to transfer the data
-    // Schema_pgsql::getFieldTypeMap() will return possibilities that are not
-    // 'cast-able' such as 'serial' - so they need to be casted int instead.
-    if (in_array($spec['pgsql_type'], array('serial', 'bigserial', 'numeric'))) {
-      $typecast = 'int';
+    // Type "serial" is known to PostgreSQL, but *only* during table creation,
+    // not when altering. Because of that, we create it here as an int. After
+    // we create it we manually re-apply the sequence.
+    if (in_array($spec['pgsql_type'], array('serial', 'bigserial'))) {
+      $field_def = 'int';

So this is the only change here, the rest is a variable rename? Fair enough.

I think the asterisks around only are unneeded. I'd swap the double quotes around serial for single quotes, and add them around int, or remove it from serail.

phayes’s picture

[duplicate posting]

phayes’s picture

Status: Needs review » Needs work

Bah, sorry about all the double-posts. I was having crappy internet problems.

phayes’s picture

Attached is an updated version with the requested changes. Still needs full testing.

phayes’s picture

This is proving really difficult to test. Without the patch all sorts of tests fail for me, so it's pretty much impossible to run tests against the patch...

tim.plunkett’s picture

But with them it does pass? Isn't that enough proof?

phayes’s picture

No, they both fail equally. :-(

phayes’s picture

So how should we go forward with this?

The situation is such

1. This is a bug in D7 and D8. The fix appears to work on both.
2. The current build of D8 appears to break unit tests all over the place when testing on postgres. These breakages are unrelated to this patch.

It seems to me that we cannot reasonably wait for all of postgres to work in D8 before accepting this patch, which is breaking people's live, in-production drupal sites in D7.

I propose one of two courses of action:

A. Apply this patch to D8 after more thorough hand-testing, even if unit tests break. Backport it to D7.
B. Apply this patch to D7. Wait for it's application to D8 until all D8 unit-tests pass under postgres.

chx’s picture

A.

If your unit tests are broken already, it won't get worse. However, regressing in D8 (ie B) would be.

Brandonian’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
2.82 KB

Re-rolled the patch from #11 and hand-tested. It seems to work for me. Marking as reviewed and tested.

catch’s picture

Status: Reviewed & tested by the community » Needs review

Do the current schema tests we have fail in PostgreSQL without this patch applied? If not let's add the coverage here.

gease’s picture

I'm not sure it's a right place to post, but here's a backport of #18 to d7.

Status: Needs review » Needs work

The last submitted patch, 1668644.20.d7.untested.patch, failed testing.

yoroy’s picture

Assigned: phayes » Unassigned
yoroy’s picture

Issue summary: View changes

Using proper terminology

slippast’s picture

The D7 patch from #20 fixed the following problem with the Get Locations module, just an FYI:

Update #7102

Failed: PDOException: SQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at or near "(" LINE 1: ...ABLE getlocations_fields ALTER "latitude" TYPE int(10, 6) US... ^: ALTER TABLE {getlocations_fields} ALTER "latitude" TYPE int(10, 6) USING "latitude"::int(10, 6); Array ( ) in db_change_field() (line 3017 of /xxx/includes/database/database.inc).
milos.kroulik’s picture

Issue summary: View changes

Patch from #20 works for me. Can this change to RTBC?

Island Usurper’s picture

Where are the schema tests in D8, mentioned in #19? I was going to try to write a test, but I only found ConnectionTest, which uses a mock database connection. Are there tests for the various database drivers?

bzrudi71’s picture

Re #26, please see core/modules/system/src/Tests/Database/

Thanks!

bzrudi71’s picture

Re #26, please see core/modules/system/src/Tests/Database/

Thanks!

Island Usurper’s picture

OK, two files, one being just tests, which will fail when using PostgreSQL, and the other being the tests plus an updated version of #20 so that it applies to 8.0.x-dev.

Island Usurper’s picture

Status: Needs work » Needs review

mgifford queued 29: 1668644.29.d8.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 29: 1668644.29.d8.patch, failed testing.

Island Usurper’s picture

FileSize
6.43 KB

Apply to head. Yay, asserting field characteristics doesn't remove the field any more. I should probably make a new issue for the wrong comment about precision and scale I found.

Island Usurper’s picture

Status: Needs work » Needs review

Can't ever remember to change the status.

bzrudi71’s picture

Nice, thanks @Island Usurper. Even if currently not used in core it's a nice addition for contrib and should help with the D8 upgrade path. I did testing with PostgreSQL and all tests pass, but it seems a bit odd to create 8000+ tables resulting in 25000+ tests for Schema?

Drupal\system\Tests\Database\SchemaTest 25774 passes

Island Usurper’s picture

Yeah, I thought about that, @bzrudi71, but I eventually decided to go for completeness because I couldn't decide where "enough" was. If someone who is more familiar with testing wants to chime in to say, "We only need to make sure that one combination of precision and scale work. It's safe to assume the rest will too", that'd be great. I can certainly rewrite the patch that way, or someone else can jump in, too.

bzrudi71’s picture

Issue tags: +PostgreSQL

+1 for

We only need to make sure that one combination of precision and scale work. It's safe to assume the rest will too

:-)

Island Usurper’s picture

FileSize
6.18 KB

Alright, smaller test it is.

daffie’s picture

The patch looks good to me.
The only problem for me is the number of new tests. Almost 5700 new tests and 1900 table creations.

bzrudi71’s picture

Title: pgsql:changeField(): Impossible to change a field to serial, bigserial, or numeric » PostgreSQL: Impossible to change a field to serial, bigserial, or numeric

Yes, still way to much tests I think ;-)

daffie’s picture

Talked to catch on IRC and his suggestion would be to limit the number of new tests to 1 or 2.

daffie’s picture

Component: database system » postgresql db driver
Island Usurper’s picture

FileSize
5.87 KB

Took out the different sizes for the int columns, and half of the "not null", "initial", and "default" variations. Now SchemaTest runs with 748 total passes.

bzrudi71’s picture

We have currently 658 tests in Schema without patch:

Drupal\system\Tests\Database\SchemaTest 658 passes

with patch:

Drupal\system\Tests\Database\SchemaTest 748 passes

The test itself takes just a second or two to complete, so I no longer see a problem with the number of tests. Reviewed the patch but didn't find anything wrong.
+1 for RTBC.

daffie’s picture

Status: Needs review » Needs work
+++ b/core/modules/system/src/Tests/Database/SchemaTest.php
@@ -477,4 +477,92 @@ protected function assertFieldCharacteristics($table_name, $field_name, $field_s
+    $field_specs = array();
+
+    // Test int and float types.
+    foreach (array('int', 'float') as $type) {
+      $base_field_spec = array(
+        'type' => $type,
+        'size' => 'normal',
+      );
+      $variations = array(
+        array('not null' => FALSE),
+        array('not null' => TRUE, 'initial' => 1, 'default' => 7),
+      );
+
+      foreach ($variations as $variation) {
+        $field_specs[] = $variation + $base_field_spec;
+      }
+    }
+
+    // Test numeric types.
+    $precision = 10;
+    $scale = 2;
+
+    $base_field_spec = array(
+      'type' => 'numeric',
+      'scale' => $scale,
+      'precision' => $precision,
+    );
+    $variations = array(
+      array('not null' => FALSE),
+      array('not null' => TRUE, 'initial' => 1, 'default' => 7),
+    );
+
+    foreach ($variations as $variation) {
+      $field_specs[] = $variation + $base_field_spec;
+    }
+
+    foreach ($field_specs as $i => $old_spec) {
+      foreach ($field_specs as $j => $new_spec) {
+        if ($i === $j) {
+          // Do not change a field into itself.
+          continue;
+        }
+
+        $this->assertFieldChange($old_spec, $new_spec);
+      }
+    }
+  }

Can we rewrite this function so that it is a lot more compact and improve its readability.

Island Usurper’s picture

Status: Needs work » Needs review

The first two thirds are just setting up data structures for Schema API. I don't know that there's much that can be done there. It follows the same style and setup as other test functions in that class, so if you think this function is too verbose, maybe there should be a new issue to refactor more of the tests.

daffie’s picture

FileSize
5.58 KB
2.24 KB

Updated and compacted SchemaTest::testSchemaChangeField().

bzrudi71’s picture

Status: Needs review » Reviewed & tested by the community

I think all concerns are addressed now and I found nothing obvious during a second review. RTBC and thanks all!

alexpott’s picture

Version: 8.0.x-dev » 7.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

This issue addresses a major bug and is allowed per https://www.drupal.org/core/beta-changes. Committed 6e51cc1 and pushed to 8.0.x. Thanks!

Tested using SQLite - no new fail introduced.

diff --git a/core/modules/system/src/Tests/Database/SchemaTest.php b/core/modules/system/src/Tests/Database/SchemaTest.php
old mode 100755
new mode 100644

Fixed on commit

  • alexpott committed 6e51cc1 on 8.0.x
    Issue #1668644 by Island Usurper, phayes, daffie, Brandonian, gease:...
Island Usurper’s picture

Status: Patch (to be ported) » Needs review
FileSize
4.62 KB

Here is the patch from #47 ported to D7. I had to include a change assertFieldCharacteristics() that was in #29 because there is a change that hasn't been backported yet, but is necessary for this test to work. Basically, assertFieldCharacteristics() shouldn't drop the field it tests.

Should we find the issue that took care of that and make sure it gets properly backported before dealing with this?

  • alexpott committed 6e51cc1 on 8.1.x
    Issue #1668644 by Island Usurper, phayes, daffie, Brandonian, gease:...

  • alexpott committed 6e51cc1 on 8.3.x
    Issue #1668644 by Island Usurper, phayes, daffie, Brandonian, gease:...

  • alexpott committed 6e51cc1 on 8.3.x
    Issue #1668644 by Island Usurper, phayes, daffie, Brandonian, gease:...

  • alexpott committed 6e51cc1 on 8.4.x
    Issue #1668644 by Island Usurper, phayes, daffie, Brandonian, gease:...

  • alexpott committed 6e51cc1 on 8.4.x
    Issue #1668644 by Island Usurper, phayes, daffie, Brandonian, gease:...
joseph.olstad’s picture

poker10’s picture