From 74a54b03f0d73a62edd524fa8d0248aea7ddf344 Mon Sep 17 00:00:00 2001 From: surbhigarg92 Date: Fri, 21 Jul 2023 12:04:13 +0000 Subject: [PATCH 1/3] feat: foreign key delete cascade testing, samples (#1825) --- README.md | 3 + samples/README.md | 54 +++ samples/system-test/spanner.test.js | 52 +++ ...e-alter-with-foreign-key-delete-cascade.js | 64 ++++ ...-create-with-foreign-key-delete-cascade.js | 72 ++++ ...p-foreign-key-constraint-delete-cascade.js | 63 ++++ system-test/spanner.ts | 317 +++++++++++++++++- 7 files changed, 624 insertions(+), 1 deletion(-) create mode 100644 samples/table-alter-with-foreign-key-delete-cascade.js create mode 100644 samples/table-create-with-foreign-key-delete-cascade.js create mode 100644 samples/table-drop-foreign-key-constraint-delete-cascade.js 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', () => { From ed510e8545876e188e7bd782b6db80e677c3063c Mon Sep 17 00:00:00 2001 From: surbhigarg92 Date: Fri, 21 Jul 2023 18:12:13 +0000 Subject: [PATCH 2/3] feat: Set LAR as False (#1883) --- src/index.ts | 10 +++++----- test/spanner.ts | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/index.ts b/src/index.ts index 76eccc6b3..558382ba7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -108,8 +108,8 @@ export type GetInstanceConfigOperationsCallback = PagedCallback< /** * Session pool configuration options. - * @property {boolean} [routeToLeaderEnabled=True] If set to false leader aware routing will be disabled. - * Disabling leader aware routing would route all requests in RW/PDML transactions to any region. + * @property {boolean} [routeToLeaderEnabled=False] If set to true leader aware routing will be enabled. + * Enabling leader aware routing would route all requests in RW/PDML transactions to leader region. */ export interface SpannerOptions extends GrpcClientOptions { apiEndpoint?: string; @@ -216,7 +216,7 @@ class Spanner extends GrpcService { projectIdReplaced_: boolean; projectFormattedName_: string; resourceHeader_: {[k: string]: string}; - routeToLeaderEnabled = true; + routeToLeaderEnabled = false; /** * Placeholder used to auto populate a column with the commit timestamp. @@ -318,8 +318,8 @@ class Spanner extends GrpcService { } as {} as GrpcServiceConfig; super(config, options); - if (options.routeToLeaderEnabled === false) { - this.routeToLeaderEnabled = false; + if (options.routeToLeaderEnabled === true) { + this.routeToLeaderEnabled = true; } this.options = options; diff --git a/test/spanner.ts b/test/spanner.ts index 2ca4697f0..322566389 100644 --- a/test/spanner.ts +++ b/test/spanner.ts @@ -1491,14 +1491,14 @@ describe('Spanner with mock server', () => { }); describe('LeaderAwareRouting', () => { - let spannerWithLARDisabled: Spanner; - let instanceWithLARDisabled: Instance; + let spannerWithLAREnabled: Spanner; + let instanceWithLAREnabled: Instance; - function newTestDatabaseWithLARDisabled( + function newTestDatabaseWithLAREnabled( options?: SessionPoolOptions, queryOptions?: IQueryOptions ): Database { - return instanceWithLARDisabled.database( + return instanceWithLAREnabled.database( `database-${dbCounter++}`, options, queryOptions @@ -1506,18 +1506,18 @@ describe('Spanner with mock server', () => { } before(() => { - spannerWithLARDisabled = new Spanner({ + spannerWithLAREnabled = new Spanner({ servicePath: 'localhost', port, sslCreds: grpc.credentials.createInsecure(), - routeToLeaderEnabled: false, + routeToLeaderEnabled: true, }); // Gets a reference to a Cloud Spanner instance and database - instanceWithLARDisabled = spannerWithLARDisabled.instance('instance'); + instanceWithLAREnabled = spannerWithLAREnabled.instance('instance'); }); it('should execute with leader aware routing enabled in a read/write transaction', async () => { - const database = newTestDatabase(); + const database = newTestDatabaseWithLAREnabled(); await database.runTransactionAsync(async tx => { await tx!.runUpdate({ sql: insertSql, @@ -1539,7 +1539,7 @@ describe('Spanner with mock server', () => { }); it('should execute with leader aware routing disabled in a read/write transaction', async () => { - const database = newTestDatabaseWithLARDisabled(); + const database = newTestDatabase(); await database.runTransactionAsync(async tx => { await tx!.runUpdate({ sql: insertSql, From f2a7ae9833e64a2cf8d5faed82e4301aed975e7f Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Sat, 22 Jul 2023 10:33:30 +0530 Subject: [PATCH 3/3] chore(main): release 6.14.0 (#1882) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- samples/package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1998ef225..681038afd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://www.npmjs.com/package/nodejs-spanner?activeTab=versions +## [6.14.0](https://github.com/googleapis/nodejs-spanner/compare/v6.13.0...v6.14.0) (2023-07-21) + + +### Features + +* Foreign key delete cascade testing, samples ([#1825](https://github.com/googleapis/nodejs-spanner/issues/1825)) ([74a54b0](https://github.com/googleapis/nodejs-spanner/commit/74a54b03f0d73a62edd524fa8d0248aea7ddf344)) +* Set LAR as False ([#1883](https://github.com/googleapis/nodejs-spanner/issues/1883)) ([ed510e8](https://github.com/googleapis/nodejs-spanner/commit/ed510e8545876e188e7bd782b6db80e677c3063c)) + ## [6.13.0](https://github.com/googleapis/nodejs-spanner/compare/v6.12.0...v6.13.0) (2023-07-21) diff --git a/package.json b/package.json index 1739d3443..4e948cc35 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@google-cloud/spanner", "description": "Cloud Spanner Client Library for Node.js", - "version": "6.13.0", + "version": "6.14.0", "license": "Apache-2.0", "author": "Google Inc.", "engines": { diff --git a/samples/package.json b/samples/package.json index 029520e17..bbd9e78e4 100644 --- a/samples/package.json +++ b/samples/package.json @@ -16,7 +16,7 @@ "dependencies": { "@google-cloud/kms": "^3.0.0", "@google-cloud/precise-date": "^3.0.0", - "@google-cloud/spanner": "^6.13.0", + "@google-cloud/spanner": "^6.14.0", "yargs": "^17.0.0" }, "devDependencies": {