diff --git a/README.md b/README.md index 9769833c7..409451fa6 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,9 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-spanner/tre | Run transaction with RPC priority | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/rpc-priority-transaction.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/rpc-priority-transaction.js,samples/README.md) | | Schema | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/schema.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/schema.js,samples/README.md) | | Struct | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/struct.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/struct.js,samples/README.md) | +| Alters a table with foreign key delete cascade action | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/table-alter-with-foreign-key-delete-cascade.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/table-alter-with-foreign-key-delete-cascade.js,samples/README.md) | +| Creates a table with foreign key delete cascade action | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/table-create-with-foreign-key-delete-cascade.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/table-create-with-foreign-key-delete-cascade.js,samples/README.md) | +| Drops a foreign key constraint with delete cascade action | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/table-drop-foreign-key-constraint-delete-cascade.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/table-drop-foreign-key-constraint-delete-cascade.js,samples/README.md) | | Timestamp | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/timestamp.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/timestamp.js,samples/README.md) | | Executes a read/write transaction with transaction and request tags | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/transaction-tag.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/transaction-tag.js,samples/README.md) | | Transaction | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/transaction.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/transaction.js,samples/README.md) | diff --git a/samples/README.md b/samples/README.md index 66dddee2c..7cbe6dc2f 100644 --- a/samples/README.md +++ b/samples/README.md @@ -98,6 +98,9 @@ and automatic, synchronous replication for high availability. * [Run transaction with RPC priority](#run-transaction-with-rpc-priority) * [Schema](#schema) * [Struct](#struct) + * [Alters a table with foreign key delete cascade action](#alters-a-table-with-foreign-key-delete-cascade-action) + * [Creates a table with foreign key delete cascade action](#creates-a-table-with-foreign-key-delete-cascade-action) + * [Drops a foreign key constraint with delete cascade action](#drops-a-foreign-key-constraint-with-delete-cascade-action) * [Timestamp](#timestamp) * [Executes a read/write transaction with transaction and request tags](#executes-a-read/write-transaction-with-transaction-and-request-tags) * [Transaction](#transaction) @@ -1545,6 +1548,57 @@ __Usage:__ +### Alters a table with foreign key delete cascade action + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/table-alter-with-foreign-key-delete-cascade.js). + +[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/table-alter-with-foreign-key-delete-cascade.js,samples/README.md) + +__Usage:__ + + +`node table-alter-with-foreign-key-delete-cascade.js ` + + +----- + + + + +### Creates a table with foreign key delete cascade action + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/table-create-with-foreign-key-delete-cascade.js). + +[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/table-create-with-foreign-key-delete-cascade.js,samples/README.md) + +__Usage:__ + + +`node table-create-with-foreign-key-delete-cascade.js.js ` + + +----- + + + + +### Drops a foreign key constraint with delete cascade action + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/table-drop-foreign-key-constraint-delete-cascade.js). + +[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/table-drop-foreign-key-constraint-delete-cascade.js,samples/README.md) + +__Usage:__ + + +`node table-drop-foreign-key-constraint-delete-cascade.js ` + + +----- + + + + ### Timestamp View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/timestamp.js). diff --git a/samples/system-test/spanner.test.js b/samples/system-test/spanner.test.js index c0e995540..c632af347 100644 --- a/samples/system-test/spanner.test.js +++ b/samples/system-test/spanner.test.js @@ -43,6 +43,12 @@ const dmlCmd = 'node dml.js'; const datatypesCmd = 'node datatypes.js'; const backupsCmd = 'node backups.js'; const instanceCmd = 'node instance.js'; +const createTableWithForeignKeyDeleteCascadeCommand = + 'node table-create-with-foreign-key-delete-cascade.js'; +const alterTableWithForeignKeyDeleteCascadeCommand = + 'node table-alter-with-foreign-key-delete-cascade.js'; +const dropForeignKeyConstraintDeleteCascaseCommand = + 'node table-drop-foreign-key-constraint-delete-cascade.js'; const CURRENT_TIME = Math.round(Date.now() / 1000).toString(); const PROJECT_ID = process.env.GCLOUD_PROJECT; @@ -1367,6 +1373,52 @@ describe('Spanner', () => { assert.include(output, 'Earliest version time:'); }); + it('should create a table with foreign key delete cascade', async () => { + const output = execSync( + `${createTableWithForeignKeyDeleteCascadeCommand} "${INSTANCE_ID}" "${DATABASE_ID}" ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp(`Waiting for operation on ${DATABASE_ID} to complete...`) + ); + assert.match( + output, + new RegExp( + 'Created Customers and ShoppingCarts table with FKShoppingCartsCustomerId' + ) + ); + }); + + it('should alter a table with foreign key delete cascade', async () => { + const output = execSync( + `${alterTableWithForeignKeyDeleteCascadeCommand} "${INSTANCE_ID}" "${DATABASE_ID}" ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp(`Waiting for operation on ${DATABASE_ID} to complete...`) + ); + assert.match( + output, + new RegExp('Altered ShoppingCarts table with FKShoppingCartsCustomerName') + ); + }); + + it('should drop a foreign key constraint delete cascade', async () => { + const output = execSync( + `${dropForeignKeyConstraintDeleteCascaseCommand} "${INSTANCE_ID}" "${DATABASE_ID}" ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp(`Waiting for operation on ${DATABASE_ID} to complete...`) + ); + assert.match( + output, + new RegExp( + 'Altered ShoppingCarts table to drop FKShoppingCartsCustomerName' + ) + ); + }); + describe('leader options', () => { before(async () => { const instance = spanner.instance(SAMPLE_INSTANCE_ID); diff --git a/samples/table-alter-with-foreign-key-delete-cascade.js b/samples/table-alter-with-foreign-key-delete-cascade.js new file mode 100644 index 000000000..444e864a3 --- /dev/null +++ b/samples/table-alter-with-foreign-key-delete-cascade.js @@ -0,0 +1,64 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// sample-metadata: +// title: Alters a table with foreign key delete cascade action +// usage: node table-alter-with-foreign-key-delete-cascade.js + +'use strict'; + +function main(instanceId, databaseId, projectId) { + // [START spanner_alter_table_with_foreign_key_delete_cascade] + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance-id'; + // const databaseId = 'my-database-id'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and a database. The database does not need to exist. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + async function alterTableWithForeignKeyDeleteCascade() { + const [operation] = await database.updateSchema([ + `ALTER TABLE ShoppingCarts + ADD CONSTRAINT FKShoppingCartsCustomerName + FOREIGN KEY (CustomerName) + REFERENCES Customers(CustomerName) + ON DELETE CASCADE`, + ]); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + await operation.promise(); + + console.log('Altered ShoppingCarts table with FKShoppingCartsCustomerName'); + } + alterTableWithForeignKeyDeleteCascade(); + // [END spanner_alter_table_with_foreign_key_delete_cascade] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/samples/table-create-with-foreign-key-delete-cascade.js b/samples/table-create-with-foreign-key-delete-cascade.js new file mode 100644 index 000000000..a41faf7f5 --- /dev/null +++ b/samples/table-create-with-foreign-key-delete-cascade.js @@ -0,0 +1,72 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// sample-metadata: +// title: Creates a table with foreign key delete cascade action +// usage: node table-create-with-foreign-key-delete-cascade.js.js + +'use strict'; + +function main(instanceId, databaseId, projectId) { + // [START spanner_create_table_with_foreign_key_delete_cascade] + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance-id'; + // const databaseId = 'my-database-id'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and a database. The database does not need to exist. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + async function createTableWithForeignKeyDeleteCascade() { + const [operation] = await database.updateSchema([ + `CREATE TABLE Customers ( + CustomerId INT64, + CustomerName STRING(62) NOT NULL + ) PRIMARY KEY (CustomerId)`, + `CREATE TABLE ShoppingCarts ( + CartId INT64 NOT NULL, + CustomerId INT64 NOT NULL, + CustomerName STRING(62) NOT NULL, + CONSTRAINT FKShoppingCartsCustomerId FOREIGN KEY (CustomerId) + REFERENCES Customers (CustomerId) ON DELETE CASCADE, + ) PRIMARY KEY (CartId)`, + ]); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + await operation.promise(); + + console.log( + 'Created Customers and ShoppingCarts table with FKShoppingCartsCustomerId' + ); + } + createTableWithForeignKeyDeleteCascade(); + // [END spanner_create_table_with_foreign_key_delete_cascade] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/samples/table-drop-foreign-key-constraint-delete-cascade.js b/samples/table-drop-foreign-key-constraint-delete-cascade.js new file mode 100644 index 000000000..def4292c2 --- /dev/null +++ b/samples/table-drop-foreign-key-constraint-delete-cascade.js @@ -0,0 +1,63 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// sample-metadata: +// title: Drops a foreign key constraint with delete cascade action +// usage: node table-drop-foreign-key-constraint-delete-cascade.js + +'use strict'; + +function main(instanceId, databaseId, projectId) { + // [START spanner_drop_foreign_key_constraint_delete_cascade] + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance-id'; + // const databaseId = 'my-database-id'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and a database. The database does not need to exist. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + async function dropForeignKeyConstraintDeleteCascade() { + const [operation] = await database.updateSchema([ + `ALTER TABLE ShoppingCarts + DROP CONSTRAINT FKShoppingCartsCustomerName`, + ]); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + await operation.promise(); + + console.log( + 'Altered ShoppingCarts table to drop FKShoppingCartsCustomerName' + ); + } + dropForeignKeyConstraintDeleteCascade(); + // [END spanner_drop_foreign_key_constraint_delete_cascade] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/system-test/spanner.ts b/system-test/spanner.ts index e6d0f7809..30983c1a8 100644 --- a/system-test/spanner.ts +++ b/system-test/spanner.ts @@ -86,7 +86,7 @@ describe('Spanner', () => { : spanner.instance(generateName('instance')); const INSTANCE_CONFIG = { - config: 'regional-us-west2', + config: 'regional-us-central1', nodes: 1, labels: { [LABEL]: 'true', @@ -2564,6 +2564,321 @@ describe('Spanner', () => { await setIamPolicy(PG_DATABASE); }); }); + + describe('ForeignKeyDeleteCascadeAction', () => { + before(async function () { + if (IS_EMULATOR_ENABLED) { + this.skip(); + } + }); + + const fkadc_database_id = generateName('fkadc'); + const fkadc_database_pg_id = generateName('fkadc-pg'); + const fkadc_database = instance.database(fkadc_database_id); + const fkadc_database_pg = instance.database(fkadc_database_pg_id); + + const fkadc_schema = [ + `CREATE TABLE Customers ( + CustomerId INT64, + CustomerName STRING(62) NOT NULL + ) PRIMARY KEY (CustomerId)`, + `CREATE TABLE ShoppingCarts ( + CartId INT64 NOT NULL, + CustomerId INT64 NOT NULL, + CustomerName STRING(62) NOT NULL, + CONSTRAINT FKShoppingCartsCustomerId FOREIGN KEY (CustomerId) + REFERENCES Customers (CustomerId) ON DELETE CASCADE, + ) PRIMARY KEY (CartId)`, + ]; + const fkadc_pg_schema = [ + `CREATE TABLE Customers ( + CustomerId BIGINT, + CustomerName VARCHAR(62) NOT NULL, + PRIMARY KEY (CustomerId) + ) `, + `CREATE TABLE ShoppingCarts ( + CartId BIGINT, + CustomerId BIGINT NOT NULL, + CustomerName VARCHAR(62) NOT NULL, + CONSTRAINT "FKShoppingCartsCustomerId" FOREIGN KEY (CustomerId) + REFERENCES Customers (CustomerId) ON DELETE CASCADE, + PRIMARY KEY (CartId) + )`, + ]; + + const createDatabaseWithFKADC = async ( + dialect, + database_id, + database_schema + ) => { + const [database, operation] = await instance.createDatabase( + database_id, + {databaseDialect: dialect} + ); + await operation.promise(); + + const [operationUpdateDDL] = await database.updateSchema( + database_schema + ); + await operationUpdateDDL.promise(); + + const [schema] = await database.getSchema(); + assert.strictEqual( + schema.filter(x => x.includes('FKShoppingCartsCustomerId')).length, + 1 + ); + }; + + it('GOOGLE_STANDARD_SQL should create a database with foreign key delete cascade action', async () => { + await createDatabaseWithFKADC( + Spanner.GOOGLE_STANDARD_SQL, + fkadc_database_id, + fkadc_schema + ); + }); + + it('POSTGRESQL should create a database with foreign key delete cascade action', async () => { + await createDatabaseWithFKADC( + Spanner.POSTGRESQL, + fkadc_database_pg_id, + fkadc_pg_schema + ); + }); + + const alterDatabaseWithFKADC = async (dialect, database) => { + const constraint_name = + dialect === Spanner.POSTGRESQL + ? '"FKShoppingCartsCustomerName"' + : 'FKShoppingCartsCustomerName'; + + const ddl_statements_add_constraints = [ + `ALTER TABLE ShoppingCarts ADD CONSTRAINT ${constraint_name} FOREIGN KEY (CustomerName) REFERENCES Customers(CustomerName) ON DELETE CASCADE`, + ]; + const [operationAddConstraint] = await database.updateSchema( + ddl_statements_add_constraints + ); + await operationAddConstraint.promise(); + const [schema] = await database.getSchema(); + assert.strictEqual( + schema.filter(x => x.includes('FKShoppingCartsCustomerName')).length, + 1 + ); + + const ddl_statements_drop_constraints = [ + 'ALTER TABLE ShoppingCarts DROP CONSTRAINT FKShoppingCartsCustomerName', + ]; + const [operationDropConstraint] = await database.updateSchema( + ddl_statements_drop_constraints + ); + await operationDropConstraint.promise(); + const [schema1] = await database.getSchema(); + assert.strictEqual( + schema1.filter(x => x.includes('FKShoppingCartsCustomerName')).length, + 0 + ); + }; + + it('GOOGLE_STANDARD_SQL should alter a database with foreign key delete cascade action', async () => { + await alterDatabaseWithFKADC( + Spanner.GOOGLE_STANDARD_SQL, + fkadc_database + ); + }); + + it('POSTGRESQL should alter a database with foreign key delete cascade action', async () => { + await alterDatabaseWithFKADC(Spanner.POSTGRESQL, fkadc_database_pg); + }); + + const insertAndDeleteRowWithFKADC = async database => { + const customersTable = database.table('Customers'); + await customersTable.insert({ + CustomerId: 1, + CustomerName: 'Marc', + }); + + const cartsTable = database.table('ShoppingCarts'); + await cartsTable.insert({ + CartId: 1, + CustomerId: 1, + CustomerName: 'Marc', + }); + + const [rows] = await cartsTable.read({ + columns: ['CartId', 'CustomerId'], + }); + assert.strictEqual(rows.length, 1); + + await customersTable.deleteRows([1]); + const [rows1] = await cartsTable.read({ + columns: ['CartId', 'CustomerId'], + }); + assert.strictEqual(rows1.length, 0); + }; + + it('GOOGLE_STANDARD_SQL should insert a row and then delete with all references', async () => { + await insertAndDeleteRowWithFKADC(fkadc_database); + }); + + it('POSTGRESQL should insert a row and then delete with all references', async () => { + await insertAndDeleteRowWithFKADC(fkadc_database_pg); + }); + + const insertRowErrorWithFKADC = async database => { + const cartsTable = database.table('ShoppingCarts'); + await cartsTable.insert({ + CartId: 2, + CustomerId: 2, + CustomerName: 'Jack', + }); + }; + + it('GOOGLE_STANDARD_SQL should throw error when insert a row without reference', async () => { + try { + await insertRowErrorWithFKADC(fkadc_database); + } catch (err) { + assert.match( + (err as grpc.ServiceError).message, + /Foreign key constraint `FKShoppingCartsCustomerId` is violated on table `ShoppingCarts`\./ + ); + } + }); + + it('POSTGRESQL should throw error when insert a row without reference', async () => { + try { + await insertRowErrorWithFKADC(fkadc_database_pg); + } catch (err) { + assert.match( + (err as grpc.ServiceError).message, + /Foreign key constraint `FKShoppingCartsCustomerId` is violated on table `shoppingcarts`\./ + ); + } + }); + + const insertAndDeleteInSameTransactionErrorWithFKADC = ( + done, + database + ) => { + database.runTransaction((err, transaction) => { + assert.ifError(err); + transaction!.insert('Customers', { + CustomerId: 2, + CustomerName: 'John', + }); + transaction!.deleteRows('Customers', [2]); + transaction!.commit(err => { + assert.match( + (err as grpc.ServiceError).message.toLowerCase(), + /9 failed_precondition: cannot write a value for the referenced column `customers.customerid` and delete it in the same transaction\./ + ); + done(); + }); + }); + }; + + it('GOOGLE_STANDARD_SQL should throw error when insert and delete a referenced key', done => { + insertAndDeleteInSameTransactionErrorWithFKADC(done, fkadc_database); + }); + + it('POSTGRESQL should throw error when insert and delete a referenced key', done => { + insertAndDeleteInSameTransactionErrorWithFKADC(done, fkadc_database_pg); + }); + + const insertReferencingKeyAndDeleteReferencedKeyErrorWithFKADC = ( + done, + database + ) => { + const customersTable = database.table('Customers'); + const cartsTable = database.table('ShoppingCarts'); + customersTable.insert( + [ + { + CustomerId: 2, + CustomerName: 'Marc', + }, + { + CustomerId: 3, + CustomerName: 'John', + }, + ], + err => { + assert.ifError(err); + cartsTable.insert( + { + CartId: 2, + CustomerId: 2, + CustomerName: 'Marc', + }, + err => { + assert.ifError(err); + database.runTransaction((err, transaction) => { + assert.ifError(err); + transaction!.update('ShoppingCarts', { + CartId: 2, + CustomerId: 3, + CustomerName: 'John', + }); + transaction!.deleteRows('Customers', [2]); + transaction!.commit(err => { + assert.match( + (err as grpc.ServiceError).message.toLowerCase(), + /9 failed_precondition: cannot modify a row in the table `shoppingcarts` because a referential action is deleting it in the same transaction\./ + ); + done(); + }); + }); + } + ); + } + ); + }; + + it('GOOGLE_STANDARD_SQL should throw error when insert a referencing key and delete a referenced key', done => { + insertReferencingKeyAndDeleteReferencedKeyErrorWithFKADC( + done, + fkadc_database + ); + }); + + it('POSTGRESQL should throw error when insert a referencing key and delete a referenced key', done => { + insertReferencingKeyAndDeleteReferencedKeyErrorWithFKADC( + done, + fkadc_database_pg + ); + }); + + const deleteRuleOnInformationSchemaReferentialConstraints = ( + done, + database + ) => { + database.getSnapshot((err, transaction) => { + assert.ifError(err); + + transaction!.run( + "SELECT DELETE_RULE FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_NAME = 'FKShoppingCartsCustomerId'", + (err, rows) => { + assert.ifError(err); + assert.strictEqual(rows[0][0].value, 'CASCADE'); + transaction!.end(); + done(); + } + ); + }); + }; + + it('GOOGLE_STANDARD_SQL should test information schema referential constraints', done => { + deleteRuleOnInformationSchemaReferentialConstraints( + done, + fkadc_database + ); + }); + + it('POSTGRESQL should test information schema referential constraints', done => { + deleteRuleOnInformationSchemaReferentialConstraints( + done, + fkadc_database_pg + ); + }); + }); }); describe('Backups', () => {