diff --git a/samples/schema.js b/samples/schema.js index 6b2d9d9cf..b9dd6b09f 100644 --- a/samples/schema.js +++ b/samples/schema.js @@ -37,19 +37,20 @@ async function createDatabase(instanceID, databaseID, projectID) { const databaseAdminClient = spanner.getDatabaseAdminClient(); const createSingersTableStatement = ` - CREATE TABLE Singers ( - SingerId INT64 NOT NULL, - FirstName STRING(1024), - LastName STRING(1024), - SingerInfo BYTES(MAX) - ) PRIMARY KEY (SingerId)`; + CREATE TABLE Singers ( + SingerId INT64 NOT NULL, + FirstName STRING(1024), + LastName STRING(1024), + SingerInfo BYTES(MAX), + FullName STRING(2048) AS (ARRAY_TO_STRING([FirstName, LastName], " ")) STORED, + ) PRIMARY KEY (SingerId)`; const createAlbumsTableStatement = ` - CREATE TABLE Albums ( - SingerId INT64 NOT NULL, - AlbumId INT64 NOT NULL, - AlbumTitle STRING(MAX) - ) PRIMARY KEY (SingerId, AlbumId), - INTERLEAVE IN PARENT Singers ON DELETE CASCADE`; + CREATE TABLE Albums ( + SingerId INT64 NOT NULL, + AlbumId INT64 NOT NULL, + AlbumTitle STRING(MAX) + ) PRIMARY KEY (SingerId, AlbumId), + INTERLEAVE IN PARENT Singers ON DELETE CASCADE`; // Creates a new database try { diff --git a/samples/system-test/spanner.test.js b/samples/system-test/spanner.test.js index 630aad8db..3d18b8493 100644 --- a/samples/system-test/spanner.test.js +++ b/samples/system-test/spanner.test.js @@ -15,19 +15,34 @@ 'use strict'; const {Spanner} = require('@google-cloud/spanner'); -const pLimit = require('p-limit'); -const {describe, it, before, after, afterEach} = require('mocha'); const {KeyManagementServiceClient} = require('@google-cloud/kms'); const {assert} = require('chai'); +const {describe, it, before, after, afterEach} = require('mocha'); const cp = require('child_process'); +const pLimit = require('p-limit'); const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); -const instanceCmd = 'node instance.js'; -const schemaCmd = 'node schema.js'; -const backupsCmd = 'node backups.js'; + +const batchCmd = 'node batch.js'; const crudCmd = 'node crud.js'; -const datatypesCmd = 'node datatypes.js'; +const schemaCmd = 'node schema.js'; +const queryOptionsCmd = 'node queryoptions.js'; +const rpcPriorityRunCommand = 'node rpc-priority-run.js'; +const rpcPriorityReadCommand = 'node rpc-priority-read.js'; +const rpcPriorityBatchDMLCommand = 'node rpc-priority-batch-dml.js'; +const rpcPriorityPartitionedDMLCommand = 'node rpc-priority-partitioned-dml.js'; +const rpcPriorityTransactionCommand = 'node rpc-priority-transaction.js'; +const rpcPriorityQueryPartitionsCommand = + 'node rpc-priority-query-partitions.js'; +const transactionCmd = 'node transaction.js'; +const transactionTagCommand = 'node transaction-tag.js'; +const requestTagCommand = 'node request-tag.js'; const timestampCmd = 'node timestamp.js'; +const structCmd = 'node struct.js'; +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 = @@ -38,20 +53,20 @@ const dropForeignKeyConstraintDeleteCascaseCommand = const CURRENT_TIME = Math.round(Date.now() / 1000).toString(); const PROJECT_ID = process.env.GCLOUD_PROJECT; const PREFIX = 'test-instance'; -const SAMPLE_INSTANCE_ID = `${PREFIX}-my-sample-instance-${CURRENT_TIME}`; const INSTANCE_ID = process.env.SPANNERTEST_INSTANCE || `${PREFIX}-${CURRENT_TIME}`; +const SAMPLE_INSTANCE_ID = `${PREFIX}-my-sample-instance-${CURRENT_TIME}`; const SAMPLE_INSTANCE_CONFIG_ID = `custom-my-sample-instance-config-${CURRENT_TIME}`; -const INSTANCE_ALREADY_EXISTS = !!process.env.SPANNERTEST_INSTANCE; const BASE_INSTANCE_CONFIG_ID = 'regional-us-central1'; +const INSTANCE_ALREADY_EXISTS = !!process.env.SPANNERTEST_INSTANCE; const DATABASE_ID = `test-database-${CURRENT_TIME}`; -const ENCRYPTED_DATABASE_ID = `test-database-${CURRENT_TIME}-enc`; -const DEFAULT_LEADER_DATABASE_ID = `test-database-${CURRENT_TIME}-dl`; -const VERSION_RETENTION_DATABASE_ID = `test-database-${CURRENT_TIME}-v`; -const SEQUENCE_DATABASE_ID = `test-seq-database-${CURRENT_TIME}-r`; const PG_DATABASE_ID = `test-pg-database-${CURRENT_TIME}`; const RESTORE_DATABASE_ID = `test-database-${CURRENT_TIME}-r`; const ENCRYPTED_RESTORE_DATABASE_ID = `test-database-${CURRENT_TIME}-r-enc`; +const VERSION_RETENTION_DATABASE_ID = `test-database-${CURRENT_TIME}-v`; +const ENCRYPTED_DATABASE_ID = `test-database-${CURRENT_TIME}-enc`; +const DEFAULT_LEADER_DATABASE_ID = `test-database-${CURRENT_TIME}-dl`; +const SEQUENCE_DATABASE_ID = `test-seq-database-${CURRENT_TIME}-r`; const BACKUP_ID = `test-backup-${CURRENT_TIME}`; const COPY_BACKUP_ID = `test-copy-backup-${CURRENT_TIME}`; const ENCRYPTED_BACKUP_ID = `test-backup-${CURRENT_TIME}-enc`; @@ -121,6 +136,7 @@ async function deleteInstance(instance) { await Promise.all(backups.map(backup => backup.delete(GAX_OPTIONS))); return instance.delete(GAX_OPTIONS); } + async function getCryptoKey() { const NOT_FOUND = 5; @@ -183,6 +199,7 @@ async function getCryptoKey() { } } } + describe('Autogenerated Admin Clients', () => { const instance = spanner.instance(INSTANCE_ID); @@ -233,6 +250,7 @@ describe('Autogenerated Admin Clients', () => { } await spanner.instance(SAMPLE_INSTANCE_ID).delete(GAX_OPTIONS); }); + describe('instance', () => { afterEach(async () => { const sample_instance = spanner.instance(SAMPLE_INSTANCE_ID); @@ -324,7 +342,7 @@ describe('Autogenerated Admin Clients', () => { // create_database it('should create an example database', async () => { const output = execSync( - `${schemaCmd} createDatabase "${INSTANCE_ID}" "${DATABASE_ID}" "${PROJECT_ID}"` + `${schemaCmd} createDatabase "${INSTANCE_ID}" "${DATABASE_ID}" ${PROJECT_ID}` ); assert.match( output, @@ -367,7 +385,7 @@ describe('Autogenerated Admin Clients', () => { const key = await getCryptoKey(); const output = execSync( - `${schemaCmd} createDatabaseWithEncryptionKey "${INSTANCE_ID}" "${ENCRYPTED_DATABASE_ID}" "${PROJECT_ID}" "${key.name}"` + `${schemaCmd} createDatabaseWithEncryptionKey "${INSTANCE_ID}" "${ENCRYPTED_DATABASE_ID}" ${PROJECT_ID} "${key.name}"` ); assert.match( output, @@ -388,160 +406,14 @@ describe('Autogenerated Admin Clients', () => { }); }); - describe('postgreSQL', () => { - before(async () => { - const instance = spanner.instance(SAMPLE_INSTANCE_ID); - const [, operation] = await instance.create({ - config: PG_LOCATION_ID, - nodes: 1, - displayName: 'PostgreSQL Test', - labels: { - ['cloud_spanner_samples']: 'true', - created: Math.round(Date.now() / 1000).toString(), // current time - }, - }); - await operation.promise(); - }); - - after(async () => { - const instance = spanner.instance(SAMPLE_INSTANCE_ID); - await instance.delete(); - }); - - // create_pg_database - it('should create an example PostgreSQL database', async () => { - const output = execSync( - `node pg-database-create.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` - ); - assert.match( - output, - new RegExp(`Waiting for operation on ${PG_DATABASE_ID} to complete...`) - ); - assert.match( - output, - new RegExp( - `Created database ${PG_DATABASE_ID} on instance ${SAMPLE_INSTANCE_ID} with dialect POSTGRESQL.` - ) - ); - }); - - // pg_interleaving - it('should create an interleaved table hierarchy using PostgreSQL dialect', async () => { - const output = execSync( - `node pg-interleaving.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` - ); - assert.match( - output, - new RegExp(`Waiting for operation on ${PG_DATABASE_ID} to complete...`) - ); - assert.match( - output, - new RegExp( - `Created an interleaved table hierarchy in database ${PG_DATABASE_ID} using PostgreSQL dialect.` - ) - ); - }); - - // pg_add_column - it('should add a column to a table in the Spanner PostgreSQL database.', async () => { - const output = execSync( - `node pg-add-column.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` - ); - assert.match( - output, - new RegExp( - `Added MarketingBudget column to Albums table in database ${PG_DATABASE_ID}` - ) - ); - }); - - //pg_create_index - it('should create an index in the Spanner PostgreSQL database.', async () => { - const output = execSync( - `node pg-index-create-storing.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` - ); - assert.match(output, new RegExp('Added the AlbumsByAlbumTitle index.')); - }); - - // pg_numeric_data_type - it('should create a table, insert and query pg numeric data', async () => { - const output = execSync( - `node pg-numeric-data-type.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` - ); - assert.match( - output, - new RegExp(`Waiting for operation on ${PG_DATABASE_ID} to complete...`) - ); - assert.match( - output, - new RegExp(`Added table venues to database ${PG_DATABASE_ID}.`) - ); - assert.match(output, new RegExp('Inserted data.')); - assert.match(output, new RegExp('VenueId: 4, Revenue: 97372.3863')); - assert.match(output, new RegExp('VenueId: 19, Revenue: 7629')); - assert.match(output, new RegExp('VenueId: 398, Revenue: 0.000000123')); - }); - - // pg_jsonb_add_column - it('should add a jsonb column to a table', async () => { - const output = execSync( - `node pg-jsonb-add-column.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` - ); - assert.match( - output, - new RegExp(`Waiting for operation on ${PG_DATABASE_ID} to complete...`) - ); - assert.match( - output, - new RegExp( - `Added jsonb column to table venues to database ${PG_DATABASE_ID}.` - ) - ); - }); - - // pg_create_sequence - it('should create a sequence', async () => { - const output = execSync( - `node pg-sequence-create.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` - ); - assert.match( - output, - new RegExp('Created Seq sequence and Customers table') - ); - assert.match( - output, - new RegExp('Number of customer records inserted is: 3') - ); - }); - - // pg_alter_sequence - it('should alter a sequence', async () => { - const output = execSync( - `node pg-sequence-alter.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` - ); - assert.match( - output, - new RegExp( - 'Altered Seq sequence to skip an inclusive range between 1000 and 5000000.' - ) - ); - assert.match( - output, - new RegExp('Number of customer records inserted is: 3') - ); - }); - - // pg_drop_sequence - it('should drop a sequence', async () => { + describe('quickstart', () => { + // Running the quickstart test in here since there's already a spanner + // instance and database set up at this point. + it('should query a table', async () => { const output = execSync( - `node pg-sequence-drop.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` - ); - assert.match( - output, - new RegExp( - 'Altered Customers table to drop DEFAULT from CustomerId column and dropped the Seq sequence.' - ) + `node quickstart ${PROJECT_ID} ${INSTANCE_ID} ${DATABASE_ID}` ); + assert.match(output, /Query: \d+ found./); }); }); @@ -553,6 +425,36 @@ describe('Autogenerated Admin Clients', () => { assert.match(output, /Inserted data\./); }); + // delete_data + it('should delete and then insert rows in the example tables', async () => { + let output = execSync( + `${crudCmd} delete ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.include(output, 'Deleted individual rows in Albums.'); + assert.include(output, '2 records deleted from Singers.'); + assert.include(output, '3 records deleted from Singers.'); + output = execSync( + `${crudCmd} insert ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /Inserted data\./); + }); + + // query_data + it('should query an example table and return matching rows', async () => { + const output = execSync( + `${crudCmd} query ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk/); + }); + + // read_data + it('should read an example table', async () => { + const output = execSync( + `${crudCmd} read ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk/); + }); + // add_column it('should add a column to a table', async () => { const output = execSync( @@ -570,6 +472,24 @@ describe('Autogenerated Admin Clients', () => { assert.match(output, /Updated data\./); }); + // read_stale_data + it('should read stale data from an example table', async () => { + // read-stale-data reads data that is exactly 15 seconds old. So, make sure + // 15 seconds have elapsed since the update_data test. + await new Promise(r => setTimeout(r, 16000)); + const output = execSync( + `${crudCmd} read-stale ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + /SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk, MarketingBudget: 100000/ + ); + assert.match( + output, + /SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget: 500000/ + ); + }); + // query_data_with_new_column it('should query an example table with an additional column and return matching rows', async () => { const output = execSync( @@ -601,91 +521,468 @@ describe('Autogenerated Admin Clients', () => { assert.match(output, /Added the AlbumsByAlbumTitle2 index\./); }); - // add_timestamp_column - it('should add a timestamp column to a table', async () => { + // query_data_with_index + it('should query an example table with an index and return matching rows', async () => { const output = execSync( - `${timestampCmd} addTimestampColumn ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + `node index-query-data ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` ); - assert.match(output, /Waiting for operation to complete\.\.\./); assert.match( output, - /Added LastUpdateTime as a commit timestamp column in Albums table\./ + /AlbumId: 2, AlbumTitle: Go, Go, Go, MarketingBudget:/ ); - }); - - // update_data_with_timestamp_column - it('should update existing rows in an example table with commit timestamp column', async () => { - const output = execSync( - `${timestampCmd} updateWithTimestamp ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + assert.notMatch( + output, + /AlbumId: 1, AlbumTitle: Total Junk, MarketingBudget:/ ); - assert.match(output, /Updated data\./); }); - // query_data_with_timestamp_column - it('should query an example table with an additional timestamp column and return matching rows', async () => { + it('should respect query boundaries when querying an example table with an index', async () => { const output = execSync( - `${timestampCmd} queryWithTimestamp ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + `node index-query-data ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID} "Ardvark" "Zoo"` ); assert.match( output, - /SingerId: 1, AlbumId: 1, MarketingBudget: 1000000, LastUpdateTime:/ + /AlbumId: 1, AlbumTitle: Total Junk, MarketingBudget:/ ); assert.match( output, - /SingerId: 2, AlbumId: 2, MarketingBudget: 750000, LastUpdateTime:/ + /AlbumId: 2, AlbumTitle: Go, Go, Go, MarketingBudget:/ ); }); - // create_table_with_timestamp_column - it('should create an example table with a timestamp column', async () => { + // read_data_with_index + it('should read an example table with an index', async () => { const output = execSync( - `${timestampCmd} createTableWithTimestamp "${INSTANCE_ID}" "${DATABASE_ID}" ${PROJECT_ID}` + `node index-read-data ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` ); + assert.match(output, /AlbumId: 1, AlbumTitle: Total Junk/); + }); - assert.match( - output, - new RegExp(`Waiting for operation on ${DATABASE_ID} to complete...`) - ); - assert.match( - output, - new RegExp(`Created table Performances in database ${DATABASE_ID}.`) + // read_data_with_storing_index + it('should read an example table with a storing index', async () => { + const output = execSync( + `node index-read-data-with-storing ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` ); + assert.match(output, /AlbumId: 1, AlbumTitle: Total Junk/); }); - // insert_data_with_timestamp - it('should insert rows into an example table with timestamp column', async () => { + // spanner_create_client_with_query_options + it('should use query options from a database reference', async () => { const output = execSync( - `${timestampCmd} insertWithTimestamp ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + `${queryOptionsCmd} databaseWithQueryOptions ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + /AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget:/ ); - assert.match(output, /Inserted data\./); }); - // query_new_table_with_timestamp - it('should query an example table with a non-null timestamp column and return matching rows', async () => { + // spanner_query_with_query_options + it('should use query options on request', async () => { const output = execSync( - `${timestampCmd} queryTableWithTimestamp ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + `${queryOptionsCmd} queryWithQueryOptions ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + /AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget:/ ); - assert.match(output, /SingerId: 1, VenueId: 4, EventDate:/); - assert.match(output, /Revenue: 15000, LastUpdateTime:/); }); - // create_table_with_datatypes - it('should create Venues example table with supported datatype columns', async () => { + // query with RPC priority for run command + it('should use RPC priority from request options for run command', async () => { const output = execSync( - `${datatypesCmd} createVenuesTable "${INSTANCE_ID}" "${DATABASE_ID}" ${PROJECT_ID}` + `${rpcPriorityRunCommand} ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` ); - assert.match( output, - new RegExp(`Waiting for operation on ${DATABASE_ID} to complete...`) + /Successfully fetched \d rows using low RPC priority\./ ); assert.match( output, - new RegExp(`Created table Venues in database ${DATABASE_ID}.`) + /AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget:/ ); }); - // insert_datatypes_data + // query with RPC priority for Read command + it('should use RPC priority from request options for read command', async () => { + const output = execSync( + `${rpcPriorityReadCommand} ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + /Successfully fetched \d rows using low RPC priority\./ + ); + assert.match(output, /SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk/); + }); + + // query with RPC priority for transaction command + it('should use RPC priority from request options for transaction command', async () => { + const output = execSync( + `${rpcPriorityTransactionCommand} ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + /Successfully inserted 1 record into the Singers table using low RPC priority\./ + ); + }); + + // query with RPC priority for batch DML command + it('should use RPC priority from request options for batch DML command', async () => { + const output = execSync( + `${rpcPriorityBatchDMLCommand} ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + /Successfully executed 2 SQL statements using Batch DML using low RPC priority\./ + ); + }); + + // query with RPC priority for partitioned DML command + it('should use RPC priority from request options for partitioned DML command', async () => { + const output = execSync( + `${rpcPriorityPartitionedDMLCommand} ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp('Successfully updated (\\d+) records using low RPC priority.') + ); + }); + + // query with RPC priority for Query partitions command + it('should use RPC priority from request options for Query partition command', async () => { + const output = execSync( + `${rpcPriorityQueryPartitionsCommand} ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + /Successfully created \d query partitions using low RPC priority\./ + ); + assert.match(output, /Successfully received \d from executed partitions\./); + }); + + // read_only_transactioni + it('should read an example table using transactions', async () => { + const output = execSync( + `${transactionCmd} readOnly ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk/); + assert.match(output, /Successfully executed read-only transaction\./); + }); + + // read_write_transaction + it('should read from and write to an example table using transactions', async () => { + let output = execSync( + `${transactionCmd} readWrite ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /The first album's marketing budget: 100000/); + assert.match(output, /The second album's marketing budget: 500000/); + assert.match( + output, + /Successfully executed read-write transaction to transfer 200000 from Album 2 to Album 1./ + ); + output = execSync( + `${schemaCmd} queryNewColumn ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /SingerId: 1, AlbumId: 1, MarketingBudget: 300000/); + assert.match(output, /SingerId: 2, AlbumId: 2, MarketingBudget: 300000/); + }); + + // batch_client + it('should create and execute query partitions', async () => { + const output = execSync( + `${batchCmd} create-and-execute-query-partitions ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /Successfully created \d query partitions\./); + assert.match(output, /Successfully received \d from executed partitions\./); + }); + + // execute_partition + it('should execute a partition', async () => { + const instance = spanner.instance(INSTANCE_ID); + const database = instance.database(DATABASE_ID); + const [transaction] = await database.createBatchTransaction(); + const identifier = JSON.stringify(transaction.identifier()); + + const query = 'SELECT SingerId FROM Albums'; + const [partitions] = await transaction.createQueryPartitions(query); + const partition = JSON.stringify(partitions[0]); + + const output = execSync( + `${batchCmd} execute-partition ${INSTANCE_ID} ${DATABASE_ID} '${identifier}' '${partition}' ${PROJECT_ID}` + ); + assert.match(output, /Successfully received \d from executed partition\./); + await transaction.close(); + }); + + // add_timestamp_column + it('should add a timestamp column to a table', async () => { + const output = execSync( + `${timestampCmd} addTimestampColumn ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /Waiting for operation to complete\.\.\./); + assert.match( + output, + /Added LastUpdateTime as a commit timestamp column in Albums table\./ + ); + }); + + // update_data_with_timestamp_column + it('should update existing rows in an example table with commit timestamp column', async () => { + const output = execSync( + `${timestampCmd} updateWithTimestamp ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /Updated data\./); + }); + + // query_data_with_timestamp_column + it('should query an example table with an additional timestamp column and return matching rows', async () => { + const output = execSync( + `${timestampCmd} queryWithTimestamp ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + /SingerId: 1, AlbumId: 1, MarketingBudget: 1000000, LastUpdateTime:/ + ); + assert.match( + output, + /SingerId: 2, AlbumId: 2, MarketingBudget: 750000, LastUpdateTime:/ + ); + }); + + // create_table_with_timestamp_column + it('should create an example table with a timestamp column', async () => { + const output = execSync( + `${timestampCmd} createTableWithTimestamp "${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 table Performances in database ${DATABASE_ID}.`) + ); + }); + + // insert_data_with_timestamp + it('should insert rows into an example table with timestamp column', async () => { + const output = execSync( + `${timestampCmd} insertWithTimestamp ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /Inserted data\./); + }); + + // query_new_table_with_timestamp + it('should query an example table with a non-null timestamp column and return matching rows', async () => { + const output = execSync( + `${timestampCmd} queryTableWithTimestamp ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /SingerId: 1, VenueId: 4, EventDate:/); + assert.match(output, /Revenue: 15000, LastUpdateTime:/); + }); + + // write_data_for_struct_queries + it('should insert rows into an example table for use with struct query examples', async () => { + const output = execSync( + `${structCmd} writeDataForStructQueries ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /Inserted data\./); + }); + + // query_with_struct_param + it('should query an example table with a STRUCT param', async () => { + const output = execSync( + `${structCmd} queryDataWithStruct ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /SingerId: 6/); + }); + + // query_with_array_of_struct_param + it('should query an example table with an array of STRUCT param', async () => { + const output = execSync( + `${structCmd} queryWithArrayOfStruct ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /SingerId: 6\nSingerId: 7\nSingerId: 8/); + }); + + // query_with_struct_field_param + it('should query an example table with a STRUCT field param', async () => { + const output = execSync( + `${structCmd} queryStructField ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /SingerId: 6/); + }); + + // query_with_nested_struct_param + it('should query an example table with a nested STRUCT param', async () => { + const output = execSync( + `${structCmd} queryNestedStructField ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + /SingerId: 6, SongName: Imagination\nSingerId: 9, SongName: Imagination/ + ); + }); + + // dml_standard_insert + it('should insert rows into an example table using a DML statement', async () => { + const output = execSync( + `${dmlCmd} insertUsingDml ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + /Successfully inserted 1 record into the Singers table/ + ); + }); + + // dml_standard_update + it('should update a row in an example table using a DML statement', async () => { + const output = execSync( + `${dmlCmd} updateUsingDml ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /Successfully updated 1 record/); + }); + + // dml_standard_delete + it('should delete a row from an example table using a DML statement', async () => { + const output = execSync( + `${dmlCmd} deleteUsingDml ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /Successfully deleted 1 record\./); + }); + + // dml_standard_update_with_timestamp + it('should update the timestamp of multiple records in an example table using a DML statement', async () => { + const output = execSync( + `${dmlCmd} updateUsingDmlWithTimestamp ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /Successfully updated 3 records/); + }); + + // dml_write_then_read + it('should insert a record in an example table using a DML statement and then query the record', async () => { + const output = execSync( + `${dmlCmd} writeAndReadUsingDml ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /Timothy Campbell/); + }); + + // dml_structs + it('should update a record in an example table using a DML statement along with a struct value', async () => { + const output = execSync( + `${dmlCmd} updateUsingDmlWithStruct ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /Successfully updated 1 record/); + }); + + // dml_getting_started_insert + it('should insert multiple records into an example table using a DML statement', async () => { + const output = execSync( + `${dmlCmd} writeUsingDml ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /4 records inserted/); + }); + + // dml_query_with_parameter + it('should use a parameter query to query record that was inserted using a DML statement', async () => { + const output = execSync( + `${dmlCmd} queryWithParameter ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /SingerId: 12, FirstName: Melissa, LastName: Garcia/); + }); + + // dml_getting_started_update + it('should transfer value from one record to another using DML statements within a transaction', async () => { + const output = execSync( + `${dmlCmd} writeWithTransactionUsingDml ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + /Successfully executed read-write transaction using DML to transfer 200000 from Album 2 to Album 1/ + ); + }); + + // dml_partitioned_update + it('should update multiple records using a partitioned DML statement', async () => { + const output = execSync( + `${dmlCmd} updateUsingPartitionedDml ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /Successfully updated 3 records/); + }); + + // dml_partitioned_delete + it('should delete multiple records using a partitioned DML statement', async () => { + const output = execSync( + `${dmlCmd} deleteUsingPartitionedDml ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /Successfully deleted 6 records/); + }); + + // dml_batch_update + it('should insert and update records using Batch DML', async () => { + const output = execSync( + `${dmlCmd} updateUsingBatchDml ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + /Successfully executed 2 SQL statements using Batch DML/ + ); + }); + + // dml_returning_insert + it('should insert records using DML Returning', async () => { + const output = execSync( + `node dml-returning-insert ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp('Successfully inserted 1 record into the Singers table') + ); + assert.match(output, new RegExp('Virginia Watson')); + }); + + // dml_returning_update + it('should update records using DML Returning', async () => { + const output = execSync( + `node dml-returning-update ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp('Successfully updated 1 record into the Albums table') + ); + assert.match(output, new RegExp('2000000')); + }); + + // dml_returning_delete + it('should delete records using DML Returning', async () => { + const output = execSync( + `node dml-returning-delete ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp('Successfully deleted 1 record from the Singers table') + ); + assert.match(output, new RegExp('Virginia Watson')); + }); + + // create_table_with_datatypes + it('should create Venues example table with supported datatype columns', async () => { + const output = execSync( + `${datatypesCmd} createVenuesTable "${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 table Venues in database ${DATABASE_ID}.`) + ); + }); + + // insert_datatypes_data it('should insert multiple records into Venues example table', async () => { const output = execSync( `${datatypesCmd} insertData ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` @@ -816,6 +1113,22 @@ describe('Autogenerated Admin Clients', () => { assert.match(output, /VenueId: 4, Revenue: 35000/); }); + // query with request tag + it('should execute a query with a request tag', async () => { + const output = execSync( + `${requestTagCommand} ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk/); + }); + + // read_write_transaction with transaction tag + it('should execute a read/write transaction with a transaction tag', async () => { + const output = execSync( + `${transactionTagCommand} ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.include(output, 'Inserted new outdoor venue'); + }); + // add_json_column it('should add a VenueDetails column to Venues example table', async () => { const output = execSync( @@ -864,6 +1177,17 @@ describe('Autogenerated Admin Clients', () => { ); }); + // read_data_with_database_role + it('should read data with database role', async () => { + const output = execSync( + `node read-data-with-database-role.js ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp('SingerId: 1, FirstName: Marc, LastName: Richards') + ); + }); + // get_database_roles it('should list database roles', async () => { const output = execSync( @@ -951,16 +1275,12 @@ describe('Autogenerated Admin Clients', () => { assert.match(output, /Create Backup Operations:/); assert.match( output, - new RegExp( - `Backup (.+)${BACKUP_ID} on database (.+)${DATABASE_ID} is 100% complete.` - ) + new RegExp(`Backup (.+)${BACKUP_ID} (.+) is 100% complete`) ); assert.match(output, /Copy Backup Operations:/); assert.match( output, - new RegExp( - `Backup (.+)${COPY_BACKUP_ID} copied from source backup (.+)${BACKUP_ID} is 100% complete` - ) + new RegExp(`Backup (.+)${COPY_BACKUP_ID} (.+) is 100% complete`) ); }); @@ -1040,7 +1360,6 @@ describe('Autogenerated Admin Clients', () => { // Wait for database to finish optimizing - cannot delete a backup if a database restored from it const instance = spanner.instance(INSTANCE_ID); const database = instance.database(RESTORE_DATABASE_ID); - while ((await database.getState()) === 'READY_OPTIMIZING') { await sleep(1000); } @@ -1051,6 +1370,22 @@ describe('Autogenerated Admin Clients', () => { assert.match(output, /Backup deleted./); }); + // custom_timeout_and_retry + it('should insert with custom timeout and retry settings', async () => { + const output = execSync( + `${dmlCmd} insertWithCustomTimeoutAndRetrySettings ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, /record inserted./); + }); + + // get_commit_stats + it('should update rows in Albums example table and return CommitStats', async () => { + const output = execSync( + `${crudCmd} getCommitStats ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, new RegExp('Updated data with (\\d+) mutations')); + }); + // create_database_with_version_retention_period it('should create a database with a version retention period', async () => { const output = execSync( @@ -1102,20 +1437,215 @@ describe('Autogenerated Admin Clients', () => { ); }); - 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' - ) - ); + 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); + const [, operation] = await instance.create({ + config: 'nam6', + nodes: 1, + displayName: 'Multi-region options test', + labels: { + ['cloud_spanner_samples']: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + }); + await operation.promise(); + }); + + after(async () => { + const instance = spanner.instance(SAMPLE_INSTANCE_ID); + await instance.delete(); + }); + + // create_instance_config + it('should create an example custom instance config', async () => { + const output = execSync( + `node instance-config-create.js ${SAMPLE_INSTANCE_CONFIG_ID} ${BASE_INSTANCE_CONFIG_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp( + `Waiting for create operation for ${SAMPLE_INSTANCE_CONFIG_ID} to complete...` + ) + ); + assert.match( + output, + new RegExp(`Created instance config ${SAMPLE_INSTANCE_CONFIG_ID}.`) + ); + }); + + // update_instance_config + it('should update an example custom instance config', async () => { + const output = execSync( + `node instance-config-update.js ${SAMPLE_INSTANCE_CONFIG_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp( + `Waiting for update operation for ${SAMPLE_INSTANCE_CONFIG_ID} to complete...` + ) + ); + assert.match( + output, + new RegExp(`Updated instance config ${SAMPLE_INSTANCE_CONFIG_ID}.`) + ); + }); + + // delete_instance_config + it('should delete an example custom instance config', async () => { + const output = execSync( + `node instance-config-delete.js ${SAMPLE_INSTANCE_CONFIG_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp(`Deleting ${SAMPLE_INSTANCE_CONFIG_ID}...`) + ); + assert.match( + output, + new RegExp(`Deleted instance config ${SAMPLE_INSTANCE_CONFIG_ID}.`) + ); + }); + + // list_instance_config_operations + it('should list all instance config operations', async () => { + const output = execSync( + `node instance-config-get-operations.js ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp( + `Available instance config operations for project ${PROJECT_ID}:` + ) + ); + assert.include(output, 'Instance config operation for'); + assert.include( + output, + 'type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceConfigMetadata' + ); + }); + + // list_instance_configs + it('should list available instance configs', async () => { + const output = execSync(`node list-instance-configs.js ${PROJECT_ID}`); + assert.match( + output, + new RegExp(`Available instance configs for project ${PROJECT_ID}:`) + ); + assert.include(output, 'Available leader options for instance config'); + }); + + // get_instance_config + // TODO: Enable when the feature has been released. + it.skip('should get a specific instance config', async () => { + const output = execSync(`node get-instance-config.js ${PROJECT_ID}`); + assert.include(output, 'Available leader options for instance config'); + }); + + // create_database_with_default_leader + it('should create a database with a default leader', async () => { + const output = execSync( + `node database-create-with-default-leader.js "${SAMPLE_INSTANCE_ID}" "${DEFAULT_LEADER_DATABASE_ID}" "${DEFAULT_LEADER}" ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp( + `Waiting for creation of ${DEFAULT_LEADER_DATABASE_ID} to complete...` + ) + ); + assert.match( + output, + new RegExp( + `Created database ${DEFAULT_LEADER_DATABASE_ID} with default leader ${DEFAULT_LEADER}.` + ) + ); + }); + + // update_database_with_default_leader + it('should update a database with a default leader', async () => { + const output = execSync( + `node database-update-default-leader.js "${SAMPLE_INSTANCE_ID}" "${DEFAULT_LEADER_DATABASE_ID}" "${DEFAULT_LEADER_2}" ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp( + `Waiting for updating of ${DEFAULT_LEADER_DATABASE_ID} to complete...` + ) + ); + assert.match( + output, + new RegExp( + `Updated database ${DEFAULT_LEADER_DATABASE_ID} with default leader ${DEFAULT_LEADER_2}.` + ) + ); + }); + + // get_default_leader + it('should get the default leader option of a database', async () => { + const output = execSync( + `node database-get-default-leader.js "${SAMPLE_INSTANCE_ID}" "${DEFAULT_LEADER_DATABASE_ID}" ${PROJECT_ID}` + ); + assert.include( + output, + `The default_leader for ${DEFAULT_LEADER_DATABASE_ID} is ${DEFAULT_LEADER_2}` + ); + }); + + // list_databases + it('should list databases on the instance', async () => { + const output = execSync( + `node list-databases.js "${SAMPLE_INSTANCE_ID}" ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp( + `Databases for projects/${PROJECT_ID}/instances/${SAMPLE_INSTANCE_ID}:` + ) + ); + assert.include(output, `(default leader = ${DEFAULT_LEADER_2}`); + }); + + // get_database_ddl + it('should get the ddl of a database', async () => { + const output = execSync( + `node database-get-ddl.js "${SAMPLE_INSTANCE_ID}" "${DEFAULT_LEADER_DATABASE_ID}" ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp( + `Retrieved database DDL for projects/${PROJECT_ID}/instances/${SAMPLE_INSTANCE_ID}/databases/${DEFAULT_LEADER_DATABASE_ID}:` + ) + ); + assert.include(output, 'CREATE TABLE Singers'); + }); + + // max_commit_delay + it('should update rows in Albums example table when max commit delay is set', async () => { + const output = execSync( + `node max-commit-delay.js "${INSTANCE_ID}" "${DATABASE_ID}" "${PROJECT_ID}"` + ); + assert.match( + output, + new RegExp( + 'Successfully inserted (\\d+) record into the Singers table.' + ) + ); + }); }); describe('sequence', () => { @@ -1179,13 +1709,13 @@ describe('Autogenerated Admin Clients', () => { }); }); - describe('leader options', () => { + describe('postgreSQL', () => { before(async () => { const instance = spanner.instance(SAMPLE_INSTANCE_ID); const [, operation] = await instance.create({ - config: 'nam6', + config: PG_LOCATION_ID, nodes: 1, - displayName: 'Multi-region options test', + displayName: 'PostgreSQL Test', labels: { ['cloud_spanner_samples']: 'true', created: Math.round(Date.now() / 1000).toString(), // current time @@ -1199,182 +1729,325 @@ describe('Autogenerated Admin Clients', () => { await instance.delete(); }); - // create_instance_config - it('should create an example custom instance config', async () => { + // create_pg_database + it('should create an example PostgreSQL database', async () => { const output = execSync( - `node instance-config-create.js ${SAMPLE_INSTANCE_CONFIG_ID} ${BASE_INSTANCE_CONFIG_ID} ${PROJECT_ID}` + `node pg-database-create.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` ); assert.match( output, - new RegExp( - `Waiting for create operation for ${SAMPLE_INSTANCE_CONFIG_ID} to complete...` - ) + new RegExp(`Waiting for operation on ${PG_DATABASE_ID} to complete...`) ); assert.match( output, - new RegExp(`Created instance config ${SAMPLE_INSTANCE_CONFIG_ID}.`) + new RegExp( + `Created database ${PG_DATABASE_ID} on instance ${SAMPLE_INSTANCE_ID} with dialect POSTGRESQL.` + ) ); }); - // update_instance_config - it('should update an example custom instance config', async () => { + // pg_interleaving + it('should create an interleaved table hierarchy using PostgreSQL dialect', async () => { const output = execSync( - `node instance-config-update.js ${SAMPLE_INSTANCE_CONFIG_ID} ${PROJECT_ID}` + `node pg-interleaving.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp(`Waiting for operation on ${PG_DATABASE_ID} to complete...`) ); assert.match( output, new RegExp( - `Waiting for update operation for ${SAMPLE_INSTANCE_CONFIG_ID} to complete...` + `Created an interleaved table hierarchy in database ${PG_DATABASE_ID} using PostgreSQL dialect.` ) ); + }); + + // pg_dml_with_parameter + it('should execute a DML statement with parameters on a Spanner PostgreSQL database', async () => { + const output = execSync( + `node pg-dml-with-parameter.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` + ); assert.match( output, - new RegExp(`Updated instance config ${SAMPLE_INSTANCE_CONFIG_ID}.`) + new RegExp('Successfully executed 1 postgreSQL statements using DML') ); }); - // delete_instance_config - it('should delete an example custom instance config', async () => { + // pg_dml_batch + it('should execute a batch of DML statements on a Spanner PostgreSQL database', async () => { const output = execSync( - `node instance-config-delete.js ${SAMPLE_INSTANCE_CONFIG_ID} ${PROJECT_ID}` + `node pg-dml-batch.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` ); assert.match( output, - new RegExp(`Deleting ${SAMPLE_INSTANCE_CONFIG_ID}...`) + new RegExp( + 'Successfully executed 3 postgreSQL statements using Batch DML.' + ) + ); + }); + + // pg_dml_partitioned + it('should execute a partitioned DML on a Spanner PostgreSQL database', async () => { + const output = execSync( + `node pg-dml-partitioned.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, new RegExp('Successfully deleted 1 record.')); + }); + + // pg_query_with_parameters + it('should execute a query with parameters on a Spanner PostgreSQL database.', async () => { + const output = execSync( + `node pg-query-parameter.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` ); assert.match( output, - new RegExp(`Deleted instance config ${SAMPLE_INSTANCE_CONFIG_ID}.`) + new RegExp('SingerId: 1, FirstName: Alice, LastName: Henderson') ); }); - // list_instance_config_operations - it('should list all instance config operations', async () => { + // pg_dml_update + it('should update a table using parameterized queries on a Spanner PostgreSQL database.', async () => { const output = execSync( - `node instance-config-get-operations.js ${PROJECT_ID}` + `node pg-dml-getting-started-update.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` ); assert.match( output, - new RegExp( - `Getting list of instance config operations on project ${PROJECT_ID}...\n` - ) + new RegExp('Successfully updated 1 record in the Singers table.') + ); + }); + + // pg_add_column + it('should add a column to a table in the Spanner PostgreSQL database.', async () => { + const output = execSync( + `node pg-add-column.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` ); assert.match( output, new RegExp( - `Available instance config operations for project ${PROJECT_ID}:` + `Added MarketingBudget column to Albums table in database ${PG_DATABASE_ID}` ) ); - assert.include(output, 'Instance config operation for'); - assert.include( - output, - 'type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceConfigMetadata' + }); + + //pg_create_index + it('should create an index in the Spanner PostgreSQL database.', async () => { + const output = execSync( + `node pg-index-create-storing.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` ); + assert.match(output, new RegExp('Added the AlbumsByAlbumTitle index.')); }); - // list_instance_configs - it('should list available instance configs', async () => { - const output = execSync(`node list-instance-configs.js ${PROJECT_ID}`); + // pg_schema_information + it('should query the information schema metadata in a Spanner PostgreSQL database', async () => { + const output = execSync( + `node pg-schema-information.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, new RegExp('Table: public.albums')); + assert.match(output, new RegExp('Table: public.author')); + assert.match(output, new RegExp('Table: public.book')); + assert.match(output, new RegExp('Table: public.singers')); + }); + + // pg_ordering_nulls + it('should order nulls as per clause in a Spanner PostgreSQL database', async () => { + const output = execSync( + `node pg-ordering-nulls.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, new RegExp('Author ORDER BY FirstName')); + assert.match(output, new RegExp('Author ORDER BY FirstName DESC')); + assert.match(output, new RegExp('Author ORDER BY FirstName NULLS FIRST')); assert.match( output, - new RegExp(`Available instance configs for project ${PROJECT_ID}:`) + new RegExp('Author ORDER BY FirstName DESC NULLS LAST') ); - assert.include(output, 'Available leader options for instance config'); }); - // get_instance_config - // TODO: Enable when the feature has been released. - it.skip('should get a specific instance config', async () => { - const output = execSync(`node get-instance-config.js ${PROJECT_ID}`); - assert.include(output, 'Available leader options for instance config'); + // pg_numeric_data_type + it('should create a table, insert and query pg numeric data', async () => { + const output = execSync( + `node pg-numeric-data-type.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp(`Waiting for operation on ${PG_DATABASE_ID} to complete...`) + ); + assert.match( + output, + new RegExp(`Added table venues to database ${PG_DATABASE_ID}.`) + ); + assert.match(output, new RegExp('Inserted data.')); + assert.match(output, new RegExp('VenueId: 4, Revenue: 97372.3863')); + assert.match(output, new RegExp('VenueId: 19, Revenue: 7629')); + assert.match(output, new RegExp('VenueId: 398, Revenue: 0.000000123')); }); - // create_database_with_default_leader - it('should create a database with a default leader', async () => { + // pg_jsonb_add_column + it('should add a jsonb column to a table', async () => { const output = execSync( - `node database-create-with-default-leader.js "${SAMPLE_INSTANCE_ID}" "${DEFAULT_LEADER_DATABASE_ID}" "${DEFAULT_LEADER}" ${PROJECT_ID}` + `node pg-jsonb-add-column.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` ); assert.match( output, - new RegExp( - `Waiting for creation of ${DEFAULT_LEADER_DATABASE_ID} to complete...` - ) + new RegExp(`Waiting for operation on ${PG_DATABASE_ID} to complete...`) ); assert.match( output, new RegExp( - `Created database ${DEFAULT_LEADER_DATABASE_ID} with default leader ${DEFAULT_LEADER}.` + `Added jsonb column to table venues to database ${PG_DATABASE_ID}.` ) ); }); - // update_database_with_default_leader - it('should update a database with a default leader', async () => { + // pg_jsonb_insert_data + it('should insert pg jsonb data', async () => { const output = execSync( - `node database-update-default-leader.js "${SAMPLE_INSTANCE_ID}" "${DEFAULT_LEADER_DATABASE_ID}" "${DEFAULT_LEADER_2}" ${PROJECT_ID}` + `node pg-jsonb-update-data.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, new RegExp('Updated data.')); + }); + + // pg_jsonb_query_data + it('should query pg jsonb data', async () => { + const output = execSync( + `node pg-jsonb-query-parameter.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` ); assert.match( output, - new RegExp( - `Waiting for updating of ${DEFAULT_LEADER_DATABASE_ID} to complete...` - ) + new RegExp('VenueId: 19, Details: {"value":{"open":true,"rating":9}}') + ); + }); + + // pg_case_sensitivity + it('should create case sensitive table and query the information in a Spanner PostgreSQL database', async () => { + const output = execSync( + `node pg-case-sensitivity.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` ); assert.match( output, new RegExp( - `Updated database ${DEFAULT_LEADER_DATABASE_ID} with default leader ${DEFAULT_LEADER_2}.` + `Created table with case sensitive names in database ${PG_DATABASE_ID} using PostgreSQL dialect.` ) ); + assert.match(output, new RegExp('Inserted data using mutations.')); + assert.match(output, new RegExp('Concerts Table Data using Mutations:')); + assert.match(output, new RegExp('Concerts Table Data using Aliases:')); + assert.match(output, new RegExp('Inserted data using DML.')); }); - // get_default_leader - it('should get the default leader option of a database', async () => { + // pg_datatypes_casting + it('should use cast operator to cast from one data type to another in a Spanner PostgreSQL database', async () => { const output = execSync( - `node database-get-default-leader.js "${SAMPLE_INSTANCE_ID}" "${DEFAULT_LEADER_DATABASE_ID}" ${PROJECT_ID}` + `node pg-datatypes-casting.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` ); - assert.include( + assert.match(output, new RegExp('Data types after casting')); + }); + + // pg_functions + it('should call a server side function on a Spanner PostgreSQL database.', async () => { + const output = execSync( + `node pg-functions.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` + ); + assert.match(output, new RegExp('1284352323 seconds after epoch is')); + }); + + // pg_dml_returning_insert + it('should insert records using DML Returning in a Spanner PostgreSQL database', async () => { + const output = execSync( + `node pg-dml-returning-insert ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( output, - `The default_leader for ${DEFAULT_LEADER_DATABASE_ID} is ${DEFAULT_LEADER_2}` + new RegExp('Successfully inserted 1 record into the Singers table') ); + assert.match(output, new RegExp('Virginia Watson')); }); - // list_databases - it('should list databases on the instance', async () => { + // pg_dml_returning_update + it('should update records using DML Returning in a Spanner PostgreSQL database', async () => { const output = execSync( - `node list-databases.js "${SAMPLE_INSTANCE_ID}" ${PROJECT_ID}` + `node pg-dml-returning-update ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp('Successfully updated 1 record into the Singers table') + ); + assert.match(output, new RegExp('Virginia1 Watson1')); + }); + + // pg_dml_returning_delete + it('should delete records using DML Returning in a Spanner PostgreSQL database', async () => { + const output = execSync( + `node pg-dml-returning-delete ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp('Successfully deleted 1 record from the Singers table') + ); + assert.match(output, new RegExp('Virginia1 Watson1')); + }); + + // pg_create_sequence + it('should create a sequence', async () => { + const output = execSync( + `node pg-sequence-create.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp('Created Seq sequence and Customers table') + ); + assert.match( + output, + new RegExp('Number of customer records inserted is: 3') + ); + }); + + // pg_alter_sequence + it('should alter a sequence', async () => { + const output = execSync( + `node pg-sequence-alter.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` ); assert.match( output, new RegExp( - `Databases for projects/${PROJECT_ID}/instances/${SAMPLE_INSTANCE_ID}:` + 'Altered Seq sequence to skip an inclusive range between 1000 and 5000000.' ) ); - assert.include(output, `(default leader = ${DEFAULT_LEADER_2}`); + assert.match( + output, + new RegExp('Number of customer records inserted is: 3') + ); }); - // get_database_ddl - it('should get the ddl of a database', async () => { + // pg_drop_sequence + it('should drop a sequence', async () => { const output = execSync( - `node database-get-ddl.js "${SAMPLE_INSTANCE_ID}" "${DEFAULT_LEADER_DATABASE_ID}" ${PROJECT_ID}` + `node pg-sequence-drop.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}` ); assert.match( output, new RegExp( - `Retrieved database DDL for projects/${PROJECT_ID}/instances/${SAMPLE_INSTANCE_ID}/databases/${DEFAULT_LEADER_DATABASE_ID}:` + 'Altered Customers table to drop DEFAULT from CustomerId column and dropped the Seq sequence.' ) ); - assert.include(output, 'CREATE TABLE Singers'); }); - // max_commit_delay - it('should update rows in Albums example table when max commit delay is set', async () => { + // directed_read_options + it('should run read-only transaction with directed read options set', async () => { const output = execSync( - `node max-commit-delay.js "${INSTANCE_ID}" "${DATABASE_ID}" "${PROJECT_ID}"` + `node directed-reads.js ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` ); + console.log(output); assert.match( output, new RegExp( - 'Successfully inserted (\\d+) record into the Singers table.' + 'SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold your Peace' + ) + ); + assert.match( + output, + new RegExp( + 'Successfully executed read-only transaction with directedReadOptions' ) ); });