Verified Commit 9ebef38a authored by Dave Long's avatar Dave Long
Browse files

fix: #3561800 Using \Drupal\Core\Database\Query\Insert::from() on postgres on...

fix: #3561800 Using \Drupal\Core\Database\Query\Insert::from() on postgres on a table with a serial field can result in duplicate key error

By: alexpott
By: smustgrave
(cherry picked from commit a0acc2c1)
parent 76b064e8
Loading
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -92,6 +92,18 @@ public function execute() {
      if (isset($table_information->serial_fields[0])) {
        $last_insert_id = $stmt->fetchField();
      }

      if (!empty($this->fromQuery) && !empty($table_information->serial_fields)) {
        // Set the sequence value if the table has serial fields and the from
        // query is either using all fields or includes a serial field.
        $from_fields = $this->fromQuery->getFields();
        foreach ($table_information->serial_fields as $index => $serial_field) {
          if (empty($from_fields) || isset($from_fields[$serial_field])) {
            $this->connection->query("SELECT setval('" . $table_information->sequences[$index] . "', MAX(" . $serial_field . ")) FROM {" . $this->table . "}");
          }
        }
      }

      $this->connection->releaseSavepoint();
    }
    catch (\Exception $e) {
+43 −0
Original line number Diff line number Diff line
@@ -236,4 +236,47 @@ public function testInsertIntegrityViolation(): void {
      ->execute();
  }

  /**
   * Tests inserting from a select into a table that has a serial primary key.
   */
  public function testInsertFromWithSerialKey(): void {
    // Create a copy of the test table.
    $schema = database_test_schema();
    $this->connection->schema()->createTable('test_backup', $schema['test']);

    $this->connection->insert('test_backup')->from($this->connection->select('test')->fields('test'))->execute();
    $this->assertSame(4, (int) $this->connection->select('test_backup')->countQuery()->execute()->fetchField());
    $this->connection->insert('test_backup')->fields([
      'name' => 'Larry',
      'age' => '30',
    ])->execute();
    $this->assertSame(5, (int) $this->connection->select('test_backup')->countQuery()->execute()->fetchField());

    // Recreate table to reset the serial counters.
    $this->connection->schema()->dropTable('test_backup');
    $this->connection->schema()->createTable('test_backup', $schema['test']);

    // Add fields to the select query, so that the primary key is included.
    $this->connection->insert('test_backup')->from($this->connection->select('test')->fields('test', ['id', 'name']))->execute();
    $this->assertSame(4, (int) $this->connection->select('test_backup')->countQuery()->execute()->fetchField());
    $this->connection->insert('test_backup')->fields([
      'name' => 'Larry',
      'age' => '30',
    ])->execute();
    $this->assertSame(5, (int) $this->connection->select('test_backup')->countQuery()->execute()->fetchField());

    // Recreate table to reset the serial counters.
    $this->connection->schema()->dropTable('test_backup');
    $this->connection->schema()->createTable('test_backup', $schema['test']);

    // Add fields to the select query, so that the primary key is included.
    $this->connection->insert('test_backup')->from($this->connection->select('test')->fields('test', ['name']))->execute();
    $this->assertSame(4, (int) $this->connection->select('test_backup')->countQuery()->execute()->fetchField());
    $this->connection->insert('test_backup')->fields([
      'name' => 'Larry',
      'age' => '30',
    ])->execute();
    $this->assertSame(5, (int) $this->connection->select('test_backup')->countQuery()->execute()->fetchField());
  }

}