diff --git a/handwritten/spanner/samples/.eslintrc.yml b/handwritten/spanner/samples/.eslintrc.yml new file mode 100644 index 00000000000..282535f55f6 --- /dev/null +++ b/handwritten/spanner/samples/.eslintrc.yml @@ -0,0 +1,3 @@ +--- +rules: + no-console: off diff --git a/handwritten/spanner/samples/README.md b/handwritten/spanner/samples/README.md new file mode 100644 index 00000000000..b7670c8c7ad --- /dev/null +++ b/handwritten/spanner/samples/README.md @@ -0,0 +1,2266 @@ +[//]: # "This README.md file is auto-generated, all changes to this file will be lost." +[//]: # "To regenerate it, use `python -m synthtool`." +Google Cloud Platform logo + +# [Cloud Spanner: Node.js Samples](https://github.com/googleapis/nodejs-spanner) + +[![Open in Cloud Shell][shell_img]][shell_link] + +[Cloud Spanner](https://cloud.google.com/spanner/docs/) is a fully managed, mission-critical, relational database service that +offers transactional consistency at global scale, schemas, SQL (ANSI 2011 with extensions), +and automatic, synchronous replication for high availability. + +## Table of Contents + +* [Before you begin](#before-you-begin) +* [Samples](#samples) + * [Add and drop new database role](#add-and-drop-new-database-role) + * [Backups-cancel](#backups-cancel) + * [Copies a source backup](#copies-a-source-backup) + * [Copies a source backup](#copies-a-source-backup) + * [Backups-create-with-encryption-key](#backups-create-with-encryption-key) + * [Backups-create-with-multiple-kms-keys](#backups-create-with-multiple-kms-keys) + * [Backups-create](#backups-create) + * [Backups-delete](#backups-delete) + * [Backups-get-database-operations](#backups-get-database-operations) + * [Backups-get-operations](#backups-get-operations) + * [Backups-get](#backups-get) + * [Backups-restore-with-encryption-key](#backups-restore-with-encryption-key) + * [Backups-restore-with-multiple-kms-keys](#backups-restore-with-multiple-kms-keys) + * [Backups-restore](#backups-restore) + * [Backups-update](#backups-update) + * [Backups](#backups) + * [Batch Write](#batch-write) + * [Batch](#batch) + * [Creates a full backup schedule](#creates-a-full-backup-schedule) + * [Creates an incremental backup schedule](#creates-an-incremental-backup-schedule) + * [Create-instance-without-default-backup-schedules](#create-instance-without-default-backup-schedules) + * [CRUD](#crud) + * [Adds split points to a database.](#adds-split-points-to-a-database.) + * [Creates a new database with a specific default leader](#creates-a-new-database-with-a-specific-default-leader) + * [Database-create-with-encryption-key](#database-create-with-encryption-key) + * [Database-create-with-multiple-kms-keys](#database-create-with-multiple-kms-keys) + * [Database-create-with-version-retention-period](#database-create-with-version-retention-period) + * [Gets the schema definition of an existing database](#gets-the-schema-definition-of-an-existing-database) + * [Gets the default leader option of an existing database](#gets-the-default-leader-option-of-an-existing-database) + * [Updates the default leader of an existing database](#updates-the-default-leader-of-an-existing-database) + * [Updates a Cloud Spanner Database.](#updates-a-cloud-spanner-database.) + * [Datatypes](#datatypes) + * [Deletes a backup schedule](#deletes-a-backup-schedule) + * [Runs an execute sql request with directed read options](#runs-an-execute-sql-request-with-directed-read-options) + * [Delete using DML returning.](#delete-using-dml-returning.) + * [Insert using DML returning.](#insert-using-dml-returning.) + * [Update using DML returning.](#update-using-dml-returning.) + * [DML](#dml) + * [Enable fine grained access control](#enable-fine-grained-access-control) + * [Gets a backup schedule](#gets-a-backup-schedule) + * [Get-commit-stats](#get-commit-stats) + * [List database roles](#list-database-roles) + * [Gets the instance config metadata for the configuration nam6](#gets-the-instance-config-metadata-for-the-configuration-nam6) + * [Creates a new value-storing index](#creates-a-new-value-storing-index) + * [Creates a new index](#creates-a-new-index) + * [Executes a read-only SQL query using an existing index.](#executes-a-read-only-sql-query-using-an-existing-index.) + * [Reads data using an existing storing index.](#reads-data-using-an-existing-storing-index.) + * [Read data using an existing index.](#read-data-using-an-existing-index.) + * [Indexing](#indexing) + * [Creates a user-managed instance configuration.](#creates-a-user-managed-instance-configuration.) + * [Deletes a user-managed instance configuration.](#deletes-a-user-managed-instance-configuration.) + * [Lists the instance configuration operations.](#lists-the-instance-configuration-operations.) + * [Updates a user-managed instance configuration.](#updates-a-user-managed-instance-configuration.) + * [Creates a new instance partition](#creates-a-new-instance-partition) + * [Updates an instance.](#updates-an-instance.) + * [Creates a instance with asymmetric autoscaling config.](#creates-a-instance-with-asymmetric-autoscaling-config.) + * [Creates a instance with autoscaling config.](#creates-a-instance-with-autoscaling-config.) + * [Instance-with-processing-units](#instance-with-processing-units) + * [Instance](#instance) + * [Json-add-column](#json-add-column) + * [Json-query-parameter](#json-query-parameter) + * [Json-update-data](#json-update-data) + * [Lists backup schedules of a database](#lists-backup-schedules-of-a-database) + * [Lists all databases on the selected instance](#lists-all-databases-on-the-selected-instance) + * [Lists all the available instance configs for the selected project.](#lists-all-the-available-instance-configs-for-the-selected-project.) + * [Executes request with max commit delay](#executes-request-with-max-commit-delay) + * [Numeric-add-column](#numeric-add-column) + * [Numeric-query-parameter](#numeric-query-parameter) + * [Numeric-update-data](#numeric-update-data) + * [Observability (Tracing) with OpenTelemetry using OTLP](#observability-tracing-with-opentelemetry-using-otlp) + * [Observability (Tracing) with OpenTelemetry](#observability-tracing-with-opentelemetry) + * [Adds a column to an existing table in a Spanner PostgreSQL database.](#adds-a-column-to-an-existing-table-in-a-spanner-postgresql-database.) + * [Showcase the rules for case-sensitivity and case folding for a Spanner PostgreSQL database.](#showcase-the-rules-for-case-sensitivity-and-case-folding-for-a-spanner-postgresql-database.) + * [Creates a PostgreSQL Database.](#creates-a-postgresql-database.) + * [Use cast operator to cast from one data type to another in a Spanner PostgreSQL database.](#use-cast-operator-to-cast-from-one-data-type-to-another-in-a-spanner-postgresql-database.) + * [Execute a batch of DML statements on a Spanner PostgreSQL database.](#execute-a-batch-of-dml-statements-on-a-spanner-postgresql-database.) + * [Updates data in a table in a Spanner PostgreSQL database.](#updates-data-in-a-table-in-a-spanner-postgresql-database.) + * [Execute a Partitioned DML on a Spanner PostgreSQL database.](#execute-a-partitioned-dml-on-a-spanner-postgresql-database.) + * [Delete using DML returning on a Spanner PostgreSQL database.](#delete-using-dml-returning-on-a-spanner-postgresql-database.) + * [Insert using DML returning on a Spanner PostgreSQL database.](#insert-using-dml-returning-on-a-spanner-postgresql-database.) + * [Update using DML returning on a Spanner PostgreSQL database.](#update-using-dml-returning-on-a-spanner-postgresql-database.) + * [Execute a DML statement with parameters on a Spanner PostgreSQL database.](#execute-a-dml-statement-with-parameters-on-a-spanner-postgresql-database.) + * [Calls a server side function on a Spanner PostgreSQL database.](#calls-a-server-side-function-on-a-spanner-postgresql-database.) + * [Creates a new storing index in a Spanner PostgreSQL database.](#creates-a-new-storing-index-in-a-spanner-postgresql-database.) + * [Created interleaved table hierarchy using PostgreSQL dialect.](#created-interleaved-table-hierarchy-using-postgresql-dialect.) + * [Showcase how add a jsonb column in a PostgreSQL table.](#showcase-how-add-a-jsonb-column-in-a-postgresql-table.) + * [Showcase how query data to a jsonb column in a PostgreSQL table.](#showcase-how-query-data-to-a-jsonb-column-in-a-postgresql-table.) + * [Showcase how update data to a jsonb column in a PostgreSQL table.](#showcase-how-update-data-to-a-jsonb-column-in-a-postgresql-table.) + * [Showcase how to work with the PostgreSQL NUMERIC/DECIMAL data type on a Spanner PostgreSQL database.](#showcase-how-to-work-with-the-postgresql-numeric/decimal-data-type-on-a-spanner-postgresql-database.) + * [Showcases how a Spanner PostgreSQL database orders null values in a query.](#showcases-how-a-spanner-postgresql-database-orders-null-values-in-a-query.) + * [Execute a query with parameters on a Spanner PostgreSQL database.](#execute-a-query-with-parameters-on-a-spanner-postgresql-database.) + * [Query the information schema metadata in a Spanner PostgreSQL database.](#query-the-information-schema-metadata-in-a-spanner-postgresql-database.) + * [Alters a sequence in a PostgreSQL database.](#alters-a-sequence-in-a-postgresql-database.) + * [Creates sequence in PostgreSQL database.](#creates-sequence-in-postgresql-database.) + * [Drops a sequence in PostgreSQL database.](#drops-a-sequence-in-postgresql-database.) + * [Proto-query-data](#proto-query-data) + * [Creates a new database with a proto column and enum](#creates-a-new-database-with-a-proto-column-and-enum) + * [Proto-update-data-dml](#proto-update-data-dml) + * [Proto-update-data](#proto-update-data) + * [Queryoptions](#queryoptions) + * [Quickstart](#quickstart) + * [Read data with database role](#read-data-with-database-role) + * [Sets a request tag for a single query](#sets-a-request-tag-for-a-single-query) + * [Run Batch update with RPC priority](#run-batch-update-with-rpc-priority) + * [Run partitioned update with RPC priority](#run-partitioned-update-with-rpc-priority) + * [Create partitions with RPC priority](#create-partitions-with-rpc-priority) + * [Read data with RPC Priority](#read-data-with-rpc-priority) + * [Query data with RPC Priority](#query-data-with-rpc-priority) + * [Run transaction with RPC priority](#run-transaction-with-rpc-priority) + * [Schema](#schema) + * [Alters a sequence in a GoogleSQL database.](#alters-a-sequence-in-a-googlesql-database.) + * [Creates sequence in GoogleSQL database.](#creates-sequence-in-googlesql-database.) + * [Drops a sequence in GoogleSQL database.](#drops-a-sequence-in-googlesql-database.) + * [Executes a read/write transaction with statement timeout](#executes-a-read/write-transaction-with-statement-timeout) + * [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) + * [Executes a read/write transaction with transaction timeout](#executes-a-read/write-transaction-with-transaction-timeout) + * [Transaction](#transaction) + * [Updates a backup schedule](#updates-a-backup-schedule) + * [Updates an instance.](#updates-an-instance.) + +## Before you begin + +Before running the samples, make sure you've followed the steps outlined in +[Using the client library](https://github.com/googleapis/nodejs-spanner#using-the-client-library). + +`cd samples` + +`npm install` + +`cd ..` + +## Samples + + + +### Add and drop new database role + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/add-and-drop-new-database-role.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/add-and-drop-new-database-role.js,samples/README.md) + +__Usage:__ + + +`node add-and-drop-new-database-role.js ` + + +----- + + + + +### Backups-cancel + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-cancel.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/backups-cancel.js,samples/README.md) + +__Usage:__ + + +`node samples/backups-cancel.js` + + +----- + + + + +### Copies a source backup + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-copy-with-multiple-kms-keys.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/backups-copy-with-multiple-kms-keys.js,samples/README.md) + +__Usage:__ + + +`node spannerCopyBackup ` + + +----- + + + + +### Copies a source backup + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-copy.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/backups-copy.js,samples/README.md) + +__Usage:__ + + +`node spannerCopyBackup ` + + +----- + + + + +### Backups-create-with-encryption-key + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-create-with-encryption-key.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/backups-create-with-encryption-key.js,samples/README.md) + +__Usage:__ + + +`node samples/backups-create-with-encryption-key.js` + + +----- + + + + +### Backups-create-with-multiple-kms-keys + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-create-with-multiple-kms-keys.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/backups-create-with-multiple-kms-keys.js,samples/README.md) + +__Usage:__ + + +`node samples/backups-create-with-multiple-kms-keys.js` + + +----- + + + + +### Backups-create + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-create.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/backups-create.js,samples/README.md) + +__Usage:__ + + +`node samples/backups-create.js` + + +----- + + + + +### Backups-delete + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-delete.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/backups-delete.js,samples/README.md) + +__Usage:__ + + +`node samples/backups-delete.js` + + +----- + + + + +### Backups-get-database-operations + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-get-database-operations.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/backups-get-database-operations.js,samples/README.md) + +__Usage:__ + + +`node samples/backups-get-database-operations.js` + + +----- + + + + +### Backups-get-operations + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-get-operations.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/backups-get-operations.js,samples/README.md) + +__Usage:__ + + +`node samples/backups-get-operations.js` + + +----- + + + + +### Backups-get + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-get.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/backups-get.js,samples/README.md) + +__Usage:__ + + +`node samples/backups-get.js` + + +----- + + + + +### Backups-restore-with-encryption-key + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-restore-with-encryption-key.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/backups-restore-with-encryption-key.js,samples/README.md) + +__Usage:__ + + +`node samples/backups-restore-with-encryption-key.js` + + +----- + + + + +### Backups-restore-with-multiple-kms-keys + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-restore-with-multiple-kms-keys.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/backups-restore-with-multiple-kms-keys.js,samples/README.md) + +__Usage:__ + + +`node samples/backups-restore-with-multiple-kms-keys.js` + + +----- + + + + +### Backups-restore + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-restore.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/backups-restore.js,samples/README.md) + +__Usage:__ + + +`node samples/backups-restore.js` + + +----- + + + + +### Backups-update + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-update.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/backups-update.js,samples/README.md) + +__Usage:__ + + +`node samples/backups-update.js` + + +----- + + + + +### Backups + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups.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/backups.js,samples/README.md) + +__Usage:__ + + +`node samples/backups.js` + + +----- + + + + +### Batch Write + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/batch-write.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/batch-write.js,samples/README.md) + +__Usage:__ + + +`node batch-write.js ` + + +----- + + + + +### Batch + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/batch.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/batch.js,samples/README.md) + +__Usage:__ + + +`node samples/batch.js` + + +----- + + + + +### Creates a full backup schedule + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/create-full-backup-schedule.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/create-full-backup-schedule.js,samples/README.md) + +__Usage:__ + + +`node create-full-backup-schedule.js ` + + +----- + + + + +### Creates an incremental backup schedule + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/create-incremental-backup-schedule.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/create-incremental-backup-schedule.js,samples/README.md) + +__Usage:__ + + +`node create-incremental-backup-schedule.js ` + + +----- + + + + +### Create-instance-without-default-backup-schedules + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/create-instance-without-default-backup-schedules.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/create-instance-without-default-backup-schedules.js,samples/README.md) + +__Usage:__ + + +`node samples/create-instance-without-default-backup-schedules.js` + + +----- + + + + +### CRUD + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/crud.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/crud.js,samples/README.md) + +__Usage:__ + + +`node samples/crud.js` + + +----- + + + + +### Adds split points to a database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-add-split-points.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/database-add-split-points.js,samples/README.md) + +__Usage:__ + + +`node database-add-split-points.js ` + + +----- + + + + +### Creates a new database with a specific default leader + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-create-with-default-leader.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/database-create-with-default-leader.js,samples/README.md) + +__Usage:__ + + +`node database-create-with-default-leader.js ` + + +----- + + + + +### Database-create-with-encryption-key + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-create-with-encryption-key.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/database-create-with-encryption-key.js,samples/README.md) + +__Usage:__ + + +`node samples/database-create-with-encryption-key.js` + + +----- + + + + +### Database-create-with-multiple-kms-keys + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-create-with-multiple-kms-keys.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/database-create-with-multiple-kms-keys.js,samples/README.md) + +__Usage:__ + + +`node samples/database-create-with-multiple-kms-keys.js` + + +----- + + + + +### Database-create-with-version-retention-period + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-create-with-version-retention-period.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/database-create-with-version-retention-period.js,samples/README.md) + +__Usage:__ + + +`node samples/database-create-with-version-retention-period.js` + + +----- + + + + +### Gets the schema definition of an existing database + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-get-ddl.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/database-get-ddl.js,samples/README.md) + +__Usage:__ + + +`node database-get-ddl.js ` + + +----- + + + + +### Gets the default leader option of an existing database + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-get-default-leader.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/database-get-default-leader.js,samples/README.md) + +__Usage:__ + + +`node database-get-default-leader.js ` + + +----- + + + + +### Updates the default leader of an existing database + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-update-default-leader.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/database-update-default-leader.js,samples/README.md) + +__Usage:__ + + +`node database-update-default-leader.js ` + + +----- + + + + +### Updates a Cloud Spanner Database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-update.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/database-update.js,samples/README.md) + +__Usage:__ + + +`node database-update.js ` + + +----- + + + + +### Datatypes + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/datatypes.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/datatypes.js,samples/README.md) + +__Usage:__ + + +`node samples/datatypes.js` + + +----- + + + + +### Deletes a backup schedule + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/delete-backup-schedule.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/delete-backup-schedule.js,samples/README.md) + +__Usage:__ + + +`node delete-backup-schedule.js ` + + +----- + + + + +### Runs an execute sql request with directed read options + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/directed-reads.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/directed-reads.js,samples/README.md) + +__Usage:__ + + +`node directed-reads.js ` + + +----- + + + + +### Delete using DML returning. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/dml-returning-delete.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/dml-returning-delete.js,samples/README.md) + +__Usage:__ + + +`node dml-returning-delete.js ` + + +----- + + + + +### Insert using DML returning. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/dml-returning-insert.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/dml-returning-insert.js,samples/README.md) + +__Usage:__ + + +`node dml-returning-insert.js ` + + +----- + + + + +### Update using DML returning. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/dml-returning-update.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/dml-returning-update.js,samples/README.md) + +__Usage:__ + + +`node dml-returning-update.js ` + + +----- + + + + +### DML + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/dml.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/dml.js,samples/README.md) + +__Usage:__ + + +`node samples/dml.js` + + +----- + + + + +### Enable fine grained access control + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/enable-fine-grained-access.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/enable-fine-grained-access.js,samples/README.md) + +__Usage:__ + + +`node enable-fine-grained-access.js ` + + +----- + + + + +### Gets a backup schedule + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/get-backup-schedule.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/get-backup-schedule.js,samples/README.md) + +__Usage:__ + + +`node get-backup-schedule.js ` + + +----- + + + + +### Get-commit-stats + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/get-commit-stats.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/get-commit-stats.js,samples/README.md) + +__Usage:__ + + +`node samples/get-commit-stats.js` + + +----- + + + + +### List database roles + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/get-database-roles.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/get-database-roles.js,samples/README.md) + +__Usage:__ + + +`node get-database-roles.js ` + + +----- + + + + +### Gets the instance config metadata for the configuration nam6 + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/get-instance-config.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/get-instance-config.js,samples/README.md) + +__Usage:__ + + +`node get-instance-config.js ` + + +----- + + + + +### Creates a new value-storing index + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/index-create-storing.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/index-create-storing.js,samples/README.md) + +__Usage:__ + + +`node createStoringIndex ` + + +----- + + + + +### Creates a new index + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/index-create.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/index-create.js,samples/README.md) + +__Usage:__ + + +`node createIndex ` + + +----- + + + + +### Executes a read-only SQL query using an existing index. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/index-query-data.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/index-query-data.js,samples/README.md) + +__Usage:__ + + +`node queryDataWithIndex ` + + +----- + + + + +### Reads data using an existing storing index. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/index-read-data-with-storing.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/index-read-data-with-storing.js,samples/README.md) + +__Usage:__ + + +`node readDataWithStoringIndex ` + + +----- + + + + +### Read data using an existing index. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/index-read-data.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/index-read-data.js,samples/README.md) + +__Usage:__ + + +`node readDataWithIndex ` + + +----- + + + + +### Indexing + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/indexing.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/indexing.js,samples/README.md) + +__Usage:__ + + +`node samples/indexing.js` + + +----- + + + + +### Creates a user-managed instance configuration. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/instance-config-create.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/instance-config-create.js,samples/README.md) + +__Usage:__ + + +`node instance-config-create ` + + +----- + + + + +### Deletes a user-managed instance configuration. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/instance-config-delete.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/instance-config-delete.js,samples/README.md) + +__Usage:__ + + +`node instance-config-delete ` + + +----- + + + + +### Lists the instance configuration operations. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/instance-config-get-operations.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/instance-config-get-operations.js,samples/README.md) + +__Usage:__ + + +`node instance-config-get-operations ` + + +----- + + + + +### Updates a user-managed instance configuration. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/instance-config-update.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/instance-config-update.js,samples/README.md) + +__Usage:__ + + +`node instance-config-update ` + + +----- + + + + +### Creates a new instance partition + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/instance-partition-create.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/instance-partition-create.js,samples/README.md) + +__Usage:__ + + +`node instance-partition-create.js ` + + +----- + + + + +### Updates an instance. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/instance-update.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/instance-update.js,samples/README.md) + +__Usage:__ + + +`node instance-update.js ` + + +----- + + + + +### Creates a instance with asymmetric autoscaling config. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/instance-with-asymmetric-autoscaling-config.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/instance-with-asymmetric-autoscaling-config.js,samples/README.md) + +__Usage:__ + + +`node instance-with-asymmetric-autoscaling-config.js ` + + +----- + + + + +### Creates a instance with autoscaling config. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/instance-with-autoscaling-config.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/instance-with-autoscaling-config.js,samples/README.md) + +__Usage:__ + + +`node instance-with-autoscaling-config.js ` + + +----- + + + + +### Instance-with-processing-units + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/instance-with-processing-units.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/instance-with-processing-units.js,samples/README.md) + +__Usage:__ + + +`node samples/instance-with-processing-units.js` + + +----- + + + + +### Instance + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/instance.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/instance.js,samples/README.md) + +__Usage:__ + + +`node samples/instance.js` + + +----- + + + + +### Json-add-column + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/json-add-column.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/json-add-column.js,samples/README.md) + +__Usage:__ + + +`node samples/json-add-column.js` + + +----- + + + + +### Json-query-parameter + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/json-query-parameter.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/json-query-parameter.js,samples/README.md) + +__Usage:__ + + +`node samples/json-query-parameter.js` + + +----- + + + + +### Json-update-data + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/json-update-data.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/json-update-data.js,samples/README.md) + +__Usage:__ + + +`node samples/json-update-data.js` + + +----- + + + + +### Lists backup schedules of a database + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/list-backup-schedules.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/list-backup-schedules.js,samples/README.md) + +__Usage:__ + + +`node list-backup-schedules.js ` + + +----- + + + + +### Lists all databases on the selected instance + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/list-databases.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/list-databases.js,samples/README.md) + +__Usage:__ + + +`node list-databases.js ` + + +----- + + + + +### Lists all the available instance configs for the selected project. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/list-instance-configs.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/list-instance-configs.js,samples/README.md) + +__Usage:__ + + +`node list-instance-configs.js ` + + +----- + + + + +### Executes request with max commit delay + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/max-commit-delay.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/max-commit-delay.js,samples/README.md) + +__Usage:__ + + +`node max-commit-delay.js ` + + +----- + + + + +### Numeric-add-column + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/numeric-add-column.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/numeric-add-column.js,samples/README.md) + +__Usage:__ + + +`node samples/numeric-add-column.js` + + +----- + + + + +### Numeric-query-parameter + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/numeric-query-parameter.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/numeric-query-parameter.js,samples/README.md) + +__Usage:__ + + +`node samples/numeric-query-parameter.js` + + +----- + + + + +### Numeric-update-data + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/numeric-update-data.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/numeric-update-data.js,samples/README.md) + +__Usage:__ + + +`node samples/numeric-update-data.js` + + +----- + + + + +### Observability (Tracing) with OpenTelemetry using OTLP + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/observability-traces-otlp.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/observability-traces-otlp.js,samples/README.md) + +__Usage:__ + + +`node observability-traces-otlp.js ` + + +----- + + + + +### Observability (Tracing) with OpenTelemetry + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/observability-traces.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/observability-traces.js,samples/README.md) + +__Usage:__ + + +`node observability-traces.js ` + + +----- + + + + +### Adds a column to an existing table in a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-add-column.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/pg-add-column.js,samples/README.md) + +__Usage:__ + + +`node pg-add-column.js ` + + +----- + + + + +### Showcase the rules for case-sensitivity and case folding for a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-case-sensitivity.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/pg-case-sensitivity.js,samples/README.md) + +__Usage:__ + + +`node pg-case-sensitivity.js ` + + +----- + + + + +### Creates a PostgreSQL Database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-database-create.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/pg-database-create.js,samples/README.md) + +__Usage:__ + + +`node pg-database-create.js ` + + +----- + + + + +### Use cast operator to cast from one data type to another in a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-datatypes-casting.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/pg-datatypes-casting.js,samples/README.md) + +__Usage:__ + + +`node pg-datatypes-casting.js ` + + +----- + + + + +### Execute a batch of DML statements on a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-dml-batch.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/pg-dml-batch.js,samples/README.md) + +__Usage:__ + + +`node pg-dml-batch.js ` + + +----- + + + + +### Updates data in a table in a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-dml-getting-started-update.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/pg-dml-getting-started-update.js,samples/README.md) + +__Usage:__ + + +`node pg-dml-getting-started-update.js ` + + +----- + + + + +### Execute a Partitioned DML on a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-dml-partitioned.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/pg-dml-partitioned.js,samples/README.md) + +__Usage:__ + + +`node pg-dml-partitioned.js ` + + +----- + + + + +### Delete using DML returning on a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-dml-returning-delete.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/pg-dml-returning-delete.js,samples/README.md) + +__Usage:__ + + +`node pg-dml-returning-delete.js ` + + +----- + + + + +### Insert using DML returning on a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-dml-returning-insert.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/pg-dml-returning-insert.js,samples/README.md) + +__Usage:__ + + +`node pg-dml-returning-insert.js ` + + +----- + + + + +### Update using DML returning on a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-dml-returning-update.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/pg-dml-returning-update.js,samples/README.md) + +__Usage:__ + + +`node pg-dml-returning-update.js ` + + +----- + + + + +### Execute a DML statement with parameters on a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-dml-with-parameter.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/pg-dml-with-parameter.js,samples/README.md) + +__Usage:__ + + +`node pg-dml-with-parameter.js ` + + +----- + + + + +### Calls a server side function on a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-functions.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/pg-functions.js,samples/README.md) + +__Usage:__ + + +`node pg-functions.js ` + + +----- + + + + +### Creates a new storing index in a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-index-create-storing.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/pg-index-create-storing.js,samples/README.md) + +__Usage:__ + + +`node pg-index-create-storing.js ` + + +----- + + + + +### Created interleaved table hierarchy using PostgreSQL dialect. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-interleaving.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/pg-interleaving.js,samples/README.md) + +__Usage:__ + + +`node pg-interleaving.js ` + + +----- + + + + +### Showcase how add a jsonb column in a PostgreSQL table. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-jsonb-add-column.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/pg-jsonb-add-column.js,samples/README.md) + +__Usage:__ + + +`node pg-jsonb-add-column.js ` + + +----- + + + + +### Showcase how query data to a jsonb column in a PostgreSQL table. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-jsonb-query-parameter.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/pg-jsonb-query-parameter.js,samples/README.md) + +__Usage:__ + + +`node pg-jsonb-query-parameter.js ` + + +----- + + + + +### Showcase how update data to a jsonb column in a PostgreSQL table. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-jsonb-update-data.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/pg-jsonb-update-data.js,samples/README.md) + +__Usage:__ + + +`node pg-jsonb-update-data.js ` + + +----- + + + + +### Showcase how to work with the PostgreSQL NUMERIC/DECIMAL data type on a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-numeric-data-type.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/pg-numeric-data-type.js,samples/README.md) + +__Usage:__ + + +`node ppg-numeric-data-type.js ` + + +----- + + + + +### Showcases how a Spanner PostgreSQL database orders null values in a query. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-ordering-nulls.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/pg-ordering-nulls.js,samples/README.md) + +__Usage:__ + + +`node pg-ordering-nulls.js ` + + +----- + + + + +### Execute a query with parameters on a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-query-parameter.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/pg-query-parameter.js,samples/README.md) + +__Usage:__ + + +`node pg-query-parameter.js ` + + +----- + + + + +### Query the information schema metadata in a Spanner PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-schema-information.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/pg-schema-information.js,samples/README.md) + +__Usage:__ + + +`node pg-schema-information.js ` + + +----- + + + + +### Alters a sequence in a PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-sequence-alter.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/pg-sequence-alter.js,samples/README.md) + +__Usage:__ + + +`node pg-sequence-alter.js ` + + +----- + + + + +### Creates sequence in PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-sequence-create.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/pg-sequence-create.js,samples/README.md) + +__Usage:__ + + +`node pg-sequence-create.js ` + + +----- + + + + +### Drops a sequence in PostgreSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/pg-sequence-drop.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/pg-sequence-drop.js,samples/README.md) + +__Usage:__ + + +`node pg-sequence-drop.js ` + + +----- + + + + +### Proto-query-data + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/proto-query-data.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/proto-query-data.js,samples/README.md) + +__Usage:__ + + +`node samples/proto-query-data.js` + + +----- + + + + +### Creates a new database with a proto column and enum + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/proto-type-add-column.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/proto-type-add-column.js,samples/README.md) + +__Usage:__ + + +`node proto-type-add-column.js ` + + +----- + + + + +### Proto-update-data-dml + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/proto-update-data-dml.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/proto-update-data-dml.js,samples/README.md) + +__Usage:__ + + +`node samples/proto-update-data-dml.js` + + +----- + + + + +### Proto-update-data + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/proto-update-data.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/proto-update-data.js,samples/README.md) + +__Usage:__ + + +`node samples/proto-update-data.js` + + +----- + + + + +### Queryoptions + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/queryoptions.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/queryoptions.js,samples/README.md) + +__Usage:__ + + +`node samples/queryoptions.js` + + +----- + + + + +### Quickstart + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/quickstart.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/quickstart.js,samples/README.md) + +__Usage:__ + + +`node samples/quickstart.js` + + +----- + + + + +### Read data with database role + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/read-data-with-database-role.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/read-data-with-database-role.js,samples/README.md) + +__Usage:__ + + +`node read-data-with-database-role.js ` + + +----- + + + + +### Sets a request tag for a single query + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/request-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/request-tag.js,samples/README.md) + +__Usage:__ + + +`node request-tag.js ` + + +----- + + + + +### Run Batch update with RPC priority + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/rpc-priority-batch-dml.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-batch-dml.js,samples/README.md) + +__Usage:__ + + +`node rpc-priority-batch-dml.js ` + + +----- + + + + +### Run partitioned update with RPC priority + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/rpc-priority-partitioned-dml.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-partitioned-dml.js,samples/README.md) + +__Usage:__ + + +`node rpc-priority-partitioned-dml.js ` + + +----- + + + + +### Create partitions with RPC priority + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/rpc-priority-query-partitions.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-query-partitions.js,samples/README.md) + +__Usage:__ + + +`node rpc-priority-query-partitions.js ` + + +----- + + + + +### Read data with RPC Priority + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/rpc-priority-read.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-read.js,samples/README.md) + +__Usage:__ + + +`node rpc-priority-read.js ` + + +----- + + + + +### Query data with RPC Priority + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/rpc-priority-run.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-run.js,samples/README.md) + +__Usage:__ + + +`node rpc-priority-run.js ` + + +----- + + + + +### Run transaction with RPC priority + +View the [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) + +__Usage:__ + + +`node rpc-priority-transaction.js ` + + +----- + + + + +### Schema + +View the [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) + +__Usage:__ + + +`node samples/schema.js` + + +----- + + + + +### Alters a sequence in a GoogleSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/sequence-alter.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/sequence-alter.js,samples/README.md) + +__Usage:__ + + +`node sequence-alter.js ` + + +----- + + + + +### Creates sequence in GoogleSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/sequence-create.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/sequence-create.js,samples/README.md) + +__Usage:__ + + +`node sequence-create.js ` + + +----- + + + + +### Drops a sequence in GoogleSQL database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/sequence-drop.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/sequence-drop.js,samples/README.md) + +__Usage:__ + + +`node sequence-drop.js ` + + +----- + + + + +### Executes a read/write transaction with statement timeout + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/statement-timeout.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/statement-timeout.js,samples/README.md) + +__Usage:__ + + +`node statement-timeout.js ` + + +----- + + + + +### Struct + +View the [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) + +__Usage:__ + + +`node samples/struct.js` + + +----- + + + + +### 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). + +[![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) + +__Usage:__ + + +`node samples/timestamp.js` + + +----- + + + + +### Executes a read/write transaction with transaction and request tags + +View the [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) + +__Usage:__ + + +`node transaction-tag.js ` + + +----- + + + + +### Executes a read/write transaction with transaction timeout + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/transaction-timeout.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-timeout.js,samples/README.md) + +__Usage:__ + + +`node transaction-timeout.js ` + + +----- + + + + +### Transaction + +View the [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) + +__Usage:__ + + +`node samples/transaction.js` + + +----- + + + + +### Updates a backup schedule + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/update-backup-schedule.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/update-backup-schedule.js,samples/README.md) + +__Usage:__ + + +`node update-backup-schedule.js ` + + +----- + + + + +### Updates an instance. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/update-instance-default-backup-schedule-type.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/update-instance-default-backup-schedule-type.js,samples/README.md) + +__Usage:__ + + +`node instance-update.js ` + + + + + + +[shell_img]: https://gstatic.com/cloudssh/images/open-btn.png +[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/README.md +[product-docs]: https://cloud.google.com/spanner/docs/ diff --git a/handwritten/spanner/samples/add-and-drop-new-database-role.js b/handwritten/spanner/samples/add-and-drop-new-database-role.js new file mode 100644 index 00000000000..ef361c2ee75 --- /dev/null +++ b/handwritten/spanner/samples/add-and-drop-new-database-role.js @@ -0,0 +1,103 @@ +// Copyright 2024 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: Add and drop new database role +// usage: node add-and-drop-new-database-role.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_add_and_drop_database_role] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function addAndDropNewDatabaseRole() { + // Creates a new user defined role and grant permissions + try { + const request = [ + 'CREATE ROLE parent', + 'GRANT SELECT ON TABLE Singers TO ROLE parent', + 'CREATE ROLE child', + 'GRANT ROLE parent TO ROLE child', + ]; + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Created roles child and parent and granted privileges'); + } catch (err) { + console.error('ERROR:', err); + } + + // Revoke permissions and drop child role. + // A role can't be dropped until all its permissions are revoked. + try { + const request = ['REVOKE ROLE parent FROM ROLE child', 'DROP ROLE child']; + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Revoked privileges and dropped role child'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + } + addAndDropNewDatabaseRole(); + // [END spanner_add_and_drop_database_role] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/add-and-drop-new-database-role.js b/handwritten/spanner/samples/archived/add-and-drop-new-database-role.js new file mode 100644 index 00000000000..8b3e11c207b --- /dev/null +++ b/handwritten/spanner/samples/archived/add-and-drop-new-database-role.js @@ -0,0 +1,89 @@ +// Copyright 2022 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: Add and drop new database role +// usage: node add-and-drop-new-database-role.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_add_and_drop_database_role] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function addAndDropNewDatabaseRole() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Creates a new user defined role and grant permissions + try { + const request = [ + 'CREATE ROLE parent', + 'GRANT SELECT ON TABLE Singers TO ROLE parent', + 'CREATE ROLE child', + 'GRANT ROLE parent TO ROLE child', + ]; + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Created roles child and parent and granted privileges'); + } catch (err) { + console.error('ERROR:', err); + } + + // Revoke permissions and drop child role. + // A role can't be dropped until all its permissions are revoked. + try { + const request = ['REVOKE ROLE parent FROM ROLE child', 'DROP ROLE child']; + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Revoked privileges and dropped role child'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + } + addAndDropNewDatabaseRole(); + // [END spanner_add_and_drop_database_role] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/backups-cancel.js b/handwritten/spanner/samples/archived/backups-cancel.js new file mode 100644 index 00000000000..b33cf26d818 --- /dev/null +++ b/handwritten/spanner/samples/archived/backups-cancel.js @@ -0,0 +1,69 @@ +/** + * Copyright 2020 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. + */ + +'use strict'; + +async function cancelBackup(instanceId, databaseId, backupId, projectId) { + // [START spanner_cancel_backup_create] + // Imports the Google Cloud client library and precise date 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'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const backup = instance.backup(backupId); + + // Creates a new backup of the database + try { + console.log(`Creating backup of database ${database.formattedName_}.`); + const databasePath = database.formattedName_; + // Expire backup one day in the future + const expireTime = Date.now() + 1000 * 60 * 60 * 24; + const [, operation] = await backup.create({ + databasePath: databasePath, + expireTime: expireTime, + }); + + // Cancel the backup + await operation.cancel(); + + console.log('Backup cancelled.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Delete backup in case it got created before the cancel operation + await backup.delete(); + + // Close the database when finished. + await database.close(); + } + // [END spanner_cancel_backup_create] +} + +module.exports.cancelBackup = cancelBackup; diff --git a/handwritten/spanner/samples/archived/backups-copy.js b/handwritten/spanner/samples/archived/backups-copy.js new file mode 100644 index 00000000000..26754b958f8 --- /dev/null +++ b/handwritten/spanner/samples/archived/backups-copy.js @@ -0,0 +1,97 @@ +// Copyright 2022 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: Copies a source backup +// usage: node spannerCopyBackup + +'use strict'; + +function main( + instanceId = 'my-instance', + backupId = 'my-backup', + sourceBackupPath = 'projects/my-project-id/instances/my-source-instance/backups/my-source-backup', + projectId = 'my-project-id', +) { + // [START spanner_copy_backup] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const backupId = 'my-backup', + // const sourceBackupPath = 'projects/my-project-id/instances/my-source-instance/backups/my-source-backup', + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + const {PreciseDate} = require('@google-cloud/precise-date'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function spannerCopyBackup() { + // Gets a reference to a Cloud Spanner instance and backup + const instance = spanner.instance(instanceId); + + // Expire copy backup 14 days in the future + const expireTime = Spanner.timestamp( + Date.now() + 1000 * 60 * 60 * 24 * 14, + ).toStruct(); + + // Copy the source backup + try { + console.log(`Creating copy of the source backup ${sourceBackupPath}.`); + const [, operation] = await instance.copyBackup( + sourceBackupPath, + backupId, + { + expireTime: expireTime, + }, + ); + + console.log( + `Waiting for backup copy ${ + instance.backup(backupId).formattedName_ + } to complete...`, + ); + await operation.promise(); + + // Verify the copy backup is ready + const copyBackup = instance.backup(backupId); + const [copyBackupInfo] = await copyBackup.getMetadata(); + if (copyBackupInfo.state === 'READY') { + console.log( + `Backup copy ${copyBackupInfo.name} of size ` + + `${copyBackupInfo.sizeBytes} bytes was created at ` + + `${new PreciseDate(copyBackupInfo.createTime).toISOString()} ` + + 'with version time ' + + `${new PreciseDate(copyBackupInfo.versionTime).toISOString()}`, + ); + } else { + console.error('ERROR: Copy of backup is not ready.'); + } + } catch (err) { + console.error('ERROR:', err); + } + } + spannerCopyBackup(); + // [END spanner_copy_backup] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/backups-create-with-encryption-key.js b/handwritten/spanner/samples/archived/backups-create-with-encryption-key.js new file mode 100644 index 00000000000..f0e0c8b04d4 --- /dev/null +++ b/handwritten/spanner/samples/archived/backups-create-with-encryption-key.js @@ -0,0 +1,92 @@ +/** + * Copyright 2021 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. + */ + +'use strict'; + +async function createBackupWithEncryptionKey( + instanceId, + databaseId, + backupId, + projectId, + keyName, +) { + // [START spanner_create_backup_with_encryption_key] + // Imports the Google Cloud client library and precise date library + const {Spanner} = require('@google-cloud/spanner'); + const {PreciseDate} = require('@google-cloud/precise-date'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + // const versionTime = Date.now() - 1000 * 60 * 60 * 24; // One day ago + // const keyName = + // 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const backup = instance.backup(backupId); + + // Creates a new backup of the database + try { + console.log(`Creating backup of database ${database.formattedName_}.`); + const databasePath = database.formattedName_; + // Expire backup 14 days in the future + const expireTime = Date.now() + 1000 * 60 * 60 * 24 * 14; + // Create a backup of the state of the database at the current time. + const [, operation] = await backup.create({ + databasePath: databasePath, + expireTime: expireTime, + encryptionConfig: { + encryptionType: 'CUSTOMER_MANAGED_ENCRYPTION', + kmsKeyName: keyName, + }, + }); + + console.log(`Waiting for backup ${backup.formattedName_} to complete...`); + await operation.promise(); + + // Verify backup is ready + const [backupInfo] = await backup.getMetadata(); + if (backupInfo.state === 'READY') { + console.log( + `Backup ${backupInfo.name} of size ` + + `${backupInfo.sizeBytes} bytes was created at ` + + `${new PreciseDate(backupInfo.createTime).toISOString()} ` + + `using encryption key ${backupInfo.encryptionInfo.kmsKeyVersion}`, + ); + } else { + console.error('ERROR: Backup is not ready.'); + } + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + // [END spanner_create_backup_with_encryption_key] +} + +module.exports.createBackupWithEncryptionKey = createBackupWithEncryptionKey; diff --git a/handwritten/spanner/samples/archived/backups-create.js b/handwritten/spanner/samples/archived/backups-create.js new file mode 100644 index 00000000000..1524bcfe977 --- /dev/null +++ b/handwritten/spanner/samples/archived/backups-create.js @@ -0,0 +1,88 @@ +/** + * Copyright 2021 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. + */ + +'use strict'; + +async function createBackup( + instanceId, + databaseId, + backupId, + projectId, + versionTime, +) { + // [START spanner_create_backup] + // Imports the Google Cloud client library and precise date library + const {Spanner} = require('@google-cloud/spanner'); + const {PreciseDate} = require('@google-cloud/precise-date'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + // const versionTime = Date.now() - 1000 * 60 * 60 * 24; // One day ago + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const backup = instance.backup(backupId); + + // Creates a new backup of the database + try { + console.log(`Creating backup of database ${database.formattedName_}.`); + const databasePath = database.formattedName_; + // Expire backup 14 days in the future + const expireTime = Date.now() + 1000 * 60 * 60 * 24 * 14; + // Create a backup of the state of the database at the current time. + const [, operation] = await backup.create({ + databasePath: databasePath, + expireTime: expireTime, + versionTime: versionTime, + }); + + console.log(`Waiting for backup ${backup.formattedName_} to complete...`); + await operation.promise(); + + // Verify backup is ready + const [backupInfo] = await backup.getMetadata(); + if (backupInfo.state === 'READY') { + console.log( + `Backup ${backupInfo.name} of size ` + + `${backupInfo.sizeBytes} bytes was created at ` + + `${new PreciseDate(backupInfo.createTime).toISOString()} ` + + 'for version of database at ' + + `${new PreciseDate(backupInfo.versionTime).toISOString()}`, + ); + } else { + console.error('ERROR: Backup is not ready.'); + } + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + // [END spanner_create_backup] +} + +module.exports.createBackup = createBackup; diff --git a/handwritten/spanner/samples/archived/backups-delete.js b/handwritten/spanner/samples/archived/backups-delete.js new file mode 100644 index 00000000000..d1e8a2bfbf2 --- /dev/null +++ b/handwritten/spanner/samples/archived/backups-delete.js @@ -0,0 +1,54 @@ +/** + * Copyright 2020 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. + */ + +'use strict'; + +async function deleteBackup(instanceId, databaseId, backupId, projectId) { + // [START spanner_delete_backup] + // 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'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and backup + const instance = spanner.instance(instanceId); + const backup = instance.backup(backupId); + + // Delete the backup + console.log(`Deleting backup ${backupId}.`); + await backup.delete(); + + // Verify backup no longer exists + const exists = await backup.exists(); + if (exists) { + console.error('Error: backup still exists.'); + } else { + console.log('Backup deleted.'); + } + // [END spanner_delete_backup] +} + +module.exports.deleteBackup = deleteBackup; diff --git a/handwritten/spanner/samples/archived/backups-get-database-operations.js b/handwritten/spanner/samples/archived/backups-get-database-operations.js new file mode 100644 index 00000000000..c2cdcda7227 --- /dev/null +++ b/handwritten/spanner/samples/archived/backups-get-database-operations.js @@ -0,0 +1,60 @@ +/** + * Copyright 2020 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. + */ + +'use strict'; + +async function getDatabaseOperations(instanceId, projectId) { + // [START spanner_list_database_operations] + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance + const instance = spanner.instance(instanceId); + + // List database operations + try { + const [databaseOperations] = await instance.getDatabaseOperations({ + filter: + '(metadata.@type:type.googleapis.com/google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata)', + }); + console.log('Optimize Database Operations:'); + databaseOperations.forEach(databaseOperation => { + const metadata = + protos.google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata.decode( + databaseOperation.metadata.value, + ); + console.log( + `Database ${metadata.name} restored from backup is ` + + `${metadata.progress.progressPercent}% optimized.`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_list_database_operations] +} + +module.exports.getDatabaseOperations = getDatabaseOperations; diff --git a/handwritten/spanner/samples/archived/backups-get-operations.js b/handwritten/spanner/samples/archived/backups-get-operations.js new file mode 100644 index 00000000000..31430592947 --- /dev/null +++ b/handwritten/spanner/samples/archived/backups-get-operations.js @@ -0,0 +1,94 @@ +/** + * Copyright 2020 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. + */ + +'use strict'; + +async function getBackupOperations( + instanceId, + databaseId, + backupId, + projectId, +) { + // [START spanner_list_backup_operations] + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + // const instanceId = 'my-instance'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance + const instance = spanner.instance(instanceId); + + // List create backup operations + try { + const [backupOperations] = await instance.getBackupOperations({ + filter: + '(metadata.@type:type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) ' + + `AND (metadata.database:${databaseId})`, + }); + console.log('Create Backup Operations:'); + backupOperations.forEach(backupOperation => { + const metadata = + protos.google.spanner.admin.database.v1.CreateBackupMetadata.decode( + backupOperation.metadata.value, + ); + console.log( + `Backup ${metadata.name} on database ${metadata.database} is ` + + `${metadata.progress.progressPercent}% complete.`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } + + // List copy backup operations + try { + console.log( + '(metadata.@type:type.googleapis.com/google.spanner.admin.database.v1.CopyBackupMetadata) ' + + `AND (metadata.source_backup:${backupId})`, + ); + const [backupOperations] = await instance.getBackupOperations({ + filter: + '(metadata.@type:type.googleapis.com/google.spanner.admin.database.v1.CopyBackupMetadata) ' + + `AND (metadata.source_backup:${backupId})`, + }); + console.log('Copy Backup Operations:'); + backupOperations.forEach(backupOperation => { + const metadata = + protos.google.spanner.admin.database.v1.CopyBackupMetadata.decode( + backupOperation.metadata.value, + ); + console.log( + `Backup ${metadata.name} copied from source backup ${metadata.sourceBackup} is ` + + `${metadata.progress.progressPercent}% complete.`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_list_backup_operations] +} + +module.exports.getBackupOperations = getBackupOperations; diff --git a/handwritten/spanner/samples/archived/backups-get.js b/handwritten/spanner/samples/archived/backups-get.js new file mode 100644 index 00000000000..41cf88feef8 --- /dev/null +++ b/handwritten/spanner/samples/archived/backups-get.js @@ -0,0 +1,115 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +async function getBackups(instanceId, databaseId, backupId, projectId) { + // [START spanner_list_backups] + // 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'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance + const instance = spanner.instance(instanceId); + + try { + // List all backups + const [allBackups] = await instance.getBackups(); + console.log('All backups:'); + allBackups.forEach(backup => { + console.log(backup.id); + }); + + // List backups filtered by backup name + const [backupsByName] = await instance.getBackups({ + filter: `Name:${backupId}`, + }); + console.log('Backups matching backup name:'); + backupsByName.forEach(backup => { + console.log(backup.id); + }); + + // List backups expiring within 30 days + const expireTime = new Date(); + expireTime.setDate(expireTime.getDate() + 30); + const [backupsByExpiry] = await instance.getBackups({ + filter: `expire_time < "${expireTime.toISOString()}"`, + }); + console.log('Backups expiring within 30 days:'); + backupsByExpiry.forEach(backup => { + console.log(backup.id); + }); + + // List backups filtered by database name + const [backupsByDbName] = await instance.getBackups({ + filter: `Database:${databaseId}`, + }); + console.log('Backups matching database name:'); + backupsByDbName.forEach(backup => { + console.log(backup.id); + }); + + // List backups filtered by backup size + const [backupsBySize] = await instance.getBackups({ + filter: 'size_bytes > 100', + }); + console.log('Backups filtered by size:'); + backupsBySize.forEach(backup => { + console.log(backup.id); + }); + + // List backups that are ready that were created after a certain time + const createTime = new Date(); + createTime.setDate(createTime.getDate() - 1); + const [backupsByCreateTime] = await instance.getBackups({ + filter: `(state:READY) AND (create_time >= "${createTime.toISOString()}")`, + }); + console.log('Ready backups filtered by create time:'); + backupsByCreateTime.forEach(backup => { + console.log(backup.id); + }); + + // List backups using pagination + let getBackupsOptions = { + pageSize: 3, + gaxOptions: {autoPaginate: false}, + }; + console.log('Get backups paginated:'); + do { + const [backups, nextQuery] = await instance.getBackups(getBackupsOptions); + backups.forEach(backup => { + console.log(backup.id); + }); + getBackupsOptions = nextQuery; + } while (getBackupsOptions); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_list_backups] +} + +module.exports.getBackups = getBackups; diff --git a/handwritten/spanner/samples/archived/backups-restore-with-encryption-key.js b/handwritten/spanner/samples/archived/backups-restore-with-encryption-key.js new file mode 100644 index 00000000000..ef4170b7693 --- /dev/null +++ b/handwritten/spanner/samples/archived/backups-restore-with-encryption-key.js @@ -0,0 +1,77 @@ +/** + * Copyright 2021 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. + */ + +'use strict'; + +async function restoreBackupWithEncryptionKey( + instanceId, + databaseId, + backupId, + projectId, + keyName, +) { + // [START spanner_restore_backup_with_encryption_key] + // Imports the Google Cloud client library and precise date 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'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + // const keyName = + // 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Restore the database + console.log( + `Restoring database ${database.formattedName_} from backup ${backupId}.`, + ); + const [, restoreOperation] = await database.restore( + `projects/${projectId}/instances/${instanceId}/backups/${backupId}`, + { + encryptionConfig: { + encryptionType: 'CUSTOMER_MANAGED_ENCRYPTION', + kmsKeyName: keyName, + }, + }, + ); + + // Wait for restore to complete + console.log('Waiting for database restore to complete...'); + await restoreOperation.promise(); + + console.log('Database restored from backup.'); + const restoreInfo = await database.getRestoreInfo(); + const [data] = await database.get(); + console.log( + `Database ${restoreInfo.backupInfo.sourceDatabase} was restored ` + + `to ${databaseId} from backup ${restoreInfo.backupInfo.backup} ` + + `using encryption key ${data.metadata.encryptionConfig.kmsKeyName}.`, + ); + // [END spanner_restore_backup_with_encryption_key] +} + +module.exports.restoreBackupWithEncryptionKey = restoreBackupWithEncryptionKey; diff --git a/handwritten/spanner/samples/archived/backups-restore.js b/handwritten/spanner/samples/archived/backups-restore.js new file mode 100644 index 00000000000..ea0ead2fb00 --- /dev/null +++ b/handwritten/spanner/samples/archived/backups-restore.js @@ -0,0 +1,64 @@ +/** + * Copyright 2021 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. + */ + +'use strict'; + +async function restoreBackup(instanceId, databaseId, backupId, projectId) { + // [START spanner_restore_backup] + // Imports the Google Cloud client library and precise date library + const {Spanner} = require('@google-cloud/spanner'); + const {PreciseDate} = require('@google-cloud/precise-date'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Restore the database + console.log( + `Restoring database ${database.formattedName_} from backup ${backupId}.`, + ); + const [, restoreOperation] = await database.restore( + `projects/${projectId}/instances/${instanceId}/backups/${backupId}`, + ); + + // Wait for restore to complete + console.log('Waiting for database restore to complete...'); + await restoreOperation.promise(); + + console.log('Database restored from backup.'); + const restoreInfo = await database.getRestoreInfo(); + console.log( + `Database ${restoreInfo.backupInfo.sourceDatabase} was restored ` + + `to ${databaseId} from backup ${restoreInfo.backupInfo.backup} ` + + 'with version time ' + + `${new PreciseDate(restoreInfo.backupInfo.versionTime).toISOString()}.`, + ); + // [END spanner_restore_backup] +} + +module.exports.restoreBackup = restoreBackup; diff --git a/handwritten/spanner/samples/archived/backups-update.js b/handwritten/spanner/samples/archived/backups-update.js new file mode 100644 index 00000000000..ca37c156aff --- /dev/null +++ b/handwritten/spanner/samples/archived/backups-update.js @@ -0,0 +1,62 @@ +/** + * Copyright 2020 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. + */ + +'use strict'; + +async function updateBackup(instanceId, backupId, projectId) { + // [START spanner_update_backup] + // Imports the Google Cloud client library and precise date library + const {Spanner} = require('@google-cloud/spanner'); + const {PreciseDate} = require('@google-cloud/precise-date'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const backupId = 'my-backup'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and backup + const instance = spanner.instance(instanceId); + const backup = instance.backup(backupId); + + // Read backup metadata and update expiry time + try { + const currentExpireTime = await backup.getExpireTime(); + const maxExpireTime = backup.metadata.maxExpireTime; + const wantExpireTime = new PreciseDate(currentExpireTime); + wantExpireTime.setDate(wantExpireTime.getDate() + 1); + // New expire time should be less than the max expire time + const min = (currentExpireTime, maxExpireTime) => + currentExpireTime < maxExpireTime ? currentExpireTime : maxExpireTime; + const newExpireTime = new PreciseDate(min(wantExpireTime, maxExpireTime)); + console.log( + `Backup ${backupId} current expire time: ${currentExpireTime.toISOString()}`, + ); + console.log(`Updating expire time to ${newExpireTime.toISOString()}`); + await backup.updateExpireTime(newExpireTime); + console.log('Expire time updated.'); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_update_backup] +} + +module.exports.updateBackup = updateBackup; diff --git a/handwritten/spanner/samples/archived/backups.js b/handwritten/spanner/samples/archived/backups.js new file mode 100644 index 00000000000..42a5200ab16 --- /dev/null +++ b/handwritten/spanner/samples/archived/backups.js @@ -0,0 +1,153 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +const {createBackup} = require('./backups-create'); +const { + createBackupWithEncryptionKey, +} = require('./backups-create-with-encryption-key'); +const {cancelBackup} = require('./backups-cancel'); +const {getBackups} = require('./backups-get'); +const {getBackupOperations} = require('./backups-get-operations'); +const {getDatabaseOperations} = require('./backups-get-database-operations'); +const {updateBackup} = require('./backups-update'); +const {restoreBackup} = require('./backups-restore'); +const { + restoreBackupWithEncryptionKey, +} = require('./backups-restore-with-encryption-key'); +const {deleteBackup} = require('./backups-delete'); + +require('yargs') + .demand(1) + .command( + 'createBackup ', + 'Creates a backup of a Cloud Spanner database.', + {}, + opts => + createBackup( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + Date.parse(opts.versionTime), + ), + ) + .command( + 'createBackupWithEncryptionKey ', + 'Creates a backup of a Cloud Spanner database using an encryption key.', + {}, + opts => + createBackupWithEncryptionKey( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + opts.keyName, + ), + ) + .command( + 'cancelBackup ', + 'Creates and cancels a backup of a Cloud Spanner database.', + {}, + opts => + cancelBackup( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + ), + ) + .command( + 'getBackups ', + 'Lists backups in the instance with filters.', + {}, + opts => + getBackups( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + ), + ) + .command( + 'getBackupOperations ', + 'Lists all backup operations in the instance.', + {}, + opts => + getBackupOperations( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + ), + ) + .command( + 'getDatabaseOperations ', + 'Lists all database operations in the instance.', + {}, + opts => getDatabaseOperations(opts.instanceName, opts.projectId), + ) + .command( + 'updateBackup ', + 'Updates the expire time of a backup.', + {}, + opts => updateBackup(opts.instanceName, opts.backupName, opts.projectId), + ) + .command( + 'restoreBackup ', + 'Restores a Cloud Spanner database from a backup.', + {}, + opts => + restoreBackup( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + ), + ) + .command( + 'restoreBackupWithEncryptionKey ', + 'Restores a Cloud Spanner database from a backup with an encryption key.', + {}, + opts => + restoreBackupWithEncryptionKey( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + opts.keyName, + ), + ) + .command( + 'deleteBackup ', + 'Deletes a backup.', + {}, + opts => + deleteBackup( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + ), + ) + .example( + 'node $0 createBackup "my-instance" "my-database" "my-backup" "my-project-id"', + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/archived/database-create-with-default-leader.js b/handwritten/spanner/samples/archived/database-create-with-default-leader.js new file mode 100644 index 00000000000..8d1113f9ebe --- /dev/null +++ b/handwritten/spanner/samples/archived/database-create-with-default-leader.js @@ -0,0 +1,88 @@ +/** + * Copyright 2021 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 new database with a specific default leader +// usage: node database-create-with-default-leader.js + +'use strict'; + +function main(instanceId, databaseId, defaultLeader, projectId) { + // [START spanner_create_database_with_default_leader] + /** + * 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'; + // const defaultLeader = 'my-default-leader'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // 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 createDatabaseWithDefaultLeader() { + // Create a new database with an extra statement which will alter the + // database after creation to set the default leader. + console.log(`Creating database ${database.formattedName_}.`); + const createSingersTableStatement = ` + CREATE TABLE Singers ( + SingerId INT64 NOT NULL, + FirstName STRING(1024), + LastName STRING(1024), + SingerInfo BYTES(MAX) + ) PRIMARY KEY (SingerId)`; + const createAlbumsStatement = ` + 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`; + + // Default leader is one of the possible values in the leaderOptions field of the + // instance config of the instance where the database is created. + const setDefaultLeaderStatement = ` + ALTER DATABASE \`${databaseId}\` + SET OPTIONS (default_leader = '${defaultLeader}')`; + const [, operation] = await database.create({ + extraStatements: [ + createSingersTableStatement, + createAlbumsStatement, + setDefaultLeaderStatement, + ], + }); + + console.log(`Waiting for creation of ${database.id} to complete...`); + await operation.promise(); + console.log( + `Created database ${databaseId} with default leader ${defaultLeader}.`, + ); + } + createDatabaseWithDefaultLeader(); + // [END spanner_create_database_with_default_leader] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/database-create-with-encryption-key.js b/handwritten/spanner/samples/archived/database-create-with-encryption-key.js new file mode 100644 index 00000000000..a8e3a77b56f --- /dev/null +++ b/handwritten/spanner/samples/archived/database-create-with-encryption-key.js @@ -0,0 +1,71 @@ +// Copyright 2021 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. + +'use strict'; + +async function createDatabaseWithEncryptionKey( + instanceId, + databaseId, + projectId, + keyName, +) { + // [START spanner_create_database_with_encryption_key] + // 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'; + // const databaseId = 'my-database'; + // const keyName = + // 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance + const instance = spanner.instance(instanceId); + + const request = { + encryptionConfig: { + kmsKeyName: keyName, + }, + }; + + // Creates a database + const [database, operation] = await instance.createDatabase( + databaseId, + request, + ); + + console.log(`Waiting for operation on ${database.id} to complete...`); + await operation.promise(); + + console.log(`Created database ${databaseId} on instance ${instanceId}.`); + + // Get encryption key + const [data] = await database.get(); + + console.log( + `Database encrypted with key ${data.metadata.encryptionConfig.kmsKeyName}.`, + ); + // [END spanner_create_database_with_encryption_key] +} + +module.exports.createDatabaseWithEncryptionKey = + createDatabaseWithEncryptionKey; diff --git a/handwritten/spanner/samples/archived/database-create-with-version-retention-period.js b/handwritten/spanner/samples/archived/database-create-with-version-retention-period.js new file mode 100644 index 00000000000..bbe9d88141b --- /dev/null +++ b/handwritten/spanner/samples/archived/database-create-with-version-retention-period.js @@ -0,0 +1,72 @@ +/** + * Copyright 2021 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. + */ + +'use strict'; + +async function createDatabaseWithVersionRetentionPeriod( + instanceId, + databaseId, + projectId, +) { + // [START spanner_create_database_with_version_retention_period] + // 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'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + try { + // Create a new database with an extra statement which will alter the + // database after creation to set the version retention period. + console.log(`Creating database ${instance.formattedName_}.`); + const versionRetentionStatement = ` + ALTER DATABASE \`${databaseId}\` + SET OPTIONS (version_retention_period = '1d')`; + const [, operation] = await database.create({ + extraStatements: [versionRetentionStatement], + }); + + console.log(`Waiting for operation on ${database.id} to complete...`); + await operation.promise(); + console.log(` + Created database ${databaseId} with version retention period.`); + + const [data] = await database.get(); + console.log( + `Version retention period: ${data.metadata.versionRetentionPeriod}`, + ); + const earliestVersionTime = Spanner.timestamp( + data.metadata.earliestVersionTime, + ); + console.log(`Earliest version time: ${earliestVersionTime}`); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_database_with_version_retention_period] +} + +module.exports.createDatabaseWithVersionRetentionPeriod = + createDatabaseWithVersionRetentionPeriod; diff --git a/handwritten/spanner/samples/archived/database-get-ddl.js b/handwritten/spanner/samples/archived/database-get-ddl.js new file mode 100644 index 00000000000..65efb3a251b --- /dev/null +++ b/handwritten/spanner/samples/archived/database-get-ddl.js @@ -0,0 +1,58 @@ +/** + * Copyright 2021 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: Gets the schema definition of an existing database +// usage: node database-get-ddl.js + +'use strict'; + +function main(instanceId, databaseId, projectId) { + // [START spanner_get_database_ddl] + /** + * 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'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + // Gets a reference to a Cloud Spanner instance and a database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + async function getDatabaseDdl() { + // Get the schema definition of the database. + const [ddlStatements] = await database.getSchema(); + + console.log(`Retrieved database DDL for ${database.formattedName_}:`); + ddlStatements.forEach(statement => { + console.log(`${statement};\n`); + }); + } + getDatabaseDdl(); + // [END spanner_get_database_ddl] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/database-update-default-leader.js b/handwritten/spanner/samples/archived/database-update-default-leader.js new file mode 100644 index 00000000000..80174add783 --- /dev/null +++ b/handwritten/spanner/samples/archived/database-update-default-leader.js @@ -0,0 +1,63 @@ +/** + * Copyright 2021 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: Updates the default leader of an existing database +// usage: node database-update-default-leader.js + +'use strict'; + +function main(instanceId, databaseId, defaultLeader, projectId) { + // [START spanner_update_database_with_default_leader] + /** + * 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'; + // const defaultLeader = 'my-default-leader'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + // Gets a reference to a Cloud Spanner instance and a database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + async function updateDatabaseWithDefaultLeader() { + console.log(`Updating database ${database.formattedName_}.`); + const setDefaultLeaderStatement = ` + ALTER DATABASE \`${databaseId}\` + SET OPTIONS (default_leader = '${defaultLeader}')`; + const [operation] = await database.updateSchema(setDefaultLeaderStatement); + + console.log(`Waiting for updating of ${database.id} to complete...`); + await operation.promise(); + console.log( + `Updated database ${databaseId} with default leader ${defaultLeader}.`, + ); + } + updateDatabaseWithDefaultLeader(); + // [END spanner_update_database_with_default_leader] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/database-update.js b/handwritten/spanner/samples/archived/database-update.js new file mode 100644 index 00000000000..f188d920e72 --- /dev/null +++ b/handwritten/spanner/samples/archived/database-update.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: Updates a Cloud Spanner Database. +// usage: node database-update.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_update_database] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function updateDatabase() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + try { + console.log(`Updating database ${database.id}.`); + const [operation] = await database.setMetadata({ + enableDropProtection: true, + }); + console.log( + `Waiting for update operation for ${database.id} to complete...`, + ); + await operation.promise(); + console.log(`Updated database ${database.id}.`); + } catch (err) { + console.log('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + updateDatabase(); + // [END spanner_update_database] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/datatypes.js b/handwritten/spanner/samples/archived/datatypes.js new file mode 100644 index 00000000000..7f55d6f4acb --- /dev/null +++ b/handwritten/spanner/samples/archived/datatypes.js @@ -0,0 +1,768 @@ +// Copyright 2024 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. + +'use strict'; + +async function createVenuesTable(instanceId, databaseId, projectId) { + // [START spanner_create_table_with_datatypes] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = [ + `CREATE TABLE Venues ( + VenueId INT64 NOT NULL, + VenueName STRING(100), + VenueInfo BYTES(MAX), + Capacity INT64, + AvailableDates ARRAY, + LastContactDate Date, + OutdoorVenue BOOL, + PopularityScore FLOAT64, + LastUpdateTime TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true) + ) PRIMARY KEY (VenueId)`, + ]; + + // Creates a table in an existing database. + const [operation] = await database.updateSchema(request); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + + await operation.promise(); + + console.log(`Created table Venues in database ${databaseId}.`); + // [END spanner_create_table_with_datatypes] +} + +async function insertData(instanceId, databaseId, projectId) { + // [START spanner_insert_datatypes_data] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client. + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Instantiate Spanner table objects. + const venuesTable = database.table('Venues'); + const exampleBytes1 = new Buffer.from('Hello World 1'); + const exampleBytes2 = new Buffer.from('Hello World 2'); + const exampleBytes3 = new Buffer.from('Hello World 3'); + const availableDates1 = ['2020-12-01', '2020-12-02', '2020-12-03']; + const availableDates2 = ['2020-11-01', '2020-11-05', '2020-11-15']; + const availableDates3 = ['2020-10-01', '2020-10-07']; + + // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they + // must be converted to strings before being inserted as INT64s. + const data = [ + { + VenueId: '4', + VenueName: 'Venue 4', + VenueInfo: exampleBytes1, + Capacity: '1800', + AvailableDates: availableDates1, + LastContactDate: '2018-09-02', + OutdoorVenue: false, + PopularityScore: Spanner.float(0.85543), + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + VenueId: '19', + VenueName: 'Venue 19', + VenueInfo: exampleBytes2, + Capacity: '6300', + AvailableDates: availableDates2, + LastContactDate: '2019-01-15', + OutdoorVenue: true, + PopularityScore: Spanner.float(0.98716), + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + VenueId: '42', + VenueName: 'Venue 42', + VenueInfo: exampleBytes3, + Capacity: '3000', + AvailableDates: availableDates3, + LastContactDate: '2018-10-01', + OutdoorVenue: false, + PopularityScore: Spanner.float(0.72598), + LastUpdateTime: 'spanner.commit_timestamp()', + }, + ]; + + // Inserts rows into the Venues table. + try { + await venuesTable.insert(data); + console.log('Inserted data.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_insert_datatypes_data] +} + +async function queryWithArray(instanceId, databaseId, projectId) { + // [START spanner_query_with_array_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client. + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'date', + }; + + const parentFieldType = { + type: 'array', + child: fieldType, + }; + + const exampleArray = ['2020-10-01', '2020-11-01']; + + const query = { + sql: `SELECT VenueId, VenueName, AvailableDate FROM Venues v, + UNNEST(v.AvailableDates) as AvailableDate + WHERE AvailableDate in UNNEST(@availableDates)`, + params: { + availableDates: exampleArray, + }, + types: { + availableDates: parentFieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + rows.forEach(row => { + const availableDate = row[2]['value']; + const json = row.toJSON(); + console.log( + `VenueId: ${json.VenueId}, VenueName: ${ + json.VenueName + }, AvailableDate: ${JSON.stringify(availableDate).substring(1, 11)}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_array_parameter] +} + +async function queryWithBool(instanceId, databaseId, projectId) { + // [START spanner_query_with_bool_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'bool', + }; + + const exampleBool = true; + + const query = { + sql: `SELECT VenueId, VenueName, OutdoorVenue FROM Venues + WHERE OutdoorVenue = @outdoorVenue`, + params: { + outdoorVenue: exampleBool, + }, + types: { + outdoorVenue: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `VenueId: ${json.VenueId}, VenueName: ${json.VenueName},` + + ` OutdoorVenue: ${json.OutdoorVenue}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_bool_parameter] +} + +async function queryWithBytes(instanceId, databaseId, projectId) { + // [START spanner_query_with_bytes_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'bytes', + }; + + const exampleBytes = new Buffer.from('Hello World 1'); + + const query = { + sql: `SELECT VenueId, VenueName FROM Venues + WHERE VenueInfo = @venueInfo`, + params: { + venueInfo: exampleBytes, + }, + types: { + venueInfo: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log(`VenueId: ${json.VenueId}, VenueName: ${json.VenueName}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_bytes_parameter] +} + +async function queryWithDate(instanceId, databaseId, projectId) { + // [START spanner_query_with_date_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client. + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'date', + }; + + const exampleDate = '2019-01-01'; + + const query = { + sql: `SELECT VenueId, VenueName, LastContactDate FROM Venues + WHERE LastContactDate < @lastContactDate`, + params: { + lastContactDate: exampleDate, + }, + types: { + lastContactDate: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const date = row[2]['value']; + const json = row.toJSON(); + console.log( + `VenueId: ${json.VenueId}, VenueName: ${json.VenueName},` + + ` LastContactDate: ${JSON.stringify(date).substring(1, 11)}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_date_parameter] +} + +async function queryWithFloat(instanceId, databaseId, projectId) { + // [START spanner_query_with_float_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'float64', + }; + + const exampleFloat = Spanner.float(0.8); + + const query = { + sql: `SELECT VenueId, VenueName, PopularityScore FROM Venues + WHERE PopularityScore > @popularityScore`, + params: { + popularityScore: exampleFloat, + }, + types: { + popularityScore: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `VenueId: ${json.VenueId}, VenueName: ${json.VenueName},` + + ` PopularityScore: ${json.PopularityScore}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_float_parameter] +} + +async function queryWithInt(instanceId, databaseId, projectId) { + // [START spanner_query_with_int_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'int64', + }; + + const exampleInt = 3000; + + const query = { + sql: `SELECT VenueId, VenueName, Capacity FROM Venues + WHERE Capacity >= @capacity`, + params: { + capacity: exampleInt, + }, + types: { + capacity: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `VenueId: ${json.VenueId}, VenueName: ${json.VenueName},` + + ` Capacity: ${json.Capacity}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_int_parameter] +} + +async function queryWithString(instanceId, databaseId, projectId) { + // [START spanner_query_with_string_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'string', + }; + + const exampleString = 'Venue 42'; + + const query = { + sql: `SELECT VenueId, VenueName FROM Venues + WHERE VenueName = @venueName`, + params: { + venueName: exampleString, + }, + types: { + venueName: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log(`VenueId: ${json.VenueId}, VenueName: ${json.VenueName}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_string_parameter] +} + +async function queryWithTimestamp(instanceId, databaseId, projectId) { + // [START spanner_query_with_timestamp_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client. + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'timestamp', + }; + + const exampleTimestamp = new Date().toISOString(); + + const query = { + sql: `SELECT VenueId, VenueName, LastUpdateTime FROM Venues + WHERE LastUpdateTime < @lastUpdateTime`, + params: { + lastUpdateTime: exampleTimestamp, + }, + types: { + lastUpdateTime: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `VenueId: ${json.VenueId}, VenueName: ${json.VenueName},` + + ` LastUpdateTime: ${json.LastUpdateTime}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_timestamp_parameter] +} + +const {addNumericColumn} = require('./numeric-add-column'); +const {updateWithNumericData} = require('../numeric-update-data'); +const {queryWithNumericParameter} = require('../numeric-query-parameter'); + +const {addJsonColumn} = require('./json-add-column'); +const {updateWithJsonData} = require('../json-update-data'); +const {queryWithJsonParameter} = require('../json-query-parameter'); + +require('yargs') + .demand(1) + .command( + 'createVenuesTable ', + 'Creates sample "Venues" table containing example datatype columns in a Cloud Spanner database.', + {}, + opts => + createVenuesTable(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'insertData ', + 'Inserts new rows of data into an sample "Venues" Cloud Spanner table.', + {}, + opts => insertData(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithArray ', + "Query data from the sample 'Venues' table with an ARRAY datatype.", + {}, + opts => + queryWithArray(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithBool ', + "Query data from the sample 'Venues' table with a BOOL datatype.", + {}, + opts => queryWithBool(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithBytes ', + "Query data from the sample 'Venues' table with a BYTES datatype.", + {}, + opts => + queryWithBytes(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithDate ', + "Query data from the sample 'Venues' table with a DATE datatype.", + {}, + opts => queryWithDate(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithFloat ', + "Query data from the sample 'Venues' table with a FLOAT64 datatype.", + {}, + opts => + queryWithFloat(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithInt ', + "Query data from the sample 'Venues' table with a INT64 datatype.", + {}, + opts => queryWithInt(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithString ', + "Query data from the sample 'Venues' table with a STRING datatype.", + {}, + opts => + queryWithString(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithTimestamp ', + "Query data from the sample 'Venues' table with a TIMESTAMP datatype.", + {}, + opts => + queryWithTimestamp(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'addNumericColumn ', + 'Adds a "Revenue" column to sample "Venues" table in a Cloud Spanner database.', + {}, + opts => + addNumericColumn(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'updateWithNumericData ', + 'Updates rows to include "Revenue" in sample "Venues" Cloud Spanner table.', + {}, + opts => + updateWithNumericData( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'queryWithNumericParameter ', + "Query data from the sample 'Venues' table with a NUMERIC datatype.", + {}, + opts => + queryWithNumericParameter( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'addJsonColumn ', + 'Adds a "VenueDetails" column to sample "Venues" table in a Cloud Spanner database.', + {}, + opts => addJsonColumn(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'updateWithJsonData ', + 'Updates rows to include "VenueDetails" in sample "Venues" Cloud Spanner table.', + {}, + opts => + updateWithJsonData(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithJsonParameter ', + "Query data from the sample 'Venues' table with a JSON datatype.", + {}, + opts => + queryWithJsonParameter( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .example( + 'node $0 createVenuesTable "my-instance" "my-database" "my-project-id"', + ) + .example('node $0 insertData "my-instance" "my-database" "my-project-id"') + .example('node $0 queryWithArray "my-instance" "my-database" "my-project-id"') + .example('node $0 queryWithBool "my-instance" "my-database" "my-project-id"') + .example('node $0 queryWithBytes "my-instance" "my-database" "my-project-id"') + .example('node $0 queryWithDate "my-instance" "my-database" "my-project-id"') + .example('node $0 queryWithFloat "my-instance" "my-database" "my-project-id"') + .example('node $0 queryWithInt "my-instance" "my-database" "my-project-id"') + .example( + 'node $0 queryWithString "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 queryWithTimestamp "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 addNumericColumn "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 updateWithNumericData "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 queryWithNumericParameter "my-instance" "my-database" "my-project-id"', + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/archived/enable-fine-grained-access.js b/handwritten/spanner/samples/archived/enable-fine-grained-access.js new file mode 100644 index 00000000000..35bf80e5913 --- /dev/null +++ b/handwritten/spanner/samples/archived/enable-fine-grained-access.js @@ -0,0 +1,80 @@ +// Copyright 2024 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: Enable fine grained access control +// usage: node enable-fine-grained-access.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', + iamMember = 'user:alice@example.com', + databaseRole = 'parent', + title = 'condition title', +) { + // [START spanner_enable_fine_grained_access] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + // iamMember = 'user:alice@example.com'; + // databaseRole = 'parent'; + // title = 'condition title'; + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function enableFineGrainedAccess() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const [policy] = await database.getIamPolicy({requestedPolicyVersion: 3}); + if (policy.version < 3) { + policy.version = 3; + } + + const newBinding = { + role: 'roles/spanner.fineGrainedAccessUser', + members: [`user:${iamMember}`], + condition: { + title: title, + expression: `resource.name.endsWith("/databaseRoles/${databaseRole}")`, + }, + }; + policy.bindings.push(newBinding); + await database.setIamPolicy({policy: policy}); + // Requested Policy Version is Optional. The maximum policy version that will be used to format the policy. + // Valid values are 0, 1, and 3. Requests specifying an invalid value will be rejected. + const newPolicy = await database.getIamPolicy({requestedPolicyVersion: 3}); + console.log(newPolicy); + } + enableFineGrainedAccess(); + // [END spanner_enable_fine_grained_access] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/get-database-roles.js b/handwritten/spanner/samples/archived/get-database-roles.js new file mode 100644 index 00000000000..f550a608d8b --- /dev/null +++ b/handwritten/spanner/samples/archived/get-database-roles.js @@ -0,0 +1,61 @@ +// Copyright 2022 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: List database roles +// usage: node get-database-roles.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_list_database_roles] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function getDatabaseRoles() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Fetching database roles + const [databaseRoles] = await database.getDatabaseRoles(); + console.log(`Roles for Database: ${database.formattedName_}`); + databaseRoles.forEach(role => { + console.log(`Role: ${role.name}`); + }); + } + getDatabaseRoles(); + // [END spanner_list_database_roles] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/get-instance-config.js b/handwritten/spanner/samples/archived/get-instance-config.js new file mode 100644 index 00000000000..0fcdfb93a9b --- /dev/null +++ b/handwritten/spanner/samples/archived/get-instance-config.js @@ -0,0 +1,56 @@ +/** + * Copyright 2021 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: Gets the instance config metadata for the configuration nam6 +// usage: node get-instance-config.js + +'use strict'; + +function main(projectId) { + // [START spanner_get_instance_config] + /** + * TODO(developer): Uncomment the following line before running the sample. + */ + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function getInstanceConfig() { + // Get the instance config for the multi-region North America 6 (NAM6). + // See https://cloud.google.com/spanner/docs/instance-configurations#configuration for a list of all available + // configurations. + const [instanceConfig] = await spanner.getInstanceConfig('nam6'); + console.log( + `Available leader options for instance config ${instanceConfig.name} ('${ + instanceConfig.displayName + }'): + ${instanceConfig.leaderOptions.join()}`, + ); + } + getInstanceConfig(); + // [END spanner_get_instance_config] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/index-create-storing.js b/handwritten/spanner/samples/archived/index-create-storing.js new file mode 100644 index 00000000000..a17d3082f82 --- /dev/null +++ b/handwritten/spanner/samples/archived/index-create-storing.js @@ -0,0 +1,73 @@ +// Copyright 2021 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 new value-storing index +// usage: node createStoringIndex + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_create_storing_index] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function createStoringIndex() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = [ + 'CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)', + ]; + + // Creates a new index in the database + try { + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Added the AlbumsByAlbumTitle2 index.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + createStoringIndex(); + // [END spanner_create_storing_index] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/index-create.js b/handwritten/spanner/samples/archived/index-create.js new file mode 100644 index 00000000000..77066cdeb44 --- /dev/null +++ b/handwritten/spanner/samples/archived/index-create.js @@ -0,0 +1,71 @@ +// Copyright 2021 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 new index +// usage: node createIndex + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_create_index] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function createIndex() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = ['CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)']; + + // Creates a new index in the database + try { + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Added the AlbumsByAlbumTitle index.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + createIndex(); + // [END spanner_create_index] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/indexing.js b/handwritten/spanner/samples/archived/indexing.js new file mode 100644 index 00000000000..2a4e84877fd --- /dev/null +++ b/handwritten/spanner/samples/archived/indexing.js @@ -0,0 +1,342 @@ +// Copyright 2024 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. + +'use strict'; + +async function createIndex(instanceId, databaseId, projectId) { + // [START spanner_create_index] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = ['CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)']; + + // Creates a new index in the database + try { + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Added the AlbumsByAlbumTitle index.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_create_index] +} + +async function createStoringIndex(instanceId, databaseId, projectId) { + // [START spanner_create_storing_index] + // "Storing" indexes store copies of the columns they index + // This speeds up queries, but takes more space compared to normal indexes + // See the link below for more information: + // https://cloud.google.com/spanner/docs/secondary-indexes#storing_clause + + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = [ + 'CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)', + ]; + + // Creates a new index in the database + try { + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Added the AlbumsByAlbumTitle2 index.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_create_storing_index] +} + +async function queryDataWithIndex( + instanceId, + databaseId, + startTitle, + endTitle, + projectId, +) { + // [START spanner_query_data_with_index] + // 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'; + // const databaseId = 'my-database'; + // const startTitle = 'Ardvark'; + // const endTitle = 'Goo'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: `SELECT AlbumId, AlbumTitle, MarketingBudget + FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle} + WHERE AlbumTitle >= @startTitle AND AlbumTitle <= @endTitle`, + params: { + startTitle: startTitle, + endTitle: endTitle, + }, + }; + + // Queries rows from the Albums table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + const marketingBudget = json.MarketingBudget + ? json.MarketingBudget + : null; // This value is nullable + console.log( + `AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}, MarketingBudget: ${marketingBudget}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_data_with_index] +} + +async function readDataWithIndex(instanceId, databaseId, projectId) { + // [START spanner_read_data_with_index] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const albumsTable = database.table('Albums'); + + const query = { + columns: ['AlbumId', 'AlbumTitle'], + keySet: { + all: true, + }, + index: 'AlbumsByAlbumTitle', + }; + + // Reads the Albums table using an index + try { + const [rows] = await albumsTable.read(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log(`AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_read_data_with_index] +} + +async function readDataWithStoringIndex(instanceId, databaseId, projectId) { + // [START spanner_read_data_with_storing_index] + // "Storing" indexes store copies of the columns they index + // This speeds up queries, but takes more space compared to normal indexes + // See the link below for more information: + // https://cloud.google.com/spanner/docs/secondary-indexes#storing_clause + + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const albumsTable = database.table('Albums'); + + const query = { + columns: ['AlbumId', 'AlbumTitle', 'MarketingBudget'], + keySet: { + all: true, + }, + index: 'AlbumsByAlbumTitle2', + }; + + // Reads the Albums table using a storing index + try { + const [rows] = await albumsTable.read(query); + + rows.forEach(row => { + const json = row.toJSON(); + let rowString = `AlbumId: ${json.AlbumId}`; + rowString += `, AlbumTitle: ${json.AlbumTitle}`; + if (json.MarketingBudget) { + rowString += `, MarketingBudget: ${json.MarketingBudget}`; + } + console.log(rowString); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_read_data_with_storing_index] +} + +require('yargs') + .demand(1) + .command( + 'createIndex ', + 'Creates a new index in an example Cloud Spanner table.', + {}, + opts => createIndex(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'createStoringIndex ', + 'Creates a new value-storing index in an example Cloud Spanner table.', + {}, + opts => + createStoringIndex(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryIndex ', + `Executes a read-only SQL query against an example Cloud Spanner table using an existing index. + Returns results with titles between a start title (default: 'Ardvark') and an end title (default: 'Goo').`, + { + startTitle: { + type: 'string', + alias: 's', + default: 'Ardvark', + }, + endTitle: { + type: 'string', + alias: 'e', + default: 'Goo', + }, + }, + opts => + queryDataWithIndex( + opts.instanceName, + opts.databaseName, + opts.startTitle, + opts.endTitle, + opts.projectId, + ), + ) + .command( + 'readIndex ', + 'Reads data from an example Cloud Spanner table using an existing index.', + {}, + opts => + readDataWithIndex(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'readStoringIndex ', + 'Reads data from an example Cloud Spanner table using an existing storing index.', + {}, + opts => + readDataWithStoringIndex( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .example('node $0 createIndex "my-instance" "my-database" "my-project-id"') + .example( + 'node $0 createStoringIndex "my-instance" "my-database" "my-project-id"', + ) + .example('node $0 queryIndex "my-instance" "my-database" "my-project-id"') + .example('node $0 readIndex "my-instance" "my-database" "my-project-id"') + .example( + 'node $0 readStoringIndex "my-instance" "my-database" "my-project-id"', + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/archived/instance-config-create.js b/handwritten/spanner/samples/archived/instance-config-create.js new file mode 100644 index 00000000000..0969abb96a5 --- /dev/null +++ b/handwritten/spanner/samples/archived/instance-config-create.js @@ -0,0 +1,79 @@ +/** + * Copyright 2022 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 user-managed instance configuration. +// usage: node instance-config-create + +'use strict'; + +function main( + instanceConfigId = 'custom-my-instance-config', + baseInstanceConfigId = 'my-base-instance-config', + projectId = 'my-project-id', +) { + // [START spanner_create_instance_config] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const instanceConfigId = 'custom-my-instance-config-id' + // const baseInstanceConfigId = 'my-base-instance-config-id'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + async function createInstanceConfig() { + // Creates a new instance config + const instanceConfig = spanner.instanceConfig(instanceConfigId); + try { + const [baseInstanceConfig] = + await spanner.getInstanceConfig(baseInstanceConfigId); + console.log(`Creating instance config ${instanceConfig.formattedName_}.`); + const [, operation] = await instanceConfig.create({ + displayName: instanceConfigId, + baseConfig: baseInstanceConfig.name, + replicas: baseInstanceConfig.replicas.concat( + baseInstanceConfig.optionalReplicas[0], + ), + }); + console.log( + `Waiting for create operation for ${instanceConfig.id} to complete...`, + ); + await operation.promise(); + console.log(`Created instance config ${instanceConfigId}.`); + } catch (err) { + console.error( + 'ERROR: Creating instance config ', + instanceConfigId, + ' failed with error message ', + err, + ); + } + } + createInstanceConfig(); + // [END spanner_create_instance_config] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/instance-config-delete.js b/handwritten/spanner/samples/archived/instance-config-delete.js new file mode 100644 index 00000000000..f7719ba1da0 --- /dev/null +++ b/handwritten/spanner/samples/archived/instance-config-delete.js @@ -0,0 +1,76 @@ +/** + * Copyright 2022 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: Deletes a user-managed instance configuration. +// usage: node instance-config-delete + +'use strict'; + +function main( + instanceConfigId = 'custom-my-instance-config', + projectId = 'my-project-id', +) { + // [START spanner_delete_instance_config] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const instanceConfigId = 'custom-my-instance-config-id'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + async function deleteInstanceConfig() { + // Deletes an instance config. + const instanceConfig = spanner.instanceConfig(instanceConfigId); + try { + // Delete the instance config. + console.log(`Deleting ${instanceConfig.id}...\n`); + await instanceConfig.delete(); + // Verify that the instance config no longer exists + const exists = await instanceConfig.exists(); + if (exists) { + console.error( + 'Error: Instance config ', + instanceConfigId, + ' still exists', + ); + } else { + console.log(`Deleted instance config ${instanceConfigId}.\n`); + } + } catch (err) { + console.error( + 'ERROR: Deleting instance config ', + instanceConfigId, + ' failed with error message ', + err, + ); + } + } + deleteInstanceConfig(); + // [END spanner_delete_instance_config] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/instance-config-get-operations.js b/handwritten/spanner/samples/archived/instance-config-get-operations.js new file mode 100644 index 00000000000..a6d31604db9 --- /dev/null +++ b/handwritten/spanner/samples/archived/instance-config-get-operations.js @@ -0,0 +1,76 @@ +/** + * Copyright 2022 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: Lists the instance configuration operations. +// usage: node instance-config-get-operations + +'use strict'; + +function main(projectId = 'my-project-id') { + // [START spanner_list_instance_config_operations] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + async function getInstanceConfigOperations() { + // Lists the instance config operations. + try { + console.log( + `Getting list of instance config operations on project ${projectId}...\n`, + ); + const [instanceConfigOperations] = + await spanner.getInstanceConfigOperations({ + filter: + '(metadata.@type=type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceConfigMetadata)', + }); + console.log( + `Available instance config operations for project ${projectId}:`, + ); + instanceConfigOperations.forEach(instanceConfigOperation => { + const metadata = instanceConfigOperation.metadata; + const instanceConfig = + protos.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata.decode( + instanceConfigOperation.metadata.value, + ).instanceConfig; + console.log( + `Instance config operation for ${instanceConfig.name} of type` + + ` ${metadata.type_url} has status ${ + instanceConfigOperation.done ? 'done' : 'running' + }.`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } + } + getInstanceConfigOperations(); + // [END spanner_list_instance_config_operations] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/instance-config-update.js b/handwritten/spanner/samples/archived/instance-config-update.js new file mode 100644 index 00000000000..474ed8bac40 --- /dev/null +++ b/handwritten/spanner/samples/archived/instance-config-update.js @@ -0,0 +1,76 @@ +/** + * Copyright 2022 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: Updates a user-managed instance configuration. +// usage: node instance-config-update + +'use strict'; + +function main( + instanceConfigId = 'custom-my-instance-config', + projectId = 'my-project-id', +) { + // [START spanner_update_instance_config] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const instanceConfigId = 'custom-my-instance-config-id'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + async function updateInstanceConfig() { + // Updates an instance config + const instanceConfig = spanner.instanceConfig(instanceConfigId); + try { + console.log(`Updating instance config ${instanceConfig.formattedName_}.`); + const [operation] = await instanceConfig.setMetadata({ + instanceConfig: { + displayName: 'updated custom instance config', + labels: { + updated: 'true', + }, + }, + }); + console.log( + `Waiting for update operation for ${instanceConfig.id} to complete...`, + ); + await operation.promise(); + console.log(`Updated instance config ${instanceConfigId}.`); + } catch (err) { + console.error( + 'ERROR: Updating instance config ', + instanceConfigId, + ' failed with error message ', + err, + ); + } + } + updateInstanceConfig(); + // [END spanner_update_instance_config] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/instance-with-processing-units.js b/handwritten/spanner/samples/archived/instance-with-processing-units.js new file mode 100644 index 00000000000..bb77a41b890 --- /dev/null +++ b/handwritten/spanner/samples/archived/instance-with-processing-units.js @@ -0,0 +1,67 @@ +/** + * Copyright 2021 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. + */ + +'use strict'; + +async function createInstanceWithProcessingUnits(instanceId, projectId) { + // [START spanner_create_instance_with_processing_units] + // 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'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instance = spanner.instance(instanceId); + + // Creates a new instance + try { + console.log(`Creating instance ${instance.formattedName_}.`); + const [, operation] = await instance.create({ + config: 'regional-us-central1', + processingUnits: 500, + displayName: 'This is a display name.', + labels: { + ['cloud_spanner_samples']: 'true', + }, + }); + + console.log(`Waiting for operation on ${instance.id} to complete...`); + await operation.promise(); + + console.log(`Created instance ${instanceId}.`); + + const [metadata] = await instance.getMetadata({ + fieldNames: ['processingUnits'], + }); + console.log( + `Instance ${instanceId} has ${metadata.processingUnits} ` + + 'processing units.', + ); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_instance_with_processing_units] +} + +module.exports.createInstanceWithProcessingUnits = + createInstanceWithProcessingUnits; diff --git a/handwritten/spanner/samples/archived/instance.js b/handwritten/spanner/samples/archived/instance.js new file mode 100644 index 00000000000..42f6de35033 --- /dev/null +++ b/handwritten/spanner/samples/archived/instance.js @@ -0,0 +1,86 @@ +/** + * Copyright 2020 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. + */ + +'use strict'; + +async function createInstance(instanceId, projectId) { + // [START spanner_create_instance] + // 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'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instance = spanner.instance(instanceId); + + // Creates a new instance + try { + console.log(`Creating instance ${instance.formattedName_}.`); + const [, operation] = await instance.create({ + config: 'regional-us-west1', + nodes: 1, + displayName: 'This is a display name.', + labels: { + ['cloud_spanner_samples']: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + }); + + console.log(`Waiting for operation on ${instance.id} to complete...`); + await operation.promise(); + + console.log(`Created instance ${instanceId}.`); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_instance] +} + +const { + createInstanceWithProcessingUnits, +} = require('./instance-with-processing-units'); + +require('yargs') + .demand(1) + .command( + 'createInstance ', + 'Creates an example instance in a Cloud Spanner instance.', + {}, + opts => createInstance(opts.instanceName, opts.projectId), + ) + .example('node $0 createInstance "my-instance" "my-project-id"') + .command( + 'createInstanceWithProcessingUnits ', + 'Creates an example instance in a Cloud Spanner instance with processing units.', + {}, + opts => + createInstanceWithProcessingUnits(opts.instanceName, opts.projectId), + ) + .example( + 'node $0 createInstanceWithProcessingUnits "my-instance" "my-project-id"', + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/archived/json-add-column.js b/handwritten/spanner/samples/archived/json-add-column.js new file mode 100644 index 00000000000..4d02b408a7e --- /dev/null +++ b/handwritten/spanner/samples/archived/json-add-column.js @@ -0,0 +1,53 @@ +// Copyright 2021 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. + +'use strict'; + +async function addJsonColumn(instanceId, databaseId, projectId) { + // [START spanner_add_json_column] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = ['ALTER TABLE Venues ADD COLUMN VenueDetails JSON']; + + // Alter existing table to add a column. + const [operation] = await database.updateSchema(request); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + + await operation.promise(); + + console.log( + `Added VenueDetails column to Venues table in database ${databaseId}.`, + ); + // [END spanner_add_json_column] +} + +module.exports.addJsonColumn = addJsonColumn; diff --git a/handwritten/spanner/samples/archived/list-databases.js b/handwritten/spanner/samples/archived/list-databases.js new file mode 100644 index 00000000000..642600ff65a --- /dev/null +++ b/handwritten/spanner/samples/archived/list-databases.js @@ -0,0 +1,58 @@ +/** + * Copyright 2021 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: Lists all databases on the selected instance +// usage: node list-databases.js + +'use strict'; + +function main(instanceId, projectId) { + // [START spanner_list_databases] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + // Gets a reference to a Cloud Spanner instance + const instance = spanner.instance(instanceId); + + async function listDatabases() { + // Lists all databases on the instance. + const [databases] = await instance.getDatabases(); + console.log(`Databases for projects/${projectId}/instances/${instanceId}:`); + databases.forEach(database => { + const defaultLeader = database.metadata.defaultLeader + ? `(default leader = ${database.metadata.defaultLeader})` + : ''; + console.log(`\t${database.id} ${defaultLeader}`); + }); + } + listDatabases(); + // [END spanner_list_databases] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/list-instance-configs.js b/handwritten/spanner/samples/archived/list-instance-configs.js new file mode 100644 index 00000000000..351d44217b0 --- /dev/null +++ b/handwritten/spanner/samples/archived/list-instance-configs.js @@ -0,0 +1,59 @@ +/** + * Copyright 2021 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: Lists all the available instance configs for the selected project. +// usage: node list-instance-configs.js + +'use strict'; + +function main(projectId) { + // [START spanner_list_instance_configs] + /** + * TODO(developer): Uncomment the following line before running the sample. + */ + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function listInstanceConfigs() { + // Lists all available instance configurations in the project. + // See https://cloud.google.com/spanner/docs/instance-configurations#configuration for a list of all available + // configurations. + const [instanceConfigs] = await spanner.getInstanceConfigs(); + console.log(`Available instance configs for project ${projectId}:`); + instanceConfigs.forEach(instanceConfig => { + console.log( + `Available leader options for instance config ${ + instanceConfig.name + } ('${instanceConfig.displayName}'): + ${instanceConfig.leaderOptions.join()}`, + ); + }); + } + listInstanceConfigs(); + // [END spanner_list_instance_configs] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/numeric-add-column.js b/handwritten/spanner/samples/archived/numeric-add-column.js new file mode 100644 index 00000000000..46230791273 --- /dev/null +++ b/handwritten/spanner/samples/archived/numeric-add-column.js @@ -0,0 +1,53 @@ +// Copyright 2020 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. + +'use strict'; + +async function addNumericColumn(instanceId, databaseId, projectId) { + // [START spanner_add_numeric_column] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = ['ALTER TABLE Venues ADD COLUMN Revenue NUMERIC']; + + // Alter existing table to add a column. + const [operation] = await database.updateSchema(request); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + + await operation.promise(); + + console.log( + `Added Revenue column to Venues table in database ${databaseId}.`, + ); + // [END spanner_add_numeric_column] +} + +module.exports.addNumericColumn = addNumericColumn; diff --git a/handwritten/spanner/samples/archived/pg-add-column.js b/handwritten/spanner/samples/archived/pg-add-column.js new file mode 100644 index 00000000000..881e0a7b399 --- /dev/null +++ b/handwritten/spanner/samples/archived/pg-add-column.js @@ -0,0 +1,67 @@ +// Copyright 2022 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: Adds a column to an existing table in a Spanner PostgreSQL database. +// usage: node pg-add-column.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_add_column] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgAddColumn() { + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = ['ALTER TABLE Albums ADD COLUMN MarketingBudget BIGINT']; + + // Alter existing table to add a column. + const [operation] = await database.updateSchema(request); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + + await operation.promise(); + + console.log( + `Added MarketingBudget column to Albums table in database ${databaseId}.`, + ); + } + pgAddColumn(); + // [END spanner_postgresql_add_column] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/pg-database-create.js b/handwritten/spanner/samples/archived/pg-database-create.js new file mode 100644 index 00000000000..70d15f6a024 --- /dev/null +++ b/handwritten/spanner/samples/archived/pg-database-create.js @@ -0,0 +1,95 @@ +// Copyright 2022 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 PostgreSQL Database. +// usage: node pg-database-create.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_create_database] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function createPgDatabase() { + // Gets a reference to a Cloud Spanner instance + const instance = spanner.instance(instanceId); + + // Set Dialect as PostgreSQL + const request = { + databaseDialect: Spanner.POSTGRESQL, + }; + + // Creates a PostgreSQL database. PostgreSQL create requests may not contain any additional + // DDL statements. We need to execute these separately after the database has been created. + const [database, operationCreate] = await instance.createDatabase( + databaseId, + request, + ); + + console.log(`Waiting for operation on ${database.id} to complete...`); + await operationCreate.promise(); + await database.getMetadata(); + console.log( + `Created database ${databaseId} on instance ${instanceId} with dialect ${database.metadata.databaseDialect}.`, + ); + + // Create a couple of tables using a separate request. We must use PostgreSQL style DDL as the + // database has been created with the PostgreSQL dialect. + const statements = [ + `CREATE TABLE Singers + (SingerId bigint NOT NULL, + FirstName varchar(1024), + LastName varchar(1024), + SingerInfo bytea, + FullName character varying(2048) GENERATED ALWAYS AS (FirstName || ' ' || LastName) STORED, + PRIMARY KEY (SingerId) + ); + CREATE TABLE Albums + (AlbumId bigint NOT NULL, + SingerId bigint NOT NULL REFERENCES Singers (SingerId), + AlbumTitle text, + PRIMARY KEY (AlbumId) + );`, + ]; + const [operationUpdateDDL] = await database.updateSchema(statements); + await operationUpdateDDL.promise(); + console.log('Updated schema'); + } + createPgDatabase(); + // [END spanner_postgresql_create_database] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/pg-index-create-storing.js b/handwritten/spanner/samples/archived/pg-index-create-storing.js new file mode 100644 index 00000000000..b4fb1a4d1f7 --- /dev/null +++ b/handwritten/spanner/samples/archived/pg-index-create-storing.js @@ -0,0 +1,74 @@ +// Copyright 2022 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 new storing index in a Spanner PostgreSQL database. +// usage: node pg-index-create-storing.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_create_storing_index] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgCreateStoringIndex() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = [ + 'CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle) INCLUDE(MarketingBudget)', + ]; + + // Creates a new index in the database + try { + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Added the AlbumsByAlbumTitle index.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + pgCreateStoringIndex(); + // [END spanner_postgresql_create_storing_index] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/pg-interleaving.js b/handwritten/spanner/samples/archived/pg-interleaving.js new file mode 100644 index 00000000000..36effb54beb --- /dev/null +++ b/handwritten/spanner/samples/archived/pg-interleaving.js @@ -0,0 +1,80 @@ +// Copyright 2022 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: Created interleaved table hierarchy using PostgreSQL dialect. +// usage: node pg-interleaving.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_interleaved_table] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgInterleaving() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const statements = [ + `CREATE TABLE Author + (AuthorId bigint NOT NULL, + FirstName varchar(1024), + LastName varchar(1024), + Rating double precision, + PRIMARY KEY (AuthorId) + ); + CREATE TABLE Book + (AuthorId bigint NOT NULL, + BookId bigint NOT NULL, + BookTitle text, + PRIMARY KEY (AuthorId, BookId) + ) INTERLEAVE IN PARENT Author ON DELETE CASCADE;`, + ]; + + // Updates schema by adding new tables. + const [operation] = await database.updateSchema(statements); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + await operation.promise(); + + console.log( + `Created an interleaved table hierarchy in database ${databaseId} using PostgreSQL dialect.`, + ); + } + pgInterleaving(); + // [END spanner_postgresql_interleaved_table] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/pg-jsonb-add-column.js b/handwritten/spanner/samples/archived/pg-jsonb-add-column.js new file mode 100644 index 00000000000..4db5663f078 --- /dev/null +++ b/handwritten/spanner/samples/archived/pg-jsonb-add-column.js @@ -0,0 +1,65 @@ +// Copyright 2024 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: Showcase how add a jsonb column in a PostgreSQL table. +// usage: node pg-jsonb-add-column.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_jsonb_add_column] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgJsonbAddColumn() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = ['ALTER TABLE Venues ADD COLUMN VenueDetails JSONB']; + + // Updates schema by adding a new table. + const [operation] = await database.updateSchema(request); + console.log(`Waiting for operation on ${databaseId} to complete...`); + await operation.promise(); + console.log( + `Added jsonb column to table venues to database ${databaseId}.`, + ); + } + pgJsonbAddColumn(); + // [END spanner_postgresql_jsonb_add_column] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/pg-sequence-alter.js b/handwritten/spanner/samples/archived/pg-sequence-alter.js new file mode 100644 index 00000000000..53dae3ad4b5 --- /dev/null +++ b/handwritten/spanner/samples/archived/pg-sequence-alter.js @@ -0,0 +1,95 @@ +// Copyright 2024 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 sequence in a PostgreSQL database. +// usage: node pg-sequence-alter.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_postgresql_alter_sequence] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function alterSequence(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = ['ALTER SEQUENCE Seq SKIP RANGE 1000 5000000']; + + try { + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log( + 'Altered Seq sequence to skip an inclusive range between 1000 and 5000000.', + ); + } catch (err) { + console.error('ERROR:', err); + } + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: "INSERT INTO Customers (CustomerName) VALUES ('Lea'), ('Catalina'), ('Smith') RETURNING CustomerId", + }); + + rows.forEach(row => { + console.log( + `Inserted customer record with CustomerId: ${ + row.toJSON({wrapNumbers: true}).customerid.value + }`, + ); + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log(`Number of customer records inserted is: ${rowCount}`); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + }); + } + await alterSequence(instanceId, databaseId); + // [END spanner_postgresql_alter_sequence] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/pg-sequence-create.js b/handwritten/spanner/samples/archived/pg-sequence-create.js new file mode 100644 index 00000000000..3bdc48facf0 --- /dev/null +++ b/handwritten/spanner/samples/archived/pg-sequence-create.js @@ -0,0 +1,99 @@ +// Copyright 2024 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 sequence in PostgreSQL database. +// usage: node pg-sequence-create.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_postgresql_create_sequence] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function createSequence(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = [ + 'CREATE SEQUENCE Seq BIT_REVERSED_POSITIVE', + "CREATE TABLE Customers (CustomerId BIGINT DEFAULT nextval('Seq'), CustomerName character varying(1024), PRIMARY KEY (CustomerId))", + ]; + + // Creates a new table with sequence + try { + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log( + 'Created Seq sequence and Customers table, where the key column CustomerId uses the sequence as a default value', + ); + } catch (err) { + console.error('ERROR:', err); + } + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: "INSERT INTO Customers (CustomerName) VALUES ('Alice'), ('David'), ('Marc') RETURNING CustomerId", + }); + + rows.forEach(row => { + console.log( + `Inserted customer record with CustomerId: ${ + row.toJSON({wrapNumbers: true}).customerid.value + }`, + ); + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log(`Number of customer records inserted is: ${rowCount}`); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + }); + } + await createSequence(instanceId, databaseId); + // [END spanner_postgresql_create_sequence] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/pg-sequence-drop.js b/handwritten/spanner/samples/archived/pg-sequence-drop.js new file mode 100644 index 00000000000..cf17e9a822d --- /dev/null +++ b/handwritten/spanner/samples/archived/pg-sequence-drop.js @@ -0,0 +1,73 @@ +// Copyright 2024 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 sequence in PostgreSQL database. +// usage: node pg-sequence-drop.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_drop_sequence] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function dropSequence(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = [ + 'ALTER TABLE Customers ALTER COLUMN CustomerId DROP DEFAULT', + 'DROP SEQUENCE Seq', + ]; + + // Drop sequence from DDL + try { + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log( + 'Altered Customers table to drop DEFAULT from CustomerId column and dropped the Seq sequence.', + ); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + } + await dropSequence(instanceId, databaseId); + // [END spanner_drop_sequence] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/schema.js b/handwritten/spanner/samples/archived/schema.js new file mode 100644 index 00000000000..540f98ce1a4 --- /dev/null +++ b/handwritten/spanner/samples/archived/schema.js @@ -0,0 +1,233 @@ +// Copyright 2024 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. + +'use strict'; + +async function createDatabase(instanceId, databaseId, projectId) { + // [START spanner_create_database] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance + const instance = spanner.instance(instanceId); + + // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they + // must be converted to strings before being inserted as INT64s + const request = { + schema: [ + `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)`, + `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 database + const [database, operation] = await instance.createDatabase( + databaseId, + request, + ); + + console.log(`Waiting for operation on ${database.id} to complete...`); + await operation.promise(); + + console.log(`Created database ${databaseId} on instance ${instanceId}.`); + // [END spanner_create_database] +} + +async function addColumn(instanceId, databaseId, projectId) { + // [START spanner_add_column] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = ['ALTER TABLE Albums ADD COLUMN MarketingBudget INT64']; + + // Creates a new index in the database + try { + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Added the MarketingBudget column.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_add_column] +} + +async function queryDataWithNewColumn(instanceId, databaseId, projectId) { + // [START spanner_query_data_with_new_column] + // This sample uses the `MarketingBudget` column. You can add the column + // by running the `add_column` sample or by running this DDL statement against + // your database: + // ALTER TABLE Albums ADD COLUMN MarketingBudget INT64 + + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: 'SELECT SingerId, AlbumId, MarketingBudget FROM Albums', + }; + + // Queries rows from the Albums table + try { + const [rows] = await database.run(query); + + rows.forEach(async row => { + const json = row.toJSON(); + + console.log( + `SingerId: ${json.SingerId}, AlbumId: ${ + json.AlbumId + }, MarketingBudget: ${ + json.MarketingBudget ? json.MarketingBudget : null + }`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_data_with_new_column] +} + +const { + createDatabaseWithVersionRetentionPeriod, +} = require('./database-create-with-version-retention-period'); +const { + createDatabaseWithEncryptionKey, +} = require('./database-create-with-encryption-key'); + +require('yargs') + .demand(1) + .command( + 'createDatabase ', + 'Creates an example database with two tables in a Cloud Spanner instance.', + {}, + opts => + createDatabase(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'createDatabaseWithEncryptionKey ', + 'Creates an example database using given encryption key in a Cloud Spanner instance.', + {}, + opts => + createDatabaseWithEncryptionKey( + opts.instanceName, + opts.databaseName, + opts.projectId, + opts.keyName, + ), + ) + .command( + 'addColumn ', + 'Adds an example MarketingBudget column to an example Cloud Spanner table.', + {}, + opts => addColumn(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryNewColumn ', + 'Executes a read-only SQL query against an example Cloud Spanner table with an additional column (MarketingBudget) added by addColumn.', + {}, + opts => + queryDataWithNewColumn( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'createDatabaseWithVersionRetentionPeriod ', + 'Creates a database with a version retention period.', + {}, + opts => + createDatabaseWithVersionRetentionPeriod( + opts.instanceName, + opts.databaseId, + opts.projectId, + ), + ) + .example('node $0 createDatabase "my-instance" "my-database" "my-project-id"') + .example( + 'node $0 createDatabaseWithEncryptionKey "my-instance" "my-database" "my-project-id" "key-name"', + ) + .example('node $0 addColumn "my-instance" "my-database" "my-project-id"') + .example('node $0 queryNewColumn "my-instance" "my-database" "my-project-id"') + .example( + 'node $0 createDatabaseWithVersionRetentionPeriod "my-instance" "my-database-id" "my-project-id"', + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/archived/sequence-alter.js b/handwritten/spanner/samples/archived/sequence-alter.js new file mode 100644 index 00000000000..14ad00eb633 --- /dev/null +++ b/handwritten/spanner/samples/archived/sequence-alter.js @@ -0,0 +1,97 @@ +// Copyright 2024 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 sequence in a GoogleSQL database. +// usage: node sequence-alter.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_alter_sequence] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function alterSequence(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = [ + 'ALTER SEQUENCE Seq SET OPTIONS (skip_range_min = 1000, skip_range_max = 5000000)', + ]; + + try { + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log( + 'Altered Seq sequence to skip an inclusive range between 1000 and 5000000.', + ); + } catch (err) { + console.error('ERROR:', err); + } + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: "INSERT INTO Customers (CustomerName) VALUES ('Lea'), ('Catalina'), ('Smith') THEN RETURN CustomerId", + }); + + rows.forEach(row => { + console.log( + `Inserted customer record with CustomerId: ${ + row.toJSON({wrapNumbers: true}).CustomerId.value + }`, + ); + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log(`Number of customer records inserted is: ${rowCount}`); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + }); + } + await alterSequence(instanceId, databaseId); + // [END spanner_alter_sequence] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/sequence-create.js b/handwritten/spanner/samples/archived/sequence-create.js new file mode 100644 index 00000000000..04a33733983 --- /dev/null +++ b/handwritten/spanner/samples/archived/sequence-create.js @@ -0,0 +1,99 @@ +// Copyright 2024 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 sequence in GoogleSQL database. +// usage: node sequence-create.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_create_sequence] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function createSequence(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = [ + "CREATE SEQUENCE Seq OPTIONS (sequence_kind = 'bit_reversed_positive')", + 'CREATE TABLE Customers (CustomerId INT64 DEFAULT (GET_NEXT_SEQUENCE_VALUE(Sequence Seq)), CustomerName STRING(1024)) PRIMARY KEY (CustomerId)', + ]; + + // Creates a new table with sequence + try { + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log( + 'Created Seq sequence and Customers table, where the key column CustomerId uses the sequence as a default value.', + ); + } catch (err) { + console.error('ERROR:', err); + } + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: "INSERT INTO Customers (CustomerName) VALUES ('Alice'), ('David'), ('Marc') THEN RETURN CustomerId", + }); + + rows.forEach(row => { + console.log( + `Inserted customer record with CustomerId: ${ + row.toJSON({wrapNumbers: true}).CustomerId.value + }`, + ); + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log(`Number of customer records inserted is: ${rowCount}`); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + }); + } + await createSequence(instanceId, databaseId); + // [END spanner_create_sequence] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/sequence-drop.js b/handwritten/spanner/samples/archived/sequence-drop.js new file mode 100644 index 00000000000..913da8299f3 --- /dev/null +++ b/handwritten/spanner/samples/archived/sequence-drop.js @@ -0,0 +1,73 @@ +// Copyright 2024 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 sequence in GoogleSQL database. +// usage: node sequence-drop.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_drop_sequence] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function dropSequence(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = [ + 'ALTER TABLE Customers ALTER COLUMN CustomerId DROP DEFAULT', + 'DROP SEQUENCE Seq', + ]; + + // Drop sequence from DDL + try { + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log( + 'Altered Customers table to drop DEFAULT from CustomerId column and dropped the Seq sequence.', + ); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + } + await dropSequence(instanceId, databaseId); + // [END spanner_drop_sequence] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/archived/table-alter-with-foreign-key-delete-cascade.js b/handwritten/spanner/samples/archived/table-alter-with-foreign-key-delete-cascade.js new file mode 100644 index 00000000000..444e864a3be --- /dev/null +++ b/handwritten/spanner/samples/archived/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/handwritten/spanner/samples/archived/table-create-with-foreign-key-delete-cascade.js b/handwritten/spanner/samples/archived/table-create-with-foreign-key-delete-cascade.js new file mode 100644 index 00000000000..f48fdfd6118 --- /dev/null +++ b/handwritten/spanner/samples/archived/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/handwritten/spanner/samples/archived/table-drop-foreign-key-constraint-delete-cascade.js b/handwritten/spanner/samples/archived/table-drop-foreign-key-constraint-delete-cascade.js new file mode 100644 index 00000000000..8a83b2564fd --- /dev/null +++ b/handwritten/spanner/samples/archived/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/handwritten/spanner/samples/archived/timestamp.js b/handwritten/spanner/samples/archived/timestamp.js new file mode 100644 index 00000000000..e183e2e9a71 --- /dev/null +++ b/handwritten/spanner/samples/archived/timestamp.js @@ -0,0 +1,421 @@ +// Copyright 2024 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. + +'use strict'; + +async function createTableWithTimestamp(instanceId, databaseId, projectId) { + // [START spanner_create_table_with_timestamp_column] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they + // must be converted to strings before being inserted as INT64s + const request = [ + `CREATE TABLE Performances ( + SingerId INT64 NOT NULL, + VenueId INT64 NOT NULL, + EventDate DATE, + Revenue INT64, + LastUpdateTime TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true) + ) PRIMARY KEY (SingerId, VenueId, EventDate), + INTERLEAVE IN PARENT Singers ON DELETE CASCADE`, + ]; + + // Creates a table in an existing database + const [operation] = await database.updateSchema(request); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + + await operation.promise(); + + console.log(`Created table Performances in database ${databaseId}.`); + // [END spanner_create_table_with_timestamp_column] +} + +async function insertWithTimestamp(instanceId, databaseId, projectId) { + // [START spanner_insert_data_with_timestamp_column] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Instantiate Spanner table objects + const performancesTable = database.table('Performances'); + + const data = [ + { + SingerId: '1', + VenueId: '4', + EventDate: '2017-10-05', + Revenue: '11000', + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + SingerId: '1', + VenueId: '19', + EventDate: '2017-11-02', + Revenue: '15000', + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + SingerId: '2', + VenueId: '42', + EventDate: '2017-12-23', + Revenue: '7000', + LastUpdateTime: 'spanner.commit_timestamp()', + }, + ]; + + // Inserts rows into the Singers table + // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so + // they must be converted to strings before being inserted as INT64s + try { + await performancesTable.insert(data); + console.log('Inserted data.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished + database.close(); + } + // [END spanner_insert_data_with_timestamp_column] +} + +async function queryTableWithTimestamp(instanceId, databaseId, projectId) { + // [START spanner_query_new_table_with_timestamp_column] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: `SELECT SingerId, VenueId, EventDate, Revenue, LastUpdateTime + FROM Performances + ORDER BY LastUpdateTime DESC`, + }; + + // Queries rows from the Performances table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `SingerId: ${json.SingerId}, VenueId: ${json.VenueId}, EventDate: ${json.EventDate}, Revenue: ${json.Revenue}, LastUpdateTime: ${json.LastUpdateTime}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished + database.close(); + } + // [END spanner_query_new_table_with_timestamp_column] +} + +async function addTimestampColumn(instanceId, databaseId, projectId) { + // [START spanner_add_timestamp_column] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = [ + `ALTER TABLE Albums ADD COLUMN LastUpdateTime TIMESTAMP OPTIONS + (allow_commit_timestamp=true)`, + ]; + + // Adds a new commit timestamp column to the Albums table + try { + const [operation] = await database.updateSchema(request); + + console.log('Waiting for operation to complete...'); + + await operation.promise(); + + console.log( + 'Added LastUpdateTime as a commit timestamp column in Albums table.', + ); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_add_timestamp_column] +} + +async function updateWithTimestamp(instanceId, databaseId, projectId) { + // [START spanner_update_data_with_timestamp_column] + // [START_EXCLUDE] + // This sample uses the `MarketingBudget` column. You can add the column + // by running the `schema.js addColumn` sample or by running this DDL statement against + // your database: + // ALTER TABLE Albums ADD COLUMN MarketingBudget INT64 + // + // In addition this update expects the `LastUpdateTime` column + // added by running the `timestamp.js addTimestampColumn` sample + // or applying the DDL statement: + // ALTER TABLE Albums ADD COLUMN + // LastUpdateTime TIMESTAMP OPTIONS (allow_commit_timestamp=true) + // [END_EXCLUDE] + + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Update a row in the Albums table + // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they + // must be converted to strings before being inserted as INT64s + const albumsTable = database.table('Albums'); + + const data = [ + { + SingerId: '1', + AlbumId: '1', + MarketingBudget: '1000000', + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + SingerId: '2', + AlbumId: '2', + MarketingBudget: '750000', + LastUpdateTime: 'spanner.commit_timestamp()', + }, + ]; + + try { + await albumsTable.update(data); + console.log('Updated data.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished + database.close(); + } + // [END spanner_update_data_with_timestamp_column] +} + +async function queryWithTimestamp(instanceId, databaseId, projectId) { + // [START spanner_query_data_with_timestamp_column] + // [START_EXCLUDE] + // This sample uses the `MarketingBudget` column. You can add the column + // by running the `schema.js addColumn` sample or by running this DDL statement against + // your database: + // ALTER TABLE Albums ADD COLUMN MarketingBudget INT64 + // + // In addition this query expects the `LastUpdateTime` column + // added by running the `timestamp.js addTimestampColumn` sample + // or applying the DDL statement: + // ALTER TABLE Albums ADD COLUMN + // LastUpdateTime TIMESTAMP OPTIONS (allow_commit_timestamp=true) + // [END_EXCLUDE] + + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: `SELECT SingerId, AlbumId, MarketingBudget, LastUpdateTime + FROM Albums ORDER BY LastUpdateTime DESC`, + }; + + // Queries rows from the Albums table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + + console.log( + `SingerId: ${json.SingerId}, AlbumId: ${ + json.AlbumId + }, MarketingBudget: ${ + json.MarketingBudget ? json.MarketingBudget : null + }, LastUpdateTime: ${json.LastUpdateTime}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished + database.close(); + } + // [END spanner_query_data_with_timestamp_column] +} + +require('yargs') + .demand(1) + .command( + 'createTableWithTimestamp ', + 'Creates an example table with a commit timestamp column in a Cloud Spanner database.', + {}, + opts => + createTableWithTimestamp( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'insertWithTimestamp ', + 'Inserts new rows of data including commit timestamps into an example Cloud Spanner table.', + {}, + opts => + insertWithTimestamp(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryTableWithTimestamp ', + 'Executes a read-only SQL query against an example Cloud Spanner table created with a commit timestamp column.', + {}, + opts => + queryTableWithTimestamp( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'addTimestampColumn ', + 'Adds a example commit timestamp column to an existing example Cloud Spanner table.', + {}, + opts => + addTimestampColumn(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'updateWithTimestamp ', + 'Modifies existing rows of data in an example Cloud Spanner table with a commit timestamp column..', + {}, + opts => + updateWithTimestamp(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithTimestamp ', + `Executes a read-only SQL query against an example Cloud Spanner table with an additional + column (LastUpdateTime) added by addTimestampColumn.`, + {}, + opts => + queryWithTimestamp(opts.instanceName, opts.databaseName, opts.projectId), + ) + .example( + 'node $0 createTableWithTimestamp "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 insertWithTimestamp "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 queryTableWithTimestamp "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 addTimestampColumn "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 updateWithTimestamp "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 queryWithTimestamp "my-instance" "my-database" "my-project-id"', + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/backups-cancel.js b/handwritten/spanner/samples/backups-cancel.js new file mode 100644 index 00000000000..a02bcf95d2b --- /dev/null +++ b/handwritten/spanner/samples/backups-cancel.js @@ -0,0 +1,83 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +async function cancelBackup(instanceId, databaseId, backupId, projectId) { + // [START spanner_cancel_backup_create] + + // Imports the Google Cloud client library and precise date library + const {Spanner, protos} = require('@google-cloud/spanner'); + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + // Creates a new backup of the database + try { + console.log( + `Creating backup of database ${databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + )}.`, + ); + + // Expire backup one day in the future + const expireTime = Date.now() + 1000 * 60 * 60 * 24; + const [operation] = await databaseAdminClient.createBackup({ + parent: databaseAdminClient.instancePath(projectId, instanceId), + backupId: backupId, + backup: (protos.google.spanner.admin.database.v1.Backup = { + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + expireTime: Spanner.timestamp(expireTime).toStruct(), + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }), + }); + + // Cancel the backup + await operation.cancel(); + + console.log('Backup cancelled.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Delete backup in case it got created before the cancel operation + await databaseAdminClient.deleteBackup({ + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }); + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + // [END spanner_cancel_backup_create] +} + +module.exports.cancelBackup = cancelBackup; diff --git a/handwritten/spanner/samples/backups-copy-with-multiple-kms-keys.js b/handwritten/spanner/samples/backups-copy-with-multiple-kms-keys.js new file mode 100644 index 00000000000..ddcc075803d --- /dev/null +++ b/handwritten/spanner/samples/backups-copy-with-multiple-kms-keys.js @@ -0,0 +1,105 @@ +// Copyright 2024 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: Copies a source backup +// usage: node spannerCopyBackup + +'use strict'; + +function main( + instanceId = 'my-instance', + backupId = 'my-backup', + sourceBackupPath = 'projects/my-project-id/instances/my-source-instance/backups/my-source-backup', + projectId = 'my-project-id', + kmsKeyNames = 'key1,key2', +) { + // [START spanner_copy_backup_with_MR_CMEK] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const backupId = 'my-backup', + // const sourceBackupPath = 'projects/my-project-id/instances/my-source-instance/backups/my-source-backup', + // const projectId = 'my-project-id'; + // const kmsKeyNames = + // 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key1, + // projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key2'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + const {PreciseDate} = require('@google-cloud/precise-date'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function spannerCopyBackupWithMultipleKmsKeys() { + // Expire copy backup 14 days in the future + const expireTime = Spanner.timestamp( + Date.now() + 1000 * 60 * 60 * 24 * 14, + ).toStruct(); + + // Copy the source backup + try { + console.log(`Creating copy of the source backup ${sourceBackupPath}.`); + const [operation] = await databaseAdminClient.copyBackup({ + parent: databaseAdminClient.instancePath(projectId, instanceId), + sourceBackup: sourceBackupPath, + backupId: backupId, + expireTime: expireTime, + kmsKeyNames: kmsKeyNames.split(','), + }); + + console.log( + `Waiting for backup copy ${databaseAdminClient.backupPath( + projectId, + instanceId, + backupId, + )} to complete...`, + ); + await operation.promise(); + + // Verify the copy backup is ready + const [copyBackup] = await databaseAdminClient.getBackup({ + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }); + + if (copyBackup.state === 'READY') { + console.log( + `Backup copy ${copyBackup.name} of size ` + + `${copyBackup.sizeBytes} bytes was created at ` + + `${new PreciseDate(copyBackup.createTime).toISOString()} ` + + 'with version time ' + + `${new PreciseDate(copyBackup.versionTime).toISOString()}`, + ); + } else { + console.error('ERROR: Copy of backup is not ready.'); + } + } catch (err) { + console.error('ERROR:', err); + } + } + spannerCopyBackupWithMultipleKmsKeys(); + // [END spanner_copy_backup_with_MR_CMEK] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/backups-copy.js b/handwritten/spanner/samples/backups-copy.js new file mode 100644 index 00000000000..d830d179a62 --- /dev/null +++ b/handwritten/spanner/samples/backups-copy.js @@ -0,0 +1,100 @@ +// Copyright 2024 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: Copies a source backup +// usage: node spannerCopyBackup + +'use strict'; + +function main( + instanceId = 'my-instance', + backupId = 'my-backup', + sourceBackupPath = 'projects/my-project-id/instances/my-source-instance/backups/my-source-backup', + projectId = 'my-project-id', +) { + // [START spanner_copy_backup] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const backupId = 'my-backup', + // const sourceBackupPath = 'projects/my-project-id/instances/my-source-instance/backups/my-source-backup', + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + const {PreciseDate} = require('@google-cloud/precise-date'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function spannerCopyBackup() { + // Expire copy backup 14 days in the future + const expireTime = Spanner.timestamp( + Date.now() + 1000 * 60 * 60 * 24 * 14, + ).toStruct(); + + // Copy the source backup + try { + console.log(`Creating copy of the source backup ${sourceBackupPath}.`); + const [operation] = await databaseAdminClient.copyBackup({ + parent: databaseAdminClient.instancePath(projectId, instanceId), + sourceBackup: sourceBackupPath, + backupId: backupId, + expireTime: expireTime, + }); + + console.log( + `Waiting for backup copy ${databaseAdminClient.backupPath( + projectId, + instanceId, + backupId, + )} to complete...`, + ); + await operation.promise(); + + // Verify the copy backup is ready + const [copyBackup] = await databaseAdminClient.getBackup({ + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }); + + if (copyBackup.state === 'READY') { + console.log( + `Backup copy ${copyBackup.name} of size ` + + `${copyBackup.sizeBytes} bytes was created at ` + + `${new PreciseDate(copyBackup.createTime).toISOString()} ` + + 'with version time ' + + `${new PreciseDate(copyBackup.versionTime).toISOString()}`, + ); + } else { + console.error('ERROR: Copy of backup is not ready.'); + } + } catch (err) { + console.error('ERROR:', err); + } + } + spannerCopyBackup(); + // [END spanner_copy_backup] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/backups-create-with-encryption-key.js b/handwritten/spanner/samples/backups-create-with-encryption-key.js new file mode 100644 index 00000000000..510f514e380 --- /dev/null +++ b/handwritten/spanner/samples/backups-create-with-encryption-key.js @@ -0,0 +1,114 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +async function createBackupWithEncryptionKey( + instanceId, + databaseId, + backupId, + projectId, + keyName, +) { + // [START spanner_create_backup_with_encryption_key] + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + const {PreciseDate} = require('@google-cloud/precise-date'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + // const keyName = + // 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + // Creates a new backup of the database + try { + console.log( + `Creating backup of database ${databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + )}.`, + ); + + // Expire backup 14 days in the future + const expireTime = Date.now() + 1000 * 60 * 60 * 24 * 14; + + // Create a backup of the state of the database at the current time. + const [operation] = await databaseAdminClient.createBackup({ + parent: databaseAdminClient.instancePath(projectId, instanceId), + backupId: backupId, + backup: (protos.google.spanner.admin.database.v1.Backup = { + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + expireTime: Spanner.timestamp(expireTime).toStruct(), + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }), + encryptionConfig: { + encryptionType: 'CUSTOMER_MANAGED_ENCRYPTION', + kmsKeyName: keyName, + }, + }); + + console.log( + `Waiting for backup ${databaseAdminClient.backupPath( + projectId, + instanceId, + backupId, + )} to complete...`, + ); + await operation.promise(); + + // Verify backup is ready + const [backupInfo] = await databaseAdminClient.getBackup({ + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }); + if (backupInfo.state === 'READY') { + console.log( + `Backup ${backupInfo.name} of size ` + + `${backupInfo.sizeBytes} bytes was created at ` + + `${new PreciseDate(backupInfo.createTime).toISOString()} ` + + `using encryption key ${backupInfo.encryptionInfo.kmsKeyVersion}`, + ); + } else { + console.error('ERROR: Backup is not ready.'); + } + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + // [END spanner_create_backup_with_encryption_key] +} + +module.exports.createBackupWithEncryptionKey = createBackupWithEncryptionKey; diff --git a/handwritten/spanner/samples/backups-create-with-multiple-kms-keys.js b/handwritten/spanner/samples/backups-create-with-multiple-kms-keys.js new file mode 100644 index 00000000000..d65f81461ae --- /dev/null +++ b/handwritten/spanner/samples/backups-create-with-multiple-kms-keys.js @@ -0,0 +1,125 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + backupId = 'my-backup', + projectId = 'my-project-id', + kmsKeyNames = 'key1,key2', +) { + // [START spanner_create_backup_with_MR_CMEK] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + // const kmsKeyNames = + // 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key1, + // 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key2'; + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + const {PreciseDate} = require('@google-cloud/precise-date'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + async function createBackupWithMultipleKmsKeys() { + // Creates a new backup of the database + try { + console.log( + `Creating backup of database ${databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + )}.`, + ); + + // Expire backup 14 days in the future + const expireTime = Date.now() + 1000 * 60 * 60 * 24 * 14; + + // Create a backup of the state of the database at the current time. + const [operation] = await databaseAdminClient.createBackup({ + parent: databaseAdminClient.instancePath(projectId, instanceId), + backupId: backupId, + backup: (protos.google.spanner.admin.database.v1.Backup = { + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + expireTime: Spanner.timestamp(expireTime).toStruct(), + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }), + encryptionConfig: { + encryptionType: 'CUSTOMER_MANAGED_ENCRYPTION', + kmsKeyNames: kmsKeyNames.split(','), + }, + }); + + console.log( + `Waiting for backup ${databaseAdminClient.backupPath( + projectId, + instanceId, + backupId, + )} to complete...`, + ); + await operation.promise(); + + // Verify backup is ready + const [backupInfo] = await databaseAdminClient.getBackup({ + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }); + + const kmsKeyVersions = backupInfo.encryptionInformation + .map(encryptionInfo => encryptionInfo.kmsKeyVersion) + .join(', '); + + if (backupInfo.state === 'READY') { + console.log( + `Backup ${backupInfo.name} of size ` + + `${backupInfo.sizeBytes} bytes was created at ` + + `${new PreciseDate(backupInfo.createTime).toISOString()} ` + + `using encryption key ${kmsKeyVersions}`, + ); + } else { + console.error('ERROR: Backup is not ready.'); + } + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + } + createBackupWithMultipleKmsKeys(); + // [END spanner_create_backup_with_MR_CMEK] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/backups-create.js b/handwritten/spanner/samples/backups-create.js new file mode 100644 index 00000000000..443b8bc0700 --- /dev/null +++ b/handwritten/spanner/samples/backups-create.js @@ -0,0 +1,111 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +async function createBackup( + instanceId, + databaseId, + backupId, + projectId, + versionTime, +) { + // [START spanner_create_backup] + + // Imports the Google Cloud client library and precise date library + const {Spanner, protos} = require('@google-cloud/spanner'); + const {PreciseDate} = require('@google-cloud/precise-date'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + // const versionTime = Date.now() - 1000 * 60 * 60 * 24; // One day ago + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + // Creates a new backup of the database + try { + console.log( + `Creating backup of database ${databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + )}.`, + ); + + // Expire backup 14 days in the future + const expireTime = Date.now() + 1000 * 60 * 60 * 24 * 14; + + // Create a backup of the state of the database at the current time. + const [operation] = await databaseAdminClient.createBackup({ + parent: databaseAdminClient.instancePath(projectId, instanceId), + backupId: backupId, + backup: (protos.google.spanner.admin.database.v1.Backup = { + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + expireTime: Spanner.timestamp(expireTime).toStruct(), + versionTime: Spanner.timestamp(versionTime).toStruct(), + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }), + }); + + console.log( + `Waiting for backup ${databaseAdminClient.backupPath( + projectId, + instanceId, + backupId, + )} to complete...`, + ); + await operation.promise(); + + // Verify backup is ready + const [backupInfo] = await databaseAdminClient.getBackup({ + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }); + if (backupInfo.state === 'READY') { + console.log( + `Backup ${backupInfo.name} of size ` + + `${backupInfo.sizeBytes} bytes was created at ` + + `${new PreciseDate(backupInfo.createTime).toISOString()} ` + + 'for version of database at ' + + `${new PreciseDate(backupInfo.versionTime).toISOString()}`, + ); + } else { + console.error('ERROR: Backup is not ready.'); + } + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + // [END spanner_create_backup] +} + +module.exports.createBackup = createBackup; diff --git a/handwritten/spanner/samples/backups-delete.js b/handwritten/spanner/samples/backups-delete.js new file mode 100644 index 00000000000..27189c3c99e --- /dev/null +++ b/handwritten/spanner/samples/backups-delete.js @@ -0,0 +1,59 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +async function deleteBackup(instanceId, backupId, projectId) { + // [START spanner_delete_backup] + + // 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'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + // Delete the backup + console.log(`Deleting backup ${backupId}.`); + await databaseAdminClient.deleteBackup({ + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }); + console.log('Backup deleted.'); + + // Verify backup no longer exists + try { + await databaseAdminClient.getBackup({ + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }); + console.error('Error: backup still exists.'); + } catch (err) { + console.log('Backup deleted.'); + } + // [END spanner_delete_backup] +} + +module.exports.deleteBackup = deleteBackup; diff --git a/handwritten/spanner/samples/backups-get-database-operations.js b/handwritten/spanner/samples/backups-get-database-operations.js new file mode 100644 index 00000000000..756e8965ab5 --- /dev/null +++ b/handwritten/spanner/samples/backups-get-database-operations.js @@ -0,0 +1,63 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +async function getDatabaseOperations(instanceId, projectId) { + // [START spanner_list_database_operations] + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + // List database operations + try { + const [databaseOperations] = + await databaseAdminClient.listDatabaseOperations({ + parent: databaseAdminClient.instancePath(projectId, instanceId), + filter: + '(metadata.@type:type.googleapis.com/google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata)', + }); + console.log('Optimize Database Operations:'); + databaseOperations.forEach(databaseOperation => { + const metadata = + protos.google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata.decode( + databaseOperation.metadata.value, + ); + console.log( + `Database ${metadata.name} restored from backup is ` + + `${metadata.progress.progressPercent}% optimized.`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_list_database_operations] +} + +module.exports.getDatabaseOperations = getDatabaseOperations; diff --git a/handwritten/spanner/samples/backups-get-operations.js b/handwritten/spanner/samples/backups-get-operations.js new file mode 100644 index 00000000000..1bb90492706 --- /dev/null +++ b/handwritten/spanner/samples/backups-get-operations.js @@ -0,0 +1,97 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +async function getBackupOperations( + instanceId, + databaseId, + backupId, + projectId, +) { + // [START spanner_list_backup_operations] + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + // const instanceId = 'my-instance'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + // List create backup operations + try { + const [backupOperations] = await databaseAdminClient.listBackupOperations({ + parent: databaseAdminClient.instancePath(projectId, instanceId), + filter: + '(metadata.@type:type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) ' + + `AND (metadata.database:${databaseId})`, + }); + console.log('Create Backup Operations:'); + backupOperations.forEach(backupOperation => { + const metadata = + protos.google.spanner.admin.database.v1.CreateBackupMetadata.decode( + backupOperation.metadata.value, + ); + console.log( + `Backup ${metadata.name} on database ${metadata.database} is ` + + `${metadata.progress.progressPercent}% complete.`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } + + // List copy backup operations + try { + console.log( + '(metadata.@type:type.googleapis.com/google.spanner.admin.database.v1.CopyBackupMetadata) ' + + `AND (metadata.source_backup:${backupId})`, + ); + const [backupOperations] = await databaseAdminClient.listBackupOperations({ + parent: databaseAdminClient.instancePath(projectId, instanceId), + filter: + '(metadata.@type:type.googleapis.com/google.spanner.admin.database.v1.CopyBackupMetadata) ' + + `AND (metadata.source_backup:${backupId})`, + }); + console.log('Copy Backup Operations:'); + backupOperations.forEach(backupOperation => { + const metadata = + protos.google.spanner.admin.database.v1.CopyBackupMetadata.decode( + backupOperation.metadata.value, + ); + console.log( + `Backup ${metadata.name} copied from source backup ${metadata.sourceBackup} is ` + + `${metadata.progress.progressPercent}% complete.`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_list_backup_operations] +} + +module.exports.getBackupOperations = getBackupOperations; diff --git a/handwritten/spanner/samples/backups-get.js b/handwritten/spanner/samples/backups-get.js new file mode 100644 index 00000000000..f5ab5416f65 --- /dev/null +++ b/handwritten/spanner/samples/backups-get.js @@ -0,0 +1,165 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +async function getBackups(instanceId, databaseId, backupId, projectId) { + // [START spanner_list_backups] + + // 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'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + try { + // Get the parent(instance) of the database + const parent = databaseAdminClient.instancePath(projectId, instanceId); + + // List all backups + const [allBackups] = await databaseAdminClient.listBackups({ + parent: parent, + }); + + console.log('All backups:'); + allBackups.forEach(backups => { + if (backups.name) { + const backup = backups.name; + const delimiter = + 'projects/' + projectId + '/instances/' + instanceId + '/backups/'; + const result = backup.substring(delimiter.length); + console.log(result); + } + }); + + // List backups filtered by backup name + const [backupsByName] = await databaseAdminClient.listBackups({ + parent: parent, + filter: `Name:${backupId}`, + }); + console.log('Backups matching backup name:'); + backupsByName.forEach(backup => { + if (backup.name) { + const backupName = backup.name; + const delimiter = + 'projects/' + projectId + '/instances/' + instanceId + '/backups/'; + const result = backupName.substring(delimiter.length); + console.log(result); + } + }); + + // List backups expiring within 30 days + const expireTime = new Date(); + expireTime.setDate(expireTime.getDate() + 30); + const [backupsByExpiry] = await databaseAdminClient.listBackups({ + parent: parent, + filter: `expire_time < "${expireTime.toISOString()}"`, + }); + console.log('Backups expiring within 30 days:'); + backupsByExpiry.forEach(backup => { + if (backup.name) { + const backupName = backup.name; + const delimiter = + 'projects/' + projectId + '/instances/' + instanceId + '/backups/'; + const result = backupName.substring(delimiter.length); + console.log(result); + } + }); + + // List backups filtered by database name + const [backupsByDbName] = await databaseAdminClient.listBackups({ + parent: parent, + filter: `Database:${databaseId}`, + }); + console.log('Backups matching database name:'); + backupsByDbName.forEach(backup => { + if (backup.name) { + const backupName = backup.name; + const delimiter = + 'projects/' + projectId + '/instances/' + instanceId + '/backups/'; + const result = backupName.substring(delimiter.length); + console.log(result); + } + }); + + // List backups filtered by backup size + const [backupsBySize] = await databaseAdminClient.listBackups({ + parent: parent, + filter: 'size_bytes > 100', + }); + console.log('Backups filtered by size:'); + backupsBySize.forEach(backup => { + if (backup.name) { + const backupName = backup.name; + const delimiter = + 'projects/' + projectId + '/instances/' + instanceId + '/backups/'; + const result = backupName.substring(delimiter.length); + console.log(result); + } + }); + + // List backups that are ready that were created after a certain time + const createTime = new Date(); + createTime.setDate(createTime.getDate() - 1); + const [backupsByCreateTime] = await databaseAdminClient.listBackups({ + parent: parent, + filter: `(state:READY) AND (create_time >= "${createTime.toISOString()}")`, + }); + console.log('Ready backups filtered by create time:'); + backupsByCreateTime.forEach(backup => { + if (backup.name) { + const backupName = backup.name; + const delimiter = + 'projects/' + projectId + '/instances/' + instanceId + '/backups/'; + const result = backupName.substring(delimiter.length); + console.log(result); + } + }); + + // List backups using pagination + console.log('Get backups paginated:'); + const [backups] = await databaseAdminClient.listBackups({ + parent: parent, + pageSize: 3, + }); + backups.forEach(backup => { + if (backup.name) { + const backupName = backup.name; + const delimiter = + 'projects/' + projectId + '/instances/' + instanceId + '/backups/'; + const result = backupName.substring(delimiter.length); + console.log(result); + } + }); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_list_backups] +} + +module.exports.getBackups = getBackups; diff --git a/handwritten/spanner/samples/backups-restore-with-encryption-key.js b/handwritten/spanner/samples/backups-restore-with-encryption-key.js new file mode 100644 index 00000000000..7a644bb4b58 --- /dev/null +++ b/handwritten/spanner/samples/backups-restore-with-encryption-key.js @@ -0,0 +1,82 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +async function restoreBackupWithEncryptionKey( + instanceId, + databaseId, + backupId, + projectId, + keyName, +) { + // [START spanner_restore_backup_with_encryption_key] + + // Imports the Google Cloud client library and precise date 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'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + // const keyName = + // 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + // Restore the database + console.log( + `Restoring database ${databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + )} from backup ${backupId}.`, + ); + const [restoreOperation] = await databaseAdminClient.restoreDatabase({ + parent: databaseAdminClient.instancePath(projectId, instanceId), + databaseId: databaseId, + backup: databaseAdminClient.backupPath(projectId, instanceId, backupId), + encryptionConfig: { + encryptionType: 'CUSTOMER_MANAGED_ENCRYPTION', + kmsKeyName: keyName, + }, + }); + + // Wait for restore to complete + console.log('Waiting for database restore to complete...'); + await restoreOperation.promise(); + + console.log('Database restored from backup.'); + const [metadata] = await databaseAdminClient.getDatabase({ + name: databaseAdminClient.databasePath(projectId, instanceId, databaseId), + }); + console.log( + `Database ${metadata.restoreInfo.backupInfo.sourceDatabase} was restored ` + + `to ${databaseId} from backup ${metadata.restoreInfo.backupInfo.backup} ` + + `using encryption key ${metadata.encryptionConfig.kmsKeyName}.`, + ); + // [END spanner_restore_backup_with_encryption_key] +} + +module.exports.restoreBackupWithEncryptionKey = restoreBackupWithEncryptionKey; diff --git a/handwritten/spanner/samples/backups-restore-with-multiple-kms-keys.js b/handwritten/spanner/samples/backups-restore-with-multiple-kms-keys.js new file mode 100644 index 00000000000..3783b5baef7 --- /dev/null +++ b/handwritten/spanner/samples/backups-restore-with-multiple-kms-keys.js @@ -0,0 +1,89 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + backupId = 'my-backup', + projectId = 'my-project', + kmsKeyNames = 'key1,key2', +) { + // [START spanner_restore_backup_with_MR_CMEK] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + // const kmsKeyNames = + // 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key1, + // projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key2'; + + // Imports the Google Cloud client library and precise date library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function restoreBackupWithMultipleKmsKeys() { + // Restore the database + console.log( + `Restoring database ${databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + )} from backup ${backupId}.`, + ); + const [restoreOperation] = await databaseAdminClient.restoreDatabase({ + parent: databaseAdminClient.instancePath(projectId, instanceId), + databaseId: databaseId, + backup: databaseAdminClient.backupPath(projectId, instanceId, backupId), + encryptionConfig: { + encryptionType: 'CUSTOMER_MANAGED_ENCRYPTION', + kmsKeyNames: kmsKeyNames.split(','), + }, + }); + + // Wait for restore to complete + console.log('Waiting for database restore to complete...'); + await restoreOperation.promise(); + + console.log('Database restored from backup.'); + const [metadata] = await databaseAdminClient.getDatabase({ + name: databaseAdminClient.databasePath(projectId, instanceId, databaseId), + }); + console.log( + `Database ${metadata.restoreInfo.backupInfo.sourceDatabase} was restored ` + + `to ${databaseId} from backup ${metadata.restoreInfo.backupInfo.backup} ` + + `using encryption key ${metadata.encryptionConfig.kmsKeyNames}.`, + ); + } + restoreBackupWithMultipleKmsKeys(); + // [END spanner_restore_backup_with_MR_CMEK] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/backups-restore.js b/handwritten/spanner/samples/backups-restore.js new file mode 100644 index 00000000000..4c5e0f63b18 --- /dev/null +++ b/handwritten/spanner/samples/backups-restore.js @@ -0,0 +1,73 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +async function restoreBackup(instanceId, databaseId, backupId, projectId) { + // [START spanner_restore_backup] + // Imports the Google Cloud client library and precise date library + const {Spanner} = require('@google-cloud/spanner'); + const {PreciseDate} = require('@google-cloud/precise-date'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + // Restore the database + console.log( + `Restoring database ${databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + )} from backup ${backupId}.`, + ); + const [restoreOperation] = await databaseAdminClient.restoreDatabase({ + parent: databaseAdminClient.instancePath(projectId, instanceId), + databaseId: databaseId, + backup: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }); + + // Wait for restore to complete + console.log('Waiting for database restore to complete...'); + await restoreOperation.promise(); + + console.log('Database restored from backup.'); + const [metadata] = await databaseAdminClient.getDatabase({ + name: databaseAdminClient.databasePath(projectId, instanceId, databaseId), + }); + console.log( + `Database ${metadata.restoreInfo.backupInfo.sourceDatabase} was restored ` + + `to ${databaseId} from backup ${metadata.restoreInfo.backupInfo.backup} ` + + 'with version time ' + + `${new PreciseDate( + metadata.restoreInfo.backupInfo.versionTime, + ).toISOString()}.`, + ); + // [END spanner_restore_backup] +} + +module.exports.restoreBackup = restoreBackup; diff --git a/handwritten/spanner/samples/backups-update.js b/handwritten/spanner/samples/backups-update.js new file mode 100644 index 00000000000..fa9b52489c0 --- /dev/null +++ b/handwritten/spanner/samples/backups-update.js @@ -0,0 +1,82 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +async function updateBackup(instanceId, backupId, projectId) { + // [START spanner_update_backup] + + // Imports the Google Cloud client library and precise date library + const {Spanner, protos} = require('@google-cloud/spanner'); + const {PreciseDate} = require('@google-cloud/precise-date'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const backupId = 'my-backup'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + // Read backup metadata and update expiry time + try { + const [metadata] = await databaseAdminClient.getBackup({ + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + }); + + const currentExpireTime = metadata.expireTime; + const maxExpireTime = metadata.maxExpireTime; + const wantExpireTime = new PreciseDate(currentExpireTime); + wantExpireTime.setDate(wantExpireTime.getDate() + 1); + + // New expire time should be less than the max expire time + const min = (currentExpireTime, maxExpireTime) => + currentExpireTime < maxExpireTime ? currentExpireTime : maxExpireTime; + const newExpireTime = new PreciseDate(min(wantExpireTime, maxExpireTime)); + console.log( + `Backup ${backupId} current expire time: ${Spanner.timestamp( + currentExpireTime, + ).toISOString()}`, + ); + console.log( + `Updating expire time to ${Spanner.timestamp( + newExpireTime, + ).toISOString()}`, + ); + + await databaseAdminClient.updateBackup({ + backup: { + name: databaseAdminClient.backupPath(projectId, instanceId, backupId), + expireTime: Spanner.timestamp(newExpireTime).toStruct(), + }, + updateMask: (protos.google.protobuf.FieldMask = { + paths: ['expire_time'], + }), + }); + console.log('Expire time updated.'); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_update_backup] +} + +module.exports.updateBackup = updateBackup; diff --git a/handwritten/spanner/samples/backups.js b/handwritten/spanner/samples/backups.js new file mode 100644 index 00000000000..acb9592536d --- /dev/null +++ b/handwritten/spanner/samples/backups.js @@ -0,0 +1,168 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +const {createBackup} = require('./backups-create'); +const { + createBackupWithEncryptionKey, +} = require('./backups-create-with-encryption-key'); +const {cancelBackup} = require('./backups-cancel'); +const {getBackups} = require('./backups-get'); +const {getBackupOperations} = require('./backups-get-operations'); +const {getDatabaseOperations} = require('./backups-get-database-operations'); +const {updateBackup} = require('./backups-update'); +const {restoreBackup} = require('./backups-restore'); +const { + restoreBackupWithEncryptionKey, +} = require('./backups-restore-with-encryption-key'); +const {deleteBackup} = require('./backups-delete'); + +require('yargs') + .demand(1) + .command( + 'createBackup ', + 'Creates a backup of a Cloud Spanner database.', + {}, + opts => + createBackup( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + Date.parse(opts.versionTime), + ), + ) + .example( + 'node $0 createBackup "my-instance" "my-database" "my-backup" "my-project-id" "my-version-time"', + ) + .command( + 'createBackupWithEncryptionKey ', + 'Creates a backup of a Cloud Spanner database using an encryption key.', + {}, + opts => + createBackupWithEncryptionKey( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + opts.keyName, + ), + ) + .example( + 'node $0 createBackupWithEncryptionKey "my-instance" "my-database" "my-backup" "my-project-id" "my-key-name"', + ) + .command( + 'cancelBackup ', + 'Creates and cancels a backup of a Cloud Spanner database.', + {}, + opts => + cancelBackup( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + ), + ) + .example( + 'node $0 cancelBackup "my-instance" "my-database" "my-backup" "my-project-id"', + ) + .command( + 'getBackups ', + 'Lists backups in the instance with filters.', + {}, + opts => + getBackups( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + ), + ) + .example( + 'node $0 getBackups "my-instance" "my-database" "my-backup" "my-project-id"', + ) + .command( + 'getBackupOperations ', + 'Lists all backup operations in the instance.', + {}, + opts => + getBackupOperations( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + ), + ) + .example( + 'node $0 getBackupOperations "my-instance" "my-database" "my-backup" "my-project-id"', + ) + .command( + 'getDatabaseOperations ', + 'Lists all database operations in the instance.', + {}, + opts => getDatabaseOperations(opts.instanceName, opts.projectId), + ) + .example('node $0 getDatabaseOperations "my-instance" "my-project-id"') + .command( + 'updateBackup ', + 'Updates the expire time of a backup.', + {}, + opts => updateBackup(opts.instanceName, opts.backupName, opts.projectId), + ) + .example('node $0 updateBackup "my-instance" "my-backup" "my-project-id"') + .command( + 'restoreBackup ', + 'Restores a Cloud Spanner database from a backup.', + {}, + opts => + restoreBackup( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + ), + ) + .example( + 'node $0 restoreBackup "my-instance" "my-database" "my-backup" "my-project-id"', + ) + .command( + 'restoreBackupWithEncryptionKey ', + 'Restores a Cloud Spanner database from a backup with an encryption key.', + {}, + opts => + restoreBackupWithEncryptionKey( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId, + opts.keyName, + ), + ) + .example( + 'node $0 restoreBackupWithEncryptionKey "my-instance" "my-database" "my-backup" "my-project-id" "my-key-name"', + ) + .command( + 'deleteBackup ', + 'Deletes a backup.', + {}, + opts => deleteBackup(opts.instanceName, opts.backupName, opts.projectId), + ) + .example('node $0 deleteBackup "my-instance" "my-backup" "my-project-id"') + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/batch-write.js b/handwritten/spanner/samples/batch-write.js new file mode 100644 index 00000000000..ce7c5dd3f2c --- /dev/null +++ b/handwritten/spanner/samples/batch-write.js @@ -0,0 +1,123 @@ +/** + * Copyright 2024 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: Batch Write +// usage: node batch-write.js + +'use strict'; + +async function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_batch_write_at_least_once] + + // Imports the Google Cloud client library + const {Spanner, MutationGroup} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Create Mutation Groups + /** + * Related mutations should be placed in a group, such as insert mutations for both a parent and a child row. + * A group must contain related mutations. + * Please see {@link https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#google.spanner.v1.BatchWriteRequest.MutationGroup} + * for more details and examples. + */ + const mutationGroup1 = new MutationGroup(); + mutationGroup1.insert('Singers', { + SingerId: 1, + FirstName: 'Scarlet', + LastName: 'Terry', + }); + + const mutationGroup2 = new MutationGroup(); + mutationGroup2.insert('Singers', { + SingerId: 2, + FirstName: 'Marc', + }); + mutationGroup2.insert('Singers', { + SingerId: 3, + FirstName: 'Catalina', + LastName: 'Smith', + }); + mutationGroup2.insert('Albums', { + AlbumId: 1, + SingerId: 2, + AlbumTitle: 'Total Junk', + }); + mutationGroup2.insert('Albums', { + AlbumId: 2, + SingerId: 3, + AlbumTitle: 'Go, Go, Go', + }); + + const options = { + transactionTag: 'batch-write-tag', + }; + + try { + database + .batchWriteAtLeastOnce([mutationGroup1, mutationGroup2], options) + .on('error', console.error) + .on('data', response => { + // Check the response code of each response to determine whether the mutation group(s) were applied successfully. + if (response.status.code === 0) { + console.log( + `Mutation group indexes ${ + response.indexes + }, have been applied with commit timestamp ${Spanner.timestamp( + response.commitTimestamp, + ).toJSON()}`, + ); + } + // Mutation groups that fail to commit trigger a response with a non-zero status code. + else { + console.log( + `Mutation group indexes ${response.indexes}, could not be applied with error code ${response.status.code}, and error message ${response.status.message}`, + ); + } + }) + .on('end', () => { + console.log('Request completed successfully'); + }); + } catch (err) { + console.log(err); + } + // [END spanner_batch_write_at_least_once] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); + +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/batch.js b/handwritten/spanner/samples/batch.js new file mode 100644 index 00000000000..c47a2fc5249 --- /dev/null +++ b/handwritten/spanner/samples/batch.js @@ -0,0 +1,148 @@ +// Copyright 2018 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. + +'use strict'; + +async function createAndExecuteQueryPartitions( + instanceId, + databaseId, + projectId, +) { + // [START spanner_batch_client] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + const [transaction] = await database.createBatchTransaction(); + + const query = { + sql: 'SELECT * FROM Singers', + // DataBoost option is an optional parameter which can also be used for partition read + // and query to execute the request via spanner independent compute resources. + dataBoostEnabled: true, + }; + + // A Partition object is serializable and can be used from a different process. + const [partitions] = await transaction.createQueryPartitions(query); + console.log(`Successfully created ${partitions.length} query partitions.`); + + let row_count = 0; + const promises = []; + partitions.forEach(partition => { + promises.push( + transaction.execute(partition).then(results => { + const rows = results[0].map(row => row.toJSON()); + row_count += rows.length; + }), + ); + }); + Promise.all(promises) + .then(() => { + console.log( + `Successfully received ${row_count} from executed partitions.`, + ); + transaction.close(); + }) + .then(() => { + database.close(); + }); + // [END spanner_batch_client] +} + +async function executePartition( + instanceId, + databaseId, + identifier, + partition, + projectId, +) { + // [START spanner_batch_execute_partitions] + // 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'; + // const databaseId = 'my-database'; + // const identifier = {}; + // const partition = {}; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + const transaction = database.batchTransaction(identifier); + + const [rows] = await transaction.execute(partition); + console.log(`Successfully received ${rows.length} from executed partition.`); + // [END spanner_batch_execute_partitions] +} + +require('yargs') + .demand(1) + .command( + 'create-and-execute-query-partitions ', + 'Creates query partitions and executes them.', + {}, + opts => + createAndExecuteQueryPartitions( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'execute-partition ', + 'Executes a partition.', + {}, + opts => + executePartition( + opts.instanceName, + opts.databaseName, + JSON.parse(opts.identifier), + JSON.parse(opts.partition), + opts.projectId, + ), + ) + .example( + 'node $0 create-and-execute-query-partitions "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 execute-partition "my-instance" "my-database" "{}" "{}" "my-project-id"', + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/create-full-backup-schedule.js b/handwritten/spanner/samples/create-full-backup-schedule.js new file mode 100644 index 00000000000..3e66845bd45 --- /dev/null +++ b/handwritten/spanner/samples/create-full-backup-schedule.js @@ -0,0 +1,80 @@ +/** + * Copyright 2024 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 full backup schedule +// usage: node create-full-backup-schedule.js + +'use strict'; + +function main( + projectId = 'my-project-id', + instanceId = 'my-instance-id', + databaseId = 'my-database-id', + scheduleId = 'my-schedule-id', +) { + async function createFullBackupSchedule() { + // [START spanner_create_full_backup_schedule] + // Import the Google Cloud client library for Spanner. + const {Spanner} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance-id'; + // const databaseId = 'my-database-id'; + // const scheduleId = 'my-schedule-id'; + + // Create a Spanner database admin client. + const spanner = new Spanner({projectId}); + const client = spanner.getDatabaseAdminClient(); + + try { + // Create a schedule to create full backups daily at 12:30 AM, using the + // database's encryption config, and retained for 24 hours. + const [response] = await client.createBackupSchedule({ + parent: client.databasePath(projectId, instanceId, databaseId), + backupScheduleId: scheduleId, + backupSchedule: { + spec: { + cronSpec: { + text: '30 12 * * *', + }, + }, + retentionDuration: { + seconds: 86400, + }, + encryptionConfig: { + encryptionType: 'USE_DATABASE_ENCRYPTION', + }, + fullBackupSpec: {}, + }, + }); + console.log('Created full backup schedule:', response); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_full_backup_schedule] + } + + createFullBackupSchedule(); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/create-incremental-backup-schedule.js b/handwritten/spanner/samples/create-incremental-backup-schedule.js new file mode 100644 index 00000000000..64bfdf1da5a --- /dev/null +++ b/handwritten/spanner/samples/create-incremental-backup-schedule.js @@ -0,0 +1,80 @@ +/** + * Copyright 2024 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 an incremental backup schedule +// usage: node create-incremental-backup-schedule.js + +'use strict'; + +function main( + projectId = 'my-project-id', + instanceId = 'my-instance-id', + databaseId = 'my-database-id', + scheduleId = 'my-schedule-id', +) { + async function createIncrementalBackupSchedule() { + // [START spanner_create_incremental_backup_schedule] + // Import the Google Cloud client library for Spanner. + const {Spanner} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance-id'; + // const databaseId = 'my-database-id'; + // const scheduleId = 'my-schedule-id'; + + // Create a Spanner database admin client. + const spanner = new Spanner({projectId}); + const client = spanner.getDatabaseAdminClient(); + + try { + // Create a schedule to create incremental backups daily at 12:30 AM, + // using Google-managed encryption, and retained for 24 hours. + const [response] = await client.createBackupSchedule({ + parent: client.databasePath(projectId, instanceId, databaseId), + backupScheduleId: scheduleId, + backupSchedule: { + spec: { + cronSpec: { + text: '30 12 * * *', + }, + }, + retentionDuration: { + seconds: 86400, + }, + encryptionConfig: { + encryptionType: 'GOOGLE_DEFAULT_ENCRYPTION', + }, + incrementalBackupSpec: {}, + }, + }); + console.log('Created incremental backup schedule:', response); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_incremental_backup_schedule] + } + + createIncrementalBackupSchedule(); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/create-instance-without-default-backup-schedules.js b/handwritten/spanner/samples/create-instance-without-default-backup-schedules.js new file mode 100644 index 00000000000..bf8ef3c7c43 --- /dev/null +++ b/handwritten/spanner/samples/create-instance-without-default-backup-schedules.js @@ -0,0 +1,73 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +function main(instanceId, projectId) { + async function createInstanceWithoutDefaultBackupSchedules() { + // [START spanner_create_instance_without_default_backup_schedule] + /** + * TODO(developer): Uncomment the following lines before running the sample. + **/ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = await spanner.getInstanceAdminClient(); + // Creates a new instance + try { + const [operation] = await instanceAdminClient.createInstance({ + instanceId: instanceId, + parent: instanceAdminClient.projectPath(projectId), + instance: { + config: instanceAdminClient.instanceConfigPath( + projectId, + 'regional-me-central2', + ), + nodeCount: 1, + displayName: 'Display name for the instance.', + labels: { + cloud_spanner_samples: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + defaultBackupScheduleType: + protos.google.spanner.admin.instance.v1.Instance + .DefaultBackupScheduleType.NONE, + }, + }); + await operation.promise(); + + console.log( + `Created instance ${instanceId} without default backup schedules.`, + ); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_instance_without_default_backup_schedule] + } + createInstanceWithoutDefaultBackupSchedules(); +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/crud.js b/handwritten/spanner/samples/crud.js new file mode 100644 index 00000000000..3ea486e9268 --- /dev/null +++ b/handwritten/spanner/samples/crud.js @@ -0,0 +1,396 @@ +// Copyright 2017 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. + +'use strict'; + +// sample-metadata: +// title: CRUD + +async function updateData(instanceId, databaseId, projectId) { + // [START spanner_update_data] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Update a row in the Albums table + // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they + // must be converted to strings before being inserted as INT64s + const albumsTable = database.table('Albums'); + + try { + await albumsTable.update([ + {SingerId: '1', AlbumId: '1', MarketingBudget: '100000'}, + {SingerId: '2', AlbumId: '2', MarketingBudget: '500000'}, + ]); + console.log('Updated data.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_update_data] +} + +async function insertData(instanceId, databaseId, projectId) { + // [START spanner_insert_data] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Instantiate Spanner table objects + const singersTable = database.table('Singers'); + const albumsTable = database.table('Albums'); + + // Inserts rows into the Singers table + // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so + // they must be converted to strings before being inserted as INT64s + try { + await singersTable.insert([ + {SingerId: '1', FirstName: 'Marc', LastName: 'Richards'}, + {SingerId: '2', FirstName: 'Catalina', LastName: 'Smith'}, + {SingerId: '3', FirstName: 'Alice', LastName: 'Trentor'}, + {SingerId: '4', FirstName: 'Lea', LastName: 'Martin'}, + {SingerId: '5', FirstName: 'David', LastName: 'Lomond'}, + ]); + + await albumsTable.insert([ + {SingerId: '1', AlbumId: '1', AlbumTitle: 'Total Junk'}, + {SingerId: '1', AlbumId: '2', AlbumTitle: 'Go, Go, Go'}, + {SingerId: '2', AlbumId: '1', AlbumTitle: 'Green'}, + {SingerId: '2', AlbumId: '2', AlbumTitle: 'Forever Hold your Peace'}, + {SingerId: '2', AlbumId: '3', AlbumTitle: 'Terrified'}, + ]); + + console.log('Inserted data.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + await database.close(); + } + // [END spanner_insert_data] +} + +async function deleteData(instanceId, databaseId, projectId) { + // [START spanner_delete_data] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Instantiate Spanner table object + const albumsTable = database.table('Albums'); + + // Deletes individual rows from the Albums table. + try { + const keys = [ + [2, 1], + [2, 3], + ]; + await albumsTable.deleteRows(keys); + console.log('Deleted individual rows in Albums.'); + } catch (err) { + console.error('ERROR:', err); + } + + // Delete a range of rows where the column key is >=3 and <5 + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rowCount] = await transaction.runUpdate({ + sql: 'DELETE FROM Singers WHERE SingerId >= 3 AND SingerId < 5', + }); + console.log(`${rowCount} records deleted from Singers.`); + } catch (err) { + console.error('ERROR:', err); + } + + // Deletes remaining rows from the Singers table and the Albums table, + // because Albums table is defined with ON DELETE CASCADE. + try { + // The WHERE clause is required for DELETE statements to prevent + // accidentally deleting all rows in a table. + // https://cloud.google.com/spanner/docs/dml-syntax#where_clause + const [rowCount] = await transaction.runUpdate({ + sql: 'DELETE FROM Singers WHERE true', + }); + console.log(`${rowCount} records deleted from Singers.`); + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + }); + // [END spanner_delete_data] +} + +async function queryData(instanceId, databaseId, projectId) { + // [START spanner_query_data] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: 'SELECT SingerId, AlbumId, AlbumTitle FROM Albums', + }; + + // Queries rows from the Albums table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `SingerId: ${json.SingerId}, AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + // [END spanner_query_data] +} + +async function readData(instanceId, databaseId, projectId) { + // [START spanner_read_data] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Reads rows from the Albums table + const albumsTable = database.table('Albums'); + + const query = { + columns: ['SingerId', 'AlbumId', 'AlbumTitle'], + keySet: { + all: true, + }, + }; + + try { + const [rows] = await albumsTable.read(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `SingerId: ${json.SingerId}, AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + // [END spanner_read_data] +} + +async function readStaleData(instanceId, databaseId, projectId) { + // [START spanner_read_stale_data] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Reads rows from the Albums table + const albumsTable = database.table('Albums'); + + const query = { + columns: ['SingerId', 'AlbumId', 'AlbumTitle', 'MarketingBudget'], + keySet: { + all: true, + }, + }; + + const options = { + // Guarantees that all writes committed more than 15000 milliseconds ago are visible + exactStaleness: 15000, + }; + + try { + const [rows] = await albumsTable.read(query, options); + + rows.forEach(row => { + const json = row.toJSON(); + const id = json.SingerId; + const album = json.AlbumId; + const title = json.AlbumTitle; + const budget = json.MarketingBudget ? json.MarketingBudget : ''; + console.log( + `SingerId: ${id}, AlbumId: ${album}, AlbumTitle: ${title}, MarketingBudget: ${budget}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + // [END spanner_read_stale_data] +} + +const {getCommitStats} = require('./get-commit-stats'); + +require('yargs') + .demand(1) + .command( + 'update ', + 'Modifies existing rows of data in an example Cloud Spanner table.', + {}, + opts => updateData(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'query ', + 'Executes a read-only SQL query against an example Cloud Spanner table.', + {}, + opts => queryData(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'insert ', + 'Inserts new rows of data into an example Cloud Spanner table.', + {}, + opts => insertData(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'delete ', + 'Deletes rows from an example Cloud Spanner table.', + {}, + opts => deleteData(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'read ', + 'Reads data in an example Cloud Spanner table.', + {}, + opts => readData(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'read-stale ', + 'Reads stale data in an example Cloud Spanner table.', + {}, + opts => readStaleData(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'getCommitStats ', + 'Updates rows in example Cloud Spanner table and reads CommitStats.', + {}, + opts => + getCommitStats(opts.instanceName, opts.databaseName, opts.projectId), + ) + .example('node $0 update "my-instance" "my-database" "my-project-id"') + .example('node $0 query "my-instance" "my-database" "my-project-id"') + .example('node $0 insert "my-instance" "my-database" "my-project-id"') + .example('node $0 delete "my-instance" "my-database" "my-project-id"') + .example('node $0 read "my-instance" "my-database" "my-project-id"') + .example('node $0 read-stale "my-instance" "my-database" "my-project-id"') + .example('node $0 getCommitStats "my-instance" "my-database" "my-project-id"') + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/database-add-split-points.js b/handwritten/spanner/samples/database-add-split-points.js new file mode 100644 index 00000000000..996ea1ab020 --- /dev/null +++ b/handwritten/spanner/samples/database-add-split-points.js @@ -0,0 +1,123 @@ +/** + * Copyright 2025 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: Adds split points to a database. +// usage: node database-add-split-points.js + +'use strict'; + +function main( + instanceId = 'my-instance-id', + databaseId = 'my-database-id', + projectId = 'my-project-id', +) { + async function addSplitPoints() { + // [START spanner_database_add_split_points] + // Import the Google Cloud client library for Spanner. + const {Spanner} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance-id'; + // const databaseId = 'my-database-id'; + + // Create a Spanner database admin client. + const spanner = new Spanner({projectId}); + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + try { + // Add split points to table and index + // first is a table level split that takes table primary key value + // second is index level split with index key parts + // third is index level split having index key part and table key part + // Assume the following table and index structure + // CREATE TABLE Singers ( + // SingerId INT64 NOT NULL, + // FirstName STRING(1024), + // LastName STRING(1024), + // SingerInfo BYTES(MAX), + // ) PRIMARY KEY(SingerId); + // + // CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName); + const request = [ + 'CREATE INDEX IF NOT EXISTS SingersByFirstLastName ON Singers(FirstName, LastName)', + ]; + + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Added the SingersByFirstLastName index.'); + + databaseAdminClient.addSplitPoints({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + splitPoints: [ + { + table: 'Singers', + keys: [{keyParts: {values: [{stringValue: '42'}]}}], + }, + { + index: 'SingersByFirstLastName', + keys: [ + { + keyParts: { + values: [{stringValue: 'John'}, {stringValue: 'Doe'}], + }, + }, + ], + }, + { + index: 'SingersByFirstLastName', + keys: [ + { + keyParts: { + values: [{stringValue: 'Jane'}, {stringValue: 'Doe'}], + }, + }, + {keyParts: {values: [{stringValue: '38'}]}}, + ], + }, + ], + }); + console.log('Added Split Points'); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_database_add_split_points] + } + + addSplitPoints(); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/database-create-with-default-leader.js b/handwritten/spanner/samples/database-create-with-default-leader.js new file mode 100644 index 00000000000..c02bf08dfaa --- /dev/null +++ b/handwritten/spanner/samples/database-create-with-default-leader.js @@ -0,0 +1,97 @@ +/** + * Copyright 2024 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 new database with a specific default leader +// usage: node database-create-with-default-leader.js + +'use strict'; + +function main(instanceId, databaseId, defaultLeader, projectId) { + // [START spanner_create_database_with_default_leader] + /** + * 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'; + // const defaultLeader = 'my-default-leader'; example: 'asia-northeast1' + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function createDatabaseWithDefaultLeader() { + // Create a new database with an extra statement which will alter the + // database after creation to set the default leader. + console.log( + `Creating database ${databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + )}.`, + ); + const createSingersTableStatement = ` + CREATE TABLE Singers ( + SingerId INT64 NOT NULL, + FirstName STRING(1024), + LastName STRING(1024), + SingerInfo BYTES(MAX) + ) PRIMARY KEY (SingerId)`; + const createAlbumsStatement = ` + 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`; + + // Default leader is one of the possible values in the leaderOptions field of the + // instance config of the instance where the database is created. + const setDefaultLeaderStatement = ` + ALTER DATABASE \`${databaseId}\` + SET OPTIONS (default_leader = '${defaultLeader}')`; + + const [operation] = await databaseAdminClient.createDatabase({ + createStatement: 'CREATE DATABASE `' + databaseId + '`', + extraStatements: [ + createSingersTableStatement, + createAlbumsStatement, + setDefaultLeaderStatement, + ], + parent: databaseAdminClient.instancePath(projectId, instanceId), + }); + + console.log(`Waiting for creation of ${databaseId} to complete...`); + await operation.promise(); + console.log( + `Created database ${databaseId} with default leader ${defaultLeader}.`, + ); + } + createDatabaseWithDefaultLeader(); + // [END spanner_create_database_with_default_leader] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/database-create-with-encryption-key.js b/handwritten/spanner/samples/database-create-with-encryption-key.js new file mode 100644 index 00000000000..3b697858d5a --- /dev/null +++ b/handwritten/spanner/samples/database-create-with-encryption-key.js @@ -0,0 +1,72 @@ +// Copyright 2024 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. + +'use strict'; + +async function createDatabaseWithEncryptionKey( + instanceId, + databaseId, + projectId, + keyName, +) { + // [START spanner_create_database_with_encryption_key] + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const keyName = + // 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key'; + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + // Creates a database + const [operation] = await databaseAdminClient.createDatabase({ + createStatement: 'CREATE DATABASE `' + databaseId + '`', + parent: databaseAdminClient.instancePath(projectId, instanceId), + encryptionConfig: + (protos.google.spanner.admin.database.v1.EncryptionConfig = { + kmsKeyName: keyName, + }), + }); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + await operation.promise(); + + console.log(`Created database ${databaseId} on instance ${instanceId}.`); + + // Get encryption key + const [metadata] = await databaseAdminClient.getDatabase({ + name: databaseAdminClient.databasePath(projectId, instanceId, databaseId), + }); + + console.log( + `Database encrypted with key ${metadata.encryptionConfig.kmsKeyName}.`, + ); + // [END spanner_create_database_with_encryption_key] +} + +module.exports.createDatabaseWithEncryptionKey = + createDatabaseWithEncryptionKey; diff --git a/handwritten/spanner/samples/database-create-with-multiple-kms-keys.js b/handwritten/spanner/samples/database-create-with-multiple-kms-keys.js new file mode 100644 index 00000000000..87e9984ac98 --- /dev/null +++ b/handwritten/spanner/samples/database-create-with-multiple-kms-keys.js @@ -0,0 +1,77 @@ +// Copyright 2024 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. + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project', + kmsKeyNames = 'key1,key2,key3', +) { + // [START spanner_create_database_with_MR_CMEK] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const kmsKeyNames = + // 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key1,projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key2'; + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function createDatabaseWithMultipleKmsKeys() { + // Creates a database + const [operation] = await databaseAdminClient.createDatabase({ + createStatement: 'CREATE DATABASE `' + databaseId + '`', + parent: databaseAdminClient.instancePath(projectId, instanceId), + encryptionConfig: + (protos.google.spanner.admin.database.v1.EncryptionConfig = { + kmsKeyNames: kmsKeyNames.split(','), + }), + }); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + await operation.promise(); + + console.log(`Created database ${databaseId} on instance ${instanceId}.`); + + // Get encryption key + const [metadata] = await databaseAdminClient.getDatabase({ + name: databaseAdminClient.databasePath(projectId, instanceId, databaseId), + }); + + console.log( + `Database encrypted with keys ${metadata.encryptionConfig.kmsKeyNames}.`, + ); + } + createDatabaseWithMultipleKmsKeys(); + // [END spanner_create_database_with_MR_CMEK] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/database-create-with-version-retention-period.js b/handwritten/spanner/samples/database-create-with-version-retention-period.js new file mode 100644 index 00000000000..0bb8988c3bb --- /dev/null +++ b/handwritten/spanner/samples/database-create-with-version-retention-period.js @@ -0,0 +1,83 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +async function createDatabaseWithVersionRetentionPeriod( + instanceId, + databaseId, + projectId, +) { + // [START spanner_create_database_with_version_retention_period] + + // 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'; + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + try { + // Create a new database with an extra statement which will alter the + // database after creation to set the version retention period. + console.log( + `Creating database ${databaseAdminClient.instancePath( + projectId, + instanceId, + )}.`, + ); + const versionRetentionStatement = ` + ALTER DATABASE \`${databaseId}\` + SET OPTIONS (version_retention_period = '1d')`; + + const [operation] = await databaseAdminClient.createDatabase({ + createStatement: 'CREATE DATABASE `' + databaseId + '`', + extraStatements: [versionRetentionStatement], + parent: databaseAdminClient.instancePath(projectId, instanceId), + }); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + await operation.promise(); + console.log(` + Created database ${databaseId} with version retention period.`); + + const [metadata] = await databaseAdminClient.getDatabase({ + name: databaseAdminClient.databasePath(projectId, instanceId, databaseId), + }); + + console.log(`Version retention period: ${metadata.versionRetentionPeriod}`); + const milliseconds = + parseInt(metadata.earliestVersionTime.seconds, 10) * 1000 + + parseInt(metadata.earliestVersionTime.nanos, 10) / 1e6; + const date = new Date(milliseconds); + console.log(`Earliest version time: ${date.toString()}`); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_database_with_version_retention_period] +} + +module.exports.createDatabaseWithVersionRetentionPeriod = + createDatabaseWithVersionRetentionPeriod; diff --git a/handwritten/spanner/samples/database-get-ddl.js b/handwritten/spanner/samples/database-get-ddl.js new file mode 100644 index 00000000000..6e40ad3a3fb --- /dev/null +++ b/handwritten/spanner/samples/database-get-ddl.js @@ -0,0 +1,69 @@ +/** + * Copyright 2024 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: Gets the schema definition of an existing database +// usage: node database-get-ddl.js + +'use strict'; + +function main(instanceId, databaseId, projectId) { + // [START spanner_get_database_ddl] + /** + * 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'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function getDatabaseDdl() { + // Get the schema definition of the database. + const [ddlStatements] = await databaseAdminClient.getDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + }); + + console.log( + `Retrieved database DDL for ${databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + )}:`, + ); + ddlStatements.statements.forEach(element => { + console.log(element); + }); + } + getDatabaseDdl(); + // [END spanner_get_database_ddl] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/database-get-default-leader.js b/handwritten/spanner/samples/database-get-default-leader.js new file mode 100644 index 00000000000..77f6ec625af --- /dev/null +++ b/handwritten/spanner/samples/database-get-default-leader.js @@ -0,0 +1,69 @@ +/** + * Copyright 2021 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: Gets the default leader option of an existing database +// usage: node database-get-default-leader.js + +'use strict'; + +function main(instanceId, databaseId, projectId) { + // [START spanner_query_information_schema_database_options] + /** + * 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'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + // Gets a reference to a Cloud Spanner instance and a database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + async function getDatabaseDdl() { + // Get the default leader option for the database. + const [rows] = await database.run({ + sql: ` + SELECT s.OPTION_NAME, s.OPTION_VALUE + FROM INFORMATION_SCHEMA.DATABASE_OPTIONS s + WHERE s.OPTION_NAME = 'default_leader'`, + json: true, + }); + if (rows.length > 0) { + const option = rows[0]; + console.log( + `The ${option.OPTION_NAME} for ${databaseId} is ${option.OPTION_VALUE}`, + ); + } else { + console.log( + `Database ${databaseId} does not have a value for option 'default_leader'`, + ); + } + } + getDatabaseDdl(); + // [END spanner_query_information_schema_database_options] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/database-update-default-leader.js b/handwritten/spanner/samples/database-update-default-leader.js new file mode 100644 index 00000000000..60edace6a80 --- /dev/null +++ b/handwritten/spanner/samples/database-update-default-leader.js @@ -0,0 +1,75 @@ +/** + * Copyright 2024 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: Updates the default leader of an existing database +// usage: node database-update-default-leader.js + +'use strict'; + +function main(instanceId, databaseId, defaultLeader, projectId) { + // [START spanner_update_database_with_default_leader] + /** + * 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'; + // const defaultLeader = 'my-default-leader'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function updateDatabaseWithDefaultLeader() { + console.log( + `Updating database ${databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + )}.`, + ); + const setDefaultLeaderStatement = ` + ALTER DATABASE \`${databaseId}\` + SET OPTIONS (default_leader = '${defaultLeader}')`; + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: [setDefaultLeaderStatement], + }); + + console.log(`Waiting for updating of ${databaseId} to complete...`); + await operation.promise(); + console.log( + `Updated database ${databaseId} with default leader ${defaultLeader}.`, + ); + } + updateDatabaseWithDefaultLeader(); + // [END spanner_update_database_with_default_leader] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/database-update.js b/handwritten/spanner/samples/database-update.js new file mode 100644 index 00000000000..d1d8fbe11a4 --- /dev/null +++ b/handwritten/spanner/samples/database-update.js @@ -0,0 +1,85 @@ +// Copyright 2024 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: Updates a Cloud Spanner Database. +// usage: node database-update.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_update_database] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function updateDatabase() { + // Update the database metadata fields + try { + console.log( + `Updating database ${databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + )}.`, + ); + const [operation] = await databaseAdminClient.updateDatabase({ + database: { + name: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + enableDropProtection: true, + }, + // updateMask contains the fields to be updated in database + updateMask: (protos.google.protobuf.FieldMask = { + paths: ['enable_drop_protection'], + }), + }); + console.log( + `Waiting for update operation for ${databaseId} to complete...`, + ); + await operation.promise(); + console.log(`Updated database ${databaseId}.`); + } catch (err) { + console.log('ERROR:', err); + } + } + updateDatabase(); + // [END spanner_update_database] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/datatypes.js b/handwritten/spanner/samples/datatypes.js new file mode 100644 index 00000000000..fb7db8b529c --- /dev/null +++ b/handwritten/spanner/samples/datatypes.js @@ -0,0 +1,774 @@ +// Copyright 2024 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. + +'use strict'; + +async function createVenuesTable(instanceId, databaseId, projectId) { + // [START spanner_create_table_with_datatypes] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const request = [ + `CREATE TABLE Venues ( + VenueId INT64 NOT NULL, + VenueName STRING(100), + VenueInfo BYTES(MAX), + Capacity INT64, + AvailableDates ARRAY, + LastContactDate Date, + OutdoorVenue BOOL, + PopularityScore FLOAT64, + LastUpdateTime TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true) + ) PRIMARY KEY (VenueId)`, + ]; + + // Creates a table in an existing database. + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + + await operation.promise(); + + console.log(`Created table Venues in database ${databaseId}.`); + // [END spanner_create_table_with_datatypes] +} + +async function insertData(instanceId, databaseId, projectId) { + // [START spanner_insert_datatypes_data] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client. + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Instantiate Spanner table objects. + const venuesTable = database.table('Venues'); + const exampleBytes1 = new Buffer.from('Hello World 1'); + const exampleBytes2 = new Buffer.from('Hello World 2'); + const exampleBytes3 = new Buffer.from('Hello World 3'); + const availableDates1 = ['2020-12-01', '2020-12-02', '2020-12-03']; + const availableDates2 = ['2020-11-01', '2020-11-05', '2020-11-15']; + const availableDates3 = ['2020-10-01', '2020-10-07']; + + // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they + // must be converted to strings before being inserted as INT64s. + const data = [ + { + VenueId: '4', + VenueName: 'Venue 4', + VenueInfo: exampleBytes1, + Capacity: '1800', + AvailableDates: availableDates1, + LastContactDate: '2018-09-02', + OutdoorVenue: false, + PopularityScore: Spanner.float(0.85543), + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + VenueId: '19', + VenueName: 'Venue 19', + VenueInfo: exampleBytes2, + Capacity: '6300', + AvailableDates: availableDates2, + LastContactDate: '2019-01-15', + OutdoorVenue: true, + PopularityScore: Spanner.float(0.98716), + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + VenueId: '42', + VenueName: 'Venue 42', + VenueInfo: exampleBytes3, + Capacity: '3000', + AvailableDates: availableDates3, + LastContactDate: '2018-10-01', + OutdoorVenue: false, + PopularityScore: Spanner.float(0.72598), + LastUpdateTime: 'spanner.commit_timestamp()', + }, + ]; + + // Inserts rows into the Venues table. + try { + await venuesTable.insert(data); + console.log('Inserted data.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_insert_datatypes_data] +} + +async function queryWithArray(instanceId, databaseId, projectId) { + // [START spanner_query_with_array_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client. + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'date', + }; + + const parentFieldType = { + type: 'array', + child: fieldType, + }; + + const exampleArray = ['2020-10-01', '2020-11-01']; + + const query = { + sql: `SELECT VenueId, VenueName, AvailableDate FROM Venues v, + UNNEST(v.AvailableDates) as AvailableDate + WHERE AvailableDate in UNNEST(@availableDates)`, + params: { + availableDates: exampleArray, + }, + types: { + availableDates: parentFieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + rows.forEach(row => { + const availableDate = row[2]['value']; + const json = row.toJSON(); + console.log( + `VenueId: ${json.VenueId}, VenueName: ${ + json.VenueName + }, AvailableDate: ${JSON.stringify(availableDate).substring(1, 11)}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_array_parameter] +} + +async function queryWithBool(instanceId, databaseId, projectId) { + // [START spanner_query_with_bool_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'bool', + }; + + const exampleBool = true; + + const query = { + sql: `SELECT VenueId, VenueName, OutdoorVenue FROM Venues + WHERE OutdoorVenue = @outdoorVenue`, + params: { + outdoorVenue: exampleBool, + }, + types: { + outdoorVenue: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `VenueId: ${json.VenueId}, VenueName: ${json.VenueName},` + + ` OutdoorVenue: ${json.OutdoorVenue}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_bool_parameter] +} + +async function queryWithBytes(instanceId, databaseId, projectId) { + // [START spanner_query_with_bytes_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'bytes', + }; + + const exampleBytes = new Buffer.from('Hello World 1'); + + const query = { + sql: `SELECT VenueId, VenueName FROM Venues + WHERE VenueInfo = @venueInfo`, + params: { + venueInfo: exampleBytes, + }, + types: { + venueInfo: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log(`VenueId: ${json.VenueId}, VenueName: ${json.VenueName}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_bytes_parameter] +} + +async function queryWithDate(instanceId, databaseId, projectId) { + // [START spanner_query_with_date_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client. + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'date', + }; + + const exampleDate = '2019-01-01'; + + const query = { + sql: `SELECT VenueId, VenueName, LastContactDate FROM Venues + WHERE LastContactDate < @lastContactDate`, + params: { + lastContactDate: exampleDate, + }, + types: { + lastContactDate: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const date = row[2]['value']; + const json = row.toJSON(); + console.log( + `VenueId: ${json.VenueId}, VenueName: ${json.VenueName},` + + ` LastContactDate: ${JSON.stringify(date).substring(1, 11)}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_date_parameter] +} + +async function queryWithFloat(instanceId, databaseId, projectId) { + // [START spanner_query_with_float_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'float64', + }; + + const exampleFloat = Spanner.float(0.8); + + const query = { + sql: `SELECT VenueId, VenueName, PopularityScore FROM Venues + WHERE PopularityScore > @popularityScore`, + params: { + popularityScore: exampleFloat, + }, + types: { + popularityScore: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `VenueId: ${json.VenueId}, VenueName: ${json.VenueName},` + + ` PopularityScore: ${json.PopularityScore}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_float_parameter] +} + +async function queryWithInt(instanceId, databaseId, projectId) { + // [START spanner_query_with_int_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'int64', + }; + + const exampleInt = 3000; + + const query = { + sql: `SELECT VenueId, VenueName, Capacity FROM Venues + WHERE Capacity >= @capacity`, + params: { + capacity: exampleInt, + }, + types: { + capacity: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `VenueId: ${json.VenueId}, VenueName: ${json.VenueName},` + + ` Capacity: ${json.Capacity}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_int_parameter] +} + +async function queryWithString(instanceId, databaseId, projectId) { + // [START spanner_query_with_string_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'string', + }; + + const exampleString = 'Venue 42'; + + const query = { + sql: `SELECT VenueId, VenueName FROM Venues + WHERE VenueName = @venueName`, + params: { + venueName: exampleString, + }, + types: { + venueName: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log(`VenueId: ${json.VenueId}, VenueName: ${json.VenueName}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_string_parameter] +} + +async function queryWithTimestamp(instanceId, databaseId, projectId) { + // [START spanner_query_with_timestamp_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client. + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'timestamp', + }; + + const exampleTimestamp = new Date().toISOString(); + + const query = { + sql: `SELECT VenueId, VenueName, LastUpdateTime FROM Venues + WHERE LastUpdateTime < @lastUpdateTime`, + params: { + lastUpdateTime: exampleTimestamp, + }, + types: { + lastUpdateTime: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `VenueId: ${json.VenueId}, VenueName: ${json.VenueName},` + + ` LastUpdateTime: ${json.LastUpdateTime}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_timestamp_parameter] +} + +const {addNumericColumn} = require('./numeric-add-column'); +const {updateWithNumericData} = require('./numeric-update-data'); +const {queryWithNumericParameter} = require('./numeric-query-parameter'); +const {addJsonColumn} = require('./json-add-column'); +const {updateWithJsonData} = require('./json-update-data'); +const {queryWithJsonParameter} = require('./json-query-parameter'); + +require('yargs') + .demand(1) + .command( + 'createVenuesTable ', + 'Creates sample "Venues" table containing example datatype columns in a Cloud Spanner database.', + {}, + opts => + createVenuesTable(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'insertData ', + 'Inserts new rows of data into an sample "Venues" Cloud Spanner table.', + {}, + opts => insertData(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithArray ', + "Query data from the sample 'Venues' table with an ARRAY datatype.", + {}, + opts => + queryWithArray(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithBool ', + "Query data from the sample 'Venues' table with a BOOL datatype.", + {}, + opts => queryWithBool(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithBytes ', + "Query data from the sample 'Venues' table with a BYTES datatype.", + {}, + opts => + queryWithBytes(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithDate ', + "Query data from the sample 'Venues' table with a DATE datatype.", + {}, + opts => queryWithDate(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithFloat ', + "Query data from the sample 'Venues' table with a FLOAT64 datatype.", + {}, + opts => + queryWithFloat(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithInt ', + "Query data from the sample 'Venues' table with a INT64 datatype.", + {}, + opts => queryWithInt(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithString ', + "Query data from the sample 'Venues' table with a STRING datatype.", + {}, + opts => + queryWithString(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithTimestamp ', + "Query data from the sample 'Venues' table with a TIMESTAMP datatype.", + {}, + opts => + queryWithTimestamp(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'addNumericColumn ', + 'Adds a "Revenue" column to sample "Venues" table in a Cloud Spanner database.', + {}, + opts => + addNumericColumn(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'updateWithNumericData ', + 'Updates rows to include "Revenue" in sample "Venues" Cloud Spanner table.', + {}, + opts => + updateWithNumericData( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'queryWithNumericParameter ', + "Query data from the sample 'Venues' table with a NUMERIC datatype.", + {}, + opts => + queryWithNumericParameter( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'addJsonColumn ', + 'Adds a "VenueDetails" column to sample "Venues" table in a Cloud Spanner database.', + {}, + opts => addJsonColumn(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'updateWithJsonData ', + 'Updates rows to include "VenueDetails" in sample "Venues" Cloud Spanner table.', + {}, + opts => + updateWithJsonData(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithJsonParameter ', + "Query data from the sample 'Venues' table with a JSON datatype.", + {}, + opts => + queryWithJsonParameter( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .example( + 'node $0 createVenuesTable "my-instance" "my-database" "my-project-id"', + ) + .example('node $0 insertData "my-instance" "my-database" "my-project-id"') + .example('node $0 queryWithArray "my-instance" "my-database" "my-project-id"') + .example('node $0 queryWithBool "my-instance" "my-database" "my-project-id"') + .example('node $0 queryWithBytes "my-instance" "my-database" "my-project-id"') + .example('node $0 queryWithDate "my-instance" "my-database" "my-project-id"') + .example('node $0 queryWithFloat "my-instance" "my-database" "my-project-id"') + .example('node $0 queryWithInt "my-instance" "my-database" "my-project-id"') + .example( + 'node $0 queryWithString "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 queryWithTimestamp "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 addNumericColumn "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 updateWithNumericData "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 queryWithNumericParameter "my-instance" "my-database" "my-project-id"', + ) + .example('node $0 addJsonColumn "my-instance" "my-database" "my-project-id"') + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/delete-backup-schedule.js b/handwritten/spanner/samples/delete-backup-schedule.js new file mode 100644 index 00000000000..bdc04dbc393 --- /dev/null +++ b/handwritten/spanner/samples/delete-backup-schedule.js @@ -0,0 +1,69 @@ +/** + * Copyright 2024 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: Deletes a backup schedule +// usage: node delete-backup-schedule.js + +'use strict'; + +function main( + projectId = 'my-project-id', + instanceId = 'my-instance-id', + databaseId = 'my-database-id', + scheduleId = 'my-schedule-id', +) { + async function deleteBackupSchedule() { + // [START spanner_delete_backup_schedule] + // Import the Google Cloud client library for Spanner. + const {Spanner} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance-id'; + // const databaseId = 'my-database-id'; + // const scheduleId = 'my-schedule-id'; + + // Create a Spanner database admin client. + const spanner = new Spanner({projectId}); + const client = spanner.getDatabaseAdminClient(); + + try { + // Delete the backup schedule. + await client.deleteBackupSchedule({ + name: client.backupSchedulePath( + projectId, + instanceId, + databaseId, + scheduleId, + ), + }); + console.log('Deleted backup schedule'); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_delete_backup_schedule] + } + + deleteBackupSchedule(); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/directed-reads.js b/handwritten/spanner/samples/directed-reads.js new file mode 100644 index 00000000000..233ef94012f --- /dev/null +++ b/handwritten/spanner/samples/directed-reads.js @@ -0,0 +1,117 @@ +// Copyright 2024 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: Runs an execute sql request with directed read options +// usage: node directed-reads.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_directed_read] + // Imports the Google Cloud Spanner client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // Only one of excludeReplicas or includeReplicas can be set + // Each accepts a list of replicaSelections which contains location and type + // * `location` - The location must be one of the regions within the + // multi-region configuration of your database. + // * `type` - The type of the replica + // Some examples of using replicaSelectors are: + // * `location:us-east1` --> The "us-east1" replica(s) of any available type + // will be used to process the request. + // * `type:READ_ONLY` --> The "READ_ONLY" type replica(s) in nearest + //. available location will be used to process the + // request. + // * `location:us-east1 type:READ_ONLY` --> The "READ_ONLY" type replica(s) + // in location "us-east1" will be used to process + // the request. + // includeReplicas also contains an option for autoFailover which when set + // Spanner will not route requests to a replica outside the + // includeReplicas list when all the specified replicas are unavailable + // or unhealthy. The default value is `false` + const directedReadOptionsForClient = { + excludeReplicas: { + replicaSelections: [ + { + location: 'us-east4', + }, + ], + }, + }; + + // Instantiates a client with directedReadOptions + const spanner = new Spanner({ + projectId: projectId, + directedReadOptions: directedReadOptionsForClient, + }); + + async function spannerDirectedReads() { + // Gets a reference to a Cloud Spanner instance and backup + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + const directedReadOptionsForRequest = { + includeReplicas: { + replicaSelections: [ + { + type: protos.google.spanner.v1.DirectedReadOptions.ReplicaSelection + .Type.READ_ONLY, + }, + ], + autoFailoverDisabled: true, + }, + }; + + await database.getSnapshot(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + // Read rows while passing directedReadOptions directly to the query. + // These will override the options passed at Client level. + const [rows] = await transaction.run({ + sql: 'SELECT SingerId, AlbumId, AlbumTitle FROM Albums', + directedReadOptions: directedReadOptionsForRequest, + }); + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `SingerId: ${json.SingerId}, AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`, + ); + }); + console.log( + 'Successfully executed read-only transaction with directedReadOptions', + ); + } catch (err) { + console.error('ERROR:', err); + } finally { + transaction.end(); + // Close the database when finished. + await database.close(); + } + }); + } + spannerDirectedReads(); + // [END spanner_directed_read] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/dml-returning-delete.js b/handwritten/spanner/samples/dml-returning-delete.js new file mode 100644 index 00000000000..2c68ffe3140 --- /dev/null +++ b/handwritten/spanner/samples/dml-returning-delete.js @@ -0,0 +1,78 @@ +// Copyright 2022 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: Delete using DML returning. +// usage: node dml-returning-delete.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_delete_dml_returning] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + function deleteUsingDmlReturning(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: 'DELETE FROM Singers WHERE SingerId = 18 THEN RETURN FullName', + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log( + `Successfully deleted ${rowCount} record from the Singers table.`, + ); + rows.forEach(row => { + console.log(row.toJSON().FullName); + }); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + } + deleteUsingDmlReturning(instanceId, databaseId); + // [END spanner_delete_dml_returning] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/dml-returning-insert.js b/handwritten/spanner/samples/dml-returning-insert.js new file mode 100644 index 00000000000..48370d79fda --- /dev/null +++ b/handwritten/spanner/samples/dml-returning-insert.js @@ -0,0 +1,83 @@ +// Copyright 2022 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: Insert using DML returning. +// usage: node dml-returning-insert.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_insert_dml_returning] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + function insertUsingDmlReturning(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: 'INSERT Singers (SingerId, FirstName, LastName) VALUES (@id, @firstName, @lastName) THEN RETURN FullName', + params: { + id: 18, + firstName: 'Virginia', + lastName: 'Watson', + }, + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log( + `Successfully inserted ${rowCount} record into the Singers table.`, + ); + rows.forEach(row => { + console.log(row.toJSON().FullName); + }); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + } + insertUsingDmlReturning(instanceId, databaseId); + // [END spanner_insert_dml_returning] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/dml-returning-update.js b/handwritten/spanner/samples/dml-returning-update.js new file mode 100644 index 00000000000..790ff72e5df --- /dev/null +++ b/handwritten/spanner/samples/dml-returning-update.js @@ -0,0 +1,78 @@ +// Copyright 2022 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: Update using DML returning. +// usage: node dml-returning-update.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_update_dml_returning] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + function updateUsingDmlReturning(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: 'UPDATE Albums SET MarketingBudget = 2000000 WHERE SingerId = 1 and AlbumId = 1 THEN RETURN MarketingBudget', + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log( + `Successfully updated ${rowCount} record into the Albums table.`, + ); + rows.forEach(row => { + console.log(row.toJSON().MarketingBudget); + }); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + } + updateUsingDmlReturning(instanceId, databaseId); + // [END spanner_update_dml_returning] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/dml.js b/handwritten/spanner/samples/dml.js new file mode 100644 index 00000000000..2b7ded982a3 --- /dev/null +++ b/handwritten/spanner/samples/dml.js @@ -0,0 +1,859 @@ +// Copyright 2018 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. + +'use strict'; + +// sample-metadata: +// title: DML + +function insertUsingDml(instanceId, databaseId, projectId) { + // [START spanner_dml_standard_insert] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rowCount] = await transaction.runUpdate({ + sql: 'INSERT Singers (SingerId, FirstName, LastName) VALUES (10, @firstName, @lastName)', + params: { + firstName: 'Virginia', + lastName: 'Watson', + }, + }); + + console.log( + `Successfully inserted ${rowCount} record into the Singers table.`, + ); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + // [END spanner_dml_standard_insert] +} + +function updateUsingDml(instanceId, databaseId, projectId) { + // [START spanner_dml_standard_update] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rowCount] = await transaction.runUpdate({ + sql: `UPDATE Albums SET MarketingBudget = MarketingBudget * 2 + WHERE SingerId = 1 and AlbumId = 1`, + }); + + console.log(`Successfully updated ${rowCount} record.`); + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + // [END spanner_dml_standard_update] +} + +function deleteUsingDml(instanceId, databaseId, projectId) { + // [START spanner_dml_standard_delete] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rowCount] = await transaction.runUpdate({ + sql: "DELETE FROM Singers WHERE FirstName = 'Alice'", + }); + + console.log(`Successfully deleted ${rowCount} record.`); + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + // [END spanner_dml_standard_delete] +} + +function updateUsingDmlWithTimestamp(instanceId, databaseId, projectId) { + // [START spanner_dml_standard_update_with_timestamp] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rowCount] = await transaction.runUpdate({ + sql: `UPDATE Albums + SET LastUpdateTime = PENDING_COMMIT_TIMESTAMP() + WHERE SingerId = 1`, + }); + + console.log(`Successfully updated ${rowCount} records.`); + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + // [END spanner_dml_standard_update_with_timestamp] +} + +function writeAndReadUsingDml(instanceId, databaseId, projectId) { + // [START spanner_dml_write_then_read] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + await transaction.runUpdate({ + sql: `INSERT Singers (SingerId, FirstName, LastName) + VALUES (11, 'Timothy', 'Campbell')`, + }); + + const [rows] = await transaction.run({ + sql: 'SELECT FirstName, LastName FROM Singers', + }); + rows.forEach(row => { + const json = row.toJSON(); + console.log(`${json.FirstName} ${json.LastName}`); + }); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + // [END spanner_dml_write_then_read] +} + +function updateUsingDmlWithStruct(instanceId, databaseId, projectId) { + // [START spanner_dml_structs] + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + const nameStruct = Spanner.struct({ + FirstName: 'Timothy', + LastName: 'Campbell', + }); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rowCount] = await transaction.runUpdate({ + sql: `UPDATE Singers SET LastName = 'Grant' + WHERE STRUCT(FirstName, LastName) = @name`, + params: { + name: nameStruct, + }, + }); + + console.log(`Successfully updated ${rowCount} record.`); + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + // [END spanner_dml_structs] +} + +function writeUsingDml(instanceId, databaseId, projectId) { + // [START spanner_dml_getting_started_insert] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rowCount] = await transaction.runUpdate({ + sql: `INSERT Singers (SingerId, FirstName, LastName) VALUES + (12, 'Melissa', 'Garcia'), + (13, 'Russell', 'Morales'), + (14, 'Jacqueline', 'Long'), + (15, 'Dylan', 'Shaw')`, + }); + console.log(`${rowCount} records inserted.`); + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + + // [END spanner_dml_getting_started_insert] +} + +async function queryDataWithParameter(instanceId, databaseId, projectId) { + // [START spanner_query_with_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: `SELECT SingerId, FirstName, LastName + FROM Singers WHERE LastName = @lastName`, + params: { + lastName: 'Garcia', + }, + }; + + // Queries rows from the Albums table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `SingerId: ${json.SingerId}, FirstName: ${json.FirstName}, LastName: ${json.LastName}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_parameter] +} + +function writeWithTransactionUsingDml(instanceId, databaseId, projectId) { + // [START spanner_dml_getting_started_update] + // This sample transfers 200,000 from the MarketingBudget field + // of the second Album to the first Album, as long as the second + // Album has enough money in its budget. Make sure to run the + // addColumn and updateData samples first (in that order). + + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const transferAmount = 200000; + + database.runTransaction((err, transaction) => { + if (err) { + console.error(err); + return; + } + let firstBudget, secondBudget; + const queryOne = `SELECT MarketingBudget FROM Albums + WHERE SingerId = 2 AND AlbumId = 2`; + + const queryTwo = `SELECT MarketingBudget FROM Albums + WHERE SingerId = 1 AND AlbumId = 1`; + + Promise.all([ + // Reads the second album's budget + transaction.run(queryOne).then(results => { + // Gets second album's budget + const rows = results[0].map(row => row.toJSON()); + secondBudget = rows[0].MarketingBudget; + console.log(`The second album's marketing budget: ${secondBudget}`); + + // Makes sure the second album's budget is large enough + if (secondBudget < transferAmount) { + throw new Error( + `The second album's budget (${secondBudget}) is less than the transfer amount (${transferAmount}).`, + ); + } + }), + + // Reads the first album's budget + transaction.run(queryTwo).then(results => { + // Gets first album's budget + const rows = results[0].map(row => row.toJSON()); + firstBudget = rows[0].MarketingBudget; + console.log(`The first album's marketing budget: ${firstBudget}`); + }), + ]) + .then(() => { + // Transfers the budgets between the albums + console.log(firstBudget, secondBudget); + firstBudget += transferAmount; + secondBudget -= transferAmount; + + console.log(firstBudget, secondBudget); + + // Updates the database + // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they + // must be converted (back) to strings before being inserted as INT64s. + + return transaction + .runUpdate({ + sql: `UPDATE Albums SET MarketingBudget = @Budget + WHERE SingerId = 1 and AlbumId = 1`, + params: { + Budget: firstBudget, + }, + }) + .then(() => + transaction.runUpdate({ + sql: `UPDATE Albums SET MarketingBudget = @Budget + WHERE SingerId = 2 and AlbumId = 2`, + params: { + Budget: secondBudget, + }, + }), + ); + }) + .then(() => { + // Commits the transaction and send the changes to the database + return transaction.commit(); + }) + .then(() => { + console.log( + `Successfully executed read-write transaction using DML to transfer ${transferAmount} from Album 2 to Album 1.`, + ); + }) + .then(() => { + // Closes the database when finished + database.close(); + }); + }); + // [END spanner_dml_getting_started_update] +} + +async function updateUsingPartitionedDml(instanceId, databaseId, projectId) { + // [START spanner_dml_partitioned_update] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + try { + const [rowCount] = await database.runPartitionedUpdate({ + sql: 'UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1', + }); + console.log(`Successfully updated ${rowCount} records.`); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_dml_partitioned_update] +} + +async function deleteUsingPartitionedDml(instanceId, databaseId, projectId) { + // [START spanner_dml_partitioned_delete] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + try { + const [rowCount] = await database.runPartitionedUpdate({ + sql: 'DELETE FROM Singers WHERE SingerId > 10', + }); + console.log(`Successfully deleted ${rowCount} records.`); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_dml_partitioned_delete] +} + +async function updateUsingBatchDml(instanceId, databaseId, projectId) { + // [START spanner_dml_batch_update] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const insert = { + sql: `INSERT INTO Albums (SingerId, AlbumId, AlbumTitle, MarketingBudget) + VALUES (1, 3, "Test Album Title", 10000)`, + }; + + const update = { + sql: `UPDATE Albums SET MarketingBudget = MarketingBudget * 2 + WHERE SingerId = 1 and AlbumId = 3`, + }; + + const dmlStatements = [insert, update]; + + try { + await database.runTransactionAsync(async transaction => { + const [rowCounts] = await transaction.batchUpdate(dmlStatements); + await transaction.commit(); + console.log( + `Successfully executed ${rowCounts.length} SQL statements using Batch DML.`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + throw err; + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_dml_batch_update] +} + +async function insertWithCustomTimeoutAndRetrySettings( + instanceId, + databaseId, + projectId, +) { + // [START spanner_set_custom_timeout_and_retry] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + const UNAVAILABLE_STATUS_CODE = 14; + const retryAndTimeoutSettings = { + retry: { + // The set of error codes that will be retried. + retryCodes: [UNAVAILABLE_STATUS_CODE], + backoffSettings: { + // Configure retry delay settings. + // The initial amount of time to wait before retrying the request. + initialRetryDelayMillis: 500, + // The maximum amount of time to wait before retrying. I.e. after this + // value is reached, the wait time will not increase further by the + // multiplier. + maxRetryDelayMillis: 16000, + // The previous wait time is multiplied by this multiplier to come up + // with the next wait time, until the max is reached. + retryDelayMultiplier: 1.5, + + // Configure RPC and total timeout settings. + // Timeout for the first RPC call. Subsequent retries will be based off + // this value. + initialRpcTimeoutMillis: 60000, + // Controls the change of timeout for each retry. + rpcTimeoutMultiplier: 1.0, + // The max for the per RPC timeout. + maxRpcTimeoutMillis: 60000, + // The timeout for all calls (first call + all retries). + totalTimeoutMillis: 60000, + }, + }, + }; + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + const table = database.table('Singers'); + + const row = { + SingerId: 16, + FirstName: 'Martha', + LastName: 'Waller', + }; + + await table.insert(row, retryAndTimeoutSettings); + + console.log('record inserted.'); + // [END spanner_set_custom_timeout_and_retry] +} + +require('yargs') + .demand(1) + .command( + 'insertUsingDml ', + 'Inserts one record using DML into an example Cloud Spanner table.', + {}, + opts => + insertUsingDml(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'updateUsingDml ', + 'Updates one record using DML.', + {}, + opts => + updateUsingDml(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'deleteUsingDml ', + 'Deletes one record using DML.', + {}, + opts => + deleteUsingDml(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'updateUsingDmlWithTimestamp ', + 'Updates records with timestamp using DML.', + {}, + opts => + updateUsingDmlWithTimestamp( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'writeAndReadUsingDml ', + 'Inserts and reads one record using DML.', + {}, + opts => + writeAndReadUsingDml( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'updateUsingDmlWithStruct ', + 'Updates one record using DML and a struct value.', + {}, + opts => + updateUsingDmlWithStruct( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'writeUsingDml ', + 'Inserts multiple records using DML.', + {}, + opts => writeUsingDml(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithParameter ', + 'Query record inserted using DML with a query parameter.', + {}, + opts => + queryDataWithParameter( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'writeWithTransactionUsingDml ', + 'Execute a read-write transaction using DML.', + {}, + opts => + writeWithTransactionUsingDml( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'updateUsingPartitionedDml ', + 'Updates multiple records using DML.', + {}, + opts => + updateUsingPartitionedDml( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'deleteUsingPartitionedDml ', + 'Deletes multilple records using DML.', + {}, + opts => + deleteUsingPartitionedDml( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'updateUsingBatchDml ', + 'Insert and Update records using Batch DML.', + {}, + opts => + updateUsingBatchDml(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'insertWithCustomTimeoutAndRetrySettings ', + 'Insert records using custom timeout and retry settings.', + {}, + opts => + insertWithCustomTimeoutAndRetrySettings( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .example('node $0 insertUsingDml "my-instance" "my-database" "my-project-id"') + .example('node $0 updateUsingDml "my-instance" "my-database" "my-project-id"') + .example('node $0 deleteUsingDml "my-instance" "my-database" "my-project-id"') + .example( + 'node $0 updateUsingDmlWithTimestamp "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 writeAndReadUsingDml "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 updateUsingDmlWithStruct "my-instance" "my-database" "my-project-id"', + ) + .example('node $0 writeUsingDml "my-instance" "my-database" "my-project-id"') + .example( + 'node $0 queryWithParameter "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 writeWithTransactionUsingDml "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 updateUsingPartitionedDml "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 deleteUsingPartitionedDml "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 updateUsingBatchDml "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 insertWithCustomTimeoutAndRetrySettings "my-instance" "my-database" "my-project-id"', + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/enable-fine-grained-access.js b/handwritten/spanner/samples/enable-fine-grained-access.js new file mode 100644 index 00000000000..2176682683a --- /dev/null +++ b/handwritten/spanner/samples/enable-fine-grained-access.js @@ -0,0 +1,104 @@ +// Copyright 2024 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: Enable fine grained access control +// usage: node enable-fine-grained-access.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', + iamMember = 'user:alice@example.com', + databaseRole = 'parent', + title = 'condition title', +) { + // [START spanner_enable_fine_grained_access] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + // iamMember = 'user:alice@example.com'; + // databaseRole = 'parent'; + // title = 'condition title'; + // Imports the Google Cloud Spanner client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function enableFineGrainedAccess() { + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const [policy] = await databaseAdminClient.getIamPolicy({ + resource: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + options: (protos.google.iam.v1.GetPolicyOptions = { + requestedPolicyVersion: 3, + }), + }); + if (policy.version < 3) { + policy.version = 3; + } + + const newBinding = { + role: 'roles/spanner.fineGrainedAccessUser', + members: [`user:${iamMember}`], + condition: { + title: title, + expression: `resource.name.endsWith("/databaseRoles/${databaseRole}")`, + }, + }; + policy.bindings.push(newBinding); + await databaseAdminClient.setIamPolicy({ + resource: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + policy: policy, + }); + // Requested Policy Version is Optional. The maximum policy version that will be used to format the policy. + // Valid values are 0, 1, and 3. Requests specifying an invalid value will be rejected. + const newPolicy = await databaseAdminClient.getIamPolicy({ + resource: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + options: (protos.google.iam.v1.GetPolicyOptions = { + requestedPolicyVersion: 3, + }), + }); + console.log(newPolicy); + } + enableFineGrainedAccess(); + // [END spanner_enable_fine_grained_access] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/get-backup-schedule.js b/handwritten/spanner/samples/get-backup-schedule.js new file mode 100644 index 00000000000..3671021c674 --- /dev/null +++ b/handwritten/spanner/samples/get-backup-schedule.js @@ -0,0 +1,69 @@ +/** + * Copyright 2024 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: Gets a backup schedule +// usage: node get-backup-schedule.js + +'use strict'; + +function main( + projectId = 'my-project-id', + instanceId = 'my-instance-id', + databaseId = 'my-database-id', + scheduleId = 'my-schedule-id', +) { + async function getBackupSchedule() { + // [START spanner_get_backup_schedule] + // Import the Google Cloud client library for Spanner. + const {Spanner} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance-id'; + // const databaseId = 'my-database-id'; + // const scheduleId = 'my-schedule-id'; + + // Create a Spanner database admin client. + const spanner = new Spanner({projectId}); + const client = spanner.getDatabaseAdminClient(); + + try { + // Get the backup schedule. + const [response] = await client.getBackupSchedule({ + name: client.backupSchedulePath( + projectId, + instanceId, + databaseId, + scheduleId, + ), + }); + console.log('Backup schedule:', response); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_get_backup_schedule] + } + + getBackupSchedule(); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/get-commit-stats.js b/handwritten/spanner/samples/get-commit-stats.js new file mode 100644 index 00000000000..ada5916a45b --- /dev/null +++ b/handwritten/spanner/samples/get-commit-stats.js @@ -0,0 +1,62 @@ +// Copyright 2021 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. + +'use strict'; + +async function getCommitStats(instanceId, databaseId, projectId) { + // [START spanner_get_commit_stats] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client. + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Instantiate Spanner table objects. + const albumsTable = database.table('Albums'); + + // Updates rows in the Venues table. + try { + const [response] = await albumsTable.upsert( + [ + {SingerId: '1', AlbumId: '1', MarketingBudget: '200000'}, + {SingerId: '2', AlbumId: '2', MarketingBudget: '400000'}, + ], + {returnCommitStats: true}, + ); + console.log( + `Updated data with ${response.commitStats.mutationCount} mutations.`, + ); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_get_commit_stats] +} + +module.exports.getCommitStats = getCommitStats; diff --git a/handwritten/spanner/samples/get-database-roles.js b/handwritten/spanner/samples/get-database-roles.js new file mode 100644 index 00000000000..e1dd538741a --- /dev/null +++ b/handwritten/spanner/samples/get-database-roles.js @@ -0,0 +1,72 @@ +// Copyright 2024 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: List database roles +// usage: node get-database-roles.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_list_database_roles] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function getDatabaseRoles() { + // Fetching database roles + const [databaseRoles] = await databaseAdminClient.listDatabaseRoles({ + parent: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + }); + console.log( + `Roles for Database: ${databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + )}`, + ); + databaseRoles.forEach(role => { + console.log(`Role: ${role.name}`); + }); + } + getDatabaseRoles(); + // [END spanner_list_database_roles] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/get-instance-config.js b/handwritten/spanner/samples/get-instance-config.js new file mode 100644 index 00000000000..2fa37f55d5b --- /dev/null +++ b/handwritten/spanner/samples/get-instance-config.js @@ -0,0 +1,61 @@ +/** + * Copyright 2024 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: Gets the instance config metadata for the configuration nam6 +// usage: node get-instance-config.js + +'use strict'; + +function main(projectId) { + // [START spanner_get_instance_config] + + /** + * TODO(developer): Uncomment the following line before running the sample. + */ + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + async function getInstanceConfig() { + // Get the instance config for the multi-region North America 6 (NAM6). + // See https://cloud.google.com/spanner/docs/instance-configurations#configuration for a list of all available + // configurations. + const [instanceConfig] = await instanceAdminClient.getInstanceConfig({ + name: instanceAdminClient.instanceConfigPath(projectId, 'nam6'), + }); + console.log( + `Available leader options for instance config ${instanceConfig.name} ('${ + instanceConfig.displayName + }'): + ${instanceConfig.leaderOptions.join()}`, + ); + } + getInstanceConfig(); + // [END spanner_get_instance_config] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/index-create-storing.js b/handwritten/spanner/samples/index-create-storing.js new file mode 100644 index 00000000000..ecb1a22c9e7 --- /dev/null +++ b/handwritten/spanner/samples/index-create-storing.js @@ -0,0 +1,79 @@ +// Copyright 2024 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 new value-storing index +// usage: node createStoringIndex + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_create_storing_index] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function createStoringIndex() { + const request = [ + 'CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)', + ]; + + // Creates a new index in the database + try { + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Added the AlbumsByAlbumTitle2 index.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + } + createStoringIndex(); + // [END spanner_create_storing_index] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/index-create.js b/handwritten/spanner/samples/index-create.js new file mode 100644 index 00000000000..c298a11ca4e --- /dev/null +++ b/handwritten/spanner/samples/index-create.js @@ -0,0 +1,77 @@ +// Copyright 2024 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 new index +// usage: node createIndex + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_create_index] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function createIndex() { + const request = ['CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)']; + + // Creates a new index in the database + try { + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Added the AlbumsByAlbumTitle index.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + } + createIndex(); + // [END spanner_create_index] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/index-query-data.js b/handwritten/spanner/samples/index-query-data.js new file mode 100644 index 00000000000..9fb51677349 --- /dev/null +++ b/handwritten/spanner/samples/index-query-data.js @@ -0,0 +1,88 @@ +// Copyright 2021 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: Executes a read-only SQL query using an existing index. +// usage: node queryDataWithIndex + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', + startTitle = 'Ardvark', + endTitle = 'Goo', +) { + // [START spanner_query_data_with_index] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + // const startTitle = 'Ardvark'; + // const endTitle = 'Goo'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function queryDataWithIndex() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: `SELECT AlbumId, AlbumTitle, MarketingBudget + FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle} + WHERE AlbumTitle >= @startTitle AND AlbumTitle <= @endTitle`, + params: { + startTitle: startTitle, + endTitle: endTitle, + }, + }; + + // Queries rows from the Albums table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + const marketingBudget = json.MarketingBudget + ? json.MarketingBudget + : null; // This value is nullable + console.log( + `AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}, MarketingBudget: ${marketingBudget}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + queryDataWithIndex(); + // [END spanner_query_data_with_index] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/index-read-data-with-storing.js b/handwritten/spanner/samples/index-read-data-with-storing.js new file mode 100644 index 00000000000..3d1f6d0f63e --- /dev/null +++ b/handwritten/spanner/samples/index-read-data-with-storing.js @@ -0,0 +1,88 @@ +// Copyright 2021 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: Reads data using an existing storing index. +// usage: node readDataWithStoringIndex + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_read_data_with_storing_index] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // "Storing" indexes store copies of the columns they index + // This speeds up queries, but takes more space compared to normal indexes + // See the link below for more information: + // https://cloud.google.com/spanner/docs/secondary-indexes#storing_clause + async function readDataWithStoringIndex() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const albumsTable = database.table('Albums'); + + const query = { + columns: ['AlbumId', 'AlbumTitle', 'MarketingBudget'], + keySet: { + all: true, + }, + index: 'AlbumsByAlbumTitle2', + }; + + // Reads the Albums table using a storing index + try { + const [rows] = await albumsTable.read(query); + + rows.forEach(row => { + const json = row.toJSON(); + let rowString = `AlbumId: ${json.AlbumId}`; + rowString += `, AlbumTitle: ${json.AlbumTitle}`; + if (json.MarketingBudget) { + rowString += `, MarketingBudget: ${json.MarketingBudget}`; + } + console.log(rowString); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + readDataWithStoringIndex(); + // [END spanner_read_data_with_storing_index] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/index-read-data.js b/handwritten/spanner/samples/index-read-data.js new file mode 100644 index 00000000000..e81ac98dbe5 --- /dev/null +++ b/handwritten/spanner/samples/index-read-data.js @@ -0,0 +1,79 @@ +// Copyright 2021 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: Read data using an existing index. +// usage: node readDataWithIndex + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_read_data_with_index] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function readDataWithIndex() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const albumsTable = database.table('Albums'); + + const query = { + columns: ['AlbumId', 'AlbumTitle'], + keySet: { + all: true, + }, + index: 'AlbumsByAlbumTitle', + }; + + // Reads the Albums table using an index + try { + const [rows] = await albumsTable.read(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log(`AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + readDataWithIndex(); + // [END spanner_read_data_with_index] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/indexing.js b/handwritten/spanner/samples/indexing.js new file mode 100644 index 00000000000..111c2cb0a64 --- /dev/null +++ b/handwritten/spanner/samples/indexing.js @@ -0,0 +1,354 @@ +// Copyright 2024 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. + +'use strict'; + +async function createIndex(instanceId, databaseId, projectId) { + // [START spanner_create_index] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const request = ['CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)']; + + // Creates a new index in the database + try { + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Added the AlbumsByAlbumTitle index.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + // [END spanner_create_index] +} + +async function createStoringIndex(instanceId, databaseId, projectId) { + // [START spanner_create_storing_index] + // "Storing" indexes store copies of the columns they index + // This speeds up queries, but takes more space compared to normal indexes + // See the link below for more information: + // https://cloud.google.com/spanner/docs/secondary-indexes#storing_clause + + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const request = [ + 'CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)', + ]; + + // Creates a new index in the database + try { + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Added the AlbumsByAlbumTitle2 index.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + // [END spanner_create_storing_index] +} + +async function queryDataWithIndex( + instanceId, + databaseId, + startTitle, + endTitle, + projectId, +) { + // [START spanner_query_data_with_index] + // 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'; + // const databaseId = 'my-database'; + // const startTitle = 'Ardvark'; + // const endTitle = 'Goo'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: `SELECT AlbumId, AlbumTitle, MarketingBudget + FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle} + WHERE AlbumTitle >= @startTitle AND AlbumTitle <= @endTitle`, + params: { + startTitle: startTitle, + endTitle: endTitle, + }, + }; + + // Queries rows from the Albums table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + const marketingBudget = json.MarketingBudget + ? json.MarketingBudget + : null; // This value is nullable + console.log( + `AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}, MarketingBudget: ${marketingBudget}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_data_with_index] +} + +async function readDataWithIndex(instanceId, databaseId, projectId) { + // [START spanner_read_data_with_index] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const albumsTable = database.table('Albums'); + + const query = { + columns: ['AlbumId', 'AlbumTitle'], + keySet: { + all: true, + }, + index: 'AlbumsByAlbumTitle', + }; + + // Reads the Albums table using an index + try { + const [rows] = await albumsTable.read(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log(`AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_read_data_with_index] +} + +async function readDataWithStoringIndex(instanceId, databaseId, projectId) { + // [START spanner_read_data_with_storing_index] + // "Storing" indexes store copies of the columns they index + // This speeds up queries, but takes more space compared to normal indexes + // See the link below for more information: + // https://cloud.google.com/spanner/docs/secondary-indexes#storing_clause + + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const albumsTable = database.table('Albums'); + + const query = { + columns: ['AlbumId', 'AlbumTitle', 'MarketingBudget'], + keySet: { + all: true, + }, + index: 'AlbumsByAlbumTitle2', + }; + + // Reads the Albums table using a storing index + try { + const [rows] = await albumsTable.read(query); + + rows.forEach(row => { + const json = row.toJSON(); + let rowString = `AlbumId: ${json.AlbumId}`; + rowString += `, AlbumTitle: ${json.AlbumTitle}`; + if (json.MarketingBudget) { + rowString += `, MarketingBudget: ${json.MarketingBudget}`; + } + console.log(rowString); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_read_data_with_storing_index] +} + +require('yargs') + .demand(1) + .command( + 'createIndex ', + 'Creates a new index in an example Cloud Spanner table.', + {}, + opts => createIndex(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'createStoringIndex ', + 'Creates a new value-storing index in an example Cloud Spanner table.', + {}, + opts => + createStoringIndex(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryIndex ', + `Executes a read-only SQL query against an example Cloud Spanner table using an existing index. + Returns results with titles between a start title (default: 'Ardvark') and an end title (default: 'Goo').`, + { + startTitle: { + type: 'string', + alias: 's', + default: 'Ardvark', + }, + endTitle: { + type: 'string', + alias: 'e', + default: 'Goo', + }, + }, + opts => + queryDataWithIndex( + opts.instanceName, + opts.databaseName, + opts.startTitle, + opts.endTitle, + opts.projectId, + ), + ) + .command( + 'readIndex ', + 'Reads data from an example Cloud Spanner table using an existing index.', + {}, + opts => + readDataWithIndex(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'readStoringIndex ', + 'Reads data from an example Cloud Spanner table using an existing storing index.', + {}, + opts => + readDataWithStoringIndex( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .example('node $0 createIndex "my-instance" "my-database" "my-project-id"') + .example( + 'node $0 createStoringIndex "my-instance" "my-database" "my-project-id"', + ) + .example('node $0 queryIndex "my-instance" "my-database" "my-project-id"') + .example('node $0 readIndex "my-instance" "my-database" "my-project-id"') + .example( + 'node $0 readStoringIndex "my-instance" "my-database" "my-project-id"', + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/instance-config-create.js b/handwritten/spanner/samples/instance-config-create.js new file mode 100644 index 00000000000..139f7e406e5 --- /dev/null +++ b/handwritten/spanner/samples/instance-config-create.js @@ -0,0 +1,101 @@ +/** + * Copyright 2024 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 user-managed instance configuration. +// usage: node instance-config-create + +'use strict'; + +function main( + instanceConfigId = 'custom-my-instance-config', + baseInstanceConfigId = 'my-base-instance-config', + projectId = 'my-project-id', +) { + // [START spanner_create_instance_config] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const instanceConfigId = 'custom-my-instance-config-id' + // const baseInstanceConfigId = 'my-base-instance-config-id'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + // Creates a new instance config + async function createInstanceConfig() { + const [baseInstanceConfig] = await instanceAdminClient.getInstanceConfig({ + name: instanceAdminClient.instanceConfigPath( + projectId, + baseInstanceConfigId, + ), + }); + try { + console.log( + `Creating instance config ${instanceAdminClient.instanceConfigPath( + projectId, + instanceConfigId, + )}.`, + ); + const [operation] = await instanceAdminClient.createInstanceConfig({ + instanceConfigId: instanceConfigId, + parent: instanceAdminClient.projectPath(projectId), + instanceConfig: { + name: instanceAdminClient.instanceConfigPath( + projectId, + instanceConfigId, + ), + baseConfig: instanceAdminClient.instanceConfigPath( + projectId, + baseInstanceConfigId, + ), + displayName: instanceConfigId, + replicas: baseInstanceConfig.replicas.concat( + baseInstanceConfig.optionalReplicas[0], + ), + }, + }); + console.log( + `Waiting for create operation for ${instanceConfigId} to complete...`, + ); + await operation.promise(); + console.log(`Created instance config ${instanceConfigId}.`); + } catch (err) { + console.error( + 'ERROR: Creating instance config ', + instanceConfigId, + ' failed with error message ', + err, + ); + } + } + createInstanceConfig(); + // [END spanner_create_instance_config] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/instance-config-delete.js b/handwritten/spanner/samples/instance-config-delete.js new file mode 100644 index 00000000000..6a879450a92 --- /dev/null +++ b/handwritten/spanner/samples/instance-config-delete.js @@ -0,0 +1,73 @@ +/** + * Copyright 2024 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: Deletes a user-managed instance configuration. +// usage: node instance-config-delete + +'use strict'; + +function main( + instanceConfigId = 'custom-my-instance-config', + projectId = 'my-project-id', +) { + // [START spanner_delete_instance_config] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const instanceConfigId = 'custom-my-instance-config-id'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + async function deleteInstanceConfig() { + // Deletes an instance config. + + try { + // Delete the instance config. + console.log(`Deleting ${instanceConfigId}...\n`); + await instanceAdminClient.deleteInstanceConfig({ + name: instanceAdminClient.instanceConfigPath( + projectId, + instanceConfigId, + ), + }); + console.log(`Deleted instance config ${instanceConfigId}.\n`); + } catch (err) { + console.error( + 'ERROR: Deleting instance config ', + instanceConfigId, + ' failed with error message ', + err, + ); + } + } + deleteInstanceConfig(); + // [END spanner_delete_instance_config] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/instance-config-get-operations.js b/handwritten/spanner/samples/instance-config-get-operations.js new file mode 100644 index 00000000000..9d8d98eba41 --- /dev/null +++ b/handwritten/spanner/samples/instance-config-get-operations.js @@ -0,0 +1,81 @@ +/** + * Copyright 2024 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: Lists the instance configuration operations. +// usage: node instance-config-get-operations + +'use strict'; + +function main(projectId = 'my-project-id') { + // [START spanner_list_instance_config_operations] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + async function getInstanceConfigOperations() { + // Lists the instance config operations. + try { + console.log( + `Getting list of instance config operations on project ${projectId}...\n`, + ); + const [instanceConfigOperations] = + await instanceAdminClient.listInstanceConfigOperations({ + parent: instanceAdminClient.projectPath(projectId), + // This filter ensures that only operations with metadata type CreateInstanceConfigMetadata + filter: + '(metadata.@type=type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceConfigMetadata)', + }); + console.log( + `Available instance config operations for project ${projectId}:`, + ); + instanceConfigOperations.forEach(instanceConfigOperation => { + const metadata = instanceConfigOperation.metadata; + const instanceConfig = + protos.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata.decode( + instanceConfigOperation.metadata.value, + ).instanceConfig; + console.log( + `Instance config operation for ${instanceConfig.name} of type` + + ` ${metadata.type_url} has status ${ + instanceConfigOperation.done ? 'done' : 'running' + }.`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } + } + getInstanceConfigOperations(); + // [END spanner_list_instance_config_operations] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/instance-config-update.js b/handwritten/spanner/samples/instance-config-update.js new file mode 100644 index 00000000000..7bacc7e0b25 --- /dev/null +++ b/handwritten/spanner/samples/instance-config-update.js @@ -0,0 +1,93 @@ +/** + * Copyright 2024 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: Updates a user-managed instance configuration. +// usage: node instance-config-update + +'use strict'; + +function main( + instanceConfigId = 'custom-my-instance-config', + projectId = 'my-project-id', +) { + // [START spanner_update_instance_config] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const instanceConfigId = 'custom-my-instance-config-id'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + async function updateInstanceConfig() { + // Updates an instance config + try { + console.log( + `Updating instance config ${instanceAdminClient.instanceConfigPath( + projectId, + instanceConfigId, + )}.`, + ); + const [operation] = await instanceAdminClient.updateInstanceConfig({ + instanceConfig: { + name: instanceAdminClient.instanceConfigPath( + projectId, + instanceConfigId, + ), + displayName: 'updated custom instance config', + labels: { + updated: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + }, + // Field mask specifying fields that should get updated in InstanceConfig + // Only display_name and labels can be updated + updateMask: (protos.google.protobuf.FieldMask = { + paths: ['display_name', 'labels'], + }), + }); + console.log( + `Waiting for update operation for ${instanceConfigId} to complete...`, + ); + await operation.promise(); + console.log(`Updated instance config ${instanceConfigId}.`); + } catch (err) { + console.error( + 'ERROR: Updating instance config ', + instanceConfigId, + ' failed with error message ', + err, + ); + } + } + updateInstanceConfig(); + // [END spanner_update_instance_config] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/instance-partition-create.js b/handwritten/spanner/samples/instance-partition-create.js new file mode 100644 index 00000000000..3af48fb5ae8 --- /dev/null +++ b/handwritten/spanner/samples/instance-partition-create.js @@ -0,0 +1,84 @@ +/** + * Copyright 2024 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 new instance partition +// usage: node instance-partition-create.js + +'use strict'; + +function main( + instanceId = 'my-instance', + instancePartitionId = 'my-instance-partition', + projectId = 'my-project-id', +) { + async function createInstancePartition() { + // [START spanner_create_instance_partition] + // 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'; + // const instancePartitionId = 'my-instance-partition'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Get the instance admin client + const instanceAdminClient = spanner.getInstanceAdminClient(); + + // Creates a new instance partition + try { + console.log( + `Creating instance partition ${instanceAdminClient.instancePartitionPath( + projectId, + instanceId, + instancePartitionId, + )}.`, + ); + const [operation] = await instanceAdminClient.createInstancePartition({ + instancePartitionId: instancePartitionId, + parent: instanceAdminClient.instancePath(projectId, instanceId), + instancePartition: { + config: instanceAdminClient.instanceConfigPath(projectId, 'nam3'), + nodeCount: 1, + displayName: 'Test instance partition', + }, + }); + + console.log( + `Waiting for operation on ${instancePartitionId} to complete...`, + ); + await operation.promise(); + + console.log(`Created instance partition ${instancePartitionId}.`); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_instance_partition] + } + createInstancePartition(); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/instance-update.js b/handwritten/spanner/samples/instance-update.js new file mode 100644 index 00000000000..3dea1fae43d --- /dev/null +++ b/handwritten/spanner/samples/instance-update.js @@ -0,0 +1,88 @@ +/** + * Copyright 2024 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: Updates an instance. +// usage: node instance-update.js + +'use strict'; + +function main(instanceId, projectId) { + async function updateInstance() { + // [START spanner_update_instance] + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + // Updates an instance + try { + console.log( + `Updating instance ${instanceAdminClient.instancePath( + projectId, + instanceId, + )}.`, + ); + const [operation] = await instanceAdminClient.updateInstance({ + instance: { + name: instanceAdminClient.instancePath(projectId, instanceId), + labels: { + updated: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + edition: + protos.google.spanner.admin.instance.v1.Instance.Edition.ENTERPRISE, //optional + }, + // Field mask specifying fields that should get updated in an Instance + fieldMask: (protos.google.protobuf.FieldMask = { + paths: ['labels', 'edition'], + }), + }); + + console.log(`Waiting for operation on ${instanceId} to complete...`); + await operation.promise(); + console.log(`Updated instance ${instanceId}.`); + const [metadata] = await instanceAdminClient.getInstance({ + name: instanceAdminClient.instancePath(projectId, instanceId), + }); + console.log( + `Instance ${instanceId} has been updated with the ${metadata.edition} ` + + 'edition.', + ); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_update_instance] + } + updateInstance(); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/instance-with-asymmetric-autoscaling-config.js b/handwritten/spanner/samples/instance-with-asymmetric-autoscaling-config.js new file mode 100644 index 00000000000..b51e41867e3 --- /dev/null +++ b/handwritten/spanner/samples/instance-with-asymmetric-autoscaling-config.js @@ -0,0 +1,174 @@ +/** + * Copyright 2025 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 instance with asymmetric autoscaling config. +// usage: node instance-with-asymmetric-autoscaling-config.js + +'use strict'; + +function main(instanceId = 'my-instance', projectId = 'my-project-id') { + async function createInstanceWithAsymmetricAutoscalingConfig() { + // [START spanner_create_instance_with_asymmetric_autoscaling_config] + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Get the instance admin client + const instanceAdminClient = spanner.getInstanceAdminClient(); + + const autoscalingConfig = + protos.google.spanner.admin.instance.v1.AutoscalingConfig.create({ + // Only one of minNodes/maxNodes or minProcessingUnits/maxProcessingUnits can be set. + autoscalingLimits: + protos.google.spanner.admin.instance.v1.AutoscalingConfig.AutoscalingLimits.create( + { + minNodes: 1, + maxNodes: 2, + }, + ), + // highPriorityCpuUtilizationPercent and storageUtilizationPercent are both + // percentages and must lie between 0 and 100. + autoscalingTargets: + protos.google.spanner.admin.instance.v1.AutoscalingConfig.AutoscalingTargets.create( + { + highPriorityCpuUtilizationPercent: 65, + storageUtilizationPercent: 95, + }, + ), + // The read-only replicas listed in the asymmetric autoscaling options scale independently + // from other replicas. + asymmetricAutoscalingOptions: [ + protos.google.spanner.admin.instance.v1.AutoscalingConfig.AsymmetricAutoscalingOption.create( + { + replicaSelection: + protos.google.spanner.admin.instance.v1.ReplicaSelection.create( + { + location: 'europe-west1', + }, + ), + }, + ), + protos.google.spanner.admin.instance.v1.AutoscalingConfig.AsymmetricAutoscalingOption.create( + { + replicaSelection: + protos.google.spanner.admin.instance.v1.ReplicaSelection.create( + { + location: 'europe-west4', + }, + ), + }, + ), + protos.google.spanner.admin.instance.v1.AutoscalingConfig.AsymmetricAutoscalingOption.create( + { + replicaSelection: + protos.google.spanner.admin.instance.v1.ReplicaSelection.create( + { + location: 'asia-east1', + }, + ), + }, + ), + ], + }); + + // Creates a new instance with autoscaling configuration and asymmetric autoscaling option + // When autoscalingConfig is enabled, nodeCount and processingUnits fields + // need not be specified. + try { + console.log( + `Creating instance ${instanceAdminClient.instancePath( + projectId, + instanceId, + )}.`, + ); + const [operation] = await instanceAdminClient.createInstance({ + instanceId: instanceId, + parent: instanceAdminClient.projectPath(projectId), + instance: { + config: instanceAdminClient.instanceConfigPath( + projectId, + 'nam-eur-asia3', + ), + displayName: 'Display name for the instance.', + autoscalingConfig: autoscalingConfig, + labels: { + cloud_spanner_samples: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + // Feature MULTI_REGION is available only for ENTERPRISE_PLUS edition + edition: + protos.google.spanner.admin.instance.v1.Instance.Edition + .ENTERPRISE_PLUS, + }, + }); + + console.log(`Waiting for operation on ${instanceId} to complete...`); + await operation.promise(); + console.log(`Created instance ${instanceId}.`); + + // get instance metadata + const [metadata] = await instanceAdminClient.getInstance({ + name: instanceAdminClient.instancePath(projectId, instanceId), + }); + console.log( + `Autoscaling configurations of ${instanceId} are: ` + + '\n' + + `Min nodes: ${metadata.autoscalingConfig.autoscalingLimits.minNodes} ` + + 'nodes.' + + '\n' + + `Max nodes: ${metadata.autoscalingConfig.autoscalingLimits.maxNodes}` + + ' nodes.' + + '\n' + + `High priority cpu utilization percent: ${metadata.autoscalingConfig.autoscalingTargets.highPriorityCpuUtilizationPercent}.` + + '\n' + + `Storage utilization percent: ${metadata.autoscalingConfig.autoscalingTargets.storageUtilizationPercent}.` + + '\n' + + `Asymmetric Autoscaling Options: ${ + metadata.autoscalingConfig.asymmetricAutoscalingOptions && + metadata.autoscalingConfig.asymmetricAutoscalingOptions.length > 0 + ? metadata.autoscalingConfig.asymmetricAutoscalingOptions + .map(option => + option.replicaSelection && option.replicaSelection.location + ? option.replicaSelection.location + : 'N/A', + ) + .join(', ') + : 'None' + }`, + ); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_instance_with_asymmetric_autoscaling_config] + } + createInstanceWithAsymmetricAutoscalingConfig(); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/instance-with-autoscaling-config.js b/handwritten/spanner/samples/instance-with-autoscaling-config.js new file mode 100644 index 00000000000..3eb332d21fa --- /dev/null +++ b/handwritten/spanner/samples/instance-with-autoscaling-config.js @@ -0,0 +1,126 @@ +/** + * Copyright 2024 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 instance with autoscaling config. +// usage: node instance-with-autoscaling-config.js + +'use strict'; + +function main(instanceId = 'my-instance', projectId = 'my-project-id') { + async function createInstanceWithAutoscalingConfig() { + // [START spanner_create_instance_with_autoscaling_config] + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Get the instance admin client + const instanceAdminClient = spanner.getInstanceAdminClient(); + + const autoscalingConfig = + protos.google.spanner.admin.instance.v1.AutoscalingConfig.create({ + // Only one of minNodes/maxNodes or minProcessingUnits/maxProcessingUnits can be set. + autoscalingLimits: + protos.google.spanner.admin.instance.v1.AutoscalingConfig.AutoscalingLimits.create( + { + minNodes: 1, + maxNodes: 2, + }, + ), + // highPriorityCpuUtilizationPercent and storageUtilizationPercent are both + // percentages and must lie between 0 and 100. + autoscalingTargets: + protos.google.spanner.admin.instance.v1.AutoscalingConfig.AutoscalingTargets.create( + { + highPriorityCpuUtilizationPercent: 65, + storageUtilizationPercent: 95, + }, + ), + }); + + // Creates a new instance with autoscaling configuration + // When autoscalingConfig is enabled, nodeCount and processingUnits fields + // need not be specified. + try { + console.log( + `Creating instance ${instanceAdminClient.instancePath( + projectId, + instanceId, + )}.`, + ); + const [operation] = await instanceAdminClient.createInstance({ + instanceId: instanceId, + parent: instanceAdminClient.projectPath(projectId), + instance: { + config: instanceAdminClient.instanceConfigPath( + projectId, + 'regional-us-central1', + ), + displayName: 'Display name for the instance.', + autoscalingConfig: autoscalingConfig, + labels: { + cloud_spanner_samples: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + // Managed autoscaler is available only for ENTERPRISE edition + edition: + protos.google.spanner.admin.instance.v1.Instance.Edition.ENTERPRISE, + }, + }); + + console.log(`Waiting for operation on ${instanceId} to complete...`); + await operation.promise(); + console.log(`Created instance ${instanceId}.`); + + // get instance metadata + const [metadata] = await instanceAdminClient.getInstance({ + name: instanceAdminClient.instancePath(projectId, instanceId), + }); + console.log( + `Autoscaling configurations of ${instanceId} are: ` + + '\n' + + `Min nodes: ${metadata.autoscalingConfig.autoscalingLimits.minNodes} ` + + 'nodes.' + + '\n' + + `Max nodes: ${metadata.autoscalingConfig.autoscalingLimits.maxNodes}` + + ' nodes.' + + '\n' + + `High priority cpu utilization percent: ${metadata.autoscalingConfig.autoscalingTargets.highPriorityCpuUtilizationPercent}.` + + '\n' + + `Storage utilization percent: ${metadata.autoscalingConfig.autoscalingTargets.storageUtilizationPercent}.`, + ); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_instance_with_autoscaling_config] + } + createInstanceWithAutoscalingConfig(); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/instance-with-processing-units.js b/handwritten/spanner/samples/instance-with-processing-units.js new file mode 100644 index 00000000000..d4a33416d0a --- /dev/null +++ b/handwritten/spanner/samples/instance-with-processing-units.js @@ -0,0 +1,79 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +async function createInstanceWithProcessingUnits(instanceId, projectId) { + // [START spanner_create_instance_with_processing_units] + + // 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'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + // Creates a new instance + try { + console.log( + `Creating instance ${instanceAdminClient.instancePath( + projectId, + instanceId, + )}.`, + ); + const [operation] = await instanceAdminClient.createInstance({ + instanceId: instanceId, + instance: { + config: instanceAdminClient.instanceConfigPath( + projectId, + 'regional-us-central1', + ), + displayName: 'Display name for the instance.', + processingUnits: 500, + labels: { + cloud_spanner_samples: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + }, + parent: instanceAdminClient.projectPath(projectId), + }); + + console.log(`Waiting for operation on ${instanceId} to complete...`); + await operation.promise(); + console.log(`Created instance ${instanceId}.`); + const [metadata] = await instanceAdminClient.getInstance({ + name: instanceAdminClient.instancePath(projectId, instanceId), + }); + console.log( + `Instance ${instanceId} has ${metadata.processingUnits} ` + + 'processing units.', + ); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_instance_with_processing_units] +} + +module.exports.createInstanceWithProcessingUnits = + createInstanceWithProcessingUnits; diff --git a/handwritten/spanner/samples/instance.js b/handwritten/spanner/samples/instance.js new file mode 100644 index 00000000000..e43ee3ea69d --- /dev/null +++ b/handwritten/spanner/samples/instance.js @@ -0,0 +1,101 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +// creates an instance using Instance Admin Client +async function createInstance(instanceId, projectId) { + // [START spanner_create_instance] + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = await spanner.getInstanceAdminClient(); + /** + * TODO(developer): Uncomment the following lines before running the sample. + **/ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + + // Creates a new instance + try { + console.log( + `Creating instance ${instanceAdminClient.instancePath( + projectId, + instanceId, + )}.`, + ); + const [operation] = await instanceAdminClient.createInstance({ + instanceId: instanceId, + parent: instanceAdminClient.projectPath(projectId), + instance: { + config: instanceAdminClient.instanceConfigPath( + projectId, + 'regional-us-central1', + ), + nodeCount: 1, + displayName: 'Display name for the instance.', + labels: { + cloud_spanner_samples: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + edition: + protos.google.spanner.admin.instance.v1.Instance.Edition.STANDARD, //optional + }, + }); + + console.log(`Waiting for operation on ${instanceId} to complete...`); + await operation.promise(); + + console.log(`Created instance ${instanceId}.`); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_instance] +} + +const { + createInstanceWithProcessingUnits, +} = require('./instance-with-processing-units'); + +require('yargs') + .demand(1) + .command( + 'createInstance ', + 'Creates an example instance in a Cloud Spanner instance using Instance Admin Client.', + {}, + opts => createInstance(opts.instanceName, opts.projectId), + ) + .example('node $0 createInstance "my-instance" "my-project-id"') + .command( + 'createInstanceWithProcessingUnits ', + 'Creates an example instance in a Cloud Spanner instance with processing units.', + {}, + opts => + createInstanceWithProcessingUnits(opts.instanceName, opts.projectId), + ) + .example( + 'node $0 createInstanceWithProcessingUnits "my-instance" "my-project-id"', + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/json-add-column.js b/handwritten/spanner/samples/json-add-column.js new file mode 100644 index 00000000000..5013fb82eec --- /dev/null +++ b/handwritten/spanner/samples/json-add-column.js @@ -0,0 +1,59 @@ +// Copyright 2024 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. + +'use strict'; + +async function addJsonColumn(instanceId, databaseId, projectId) { + // [START spanner_add_json_column] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const request = ['ALTER TABLE Venues ADD COLUMN VenueDetails JSON']; + + // Alter existing table to add a column. + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + + await operation.promise(); + + console.log( + `Added VenueDetails column to Venues table in database ${databaseId}.`, + ); + // [END spanner_add_json_column] +} + +module.exports.addJsonColumn = addJsonColumn; diff --git a/handwritten/spanner/samples/json-query-parameter.js b/handwritten/spanner/samples/json-query-parameter.js new file mode 100644 index 00000000000..c68a46d0b80 --- /dev/null +++ b/handwritten/spanner/samples/json-query-parameter.js @@ -0,0 +1,76 @@ +// Copyright 2021 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. + +'use strict'; + +async function queryWithJsonParameter(instanceId, databaseId, projectId) { + // [START spanner_query_with_json_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'json', + }; + + const jsonValue = {rating: 9}; + + const query = { + sql: `SELECT VenueId, VenueDetails FROM Venues + WHERE JSON_VALUE(VenueDetails, '$.rating') = JSON_VALUE(@details, '$.rating')`, + params: { + details: jsonValue, + }, + types: { + details: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `VenueId: ${json.VenueId}, Details: ${JSON.stringify( + json.VenueDetails, + )}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_json_parameter] +} + +module.exports.queryWithJsonParameter = queryWithJsonParameter; diff --git a/handwritten/spanner/samples/json-update-data.js b/handwritten/spanner/samples/json-update-data.js new file mode 100644 index 00000000000..b87fdd54028 --- /dev/null +++ b/handwritten/spanner/samples/json-update-data.js @@ -0,0 +1,97 @@ +// Copyright 2021 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. + +'use strict'; + +async function updateWithJsonData(instanceId, databaseId, projectId) { + // [START spanner_update_data_with_json_column] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client. + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Instantiate Spanner table objects. + const venuesTable = database.table('Venues'); + + const data = [ + { + VenueId: '19', + VenueDetails: {rating: 9, open: true}, + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + VenueId: '4', + // VenueDetails must be specified as a string, as it contains a top-level + // array of objects that should be inserted into a JSON column. If we were + // to specify this value as an array instead of a string, the client + // library would encode this value as ARRAY instead of JSON. + VenueDetails: `[ + { + "name": null, + "open": true + }, + { + "name": "room 2", + "open": false + }, + { + "main hall": { + "description": "this is the biggest space", + "size": 200 + } + } + ]`, + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + VenueId: '42', + VenueDetails: { + name: null, + open: { + Monday: true, + Tuesday: false, + }, + tags: ['large', 'airy'], + }, + LastUpdateTime: 'spanner.commit_timestamp()', + }, + ]; + // Updates rows in the Venues table. + try { + await venuesTable.update(data); + console.log('Updated data.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_update_data_with_json_column] +} + +module.exports.updateWithJsonData = updateWithJsonData; diff --git a/handwritten/spanner/samples/list-backup-schedules.js b/handwritten/spanner/samples/list-backup-schedules.js new file mode 100644 index 00000000000..325781f5af9 --- /dev/null +++ b/handwritten/spanner/samples/list-backup-schedules.js @@ -0,0 +1,64 @@ +/** + * Copyright 2024 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: Lists backup schedules of a database +// usage: node list-backup-schedules.js + +'use strict'; + +function main( + projectId = 'my-project-id', + instanceId = 'my-instance-id', + databaseId = 'my-database-id', +) { + async function listBackupSchedules() { + // [START spanner_list_backup_schedules] + // Import the Google Cloud client library for Spanner. + const {Spanner} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance-id'; + // const databaseId = 'my-database-id'; + + // Create a Spanner database admin client. + const spanner = new Spanner({projectId}); + const client = spanner.getDatabaseAdminClient(); + + try { + // List backup schedules of a database. + const [schedules] = await client.listBackupSchedules({ + parent: client.databasePath(projectId, instanceId, databaseId), + }); + schedules.forEach(schedule => { + console.log('Backup schedule:', schedule); + }); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_list_backup_schedules] + } + + listBackupSchedules(); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/list-databases.js b/handwritten/spanner/samples/list-databases.js new file mode 100644 index 00000000000..e24dbae6fa7 --- /dev/null +++ b/handwritten/spanner/samples/list-databases.js @@ -0,0 +1,60 @@ +/** + * Copyright 2024 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: Lists all databases on the selected instance +// usage: node list-databases.js + +'use strict'; + +function main(instanceId, projectId) { + // [START spanner_list_databases] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function listDatabases() { + // Lists all databases on the instance. + const [databases] = await databaseAdminClient.listDatabases({ + parent: databaseAdminClient.instancePath(projectId, instanceId), + }); + console.log(`Databases for projects/${projectId}/instances/${instanceId}:`); + databases.forEach(database => { + const defaultLeader = database.defaultLeader + ? `(default leader = ${database.defaultLeader})` + : ''; + console.log(`\t${database.name} ${defaultLeader}`); + }); + } + listDatabases(); + // [END spanner_list_databases] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/list-instance-configs.js b/handwritten/spanner/samples/list-instance-configs.js new file mode 100644 index 00000000000..58e9283ff7b --- /dev/null +++ b/handwritten/spanner/samples/list-instance-configs.js @@ -0,0 +1,63 @@ +/** + * Copyright 2024 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: Lists all the available instance configs for the selected project. +// usage: node list-instance-configs.js + +'use strict'; + +function main(projectId) { + // [START spanner_list_instance_configs] + /** + * TODO(developer): Uncomment the following line before running the sample. + */ + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + async function listInstanceConfigs() { + // Lists all available instance configurations in the project. + // See https://cloud.google.com/spanner/docs/instance-configurations#configuration for a list of all available + // configurations. + const [instanceConfigs] = await instanceAdminClient.listInstanceConfigs({ + parent: instanceAdminClient.projectPath(projectId), + }); + console.log(`Available instance configs for project ${projectId}:`); + instanceConfigs.forEach(instanceConfig => { + console.log( + `Available leader options for instance config ${ + instanceConfig.name + } ('${instanceConfig.displayName}'): + ${instanceConfig.leaderOptions.join()}`, + ); + }); + } + listInstanceConfigs(); + // [END spanner_list_instance_configs] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/max-commit-delay.js b/handwritten/spanner/samples/max-commit-delay.js new file mode 100644 index 00000000000..3dbbabcea55 --- /dev/null +++ b/handwritten/spanner/samples/max-commit-delay.js @@ -0,0 +1,83 @@ +// Copyright 2024 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: Executes request with max commit delay +// usage: node max-commit-delay.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_set_max_commit_delay] + const {Spanner, protos} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Creates a client. + const spanner = new Spanner({ + projectId: projectId, + }); + + async function setMaxCommitDelay() { + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rowCount] = await transaction.runUpdate({ + sql: 'INSERT Singers (SingerId, FirstName, LastName) VALUES (111, @firstName, @lastName)', + params: { + firstName: 'Virginia', + lastName: 'Watson', + }, + }); + + console.log( + `Successfully inserted ${rowCount} record into the Singers table.`, + ); + + await transaction.commit({ + maxCommitDelay: protos.google.protobuf.Duration({ + seconds: 0, // 0 seconds + nanos: 100000000, // 100 milliseconds + }), + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + } + setMaxCommitDelay(); + // [END spanner_set_max_commit_delay] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/numeric-add-column.js b/handwritten/spanner/samples/numeric-add-column.js new file mode 100644 index 00000000000..78a72579e98 --- /dev/null +++ b/handwritten/spanner/samples/numeric-add-column.js @@ -0,0 +1,59 @@ +// Copyright 2024 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. + +'use strict'; + +async function addNumericColumn(instanceId, databaseId, projectId) { + // [START spanner_add_numeric_column] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const request = ['ALTER TABLE Venues ADD COLUMN Revenue NUMERIC']; + + // Alter existing table to add a column. + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + + await operation.promise(); + + console.log( + `Added Revenue column to Venues table in database ${databaseId}.`, + ); + // [END spanner_add_numeric_column] +} + +module.exports.addNumericColumn = addNumericColumn; diff --git a/handwritten/spanner/samples/numeric-query-parameter.js b/handwritten/spanner/samples/numeric-query-parameter.js new file mode 100644 index 00000000000..7895634f612 --- /dev/null +++ b/handwritten/spanner/samples/numeric-query-parameter.js @@ -0,0 +1,72 @@ +// Copyright 2020 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. + +'use strict'; + +async function queryWithNumericParameter(instanceId, databaseId, projectId) { + // [START spanner_query_with_numeric_parameter] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'numeric', + }; + + const exampleNumeric = Spanner.numeric('100000'); + + const query = { + sql: `SELECT VenueId, VenueName, Revenue FROM Venues + WHERE Revenue < @revenue`, + params: { + revenue: exampleNumeric, + }, + types: { + revenue: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log(`VenueId: ${json.VenueId}, Revenue: ${json.Revenue.value}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_numeric_parameter] +} + +module.exports.queryWithNumericParameter = queryWithNumericParameter; diff --git a/handwritten/spanner/samples/numeric-update-data.js b/handwritten/spanner/samples/numeric-update-data.js new file mode 100644 index 00000000000..9b9a115c355 --- /dev/null +++ b/handwritten/spanner/samples/numeric-update-data.js @@ -0,0 +1,72 @@ +// Copyright 2020 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. + +'use strict'; + +async function updateWithNumericData(instanceId, databaseId, projectId) { + // [START spanner_update_data_with_numeric_column] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client. + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Instantiate Spanner table objects. + const venuesTable = database.table('Venues'); + + const data = [ + { + VenueId: '4', + Revenue: Spanner.numeric('35000'), + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + VenueId: '19', + Revenue: Spanner.numeric('104500'), + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + VenueId: '42', + Revenue: Spanner.numeric('99999999999999999999999999999.99'), + LastUpdateTime: 'spanner.commit_timestamp()', + }, + ]; + + // Updates rows in the Venues table. + try { + await venuesTable.update(data); + console.log('Updated data.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_update_data_with_numeric_column] +} + +module.exports.updateWithNumericData = updateWithNumericData; diff --git a/handwritten/spanner/samples/observability-traces-otlp.js b/handwritten/spanner/samples/observability-traces-otlp.js new file mode 100644 index 00000000000..321923d374a --- /dev/null +++ b/handwritten/spanner/samples/observability-traces-otlp.js @@ -0,0 +1,103 @@ +/*! + * Copyright 2025 Google LLC. All Rights Reserved. + * + * 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: Observability (Tracing) with OpenTelemetry using OTLP +// usage: node observability-traces-otlp.js + +'use strict'; + +async function main( + projectId = 'my-project-id', + instanceId = 'my-instance-id', + databaseId = 'my-project-id', +) { + // [START spanner_opentelemetry_traces_otlp_usage] + const {NodeTracerProvider} = require('@opentelemetry/sdk-trace-node'); + const { + OTLPTraceExporter, + } = require('@opentelemetry/exporter-trace-otlp-grpc'); + const { + BatchSpanProcessor, + TraceIdRatioBasedSampler, + } = require('@opentelemetry/sdk-trace-base'); + const {Resource} = require('@opentelemetry/resources'); + const {Spanner} = require('@google-cloud/spanner'); + + // Define a Resource with service metadata + const resource = new Resource({ + 'service.name': 'my-service', + 'service.version': '1.0.0', + }); + + // Create an OTLP gRPC trace exporter + const traceExporter = new OTLPTraceExporter({ + url: 'http://localhost:4317', // Default OTLP gRPC endpoint + }); + + // Create a provider with a custom sampler + const provider = new NodeTracerProvider({ + sampler: new TraceIdRatioBasedSampler(1.0), // Sample 100% of traces + resource, + spanProcessors: [new BatchSpanProcessor(traceExporter)], + }); + + // Uncomment following line to register tracerProvider globally or pass it in Spanner object + // provider.register(); + + // Create the Cloud Spanner Client. + const spanner = new Spanner({ + projectId: projectId, + observabilityOptions: { + tracerProvider: provider, + enableExtendedTracing: true, + enableEndToEndTracing: true, + }, + }); + + // [END spanner_opentelemetry_traces_otlp_usage] + + // Acquire the database handle. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + try { + const query = { + sql: 'SELECT 1', + }; + const [rows] = await database.run(query); + console.log(`Query: ${rows.length} found.`); + rows.forEach(row => console.log(row)); + } finally { + spanner.close(); + } + + provider.forceFlush(); + + // This sleep gives ample time for the trace + // spans to be exported to Google Cloud Trace. + await new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 8800); + }); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/observability-traces.js b/handwritten/spanner/samples/observability-traces.js new file mode 100644 index 00000000000..5314f74f186 --- /dev/null +++ b/handwritten/spanner/samples/observability-traces.js @@ -0,0 +1,99 @@ +/*! + * Copyright 2025 Google LLC. All Rights Reserved. + * + * 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: Observability (Tracing) with OpenTelemetry +// usage: node observability-traces.js + +'use strict'; + +async function main( + projectId = 'my-project-id', + instanceId = 'my-instance-id', + databaseId = 'my-project-id', +) { + // [START spanner_opentelemetry_traces_cloudtrace_usage] + + const {NodeTracerProvider} = require('@opentelemetry/sdk-trace-node'); + const { + TraceExporter, + } = require('@google-cloud/opentelemetry-cloud-trace-exporter'); + const { + BatchSpanProcessor, + TraceIdRatioBasedSampler, + } = require('@opentelemetry/sdk-trace-base'); + const {Spanner} = require('@google-cloud/spanner'); + + const traceExporter = new TraceExporter(); + + // Create a provider with a custom sampler + const provider = new NodeTracerProvider({ + sampler: new TraceIdRatioBasedSampler(1.0), // Sample 100% of traces + spanProcessors: [new BatchSpanProcessor(traceExporter)], + }); + + // Uncomment following line to register tracerProvider globally or pass it in Spanner object + // provider.register(); + + // Set global propagator to propogate the trace context for end to end tracing. + const {propagation} = require('@opentelemetry/api'); + const {W3CTraceContextPropagator} = require('@opentelemetry/core'); + propagation.setGlobalPropagator(new W3CTraceContextPropagator()); + + const spanner = new Spanner({ + projectId: projectId, + observabilityOptions: { + tracerProvider: provider, + // Enable extended tracing to allow your SQL statements to be annotated. + enableExtendedTracing: true, + // Enable end to end tracing. + enableEndToEndTracing: true, + }, + }); + + // [END spanner_opentelemetry_traces_cloudtrace_usage] + + // Acquire the database handle. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + try { + const query = { + sql: 'SELECT 1', + }; + const [rows] = await database.run(query); + console.log(`Query: ${rows.length} found.`); + rows.forEach(row => console.log(row)); + } finally { + spanner.close(); + } + + provider.forceFlush(); + + // This sleep gives ample time for the trace + // spans to be exported to Google Cloud Trace. + await new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 8800); + }); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/package.json b/handwritten/spanner/samples/package.json new file mode 100644 index 00000000000..a0dcc8655af --- /dev/null +++ b/handwritten/spanner/samples/package.json @@ -0,0 +1,38 @@ +{ + "name": "nodejs-docs-samples-spanner", + "private": true, + "license": "Apache-2.0", + "author": "Google Inc.", + "repository": "googleapis/nodejs-spanner", + "files": [ + "*.js" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "test-with-archived": "mocha system-test --timeout 1600000", + "test": "mocha system-test/spanner.test.js --timeout 1600000" + }, + "dependencies": { + "@google-cloud/kms": "^5.0.0", + "@google-cloud/precise-date": "^5.0.0", + "@google-cloud/spanner": "^8.0.0", + "protobufjs": "^7.0.0", + "yargs": "^17.0.0" + }, + "devDependencies": { + "@google-cloud/opentelemetry-cloud-trace-exporter": "^2.4.1", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.57.0", + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/instrumentation-grpc": "^0.57.0", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-trace-base": "~1.30.1", + "@opentelemetry/sdk-trace-node": "^1.30.1", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^1.30.1", + "chai": "^4.2.0", + "mocha": "^9.0.0", + "p-limit": "^3.0.1" + } +} \ No newline at end of file diff --git a/handwritten/spanner/samples/pg-add-column.js b/handwritten/spanner/samples/pg-add-column.js new file mode 100644 index 00000000000..752c727afd0 --- /dev/null +++ b/handwritten/spanner/samples/pg-add-column.js @@ -0,0 +1,73 @@ +// Copyright 2024 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: Adds a column to an existing table in a Spanner PostgreSQL database. +// usage: node pg-add-column.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_add_column] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function pgAddColumn() { + const request = ['ALTER TABLE Albums ADD COLUMN MarketingBudget BIGINT']; + + // Alter existing table to add a column. + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + + await operation.promise(); + + console.log( + `Added MarketingBudget column to Albums table in database ${databaseId}.`, + ); + } + pgAddColumn(); + // [END spanner_postgresql_add_column] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-case-sensitivity.js b/handwritten/spanner/samples/pg-case-sensitivity.js new file mode 100644 index 00000000000..e4ad4db7727 --- /dev/null +++ b/handwritten/spanner/samples/pg-case-sensitivity.js @@ -0,0 +1,163 @@ +// Copyright 2022 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: Showcase the rules for case-sensitivity and case folding for a Spanner PostgreSQL database. +// usage: node pg-case-sensitivity.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_case_sensitivity] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgCaseSensitivity() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // DDL: Spanner PostgreSQL follows the case sensitivity rules of PostgreSQL. This means that: + // 1. Identifiers that are not double-quoted are folded to lower case. + // 2. Identifiers that are double-quoted retain their case and are case-sensitive. + // See https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS + // for more information. + const statements = [ + `CREATE TABLE Concerts + ( + -- ConcertId will be folded to "concertid" + ConcertId bigint NOT NULL PRIMARY KEY, + -- Location and Time are double-quoted and will therefore retain their + -- mixed case and are case-sensitive. This means that any statement that + -- references any of these columns must use double quotes. + "Location" varchar(1024) NOT NULL, + "Time" timestamptz NOT NULL + )`, + ]; + + // Updates schema by adding new tables. + const [operation] = await database.updateSchema(statements); + await operation.promise(); + console.log( + `Created table with case sensitive names in database ${databaseId} using PostgreSQL dialect.`, + ); + + // Mutations: Column names in mutations are always case-insensitive, regardless whether the + // columns were double-quoted or not during creation. + const concertsTable = database.table('concerts'); + const data = [ + { + concertid: '4', + location: 'Venue 4', + time: new Date().toISOString(), + }, + ]; + try { + await concertsTable.insert(data); + console.log('Inserted data using mutations.'); + } catch (err) { + console.error('ERROR:', err); + } + + try { + const [rows] = await database.run({ + sql: 'SELECT * FROM Concerts', + }); + + console.log('Concerts Table Data using Mutations:'); + rows.forEach(row => { + const json = row.toJSON(); + // Queries: ConcertId is automatically folded to lower case. Accessing the column by its name in + // a result set must therefore use all lower-case letters. Location and Time were double-quoted + // during creation, and retain their mixed case when returned in a result set. + console.log( + `concertid: ${json.concertid}, Location: ${json.Location}, Time: ${json.Time}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } + + // Aliases: Aliases are also identifiers, and specifying an alias in double quotes will make the alias + // retain its case. + try { + const [rows] = await database.run({ + sql: + 'SELECT concertid AS "ConcertId",' + + '"Location" AS "venue", "Time" FROM Concerts', + }); + + console.log('Concerts Table Data using Aliases:'); + rows.forEach(row => { + const json = row.toJSON(); + // The aliases are double-quoted and therefore retains their mixed case. + console.log( + `concertid: ${json.ConcertId}, Location: ${json.venue}, Time: ${json.Time}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } + + // DML: Statements must also follow the PostgreSQL case rules. + try { + await database.runTransactionAsync(async transaction => { + await transaction.runUpdate({ + sql: `INSERT INTO Concerts (ConcertId, "Location", "Time") + VALUES ($1, $2, $3)`, + params: { + p1: 7, + p2: 'Venue 7', + p3: new Date().toISOString(), + }, + types: { + p1: {type: 'int64'}, + p2: {type: 'string'}, + p3: {type: 'timestamp'}, + }, + }); + console.log('Inserted data using DML.'); + await transaction.commit(); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + pgCaseSensitivity(); + // [END spanner_postgresql_case_sensitivity] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-database-create.js b/handwritten/spanner/samples/pg-database-create.js new file mode 100644 index 00000000000..2e9952717f7 --- /dev/null +++ b/handwritten/spanner/samples/pg-database-create.js @@ -0,0 +1,100 @@ +// Copyright 2024 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 PostgreSQL Database. +// usage: node pg-database-create.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_create_database] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function createPgDatabase() { + // Creates a PostgreSQL database. PostgreSQL create requests may not contain any additional + // DDL statements. We need to execute these separately after the database has been created. + const [operationCreate] = await databaseAdminClient.createDatabase({ + createStatement: 'CREATE DATABASE "' + databaseId + '"', + parent: databaseAdminClient.instancePath(projectId, instanceId), + databaseDialect: + protos.google.spanner.admin.database.v1.DatabaseDialect.POSTGRESQL, + }); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + await operationCreate.promise(); + const [metadata] = await databaseAdminClient.getDatabase({ + name: databaseAdminClient.databasePath(projectId, instanceId, databaseId), + }); + console.log( + `Created database ${databaseId} on instance ${instanceId} with dialect ${metadata.databaseDialect}.`, + ); + + // Create a couple of tables using a separate request. We must use PostgreSQL style DDL as the + // database has been created with the PostgreSQL dialect. + const statements = [ + `CREATE TABLE Singers + (SingerId bigint NOT NULL, + FirstName varchar(1024), + LastName varchar(1024), + SingerInfo bytea, + FullName character varying(2048) GENERATED ALWAYS AS (FirstName || ' ' || LastName) STORED, + PRIMARY KEY (SingerId) + ); + CREATE TABLE Albums + (AlbumId bigint NOT NULL, + SingerId bigint NOT NULL REFERENCES Singers (SingerId), + AlbumTitle text, + PRIMARY KEY (AlbumId) + );`, + ]; + const [operationUpdateDDL] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: [statements], + }); + await operationUpdateDDL.promise(); + console.log('Updated schema'); + } + createPgDatabase(); + // [END spanner_postgresql_create_database] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-datatypes-casting.js b/handwritten/spanner/samples/pg-datatypes-casting.js new file mode 100644 index 00000000000..112b7a02ecb --- /dev/null +++ b/handwritten/spanner/samples/pg-datatypes-casting.js @@ -0,0 +1,85 @@ +// Copyright 2022 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: Use cast operator to cast from one data type to another in a Spanner PostgreSQL database. +// usage: node pg-datatypes-casting.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_cast_data_type] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgDatatypeCasting() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // The `::` cast operator can be used to cast from one data type to another. + const query = { + sql: + "select 1::varchar as str, '2'::int as int, 3::decimal as dec, '4'::bytea as bytes, " + + "5::float as float, 'true'::bool as bool, " + + "'2021-11-03T09:35:01UTC'::timestamptz as timestamp", + }; + + try { + const [rows] = await database.run(query); + rows.forEach(row => { + const json = row.toJSON(); + console.log( + 'Data types after casting \n' + + `String: ${json.str} \n` + + `Int: ${json.int} \n` + + `Decimal: ${json.dec.value} \n` + + `Bytes: ${json.bytes.valueOf()} \n` + + `Float: ${json.float} \n` + + `Bool: ${json.bool} \n` + + `Timestamp: ${Spanner.timestamp(json.timestamp)}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + } + pgDatatypeCasting(); + // [END spanner_postgresql_cast_data_type] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-dml-batch.js b/handwritten/spanner/samples/pg-dml-batch.js new file mode 100644 index 00000000000..4e4a49f3458 --- /dev/null +++ b/handwritten/spanner/samples/pg-dml-batch.js @@ -0,0 +1,105 @@ +// Copyright 2022 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: Execute a batch of DML statements on a Spanner PostgreSQL database. +// usage: node pg-dml-batch.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_batch_dml] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgBatchDml() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const insert1 = { + sql: `INSERT INTO singers (singerid, firstname, lastname) + VALUES ($1, $2, $3)`, + params: { + p1: 1, + p2: 'Alice', + p3: 'Henderson', + }, + types: { + p1: 'int64', + p2: 'string', + p3: 'string', + }, + }; + + const insert2 = { + sql: `INSERT INTO singers (singerid, firstname, lastname) + VALUES (2, 'Shruti', 'Hassan')`, + }; + + const insert3 = { + sql: `INSERT INTO singers (singerid, firstname, lastname) + VALUES ($1, $2, $3)`, + params: { + p1: 3, + p2: 'Olivia', + p3: 'Garcia', + }, + }; + + const dmlStatements = [insert1, insert2, insert3]; + + // Batches multiple DML statements into one request, which reduces the number of round trips that is needed for + // multiple DML statements. + try { + await database.runTransactionAsync(async transaction => { + const [rowCounts] = await transaction.batchUpdate(dmlStatements); + await transaction.commit(); + console.log( + `Successfully executed ${rowCounts.length} postgreSQL statements using Batch DML.`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + throw err; + } finally { + // Close the database when finished. + await database.close(); + } + } + pgBatchDml(); + // [END spanner_postgresql_batch_dml] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-dml-getting-started-update.js b/handwritten/spanner/samples/pg-dml-getting-started-update.js new file mode 100644 index 00000000000..bf89a59ef22 --- /dev/null +++ b/handwritten/spanner/samples/pg-dml-getting-started-update.js @@ -0,0 +1,81 @@ +// Copyright 2022 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: Updates data in a table in a Spanner PostgreSQL database. +// usage: node pg-dml-getting-started-update.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_dml_getting_started_update] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + function updateUsingDml() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rowCount] = await transaction.runUpdate({ + sql: 'UPDATE singers SET FirstName = $1 WHERE singerid = 1', + params: { + p1: 'Virginia', + }, + }); + + console.log( + `Successfully updated ${rowCount} record in the Singers table.`, + ); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + }); + } + updateUsingDml(); + // [END spanner_postgresql_dml_getting_started_update] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-dml-partitioned.js b/handwritten/spanner/samples/pg-dml-partitioned.js new file mode 100644 index 00000000000..05294ba9c94 --- /dev/null +++ b/handwritten/spanner/samples/pg-dml-partitioned.js @@ -0,0 +1,71 @@ +// Copyright 2022 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: Execute a Partitioned DML on a Spanner PostgreSQL database. +// usage: node pg-dml-partitioned.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_partitioned_dml] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgPartitionedDml() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Spanner PostgreSQL has the same transaction limits as normal Spanner. This includes a + // maximum of 20,000 mutations in a single read/write transaction. Large update operations can + // be executed using Partitioned DML. This is also supported on Spanner PostgreSQL. + // See https://cloud.google.com/spanner/docs/dml-partitioned for more information. + try { + const [rowCount] = await database.runPartitionedUpdate({ + sql: 'DELETE FROM Singers WHERE SingerId > 3', + }); + console.log(`Successfully deleted ${rowCount} record.`); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + pgPartitionedDml(); + // [END spanner_postgresql_partitioned_dml] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-dml-returning-delete.js b/handwritten/spanner/samples/pg-dml-returning-delete.js new file mode 100644 index 00000000000..ecfda0b4fbc --- /dev/null +++ b/handwritten/spanner/samples/pg-dml-returning-delete.js @@ -0,0 +1,78 @@ +// Copyright 2022 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: Delete using DML returning on a Spanner PostgreSQL database. +// usage: node pg-dml-returning-delete.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_postgresql_delete_dml_returning] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + function pgDeleteUsingDmlReturning(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: 'DELETE FROM Singers WHERE SingerId = 18 RETURNING FullName', + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log( + `Successfully deleted ${rowCount} record from the Singers table.`, + ); + rows.forEach(row => { + console.log(row.toJSON().fullname); + }); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + } + pgDeleteUsingDmlReturning(instanceId, databaseId); + // [END spanner_postgresql_delete_dml_returning] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-dml-returning-insert.js b/handwritten/spanner/samples/pg-dml-returning-insert.js new file mode 100644 index 00000000000..eeeb6535160 --- /dev/null +++ b/handwritten/spanner/samples/pg-dml-returning-insert.js @@ -0,0 +1,83 @@ +// Copyright 2022 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: Insert using DML returning on a Spanner PostgreSQL database. +// usage: node pg-dml-returning-insert.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_postgresql_insert_dml_returning] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + function pgInsertUsingDmlReturning(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: 'INSERT Into Singers (SingerId, FirstName, LastName) VALUES ($1, $2, $3) RETURNING FullName', + params: { + p1: 18, + p2: 'Virginia', + p3: 'Watson', + }, + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log( + `Successfully inserted ${rowCount} record into the Singers table.`, + ); + rows.forEach(row => { + console.log(row.toJSON().fullname); + }); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + } + pgInsertUsingDmlReturning(instanceId, databaseId); + // [END spanner_postgresql_insert_dml_returning] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-dml-returning-update.js b/handwritten/spanner/samples/pg-dml-returning-update.js new file mode 100644 index 00000000000..5de9458fbc5 --- /dev/null +++ b/handwritten/spanner/samples/pg-dml-returning-update.js @@ -0,0 +1,83 @@ +// Copyright 2022 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: Update using DML returning on a Spanner PostgreSQL database. +// usage: node pg-dml-returning-update.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_postgresql_update_dml_returning] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + function pgUpdateUsingDmlReturning(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: 'UPDATE singers SET FirstName = $1, LastName = $2 WHERE singerid = $3 RETURNING FullName', + params: { + p1: 'Virginia1', + p2: 'Watson1', + p3: 18, + }, + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log( + `Successfully updated ${rowCount} record into the Singers table.`, + ); + rows.forEach(row => { + console.log(row.toJSON().fullname); + }); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + } + pgUpdateUsingDmlReturning(instanceId, databaseId); + // [END spanner_postgresql_update_dml_returning] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-dml-with-parameter.js b/handwritten/spanner/samples/pg-dml-with-parameter.js new file mode 100644 index 00000000000..078142567df --- /dev/null +++ b/handwritten/spanner/samples/pg-dml-with-parameter.js @@ -0,0 +1,87 @@ +// Copyright 2022 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: Execute a DML statement with parameters on a Spanner PostgreSQL database. +// usage: node pg-dml-with-parameter.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_dml_with_parameters] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgDmlWithParameters() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rowCounts] = await transaction.runUpdate({ + sql: `INSERT INTO singers (singerid, firstname, lastname) + VALUES ($1, $2, $3)`, + params: { + p1: 11, + p2: 'Timothy', + p3: 'Campbell', + }, + types: { + p1: 'int64', + p2: 'string', + p3: 'string', + }, + }); + await transaction.commit(); + console.log( + `Successfully executed ${rowCounts} postgreSQL statements using DML.`, + ); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + } + pgDmlWithParameters(); + // [END spanner_postgresql_dml_with_parameters] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-functions.js b/handwritten/spanner/samples/pg-functions.js new file mode 100644 index 00000000000..b4fe1164424 --- /dev/null +++ b/handwritten/spanner/samples/pg-functions.js @@ -0,0 +1,73 @@ +// Copyright 2022 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: Calls a server side function on a Spanner PostgreSQL database. +// usage: node pg-functions.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_functions] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgFunctions() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: 'SELECT to_timestamp(1284352323) AS time', + }; + + // Use the PostgreSQL `to_timestamp` function to convert a number of seconds since epoch to a timestamp. + try { + const [rows] = await database.run(query); + rows.forEach(row => { + const json = row.toJSON(); + console.log(`1284352323 seconds after epoch is ${json.time}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + } + pgFunctions(); + // [END spanner_postgresql_functions] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-index-create-storing.js b/handwritten/spanner/samples/pg-index-create-storing.js new file mode 100644 index 00000000000..467db625139 --- /dev/null +++ b/handwritten/spanner/samples/pg-index-create-storing.js @@ -0,0 +1,80 @@ +// Copyright 2024 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 new storing index in a Spanner PostgreSQL database. +// usage: node pg-index-create-storing.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_create_storing_index] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function pgCreateStoringIndex() { + const request = [ + 'CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle) INCLUDE(MarketingBudget)', + ]; + + // Creates a new index in the database + try { + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Added the AlbumsByAlbumTitle index.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + } + pgCreateStoringIndex(); + // [END spanner_postgresql_create_storing_index] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-interleaving.js b/handwritten/spanner/samples/pg-interleaving.js new file mode 100644 index 00000000000..54abd4125c2 --- /dev/null +++ b/handwritten/spanner/samples/pg-interleaving.js @@ -0,0 +1,86 @@ +// Copyright 2024 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: Created interleaved table hierarchy using PostgreSQL dialect. +// usage: node pg-interleaving.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_interleaved_table] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function pgInterleaving() { + const statements = [ + `CREATE TABLE Author + (AuthorId bigint NOT NULL, + FirstName varchar(1024), + LastName varchar(1024), + Rating double precision, + PRIMARY KEY (AuthorId) + ); + CREATE TABLE Book + (AuthorId bigint NOT NULL, + BookId bigint NOT NULL, + BookTitle text, + PRIMARY KEY (AuthorId, BookId) + ) INTERLEAVE IN PARENT Author ON DELETE CASCADE;`, + ]; + + // Updates schema by adding new tables. + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: statements, + }); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + await operation.promise(); + + console.log( + `Created an interleaved table hierarchy in database ${databaseId} using PostgreSQL dialect.`, + ); + } + pgInterleaving(); + // [END spanner_postgresql_interleaved_table] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-jsonb-add-column.js b/handwritten/spanner/samples/pg-jsonb-add-column.js new file mode 100644 index 00000000000..daf8387bd54 --- /dev/null +++ b/handwritten/spanner/samples/pg-jsonb-add-column.js @@ -0,0 +1,71 @@ +// Copyright 2024 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: Showcase how add a jsonb column in a PostgreSQL table. +// usage: node pg-jsonb-add-column.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_jsonb_add_column] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgJsonbAddColumn() { + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const request = ['ALTER TABLE Venues ADD COLUMN VenueDetails JSONB']; + + // Updates schema by adding a new table. + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + console.log(`Waiting for operation on ${databaseId} to complete...`); + await operation.promise(); + console.log( + `Added jsonb column to table venues to database ${databaseId}.`, + ); + } + pgJsonbAddColumn(); + // [END spanner_postgresql_jsonb_add_column] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-jsonb-query-parameter.js b/handwritten/spanner/samples/pg-jsonb-query-parameter.js new file mode 100644 index 00000000000..2d8111a4655 --- /dev/null +++ b/handwritten/spanner/samples/pg-jsonb-query-parameter.js @@ -0,0 +1,83 @@ +// Copyright 2022 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: Showcase how query data to a jsonb column in a PostgreSQL table. +// usage: node pg-jsonb-query-parameter.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_jsonb_query_parameter] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgJsonbDataType() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const select_query = { + sql: `SELECT venueid, venuedetails FROM Venues + WHERE CAST(venuedetails ->> 'rating' AS INTEGER) > $1`, + params: { + p1: 2, + }, + types: { + p1: 'int64', + }, + json: true, + }; + + // Queries row from the Venues table. + try { + const [rows] = await database.run(select_query); + + rows.forEach(row => { + console.log( + `VenueId: ${row.venueid}, Details: ${JSON.stringify( + row.venuedetails, + )}`, + ); + }); + } finally { + // Close the database when finished. + await database.close(); + } + } + pgJsonbDataType(); + // [END spanner_postgresql_jsonb_query_parameter] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-jsonb-update-data.js b/handwritten/spanner/samples/pg-jsonb-update-data.js new file mode 100644 index 00000000000..87bc8c3affc --- /dev/null +++ b/handwritten/spanner/samples/pg-jsonb-update-data.js @@ -0,0 +1,107 @@ +// Copyright 2022 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: Showcase how update data to a jsonb column in a PostgreSQL table. +// usage: node pg-jsonb-update-data.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_jsonb_update_data] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgJsonbUpdateData() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + // Instantiate Spanner table objects. + const venuesTable = database.table('venues'); + + const data = [ + { + VenueId: '19', + VenueDetails: {rating: 9, open: true}, + }, + { + VenueId: '4', + // PG JSONB sorts first by key length and then lexicographically with equivalent key length + // and takes the last value in the case of duplicate keys + VenueDetails: `[ + { + "name": null, + "available": true + }, + { + "name": "room 2", + "available": false, + "name": "room 3" + }, + { + "main hall": { + "description": "this is the biggest space", + "size": 200 + } + } + ]`, + }, + { + VenueId: '42', + VenueDetails: { + name: null, + open: { + Monday: true, + Tuesday: false, + }, + tags: ['large', 'airy'], + }, + }, + ]; + + try { + await venuesTable.update(data); + console.log('Updated data.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + } + pgJsonbUpdateData(); + // [END spanner_postgresql_jsonb_update_data] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-numeric-data-type.js b/handwritten/spanner/samples/pg-numeric-data-type.js new file mode 100644 index 00000000000..67da9387332 --- /dev/null +++ b/handwritten/spanner/samples/pg-numeric-data-type.js @@ -0,0 +1,160 @@ +// Copyright 2022 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: Showcase how to work with the PostgreSQL NUMERIC/DECIMAL data type on a Spanner PostgreSQL database. +// usage: node ppg-numeric-data-type.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_numeric_data_type] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgNumericDataType() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const request = [ + `CREATE TABLE Venues + (VenueId bigint NOT NULL PRIMARY KEY, + Name varchar(1024) NOT NULL, + Revenues numeric + );`, + ]; + + // Updates schema by adding a new table. + const [operation] = await database.updateSchema(request); + console.log(`Waiting for operation on ${databaseId} to complete...`); + await operation.promise(); + console.log(`Added table venues to database ${databaseId}.`); + + // Instantiate Spanner table objects. + const venuesTable = database.table('venues'); + const revenues1 = Spanner.pgNumeric('97372.3863'); + const revenues2 = Spanner.pgNumeric(7629); + const revenues3 = Spanner.pgNumeric( + '1234567890.000012387293137871837817783828271273962412698378219372373072321997201370913293722379069869126846496978479842178917827474178248943891738912692839263826722738362982366832623281', + ); + const revenues4 = Spanner.pgNumeric('1.23e-7'); + const revenues5 = Spanner.pgNumeric('NaN'); + + const data = [ + { + venueid: '4', + name: 'Venue 4', + revenues: revenues1, + }, + { + venueid: '19', + name: 'Venue 19', + revenues: revenues2, + }, + { + venueid: '42', + name: 'Venue 42', + revenues: revenues3, + }, + { + venueid: '398', + name: 'Venue 398', + revenues: revenues4, + }, + { + venueid: '73', + name: 'Venue 73', + revenues: revenues5, + }, + { + venueid: '568', + name: 'Venue 568', + revenues: 'NaN', + }, + { + venueid: '3547', + name: 'Venue 3547', + revenues: '182730.128673921', + }, + ]; + + // Insert rows into the Venues table. + try { + await venuesTable.insert(data); + console.log('Inserted data.'); + } catch (err) { + console.error('ERROR:', err); + } + + const fieldType = { + type: 'pgNumeric', + }; + + const exampleNumeric = Spanner.pgNumeric('100000'); + + const query = { + sql: `SELECT venueid, revenues FROM venues + WHERE revenues < $1`, + params: { + p1: exampleNumeric, + }, + types: { + p1: fieldType, + }, + }; + + // Queries rows from the Venues table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + // Check if revenue field is Null + const revenue = + json.revenues === null ? json.revenues : json.revenues.value; + console.log(`VenueId: ${json.venueid}, Revenue: ${revenue}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + pgNumericDataType(); + // [END spanner_postgresql_numeric_data_type] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-ordering-nulls.js b/handwritten/spanner/samples/pg-ordering-nulls.js new file mode 100644 index 00000000000..b59f9dfcba4 --- /dev/null +++ b/handwritten/spanner/samples/pg-ordering-nulls.js @@ -0,0 +1,135 @@ +// Copyright 2022 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: Showcases how a Spanner PostgreSQL database orders null values in a query. +// usage: node pg-ordering-nulls.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_order_nulls] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgOrderingNulls() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Instantiate Spanner table objects. + const authorTable = database.table('author'); + + const data = [ + { + authorid: '4', + firstname: 'Alice', + }, + { + authorid: '5', + firstname: 'Bruce', + }, + { + authorid: '6', + firstname: null, + }, + ]; + + // Insert rows into the Author table. + try { + await authorTable.insert(data); + console.log('Inserted data.'); + } catch (err) { + console.error('ERROR:', err); + } + + try { + const [rows] = await database.run({ + sql: 'SELECT FirstName FROM Author ORDER BY FirstName', + }); + + console.log('\nAuthor ORDER BY FirstName'); + printAuthors(rows); + } catch (err) { + console.error('ERROR:', err); + } + + try { + const [rows] = await database.run({ + sql: 'SELECT FirstName FROM Author ORDER BY FirstName DESC', + }); + + console.log('\nAuthor ORDER BY FirstName DESC'); + printAuthors(rows); + } catch (err) { + console.error('ERROR:', err); + } + + try { + const [rows] = await database.run({ + sql: 'SELECT FirstName FROM Author ORDER BY FirstName NULLS FIRST', + }); + + console.log('\nAuthor ORDER BY FirstName NULLS FIRST'); + printAuthors(rows); + } catch (err) { + console.error('ERROR:', err); + } + + try { + const [rows] = await database.run({ + sql: 'SELECT FirstName FROM Author ORDER BY FirstName DESC NULLS LAST', + }); + + console.log('\nAuthor ORDER BY FirstName DESC NULLS LAST'); + printAuthors(rows); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + pgOrderingNulls(); + // [END spanner_postgresql_order_nulls] +} + +function printAuthors(rows) { + rows.forEach(row => { + const json = row.toJSON(); + console.log(`${json.firstname}`); + }); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-query-parameter.js b/handwritten/spanner/samples/pg-query-parameter.js new file mode 100644 index 00000000000..98005c61b2d --- /dev/null +++ b/handwritten/spanner/samples/pg-query-parameter.js @@ -0,0 +1,88 @@ +// Copyright 2022 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: Execute a query with parameters on a Spanner PostgreSQL database. +// usage: node pg-query-parameter.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_query_parameter] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function queryWithPgParameter() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const fieldType = { + type: 'string', + }; + + const query = { + sql: `SELECT singerid, firstname, lastname + FROM singers + WHERE firstname LIKE $1`, + params: { + p1: 'A%', + }, + types: { + p1: fieldType, + }, + }; + + // Queries rows from the Singers table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `SingerId: ${json.singerid}, FirstName: ${json.firstname}, LastName: ${json.lastname}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + queryWithPgParameter(); + // [END spanner_postgresql_query_parameter] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-schema-information.js b/handwritten/spanner/samples/pg-schema-information.js new file mode 100644 index 00000000000..d5514548b55 --- /dev/null +++ b/handwritten/spanner/samples/pg-schema-information.js @@ -0,0 +1,84 @@ +// Copyright 2022 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: Query the information schema metadata in a Spanner PostgreSQL database. +// usage: node pg-schema-information.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_postgresql_information_schema] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function pgSchemaInformation() { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Get all the user tables in the database. PostgreSQL uses the `public` schema for user + // tables. The table_catalog is equal to the database name. The `user_defined_...` columns + // are only available for PostgreSQL databases. + const query = { + sql: + 'SELECT table_schema, table_name, ' + + 'user_defined_type_catalog, ' + + 'user_defined_type_schema, ' + + 'user_defined_type_name ' + + 'FROM INFORMATION_SCHEMA.tables ' + + "WHERE table_schema='public'", + }; + + try { + const [rows] = await database.run(query); + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `Table: ${json.table_schema}.${json.table_name} ` + + `(User defined type: ${json.user_defined_type_catalog}.${json.user_defined_type_schema}.${json.user_defined_type_name})`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + } + pgSchemaInformation(); + // [END spanner_postgresql_information_schema] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-sequence-alter.js b/handwritten/spanner/samples/pg-sequence-alter.js new file mode 100644 index 00000000000..a0229d45c42 --- /dev/null +++ b/handwritten/spanner/samples/pg-sequence-alter.js @@ -0,0 +1,107 @@ +// Copyright 2024 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 sequence in a PostgreSQL database. +// usage: node pg-sequence-alter.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_postgresql_alter_sequence] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function alterSequence(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const request = ['ALTER SEQUENCE Seq SKIP RANGE 1000 5000000']; + + try { + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log( + 'Altered Seq sequence to skip an inclusive range between 1000 and 5000000.', + ); + } catch (err) { + console.error('ERROR:', err); + } + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: "INSERT INTO Customers (CustomerName) VALUES ('Lea'), ('Catalina'), ('Smith') RETURNING CustomerId", + }); + + rows.forEach(row => { + console.log( + `Inserted customer record with CustomerId: ${ + row.toJSON({wrapNumbers: true}).customerid.value + }`, + ); + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log(`Number of customer records inserted is: ${rowCount}`); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + }); + } + await alterSequence(instanceId, databaseId); + // [END spanner_postgresql_alter_sequence] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-sequence-create.js b/handwritten/spanner/samples/pg-sequence-create.js new file mode 100644 index 00000000000..2267169a639 --- /dev/null +++ b/handwritten/spanner/samples/pg-sequence-create.js @@ -0,0 +1,111 @@ +// 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 sequence in PostgreSQL database. +// usage: node pg-sequence-create.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_postgresql_create_sequence] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function createSequence(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const request = [ + 'CREATE SEQUENCE Seq BIT_REVERSED_POSITIVE', + "CREATE TABLE Customers (CustomerId BIGINT DEFAULT nextval('Seq'), CustomerName character varying(1024), PRIMARY KEY (CustomerId))", + ]; + + // Creates a new table with sequence + try { + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log( + 'Created Seq sequence and Customers table, where the key column CustomerId uses the sequence as a default value', + ); + } catch (err) { + console.error('ERROR:', err); + } + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: "INSERT INTO Customers (CustomerName) VALUES ('Alice'), ('David'), ('Marc') RETURNING CustomerId", + }); + + rows.forEach(row => { + console.log( + `Inserted customer record with CustomerId: ${ + row.toJSON({wrapNumbers: true}).customerid.value + }`, + ); + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log(`Number of customer records inserted is: ${rowCount}`); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + }); + } + await createSequence(instanceId, databaseId); + // [END spanner_postgresql_create_sequence] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/pg-sequence-drop.js b/handwritten/spanner/samples/pg-sequence-drop.js new file mode 100644 index 00000000000..525e97acfad --- /dev/null +++ b/handwritten/spanner/samples/pg-sequence-drop.js @@ -0,0 +1,80 @@ +// Copyright 2024 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 sequence in PostgreSQL database. +// usage: node pg-sequence-drop.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_drop_sequence] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function dropSequence(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const request = [ + 'ALTER TABLE Customers ALTER COLUMN CustomerId DROP DEFAULT', + 'DROP SEQUENCE Seq', + ]; + + // Drop sequence from DDL + try { + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log( + 'Altered Customers table to drop DEFAULT from CustomerId column and dropped the Seq sequence.', + ); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + } + await dropSequence(instanceId, databaseId); + // [END spanner_drop_sequence] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/proto-query-data.js b/handwritten/spanner/samples/proto-query-data.js new file mode 100644 index 00000000000..fa1e7ee7574 --- /dev/null +++ b/handwritten/spanner/samples/proto-query-data.js @@ -0,0 +1,100 @@ +// Copyright 2024 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. + +'use strict'; + +// eslint-disable-next-line n/no-unpublished-require +const singer = require('./resource/singer.js'); +const music = singer.examples.spanner.music; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_query_with_proto_types_parameter] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function queryDataWithProtoTypes() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: `SELECT SingerId, + SingerInfo, + SingerInfo.nationality, + SingerInfoArray, + SingerGenre, + SingerGenreArray + FROM Singers + WHERE SingerInfo.nationality = @country + and SingerGenre=@singerGenre`, + params: { + country: 'Country2', + singerGenre: music.Genre.FOLK, + }, + /* `columnsMetadata` is an optional parameter and is used to deserialize the + proto message and enum object back from bytearray and int respectively. + If columnsMetadata is not passed for proto messages and enums, then the data + types for these columns will be bytes and int respectively. */ + columnsMetadata: { + SingerInfo: music.SingerInfo, + SingerInfoArray: music.SingerInfo, + SingerGenre: music.Genre, + SingerGenreArray: music.Genre, + }, + }; + + // Queries rows from the Singers table. + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `SingerId: ${json.SingerId}, SingerInfo: ${json.SingerInfo}, SingerGenre: ${json.SingerGenre}, + SingerInfoArray: ${json.SingerInfoArray}, SingerGenreArray: ${json.SingerGenreArray}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + + queryDataWithProtoTypes(); + // [END spanner_query_with_proto_types_parameter] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/proto-type-add-column.js b/handwritten/spanner/samples/proto-type-add-column.js new file mode 100644 index 00000000000..a7b3ba703cd --- /dev/null +++ b/handwritten/spanner/samples/proto-type-add-column.js @@ -0,0 +1,90 @@ +/** + * Copyright 2024 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 new database with a proto column and enum +// usage: node proto-type-add-column.js + +'use strict'; + +const fs = require('fs'); + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_add_proto_type_columns] + /** + * 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'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + async function protoTypeAddColumn() { + // Adds a new Proto Message column and Proto Enum column to the Singers table. + + const request = [ + `CREATE PROTO BUNDLE ( + examples.spanner.music.SingerInfo, + examples.spanner.music.Genre, + )`, + 'ALTER TABLE Singers ADD COLUMN SingerInfo examples.spanner.music.SingerInfo', + 'ALTER TABLE Singers ADD COLUMN SingerInfoArray ARRAY', + 'ALTER TABLE Singers ADD COLUMN SingerGenre examples.spanner.music.Genre', + 'ALTER TABLE Singers ADD COLUMN SingerGenreArray ARRAY', + ]; + + // Read a proto descriptor file and convert it to a base64 string + const protoDescriptor = fs + .readFileSync('./resource/descriptors.pb') + .toString('base64'); + + // Alter existing table to add a column. + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + protoDescriptors: protoDescriptor, + }); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + await operation.promise(); + console.log( + `Altered table "Singers" on database ${databaseId} on instance ${instanceId} with proto descriptors.`, + ); + } + protoTypeAddColumn(); + // [END spanner_add_proto_type_columns] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/proto-update-data-dml.js b/handwritten/spanner/samples/proto-update-data-dml.js new file mode 100644 index 00000000000..6d9df84728d --- /dev/null +++ b/handwritten/spanner/samples/proto-update-data-dml.js @@ -0,0 +1,128 @@ +// Copyright 2024 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. + +'use strict'; + +// eslint-disable-next-line n/no-unpublished-require +const singer = require('./resource/singer.js'); +const music = singer.examples.spanner.music; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_update_data_with_proto_types_with_dml] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function updateDataUsingDmlWithProtoTypes() { + /* + Updates Singers tables in the database with the ProtoMessage + and ProtoEnum column. + This updates the `SingerInfo`, `SingerInfoArray`, `SingerGenre` and + SingerGenreArray` columns which must be created before running this sample. + You can add the column by running the `add_proto_type_columns` sample or + by running this DDL statement against your database: + + ALTER TABLE Singers ADD COLUMN SingerInfo examples.spanner.music.SingerInfo\n + ALTER TABLE Singers ADD COLUMN SingerInfoArray ARRAY\n + ALTER TABLE Singers ADD COLUMN SingerGenre examples.spanner.music.Genre\n + ALTER TABLE Singers ADD COLUMN SingerGenreArray ARRAY\n + */ + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const genre = music.Genre.ROCK; + const singerInfo = music.SingerInfo.create({ + singerId: 1, + genre: genre, + birthDate: 'January', + nationality: 'Country1', + }); + + const protoMessage = Spanner.protoMessage({ + value: singerInfo, + messageFunction: music.SingerInfo, + fullName: 'examples.spanner.music.SingerInfo', + }); + + const protoEnum = Spanner.protoEnum({ + value: genre, + enumObject: music.Genre, + fullName: 'examples.spanner.music.Genre', + }); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [, stats] = await transaction.run({ + sql: `UPDATE Singers SET SingerInfo=@singerInfo, SingerInfoArray=@singerInfoArray, + SingerGenre=@singerGenre, SingerGenreArray=@singerGenreArray WHERE SingerId = 1`, + params: { + singerInfo: protoMessage, + singerInfoArray: [protoMessage, null], + singerGenre: genre, + singerGenreArray: [protoEnum, null], + }, + }); + + const rowCount = stats[stats.rowCount]; + console.log(`${rowCount} record updated.`); + + const [, stats1] = await transaction.run({ + sql: 'UPDATE Singers SET SingerInfo.nationality=@singerNationality WHERE SingerId = 1', + params: { + singerNationality: 'Country2', + }, + }); + const rowCount1 = stats1[stats1.rowCount]; + console.log(`${rowCount1} record updated.`); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + } + + updateDataUsingDmlWithProtoTypes(); + // [END spanner_update_data_with_proto_types_with_dml] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/proto-update-data.js b/handwritten/spanner/samples/proto-update-data.js new file mode 100644 index 00000000000..529577fc1b4 --- /dev/null +++ b/handwritten/spanner/samples/proto-update-data.js @@ -0,0 +1,120 @@ +// Copyright 2024 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. + +'use strict'; + +// eslint-disable-next-line n/no-unpublished-require +const singer = require('./resource/singer.js'); +const music = singer.examples.spanner.music; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_update_data_with_proto_types] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function updateDataWithProtoTypes() { + /* + Updates Singers tables in the database with the ProtoMessage + and ProtoEnum column. + This updates the `SingerInfo`, `SingerInfoArray`, `SingerGenre` and + SingerGenreArray` columns which must be created before running this sample. + You can add the column by running the `add_proto_type_columns` sample or + by running this DDL statement against your database: + + ALTER TABLE Singers ADD COLUMN SingerInfo examples.spanner.music.SingerInfo\n + ALTER TABLE Singers ADD COLUMN SingerInfoArray ARRAY\n + ALTER TABLE Singers ADD COLUMN SingerGenre examples.spanner.music.Genre\n + ALTER TABLE Singers ADD COLUMN SingerGenreArray ARRAY\n + */ + + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const genre = music.Genre.FOLK; + const singerInfo = music.SingerInfo.create({ + singerId: 2, + genre: genre, + birthDate: 'February', + nationality: 'Country2', + }); + + const protoMessage = Spanner.protoMessage({ + value: singerInfo, + messageFunction: music.SingerInfo, + fullName: 'examples.spanner.music.SingerInfo', + }); + + const protoEnum = Spanner.protoEnum({ + value: genre, + enumObject: music.Genre, + fullName: 'examples.spanner.music.Genre', + }); + + // Get a reference to the Singers table + const table = database.table('Singers'); + + const data = [ + { + SingerId: 2, + SingerInfo: protoMessage, + SingerInfoArray: [protoMessage], + SingerGenre: protoEnum, + SingerGenreArray: [protoEnum], + }, + { + SingerId: 3, + SingerInfo: null, + SingerInfoArray: null, + SingerGenre: null, + SingerGenreArray: null, + }, + ]; + + try { + await table.update(data); + console.log('Data updated.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + } + + updateDataWithProtoTypes(); + // [END spanner_update_data_with_proto_types] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/queryoptions.js b/handwritten/spanner/samples/queryoptions.js new file mode 100644 index 00000000000..95f0ea219a6 --- /dev/null +++ b/handwritten/spanner/samples/queryoptions.js @@ -0,0 +1,164 @@ +// Copyright 2020 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. + +'use strict'; + +async function databaseWithQueryOptions(instanceId, databaseId, projectId) { + // [START spanner_create_client_with_query_options] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database( + databaseId, + {}, + { + optimizerVersion: '1', + // The list of available statistics packages can be found by querying the + // "INFORMATION_SCHEMA.SPANNER_STATISTICS" table. + optimizerStatisticsPackage: 'latest', + }, + ); + + const query = { + sql: `SELECT AlbumId, AlbumTitle, MarketingBudget + FROM Albums + ORDER BY AlbumTitle`, + }; + + // Queries rows from the Albums table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + const marketingBudget = json.MarketingBudget + ? json.MarketingBudget + : null; // This value is nullable + console.log( + `AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}, MarketingBudget: ${marketingBudget}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_create_client_with_query_options] +} + +async function queryWithQueryOptions(instanceId, databaseId, projectId) { + // [START spanner_query_with_query_options] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: `SELECT AlbumId, AlbumTitle, MarketingBudget + FROM Albums + ORDER BY AlbumTitle`, + queryOptions: { + optimizerVersion: 'latest', + // The list of available statistics packages can be found by querying the + // "INFORMATION_SCHEMA.SPANNER_STATISTICS" table. + optimizerStatisticsPackage: 'latest', + }, + }; + + // Queries rows from the Albums table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + const marketingBudget = json.MarketingBudget + ? json.MarketingBudget + : null; // This value is nullable + console.log( + `AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}, MarketingBudget: ${marketingBudget}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_with_query_options] +} + +require('yargs') + .demand(1) + .command( + 'databaseWithQueryOptions ', + 'Gets a database reference with default query options and executes a query', + {}, + opts => + databaseWithQueryOptions( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'queryWithQueryOptions ', + 'Executes a query using specific query options', + {}, + opts => + queryWithQueryOptions( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .example( + 'node $0 databaseWithQueryOptions "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 queryWithQueryOptions "my-instance" "my-database" "my-project-id"', + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/quickstart.js b/handwritten/spanner/samples/quickstart.js new file mode 100644 index 00000000000..46b76e71646 --- /dev/null +++ b/handwritten/spanner/samples/quickstart.js @@ -0,0 +1,46 @@ +// Copyright 2016 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. + +'use strict'; + +async function quickstart( + projectId = 'YOUR-PROJECT-ID', // Your Google Cloud Platform project ID + instanceId = 'my-instance', // Your Cloud Spanner instance ID + databaseId = 'my-database', // Your Cloud Spanner database ID +) { + // [START spanner_quickstart] + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({projectId}); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // The query to execute + const query = { + sql: 'SELECT 1', + }; + + // Execute a simple SQL statement + const [rows] = await database.run(query); + console.log(`Query: ${rows.length} found.`); + rows.forEach(row => console.log(row)); + // [END spanner_quickstart] +} + +const args = process.argv.slice(2); +quickstart(...args).catch(console.error); diff --git a/handwritten/spanner/samples/read-data-with-database-role.js b/handwritten/spanner/samples/read-data-with-database-role.js new file mode 100644 index 00000000000..c67a96257f6 --- /dev/null +++ b/handwritten/spanner/samples/read-data-with-database-role.js @@ -0,0 +1,78 @@ +// Copyright 2022 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: Read data with database role +// usage: node read-data-with-database-role.js + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + projectId = 'my-project-id', +) { + // [START spanner_read_data_with_database_role] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const projectId = 'my-project-id'; + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function readDataWithDatabaseRole() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + // Connect to a database using the 'parent' database role. This means that the connection will only have the permissions that have explicitly been granted to the 'parent' role. + const options = { + databaseRole: 'parent', + }; + const database = instance.database(databaseId, options); + + try { + const query = { + sql: 'SELECT SingerId, FirstName, LastName FROM Singers', + }; + const [rows] = await database.run(query); + + for (const row of rows) { + const json = row.toJSON(); + + console.log( + `SingerId: ${json.SingerId}, FirstName: ${json.FirstName}, LastName: ${json.LastName}`, + ); + } + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + } + readDataWithDatabaseRole(); + // [END spanner_read_data_with_database_role] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/request-tag.js b/handwritten/spanner/samples/request-tag.js new file mode 100644 index 00000000000..f67b20668e1 --- /dev/null +++ b/handwritten/spanner/samples/request-tag.js @@ -0,0 +1,64 @@ +/** + * Copyright 2021 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: Sets a request tag for a single query +// usage: node request-tag.js + +'use strict'; + +function main(instanceId, databaseId, projectId) { + // [START spanner_set_request_tag] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function queryTags() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Execute a query with a request tag. + const [albums] = await database.run({ + sql: 'SELECT SingerId, AlbumId, AlbumTitle FROM Albums', + requestOptions: {requestTag: 'app=concert,env=dev,action=select'}, + json: true, + }); + albums.forEach(album => { + console.log( + `SingerId: ${album.SingerId}, AlbumId: ${album.AlbumId}, AlbumTitle: ${album.AlbumTitle}`, + ); + }); + await database.close(); + } + queryTags(); + // [END spanner_set_request_tag] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/resource/README.md b/handwritten/spanner/samples/resource/README.md new file mode 100644 index 00000000000..980e6a3d733 --- /dev/null +++ b/handwritten/spanner/samples/resource/README.md @@ -0,0 +1,8 @@ +#### To generate singer.js and singer.d.ts file from singer.proto +```shell +npm install -g protobufjs-cli +cd samples/resource +pbjs -t static-module -w commonjs -o singer.js singer.proto +pbts -o singer.d.ts singer.js +protoc --proto_path=. --include_imports --descriptor_set_out=descriptors.pb singer.proto +``` diff --git a/handwritten/spanner/samples/resource/descriptors.pb b/handwritten/spanner/samples/resource/descriptors.pb new file mode 100644 index 00000000000..10193075ede Binary files /dev/null and b/handwritten/spanner/samples/resource/descriptors.pb differ diff --git a/handwritten/spanner/samples/resource/singer.d.ts b/handwritten/spanner/samples/resource/singer.d.ts new file mode 100644 index 00000000000..07913d33bea --- /dev/null +++ b/handwritten/spanner/samples/resource/singer.d.ts @@ -0,0 +1,175 @@ +// Copyright 2024 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. + +import * as $protobuf from 'protobufjs'; +import Long = require('long'); +/** Namespace examples. */ +export namespace examples { + /** Namespace spanner. */ + namespace spanner { + /** Namespace music. */ + namespace music { + /** Properties of a SingerInfo. */ + interface ISingerInfo { + /** SingerInfo singerId */ + singerId?: number | Long | null; + + /** SingerInfo birthDate */ + birthDate?: string | null; + + /** SingerInfo nationality */ + nationality?: string | null; + + /** SingerInfo genre */ + genre?: examples.spanner.music.Genre | null; + } + + /** Represents a SingerInfo. */ + class SingerInfo implements ISingerInfo { + /** + * Constructs a new SingerInfo. + * @param [properties] Properties to set + */ + constructor(properties?: examples.spanner.music.ISingerInfo); + + /** SingerInfo singerId. */ + public singerId?: number | Long | null; + + /** SingerInfo birthDate. */ + public birthDate?: string | null; + + /** SingerInfo nationality. */ + public nationality?: string | null; + + /** SingerInfo genre. */ + public genre?: examples.spanner.music.Genre | null; + + /** SingerInfo _singerId. */ + public _singerId?: 'singerId'; + + /** SingerInfo _birthDate. */ + public _birthDate?: 'birthDate'; + + /** SingerInfo _nationality. */ + public _nationality?: 'nationality'; + + /** SingerInfo _genre. */ + public _genre?: 'genre'; + + /** + * Creates a new SingerInfo instance using the specified properties. + * @param [properties] Properties to set + * @returns SingerInfo instance + */ + public static create( + properties?: examples.spanner.music.ISingerInfo, + ): examples.spanner.music.SingerInfo; + + /** + * Encodes the specified SingerInfo message. Does not implicitly {@link examples.spanner.music.SingerInfo.verify|verify} messages. + * @param message SingerInfo message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode( + message: examples.spanner.music.ISingerInfo, + writer?: $protobuf.Writer, + ): $protobuf.Writer; + + /** + * Encodes the specified SingerInfo message, length delimited. Does not implicitly {@link examples.spanner.music.SingerInfo.verify|verify} messages. + * @param message SingerInfo message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited( + message: examples.spanner.music.ISingerInfo, + writer?: $protobuf.Writer, + ): $protobuf.Writer; + + /** + * Decodes a SingerInfo message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns SingerInfo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode( + reader: $protobuf.Reader | Uint8Array, + length?: number, + ): examples.spanner.music.SingerInfo; + + /** + * Decodes a SingerInfo message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns SingerInfo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited( + reader: $protobuf.Reader | Uint8Array, + ): examples.spanner.music.SingerInfo; + + /** + * Verifies a SingerInfo message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: {[k: string]: any}): string | null; + + /** + * Creates a SingerInfo message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns SingerInfo + */ + public static fromObject(object: { + [k: string]: any; + }): examples.spanner.music.SingerInfo; + + /** + * Creates a plain object from a SingerInfo message. Also converts values to other types if specified. + * @param message SingerInfo + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject( + message: examples.spanner.music.SingerInfo, + options?: $protobuf.IConversionOptions, + ): {[k: string]: any}; + + /** + * Converts this SingerInfo to JSON. + * @returns JSON object + */ + public toJSON(): {[k: string]: any}; + + /** + * Gets the default type url for SingerInfo + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Genre enum. */ + enum Genre { + POP = 0, + JAZZ = 1, + FOLK = 2, + ROCK = 3, + } + } + } +} diff --git a/handwritten/spanner/samples/resource/singer.js b/handwritten/spanner/samples/resource/singer.js new file mode 100644 index 00000000000..df7792d17a0 --- /dev/null +++ b/handwritten/spanner/samples/resource/singer.js @@ -0,0 +1,505 @@ +// Copyright 2024 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. + +/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars*/ +'use strict'; + +var $protobuf = require('protobufjs/minimal'); + +// Common aliases +var $Reader = $protobuf.Reader, + $Writer = $protobuf.Writer, + $util = $protobuf.util; + +// Exported root namespace +var $root = $protobuf.roots['default'] || ($protobuf.roots['default'] = {}); + +$root.examples = (function () { + /** + * Namespace examples. + * @exports examples + * @namespace + */ + var examples = {}; + + examples.spanner = (function () { + /** + * Namespace spanner. + * @memberof examples + * @namespace + */ + var spanner = {}; + + spanner.music = (function () { + /** + * Namespace music. + * @memberof examples.spanner + * @namespace + */ + var music = {}; + + music.SingerInfo = (function () { + /** + * Properties of a SingerInfo. + * @memberof examples.spanner.music + * @interface ISingerInfo + * @property {number|Long|null} [singerId] SingerInfo singerId + * @property {string|null} [birthDate] SingerInfo birthDate + * @property {string|null} [nationality] SingerInfo nationality + * @property {examples.spanner.music.Genre|null} [genre] SingerInfo genre + */ + + /** + * Constructs a new SingerInfo. + * @memberof examples.spanner.music + * @classdesc Represents a SingerInfo. + * @implements ISingerInfo + * @constructor + * @param {examples.spanner.music.ISingerInfo=} [properties] Properties to set + */ + function SingerInfo(properties) { + if (properties) + for ( + var keys = Object.keys(properties), i = 0; + i < keys.length; + ++i + ) + if (properties[keys[i]] !== null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * SingerInfo singerId. + * @member {number|Long|null|undefined} singerId + * @memberof examples.spanner.music.SingerInfo + * @instance + */ + SingerInfo.prototype.singerId = null; + + /** + * SingerInfo birthDate. + * @member {string|null|undefined} birthDate + * @memberof examples.spanner.music.SingerInfo + * @instance + */ + SingerInfo.prototype.birthDate = null; + + /** + * SingerInfo nationality. + * @member {string|null|undefined} nationality + * @memberof examples.spanner.music.SingerInfo + * @instance + */ + SingerInfo.prototype.nationality = null; + + /** + * SingerInfo genre. + * @member {examples.spanner.music.Genre|null|undefined} genre + * @memberof examples.spanner.music.SingerInfo + * @instance + */ + SingerInfo.prototype.genre = null; + + // OneOf field names bound to virtual getters and setters + var $oneOfFields; + + /** + * SingerInfo _singerId. + * @member {"singerId"|undefined} _singerId + * @memberof examples.spanner.music.SingerInfo + * @instance + */ + Object.defineProperty(SingerInfo.prototype, '_singerId', { + get: $util.oneOfGetter(($oneOfFields = ['singerId'])), + set: $util.oneOfSetter($oneOfFields), + }); + + /** + * SingerInfo _birthDate. + * @member {"birthDate"|undefined} _birthDate + * @memberof examples.spanner.music.SingerInfo + * @instance + */ + Object.defineProperty(SingerInfo.prototype, '_birthDate', { + get: $util.oneOfGetter(($oneOfFields = ['birthDate'])), + set: $util.oneOfSetter($oneOfFields), + }); + + /** + * SingerInfo _nationality. + * @member {"nationality"|undefined} _nationality + * @memberof examples.spanner.music.SingerInfo + * @instance + */ + Object.defineProperty(SingerInfo.prototype, '_nationality', { + get: $util.oneOfGetter(($oneOfFields = ['nationality'])), + set: $util.oneOfSetter($oneOfFields), + }); + + /** + * SingerInfo _genre. + * @member {"genre"|undefined} _genre + * @memberof examples.spanner.music.SingerInfo + * @instance + */ + Object.defineProperty(SingerInfo.prototype, '_genre', { + get: $util.oneOfGetter(($oneOfFields = ['genre'])), + set: $util.oneOfSetter($oneOfFields), + }); + + /** + * Creates a new SingerInfo instance using the specified properties. + * @function create + * @memberof examples.spanner.music.SingerInfo + * @static + * @param {examples.spanner.music.ISingerInfo=} [properties] Properties to set + * @returns {examples.spanner.music.SingerInfo} SingerInfo instance + */ + SingerInfo.create = function create(properties) { + return new SingerInfo(properties); + }; + + /** + * Encodes the specified SingerInfo message. Does not implicitly {@link examples.spanner.music.SingerInfo.verify|verify} messages. + * @function encode + * @memberof examples.spanner.music.SingerInfo + * @static + * @param {examples.spanner.music.ISingerInfo} message SingerInfo message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + SingerInfo.encode = function encode(message, writer) { + if (!writer) writer = $Writer.create(); + if ( + message.singerId !== null && + Object.hasOwnProperty.call(message, 'singerId') + ) + writer.uint32(/* id 1, wireType 0 =*/ 8).int64(message.singerId); + if ( + message.birthDate !== null && + Object.hasOwnProperty.call(message, 'birthDate') + ) + writer.uint32(/* id 2, wireType 2 =*/ 18).string(message.birthDate); + if ( + message.nationality !== null && + Object.hasOwnProperty.call(message, 'nationality') + ) + writer + .uint32(/* id 3, wireType 2 =*/ 26) + .string(message.nationality); + if ( + message.genre !== null && + Object.hasOwnProperty.call(message, 'genre') + ) + writer.uint32(/* id 4, wireType 0 =*/ 32).int32(message.genre); + return writer; + }; + + /** + * Encodes the specified SingerInfo message, length delimited. Does not implicitly {@link examples.spanner.music.SingerInfo.verify|verify} messages. + * @function encodeDelimited + * @memberof examples.spanner.music.SingerInfo + * @static + * @param {examples.spanner.music.ISingerInfo} message SingerInfo message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + SingerInfo.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a SingerInfo message from the specified reader or buffer. + * @function decode + * @memberof examples.spanner.music.SingerInfo + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {examples.spanner.music.SingerInfo} SingerInfo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + SingerInfo.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, + message = new $root.examples.spanner.music.SingerInfo(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.singerId = reader.int64(); + break; + } + case 2: { + message.birthDate = reader.string(); + break; + } + case 3: { + message.nationality = reader.string(); + break; + } + case 4: { + message.genre = reader.int32(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a SingerInfo message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof examples.spanner.music.SingerInfo + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {examples.spanner.music.SingerInfo} SingerInfo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + SingerInfo.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a SingerInfo message. + * @function verify + * @memberof examples.spanner.music.SingerInfo + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + SingerInfo.verify = function verify(message) { + if (typeof message !== 'object' || message === null) + return 'object expected'; + var properties = {}; + if (message.singerId !== null && message.hasOwnProperty('singerId')) { + properties._singerId = 1; + if ( + !$util.isInteger(message.singerId) && + !( + message.singerId && + $util.isInteger(message.singerId.low) && + $util.isInteger(message.singerId.high) + ) + ) + return 'singerId: integer|Long expected'; + } + if ( + message.birthDate !== null && + message.hasOwnProperty('birthDate') + ) { + properties._birthDate = 1; + if (!$util.isString(message.birthDate)) + return 'birthDate: string expected'; + } + if ( + message.nationality !== null && + message.hasOwnProperty('nationality') + ) { + properties._nationality = 1; + if (!$util.isString(message.nationality)) + return 'nationality: string expected'; + } + if (message.genre !== null && message.hasOwnProperty('genre')) { + properties._genre = 1; + switch (message.genre) { + default: + return 'genre: enum value expected'; + case 0: + case 1: + case 2: + case 3: + break; + } + } + return null; + }; + + /** + * Creates a SingerInfo message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof examples.spanner.music.SingerInfo + * @static + * @param {Object.} object Plain object + * @returns {examples.spanner.music.SingerInfo} SingerInfo + */ + SingerInfo.fromObject = function fromObject(object) { + if (object instanceof $root.examples.spanner.music.SingerInfo) + return object; + var message = new $root.examples.spanner.music.SingerInfo(); + if (object.singerId !== null) + if ($util.Long) + (message.singerId = $util.Long.fromValue( + object.singerId, + )).unsigned = false; + else if (typeof object.singerId === 'string') + message.singerId = parseInt(object.singerId, 10); + else if (typeof object.singerId === 'number') + message.singerId = object.singerId; + else if (typeof object.singerId === 'object') + message.singerId = new $util.LongBits( + object.singerId.low >>> 0, + object.singerId.high >>> 0, + ).toNumber(); + if (object.birthDate !== null) + message.birthDate = String(object.birthDate); + if (object.nationality !== null) + message.nationality = String(object.nationality); + switch (object.genre) { + default: + if (typeof object.genre === 'number') { + message.genre = object.genre; + break; + } + break; + case 'POP': + case 0: + message.genre = 0; + break; + case 'JAZZ': + case 1: + message.genre = 1; + break; + case 'FOLK': + case 2: + message.genre = 2; + break; + case 'ROCK': + case 3: + message.genre = 3; + break; + } + return message; + }; + + /** + * Creates a plain object from a SingerInfo message. Also converts values to other types if specified. + * @function toObject + * @memberof examples.spanner.music.SingerInfo + * @static + * @param {examples.spanner.music.SingerInfo} message SingerInfo + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + SingerInfo.toObject = function toObject(message, options) { + if (!options) options = {}; + var object = {}; + if (message.singerId !== null && message.hasOwnProperty('singerId')) { + if (typeof message.singerId === 'number') + object.singerId = + options.longs === String + ? String(message.singerId) + : message.singerId; + else + object.singerId = + options.longs === String + ? $util.Long.prototype.toString.call(message.singerId) + : options.longs === Number + ? new $util.LongBits( + message.singerId.low >>> 0, + message.singerId.high >>> 0, + ).toNumber() + : message.singerId; + if (options.oneofs) object._singerId = 'singerId'; + } + if ( + message.birthDate !== null && + message.hasOwnProperty('birthDate') + ) { + object.birthDate = message.birthDate; + if (options.oneofs) object._birthDate = 'birthDate'; + } + if ( + message.nationality !== null && + message.hasOwnProperty('nationality') + ) { + object.nationality = message.nationality; + if (options.oneofs) object._nationality = 'nationality'; + } + if (message.genre !== null && message.hasOwnProperty('genre')) { + object.genre = + options.enums === String + ? $root.examples.spanner.music.Genre[message.genre] === + undefined + ? message.genre + : $root.examples.spanner.music.Genre[message.genre] + : message.genre; + if (options.oneofs) object._genre = 'genre'; + } + return object; + }; + + /** + * Converts this SingerInfo to JSON. + * @function toJSON + * @memberof examples.spanner.music.SingerInfo + * @instance + * @returns {Object.} JSON object + */ + SingerInfo.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for SingerInfo + * @function getTypeUrl + * @memberof examples.spanner.music.SingerInfo + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + SingerInfo.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = 'type.googleapis.com'; + } + return typeUrlPrefix + '/examples.spanner.music.SingerInfo'; + }; + + return SingerInfo; + })(); + + /** + * Genre enum. + * @name examples.spanner.music.Genre + * @enum {number} + * @property {number} POP=0 POP value + * @property {number} JAZZ=1 JAZZ value + * @property {number} FOLK=2 FOLK value + * @property {number} ROCK=3 ROCK value + */ + music.Genre = (function () { + var valuesById = {}, + values = Object.create(valuesById); + values[(valuesById[0] = 'POP')] = 0; + values[(valuesById[1] = 'JAZZ')] = 1; + values[(valuesById[2] = 'FOLK')] = 2; + values[(valuesById[3] = 'ROCK')] = 3; + return values; + })(); + + return music; + })(); + + return spanner; + })(); + + return examples; +})(); + +module.exports = $root; diff --git a/handwritten/spanner/samples/resource/singer.proto b/handwritten/spanner/samples/resource/singer.proto new file mode 100644 index 00000000000..adc79d18c3e --- /dev/null +++ b/handwritten/spanner/samples/resource/singer.proto @@ -0,0 +1,31 @@ +// 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. + +syntax = "proto3"; + +package examples.spanner.music; + +message SingerInfo { + optional int64 singer_id = 1; + optional string birth_date = 2; + optional string nationality = 3; + optional Genre genre = 4; +} + +enum Genre { + POP = 0; + JAZZ = 1; + FOLK = 2; + ROCK = 3; +} diff --git a/handwritten/spanner/samples/rpc-priority-batch-dml.js b/handwritten/spanner/samples/rpc-priority-batch-dml.js new file mode 100644 index 00000000000..5fa4008bcbe --- /dev/null +++ b/handwritten/spanner/samples/rpc-priority-batch-dml.js @@ -0,0 +1,83 @@ +// Copyright 2022 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: Run Batch update with RPC priority +// usage: node rpc-priority-batch-dml.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_rpc_priority_batch_dml] + // Imports the Google Cloud client library. + const {Spanner, protos} = require('@google-cloud/spanner'); + const Priority = protos.google.spanner.v1.RequestOptions.Priority; + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function updateUsingBatchDmlWithRpcPriority(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const insert = { + sql: `INSERT INTO Albums (SingerId, AlbumId, AlbumTitle, MarketingBudget) + VALUES (1, 4, "Test Album Title", 10000)`, + }; + + const update = { + sql: `UPDATE Albums SET MarketingBudget = MarketingBudget * 2 + WHERE SingerId = 1 and AlbumId = 4`, + }; + + const dmlStatements = [insert, update]; + + try { + await database.runTransactionAsync(async transaction => { + const [rowCounts] = await transaction.batchUpdate(dmlStatements, { + requestOptions: { + priority: Priority.PRIORITY_LOW, + }, + }); + await transaction.commit(); + console.log( + `Successfully executed ${rowCounts.length} SQL statements using Batch DML using low RPC priority.`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + throw err; + } finally { + // Close the database when finished. + database.close(); + } + } + await updateUsingBatchDmlWithRpcPriority(instanceId, databaseId); + // [END spanner_rpc_priority_batch_dml] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/rpc-priority-partitioned-dml.js b/handwritten/spanner/samples/rpc-priority-partitioned-dml.js new file mode 100644 index 00000000000..48b2b6f4bef --- /dev/null +++ b/handwritten/spanner/samples/rpc-priority-partitioned-dml.js @@ -0,0 +1,71 @@ +// Copyright 2022 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: Run partitioned update with RPC priority +// usage: node rpc-priority-partitioned-dml.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_rpc_priority_partitioned_dml] + // Imports the Google Cloud client library. + const {Spanner, protos} = require('@google-cloud/spanner'); + const Priority = protos.google.spanner.v1.RequestOptions.Priority; + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function updateUsingPartitionedDmlWithRpcPriority( + instanceId, + databaseId, + ) { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + try { + const [rowCount] = await database.runPartitionedUpdate({ + sql: 'UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId = 1', + requestOptions: { + priority: Priority.PRIORITY_LOW, + }, + }); + console.log( + `Successfully updated ${rowCount} records using low RPC priority.`, + ); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + await updateUsingPartitionedDmlWithRpcPriority(instanceId, databaseId); + // [END spanner_rpc_priority_partitioned_dml] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/rpc-priority-query-partitions.js b/handwritten/spanner/samples/rpc-priority-query-partitions.js new file mode 100644 index 00000000000..5eb3a440ed1 --- /dev/null +++ b/handwritten/spanner/samples/rpc-priority-query-partitions.js @@ -0,0 +1,93 @@ +// Copyright 2022 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: Create partitions with RPC priority +// usage: node rpc-priority-query-partitions.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_rpc_priority_query_partitions] + // Imports the Google Cloud client library. + const {Spanner, protos} = require('@google-cloud/spanner'); + const Priority = protos.google.spanner.v1.RequestOptions.Priority; + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function createAndExecuteQueryPartitionsWithRpcPriority( + instanceId, + databaseId, + ) { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + const [transaction] = await database.createBatchTransaction(); + + const [partitions] = await transaction.createQueryPartitions({ + sql: 'SELECT * FROM Singers', + requestOptions: { + priority: Priority.PRIORITY_LOW, + }, + }); + console.log( + `Successfully created ${partitions.length} query partitions using low RPC priority.`, + ); + + let row_count = 0; + const promises = []; + partitions.forEach(partition => { + // Partition object inherits priority from createQueryPartitions call. + // Priority for a partition can also be assigned or changed while executing partition. + partition.requestOptions = { + priority: Priority.PRIORITY_MEDIUM, + }; + + promises.push( + transaction.execute(partition).then(results => { + const rows = results[0].map(row => row.toJSON()); + row_count += rows.length; + }), + ); + }); + Promise.all(promises) + .then(() => { + console.log( + `Successfully received ${row_count} from executed partitions.`, + ); + transaction.close(); + }) + .then(() => { + database.close(); + }); + } + + createAndExecuteQueryPartitionsWithRpcPriority(instanceId, databaseId); + // [END spanner_rpc_priority_query_partitions] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/rpc-priority-read.js b/handwritten/spanner/samples/rpc-priority-read.js new file mode 100644 index 00000000000..6c346c1acae --- /dev/null +++ b/handwritten/spanner/samples/rpc-priority-read.js @@ -0,0 +1,85 @@ +// Copyright 2022 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: Read data with RPC Priority +// usage: node rpc-priority-read.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_rpc_priority_read] + // Imports the Google Cloud client library. + const {Spanner, protos} = require('@google-cloud/spanner'); + const Priority = protos.google.spanner.v1.RequestOptions.Priority; + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function readDataWithRpcPriority(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const albumsTable = database.table('Albums'); + + const query = { + columns: ['SingerId', 'AlbumId', 'AlbumTitle'], + keySet: { + all: true, + }, + requestOptions: { + priority: Priority.PRIORITY_LOW, + }, + }; + + // Reads the Albums table using an index + try { + const [rows] = await albumsTable.read(query); + + console.log( + `Successfully fetched ${rows.length} rows using low RPC priority.`, + ); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `SingerId: ${json.SingerId}, AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + } + readDataWithRpcPriority(instanceId, databaseId); + // [END spanner_rpc_priority_read] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/rpc-priority-run.js b/handwritten/spanner/samples/rpc-priority-run.js new file mode 100644 index 00000000000..c0833537098 --- /dev/null +++ b/handwritten/spanner/samples/rpc-priority-run.js @@ -0,0 +1,83 @@ +// Copyright 2021 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: Query data with RPC Priority +// usage: node rpc-priority-run.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_rpc_priority_run] + // Imports the Google Cloud client library. + const {Spanner, protos} = require('@google-cloud/spanner'); + const Priority = protos.google.spanner.v1.RequestOptions.Priority; + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function queryWithRpcPriority(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const sql = `SELECT AlbumId, AlbumTitle, MarketingBudget + FROM Albums + ORDER BY AlbumTitle`; + + try { + // Execute a query with low priority. Note that the default for all + // requests is PRIORITY_HIGH, and that this option can only be used to + // reduce the priority of a request. + const [rows] = await database.run({ + sql, + requestOptions: { + priority: Priority.PRIORITY_LOW, + }, + json: true, + }); + + console.log( + `Successfully fetched ${rows.length} rows using low RPC priority.`, + ); + + rows.forEach(row => { + console.log( + `AlbumId: ${row.AlbumId}, AlbumTitle: ${row.AlbumTitle}, MarketingBudget: ${row.MarketingBudget}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + } + await queryWithRpcPriority(instanceId, databaseId); + // [END spanner_rpc_priority_run] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/rpc-priority-transaction.js b/handwritten/spanner/samples/rpc-priority-transaction.js new file mode 100644 index 00000000000..eb609d88697 --- /dev/null +++ b/handwritten/spanner/samples/rpc-priority-transaction.js @@ -0,0 +1,81 @@ +// Copyright 2022 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: Run transaction with RPC priority +// usage: node rpc-priority-transaction.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_rpc_priority_transaction] + // Imports the Google Cloud client library. + const {Spanner, protos} = require('@google-cloud/spanner'); + const Priority = protos.google.spanner.v1.RequestOptions.Priority; + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + function transactionWithRpcPriority(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rowCount] = await transaction.runUpdate({ + sql: 'INSERT Singers (SingerId, FirstName, LastName) VALUES (17, @firstName, @lastName)', + params: { + firstName: 'Virginia', + lastName: 'Watson', + }, + }); + + await transaction.commit({ + priority: Priority.PRIORITY_LOW, + }); + + console.log( + `Successfully inserted ${rowCount} record into the Singers table using low RPC priority.`, + ); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + }); + } + transactionWithRpcPriority(instanceId, databaseId); + // [END spanner_rpc_priority_transaction] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/schema.js b/handwritten/spanner/samples/schema.js new file mode 100644 index 00000000000..8bc6b53db67 --- /dev/null +++ b/handwritten/spanner/samples/schema.js @@ -0,0 +1,247 @@ +/** + * Copyright 2024 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. + */ + +'use strict'; + +// creates a database using Database Admin Client +async function createDatabase(instanceID, databaseID, projectID) { + // [START spanner_create_database] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectID, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const createSingersTableStatement = ` + 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`; + + // Creates a new database + try { + const [operation] = await databaseAdminClient.createDatabase({ + createStatement: 'CREATE DATABASE `' + databaseID + '`', + extraStatements: [ + createSingersTableStatement, + createAlbumsTableStatement, + ], + parent: databaseAdminClient.instancePath(projectID, instanceID), + }); + + console.log(`Waiting for creation of ${databaseID} to complete...`); + await operation.promise(); + + console.log(`Created database ${databaseID} on instance ${instanceID}.`); + } catch (err) { + console.error('ERROR:', err); + } + + // [END spanner_create_database] +} + +async function addColumn(instanceId, databaseId, projectId) { + // [START spanner_add_column] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + // Creates a new index in the database + try { + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: ['ALTER TABLE Albums ADD COLUMN MarketingBudget INT64'], + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log('Added the MarketingBudget column.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + + // [END spanner_add_column] +} + +async function queryDataWithNewColumn(instanceId, databaseId, projectId) { + // [START spanner_query_data_with_new_column] + // This sample uses the `MarketingBudget` column. You can add the column + // by running the `add_column` sample or by running this DDL statement against + // your database: + // ALTER TABLE Albums ADD COLUMN MarketingBudget INT64 + + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: 'SELECT SingerId, AlbumId, MarketingBudget FROM Albums', + }; + + // Queries rows from the Albums table + try { + const [rows] = await database.run(query); + + rows.forEach(async row => { + const json = row.toJSON(); + + console.log( + `SingerId: ${json.SingerId}, AlbumId: ${ + json.AlbumId + }, MarketingBudget: ${ + json.MarketingBudget ? json.MarketingBudget : null + }`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_data_with_new_column] +} + +const { + createDatabaseWithVersionRetentionPeriod, +} = require('./database-create-with-version-retention-period'); + +const { + createDatabaseWithEncryptionKey, +} = require('./database-create-with-encryption-key'); + +require('yargs') + .demand(1) + .command( + 'createDatabase ', + 'Creates an example database with two tables in a Cloud Spanner instance using Database Admin Client.', + {}, + opts => + createDatabase(opts.instanceName, opts.databaseName, opts.projectId), + ) + .example('node $0 createDatabase "my-instance" "my-database" "my-project-id"') + .command( + 'addColumn ', + 'Adds an example MarketingBudget column to an example Cloud Spanner table.', + {}, + opts => addColumn(opts.instanceName, opts.databaseName, opts.projectId), + ) + .example('node $0 addColumn "my-instance" "my-database" "my-project-id"') + .command( + 'queryNewColumn ', + 'Executes a read-only SQL query against an example Cloud Spanner table with an additional column (MarketingBudget) added by addColumn.', + {}, + opts => + queryDataWithNewColumn( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .example('node $0 queryNewColumn "my-instance" "my-database" "my-project-id"') + .command( + 'createDatabaseWithVersionRetentionPeriod ', + 'Creates a database with a version retention period.', + {}, + opts => + createDatabaseWithVersionRetentionPeriod( + opts.instanceName, + opts.databaseId, + opts.projectId, + ), + ) + .example( + 'node $0 createDatabaseWithVersionRetentionPeriod "my-instance" "my-database-id" "my-project-id"', + ) + .command( + 'createDatabaseWithEncryptionKey ', + 'Creates an example database using given encryption key in a Cloud Spanner instance.', + {}, + opts => + createDatabaseWithEncryptionKey( + opts.instanceName, + opts.databaseName, + opts.projectId, + opts.keyName, + ), + ) + .example( + 'node $0 createDatabaseWithEncryptionKey "my-instance" "my-database" "my-project-id" "key-name"', + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/sequence-alter.js b/handwritten/spanner/samples/sequence-alter.js new file mode 100644 index 00000000000..fa3a77758e2 --- /dev/null +++ b/handwritten/spanner/samples/sequence-alter.js @@ -0,0 +1,109 @@ +// Copyright 2024 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 sequence in a GoogleSQL database. +// usage: node sequence-alter.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_alter_sequence] + + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function alterSequence(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const request = [ + 'ALTER SEQUENCE Seq SET OPTIONS (skip_range_min = 1000, skip_range_max = 5000000)', + ]; + + try { + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log( + 'Altered Seq sequence to skip an inclusive range between 1000 and 5000000.', + ); + } catch (err) { + console.error('ERROR:', err); + } + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: "INSERT INTO Customers (CustomerName) VALUES ('Lea'), ('Catalina'), ('Smith') THEN RETURN CustomerId", + }); + + rows.forEach(row => { + console.log( + `Inserted customer record with CustomerId: ${ + row.toJSON({wrapNumbers: true}).CustomerId.value + }`, + ); + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log(`Number of customer records inserted is: ${rowCount}`); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + }); + } + await alterSequence(instanceId, databaseId); + // [END spanner_alter_sequence] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/sequence-create.js b/handwritten/spanner/samples/sequence-create.js new file mode 100644 index 00000000000..329adc26792 --- /dev/null +++ b/handwritten/spanner/samples/sequence-create.js @@ -0,0 +1,110 @@ +// Copyright 2024 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 sequence in GoogleSQL database. +// usage: node sequence-create.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_create_sequence] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function createSequence(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const request = [ + "CREATE SEQUENCE Seq OPTIONS (sequence_kind = 'bit_reversed_positive')", + 'CREATE TABLE Customers (CustomerId INT64 DEFAULT (GET_NEXT_SEQUENCE_VALUE(Sequence Seq)), CustomerName STRING(1024)) PRIMARY KEY (CustomerId)', + ]; + + // Creates a new table with sequence + try { + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log( + 'Created Seq sequence and Customers table, where the key column CustomerId uses the sequence as a default value.', + ); + } catch (err) { + console.error('ERROR:', err); + } + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + try { + const [rows, stats] = await transaction.run({ + sql: "INSERT INTO Customers (CustomerName) VALUES ('Alice'), ('David'), ('Marc') THEN RETURN CustomerId", + }); + + rows.forEach(row => { + console.log( + `Inserted customer record with CustomerId: ${ + row.toJSON({wrapNumbers: true}).CustomerId.value + }`, + ); + }); + + const rowCount = Math.floor(stats[stats.rowCount]); + console.log(`Number of customer records inserted is: ${rowCount}`); + + await transaction.commit(); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + }); + } + await createSequence(instanceId, databaseId); + // [END spanner_create_sequence] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/sequence-drop.js b/handwritten/spanner/samples/sequence-drop.js new file mode 100644 index 00000000000..c07f8fa8466 --- /dev/null +++ b/handwritten/spanner/samples/sequence-drop.js @@ -0,0 +1,80 @@ +// Copyright 2024 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 sequence in GoogleSQL database. +// usage: node sequence-drop.js + +'use strict'; + +async function main(instanceId, databaseId, projectId) { + // [START spanner_drop_sequence] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function dropSequence(instanceId, databaseId) { + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const request = [ + 'ALTER TABLE Customers ALTER COLUMN CustomerId DROP DEFAULT', + 'DROP SEQUENCE Seq', + ]; + + // Drop sequence from DDL + try { + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + await operation.promise(); + + console.log( + 'Altered Customers table to drop DEFAULT from CustomerId column and dropped the Seq sequence.', + ); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + } + await dropSequence(instanceId, databaseId); + // [END spanner_drop_sequence] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/statement-timeout.js b/handwritten/spanner/samples/statement-timeout.js new file mode 100644 index 00000000000..da312747512 --- /dev/null +++ b/handwritten/spanner/samples/statement-timeout.js @@ -0,0 +1,73 @@ +/** + * Copyright 2025 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: Executes a read/write transaction with statement timeout +// usage: node statement-timeout.js + +'use strict'; + +function main(instanceId, databaseId, projectId) { + // [START spanner_set_statement_timeout] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function executeSqlWithTimeout() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + try { + await database.runTransactionAsync(async tx => { + // NOTE: You can use gaxOptions to set a custom timeout for a single RPC + // invocation. This timeout can however ONLY BE SHORTER than the default timeout + // for the RPC. If you set a timeout that is longer than the default timeout, then + // the default timeout will be used. + const query = { + sql: "INSERT INTO Singers (SingerId, FirstName, LastName) VALUES (110, 'George', 'Washington')", + gaxOptions: { + timeout: 60000, // 60 seconds timeout + }, + }; + const results = await tx.run(query); + console.log(`${results[1].rowCountExact} record inserted.`); + await tx.commit(); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + await database.close(); + } + } + executeSqlWithTimeout(); + // [END spanner_set_statement_timeout] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/struct.js b/handwritten/spanner/samples/struct.js new file mode 100644 index 00000000000..9d02efa9658 --- /dev/null +++ b/handwritten/spanner/samples/struct.js @@ -0,0 +1,414 @@ +// Copyright 2018 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. + +'use strict'; + +async function writeDataForStructQueries(instanceId, databaseId, projectId) { + // [START spanner_write_data_for_struct_queries] + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment and update the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Instantiates Spanner table objects + const singersTable = database.table('Singers'); + + // Inserts rows into the Singers table + // Note: Cloud Spanner interprets Javascript numbers as FLOAT64s. + // Use strings as shown in this example if you need INT64s. + try { + const data = [ + { + SingerId: '6', + FirstName: 'Elena', + LastName: 'Campbell', + }, + { + SingerId: '7', + FirstName: 'Gabriel', + LastName: 'Wright', + }, + { + SingerId: '8', + FirstName: 'Benjamin', + LastName: 'Martinez', + }, + { + SingerId: '9', + FirstName: 'Hannah', + LastName: 'Harris', + }, + ]; + + await singersTable.insert(data); + console.log('Inserted data.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_write_data_for_struct_queries] +} + +async function queryDataWithStruct(instanceId, databaseId, projectId) { + // [START spanner_create_struct_with_data] + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + const nameStruct = Spanner.struct({ + FirstName: 'Elena', + LastName: 'Campbell', + }); + // [END spanner_create_struct_with_data] + + // [START spanner_query_data_with_struct] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: + 'SELECT SingerId FROM Singers WHERE ' + + 'STRUCT(FirstName, LastName) = @name', + params: { + name: nameStruct, + }, + }; + + // Queries rows from the Singers table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log(`SingerId: ${json.SingerId}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_data_with_struct] +} + +async function queryWithArrayofStruct(instanceId, databaseId, projectId) { + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // [START spanner_create_user_defined_struct] + const nameType = { + type: 'struct', + fields: [ + { + name: 'FirstName', + type: 'string', + }, + { + name: 'LastName', + type: 'string', + }, + ], + }; + // [END spanner_create_user_defined_struct] + + // [START spanner_create_array_of_struct_with_data] + const bandMembersType = { + type: 'array', + child: nameType, + }; + + const bandMembers = [ + Spanner.struct({ + FirstName: 'Elena', + LastName: 'Campbell', + }), + Spanner.struct({ + FirstName: 'Gabriel', + LastName: 'Wright', + }), + Spanner.struct({ + FirstName: 'Benjamin', + LastName: 'Martinez', + }), + ]; + // [END spanner_create_array_of_struct_with_data] + + // [START spanner_query_data_with_array_of_struct] + const query = { + sql: + 'SELECT SingerId FROM Singers ' + + 'WHERE STRUCT(FirstName, LastName) ' + + 'IN UNNEST(@names) ' + + 'ORDER BY SingerId', + params: { + names: bandMembers, + }, + types: { + names: bandMembersType, + }, + }; + + // Queries rows from the Singers table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log(`SingerId: ${json.SingerId}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_query_data_with_array_of_struct] +} + +async function queryStructField(instanceId, databaseId, projectId) { + // [START spanner_field_access_on_struct_parameters] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const nameStruct = Spanner.struct({ + FirstName: 'Elena', + LastName: 'Campbell', + }); + const query = { + sql: 'SELECT SingerId FROM Singers WHERE FirstName = @name.FirstName', + params: { + name: nameStruct, + }, + }; + + // Queries rows from the Singers table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log(`SingerId: ${json.SingerId}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_field_access_on_struct_parameters] +} + +async function queryNestedStructField(instanceId, databaseId, projectId) { + // [START spanner_field_access_on_nested_struct_parameters] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const nameType = { + type: 'struct', + fields: [ + { + name: 'FirstName', + type: 'string', + }, + { + name: 'LastName', + type: 'string', + }, + ], + }; + + // Creates Song info STRUCT with a nested ArtistNames array + const songInfoType = { + type: 'struct', + fields: [ + { + name: 'SongName', + type: 'string', + }, + { + name: 'ArtistNames', + type: 'array', + child: nameType, + }, + ], + }; + + const songInfoStruct = Spanner.struct({ + SongName: 'Imagination', + ArtistNames: [ + Spanner.struct({FirstName: 'Elena', LastName: 'Campbell'}), + Spanner.struct({FirstName: 'Hannah', LastName: 'Harris'}), + ], + }); + + const query = { + sql: + 'SELECT SingerId, @songInfo.SongName FROM Singers ' + + 'WHERE STRUCT(FirstName, LastName) ' + + 'IN UNNEST(@songInfo.ArtistNames)', + params: { + songInfo: songInfoStruct, + }, + types: { + songInfo: songInfoType, + }, + }; + + // Queries rows from the Singers table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log(`SingerId: ${json.SingerId}, SongName: ${json.SongName}`); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + database.close(); + } + // [END spanner_field_access_on_nested_struct_parameters] +} + +require('yargs') + .demand(1) + .command( + 'writeDataForStructQueries ', + 'Inserts sample data to an existing table Singers.', + {}, + opts => + writeDataForStructQueries( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'queryDataWithStruct ', + 'Queries Singers table using STRUCT parameters.', + {}, + opts => + queryDataWithStruct(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithArrayOfStruct ', + 'Queries Singers table with an array of STRUCT parameters.', + {}, + opts => + queryWithArrayofStruct( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'queryStructField ', + 'Queries Singers table using a STRUCT parameter field.', + {}, + opts => + queryStructField(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryNestedStructField ', + 'Queries Singers table using a nested STRUCT parameter field.', + {}, + opts => + queryNestedStructField( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/system-test/archived/spanner.test.js b/handwritten/spanner/samples/system-test/archived/spanner.test.js new file mode 100644 index 00000000000..8fe6d9a0530 --- /dev/null +++ b/handwritten/spanner/samples/system-test/archived/spanner.test.js @@ -0,0 +1,2004 @@ +// Copyright 2024 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. + +'use strict'; + +const {Spanner} = require('@google-cloud/spanner'); +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 batchCmd = 'node batch.js'; +const crudCmd = 'node crud.js'; +const schemaCmd = 'node archived/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 archived/timestamp.js'; +const structCmd = 'node struct.js'; +const dmlCmd = 'node dml.js'; +const datatypesCmd = 'node archived/datatypes.js'; +const backupsCmd = 'node archived/backups.js'; +const instanceCmd = 'node archived/instance.js'; +const createTableWithForeignKeyDeleteCascadeCommand = + 'node archived/table-create-with-foreign-key-delete-cascade.js'; +const alterTableWithForeignKeyDeleteCascadeCommand = + 'node archived/table-alter-with-foreign-key-delete-cascade.js'; +const dropForeignKeyConstraintDeleteCascaseCommand = + 'node archived/table-drop-foreign-key-constraint-delete-cascade.js'; + +const CURRENT_TIME = Math.round(Date.now() / 1000).toString(); +const PROJECT_ID = process.env.GCLOUD_PROJECT; +const PREFIX = 'test-instance'; +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 BASE_INSTANCE_CONFIG_ID = 'regional-us-west2'; +const INSTANCE_ALREADY_EXISTS = !!process.env.SPANNERTEST_INSTANCE; +const DATABASE_ID = `test-database-${CURRENT_TIME}`; +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`; +const CANCELLED_BACKUP_ID = `test-backup-${CURRENT_TIME}-c`; +const LOCATION_ID = 'regional-us-central1'; +const PG_LOCATION_ID = 'regional-us-west2'; +const KEY_LOCATION_ID = 'us-central1'; +const KEY_RING_ID = 'test-key-ring-node'; +const KEY_ID = 'test-key'; +const DEFAULT_LEADER = 'us-central1'; +const DEFAULT_LEADER_2 = 'us-east1'; + +const spanner = new Spanner({ + projectId: PROJECT_ID, +}); +const LABEL = 'node-sample-tests'; +const GAX_OPTIONS = { + retry: { + retryCodes: [4, 8, 14], + backoffSettings: { + initialRetryDelayMillis: 1000, + retryDelayMultiplier: 1.3, + maxRetryDelayMillis: 32000, + initialRpcTimeoutMillis: 60000, + rpcTimeoutMultiplier: 1, + maxRpcTimeoutMillis: 60000, + totalTimeoutMillis: 600000, + }, + }, +}; + +const delay = async test => { + const retries = test.currentRetry(); + // No retry on the first failure. + if (retries === 0) return; + // See: https://cloud.google.com/storage/docs/exponential-backoff + const ms = Math.pow(2, retries) + Math.random() * 1000; + return new Promise(done => { + console.info(`retrying "${test.title}" in ${ms}ms`); + setTimeout(done, ms); + }); +}; + +async function deleteStaleInstances() { + let [instances] = await spanner.getInstances({ + filter: `(labels.${LABEL}:true) OR (labels.cloud_spanner_samples:true)`, + }); + const old = new Date(); + old.setHours(old.getHours() - 4); + + instances = instances.filter(instance => { + return ( + instance.metadata.labels['created'] && + new Date(parseInt(instance.metadata.labels['created']) * 1000) < old + ); + }); + const limit = pLimit(5); + await Promise.all( + instances.map(instance => + limit(() => setTimeout(deleteInstance, delay, instance)), + ), + ); +} + +async function deleteInstance(instance) { + const [backups] = await instance.getBackups(); + await Promise.all(backups.map(backup => backup.delete(GAX_OPTIONS))); + return instance.delete(GAX_OPTIONS); +} + +async function getCryptoKey() { + const NOT_FOUND = 5; + + // Instantiates a client. + const client = new KeyManagementServiceClient(); + + // Build the parent key ring name. + const keyRingName = client.keyRingPath( + PROJECT_ID, + KEY_LOCATION_ID, + KEY_RING_ID, + ); + + // Get key ring. + try { + await client.getKeyRing({name: keyRingName}); + } catch (err) { + // Create key ring if it doesn't exist. + if (err.code === NOT_FOUND) { + // Build the parent location name. + const locationName = client.locationPath(PROJECT_ID, KEY_LOCATION_ID); + await client.createKeyRing({ + parent: locationName, + keyRingId: KEY_RING_ID, + }); + } else { + throw err; + } + } + + // Get key. + try { + // Build the key name + const keyName = client.cryptoKeyPath( + PROJECT_ID, + KEY_LOCATION_ID, + KEY_RING_ID, + KEY_ID, + ); + const [key] = await client.getCryptoKey({ + name: keyName, + }); + return key; + } catch (err) { + // Create key if it doesn't exist. + if (err.code === NOT_FOUND) { + const [key] = await client.createCryptoKey({ + parent: keyRingName, + cryptoKeyId: KEY_ID, + cryptoKey: { + purpose: 'ENCRYPT_DECRYPT', + versionTemplate: { + algorithm: 'GOOGLE_SYMMETRIC_ENCRYPTION', + }, + }, + }); + return key; + } else { + throw err; + } + } +} + +describe('Spanner', () => { + const instance = spanner.instance(INSTANCE_ID); + + before(async () => { + await deleteStaleInstances(); + + if (!INSTANCE_ALREADY_EXISTS) { + const [, operation] = await instance.create({ + config: LOCATION_ID, + nodes: 1, + labels: { + [LABEL]: 'true', + created: CURRENT_TIME, + }, + gaxOptions: GAX_OPTIONS, + }); + return operation.promise(); + } else { + console.log( + `Not creating temp instance, using + ${instance.formattedName_}...`, + ); + } + }); + + after(async () => { + const instance = spanner.instance(INSTANCE_ID); + + if (!INSTANCE_ALREADY_EXISTS) { + // Make sure all backups are deleted before an instance can be deleted. + await Promise.all([ + instance.backup(BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(ENCRYPTED_BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(COPY_BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(CANCELLED_BACKUP_ID).delete(GAX_OPTIONS), + ]); + await instance.delete(GAX_OPTIONS); + } else { + await Promise.all([ + instance.database(DATABASE_ID).delete(), + instance.database(PG_DATABASE_ID).delete(), + instance.database(RESTORE_DATABASE_ID).delete(), + instance.database(ENCRYPTED_RESTORE_DATABASE_ID).delete(), + instance.backup(BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(COPY_BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(ENCRYPTED_BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(CANCELLED_BACKUP_ID).delete(GAX_OPTIONS), + ]); + } + await spanner.instance(SAMPLE_INSTANCE_ID).delete(GAX_OPTIONS); + }); + + describe('instance', () => { + afterEach(async () => { + const sample_instance = spanner.instance(SAMPLE_INSTANCE_ID); + await sample_instance.delete(); + }); + + // create_instance + it('should create an example instance', async () => { + const output = execSync( + `${instanceCmd} createInstance "${SAMPLE_INSTANCE_ID}" ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + `Waiting for operation on ${SAMPLE_INSTANCE_ID} to complete...`, + ), + ); + assert.match( + output, + new RegExp(`Created instance ${SAMPLE_INSTANCE_ID}.`), + ); + }); + + // create_instance_with_processing_units + it('should create an example instance with processing units', async () => { + const output = execSync( + `${instanceCmd} createInstanceWithProcessingUnits "${SAMPLE_INSTANCE_ID}" ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + `Waiting for operation on ${SAMPLE_INSTANCE_ID} to complete...`, + ), + ); + assert.match( + output, + new RegExp(`Created instance ${SAMPLE_INSTANCE_ID}.`), + ); + assert.match( + output, + new RegExp(`Instance ${SAMPLE_INSTANCE_ID} has 500 processing units.`), + ); + }); + }); + + // check that base instance was created + it('should have created an instance', async () => { + const [exists] = await instance.exists(); + assert.strictEqual( + exists, + true, + 'The main instance was not created successfully!', + ); + }); + + // create_database + it('should create an example database', async () => { + const output = execSync( + `${schemaCmd} createDatabase "${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 database ${DATABASE_ID} on instance ${INSTANCE_ID}.`), + ); + }); + + // update_database + it('should set database metadata', async () => { + const output = execSync( + `node database-update.js ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + `Waiting for update operation for ${DATABASE_ID} to complete...`, + ), + ); + assert.match(output, new RegExp(`Updated database ${DATABASE_ID}.`)); + // cleanup + const [operation] = await instance + .database(DATABASE_ID) + .setMetadata({enableDropProtection: false}); + await operation.promise(); + }); + + describe('encrypted database', () => { + after(async () => { + const instance = spanner.instance(INSTANCE_ID); + const encrypted_database = instance.database(ENCRYPTED_DATABASE_ID); + await encrypted_database.delete(); + }); + + // create_database_with_encryption_key + it('should create a database with an encryption key', async () => { + const key = await getCryptoKey(); + + const output = execSync( + `${schemaCmd} createDatabaseWithEncryptionKey "${INSTANCE_ID}" "${ENCRYPTED_DATABASE_ID}" ${PROJECT_ID} "${key.name}"`, + ); + assert.match( + output, + new RegExp( + `Waiting for operation on ${ENCRYPTED_DATABASE_ID} to complete...`, + ), + ); + assert.match( + output, + new RegExp( + `Created database ${ENCRYPTED_DATABASE_ID} on instance ${INSTANCE_ID}.`, + ), + ); + assert.match( + output, + new RegExp(`Database encrypted with key ${key.name}.`), + ); + }); + }); + + 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 quickstart ${PROJECT_ID} ${INSTANCE_ID} ${DATABASE_ID}`, + ); + assert.match(output, /Query: \d+ found./); + }); + }); + + // insert_data + it('should insert rows into an example table', async () => { + const output = execSync( + `${crudCmd} insert ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + 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( + `${schemaCmd} addColumn ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Waiting for operation to complete\.\.\./); + assert.match(output, /Added the MarketingBudget column\./); + }); + + // update_data + it('should update existing rows in an example table', async () => { + const output = execSync( + `${crudCmd} update ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + 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( + `${schemaCmd} queryNewColumn ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /SingerId: 1, AlbumId: 1, MarketingBudget: 100000/); + assert.match(output, /SingerId: 2, AlbumId: 2, MarketingBudget: 500000/); + }); + + // create_index + it('should create an index in an example table', async () => { + const output = execSync( + `node archived/index-create ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Waiting for operation to complete\.\.\./); + assert.match(output, /Added the AlbumsByAlbumTitle index\./); + }); + + // create_storing_index + it('should create a storing index in an example table', async function () { + this.retries(5); + // Delay the start of the test, if this is a retry. + await delay(this.test); + + const output = execSync( + `node archived/index-create-storing ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Waiting for operation to complete\.\.\./); + assert.match(output, /Added the AlbumsByAlbumTitle2 index\./); + }); + + // query_data_with_index + it('should query an example table with an index and return matching rows', async () => { + const output = execSync( + `node index-query-data ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /AlbumId: 2, AlbumTitle: Go, Go, Go, MarketingBudget:/, + ); + assert.notMatch( + output, + /AlbumId: 1, AlbumTitle: Total Junk, MarketingBudget:/, + ); + }); + + it('should respect query boundaries when querying an example table with an index', async () => { + const output = execSync( + `node index-query-data ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID} "Ardvark" "Zoo"`, + ); + assert.match( + output, + /AlbumId: 1, AlbumTitle: Total Junk, MarketingBudget:/, + ); + assert.match( + output, + /AlbumId: 2, AlbumTitle: Go, Go, Go, MarketingBudget:/, + ); + }); + + // read_data_with_index + it('should read an example table with an index', async () => { + const output = execSync( + `node index-read-data ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /AlbumId: 1, AlbumTitle: Total Junk/); + }); + + // 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/); + }); + + // spanner_create_client_with_query_options + it('should use query options from a database reference', async () => { + const output = execSync( + `${queryOptionsCmd} databaseWithQueryOptions ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget:/, + ); + }); + + // spanner_query_with_query_options + it('should use query options on request', async () => { + const output = execSync( + `${queryOptionsCmd} queryWithQueryOptions ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget:/, + ); + }); + + // query with RPC priority for run command + it('should use RPC priority from request options for run command', async () => { + const output = execSync( + `${rpcPriorityRunCommand} ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /Successfully fetched \d rows using low RPC priority\./, + ); + assert.match( + output, + /AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget:/, + ); + }); + + // 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}`, + ); + assert.match(output, /Inserted data./); + }); + + // query_with_array_parameter + it('should use an ARRAY query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithArray ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /VenueId: 19, VenueName: Venue 19, AvailableDate: 2020-11-01/, + ); + assert.match( + output, + /VenueId: 42, VenueName: Venue 42, AvailableDate: 2020-10-01/, + ); + }); + + // query_with_bool_parameter + it('should use a BOOL query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithBool ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /VenueId: 19, VenueName: Venue 19, OutdoorVenue: true/, + ); + }); + + // query_with_bytes_parameter + it('should use a BYTES query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithBytes ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /VenueId: 4, VenueName: Venue 4/); + }); + + // query_with_date_parameter + it('should use a DATE query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithDate ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /VenueId: 4, VenueName: Venue 4, LastContactDate: 2018-09-02/, + ); + assert.match( + output, + /VenueId: 42, VenueName: Venue 42, LastContactDate: 2018-10-01/, + ); + }); + + // query_with_float_parameter + it('should use a FLOAT64 query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithFloat ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /VenueId: 4, VenueName: Venue 4, PopularityScore: 0.8/, + ); + assert.match( + output, + /VenueId: 19, VenueName: Venue 19, PopularityScore: 0.9/, + ); + }); + + // query_with_int_parameter + it('should use a INT64 query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithInt ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /VenueId: 19, VenueName: Venue 19, Capacity: 6300/); + assert.match(output, /VenueId: 42, VenueName: Venue 42, Capacity: 3000/); + }); + + // query_with_string_parameter + it('should use a STRING query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithString ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /VenueId: 42, VenueName: Venue 42/); + }); + + // query_with_timestamp_parameter + it('should use a TIMESTAMP query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithTimestamp ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /VenueId: 4, VenueName: Venue 4, LastUpdateTime:/); + assert.match(output, /VenueId: 19, VenueName: Venue 19, LastUpdateTime:/); + assert.match(output, /VenueId: 42, VenueName: Venue 42, LastUpdateTime:/); + }); + + // add_numeric_column + it('should add a Revenue column to Venues example table', async () => { + const output = execSync( + `${datatypesCmd} addNumericColumn "${INSTANCE_ID}" "${DATABASE_ID}" ${PROJECT_ID}`, + ); + + assert.include( + output, + `Waiting for operation on ${DATABASE_ID} to complete...`, + ); + assert.include( + output, + `Added Revenue column to Venues table in database ${DATABASE_ID}.`, + ); + }); + + // update_data_with_numeric + it('should update rows in Venues example table to add data in Revenue column', async () => { + const output = execSync( + `${datatypesCmd} updateWithNumericData ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Updated data./); + }); + + // query_with_numeric_parameter + it('should use a NUMERIC query parameter to query records from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithNumericParameter ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + 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( + `${datatypesCmd} addJsonColumn "${INSTANCE_ID}" "${DATABASE_ID}" ${PROJECT_ID}`, + ); + + assert.include( + output, + `Waiting for operation on ${DATABASE_ID} to complete...`, + ); + assert.include( + output, + `Added VenueDetails column to Venues table in database ${DATABASE_ID}.`, + ); + }); + + // update_data_with_json + it('should update rows in Venues example table to add data in VenueDetails column', async () => { + const output = execSync( + `${datatypesCmd} updateWithJsonData ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Updated data./); + }); + + // query_with_json_parameter + it('should use a JSON query parameter to query records from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithJsonParameter ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /VenueId: 19, Details: {"open":true,"rating":9}/); + }); + + // add_and_drop_new_database_role + it('should add and drop new database roles', async () => { + const output = execSync( + `node archived/add-and-drop-new-database-role.js ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, new RegExp('Waiting for operation to complete...')); + assert.match( + output, + new RegExp('Created roles child and parent and granted privileges'), + ); + assert.match( + output, + new RegExp('Revoked privileges and dropped role child'), + ); + }); + + // 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( + `node archived/get-database-roles.js ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + `Role: projects/${PROJECT_ID}/instances/${INSTANCE_ID}/databases/${DATABASE_ID}/databaseRoles/public`, + ), + ); + }); + + // create_backup + it('should create a backup of the database', async () => { + const instance = spanner.instance(INSTANCE_ID); + const database = instance.database(DATABASE_ID); + const query = { + sql: 'SELECT CURRENT_TIMESTAMP() as Timestamp', + }; + const [rows] = await database.run(query); + const versionTime = rows[0].toJSON().Timestamp.toISOString(); + + const output = execSync( + `${backupsCmd} createBackup ${INSTANCE_ID} ${DATABASE_ID} ${BACKUP_ID} ${PROJECT_ID} ${versionTime}`, + ); + assert.match(output, new RegExp(`Backup (.+)${BACKUP_ID} of size`)); + }); + + // create_backup_with_encryption_key + it('should create an encrypted backup of the database', async () => { + const key = await getCryptoKey(); + + const output = execSync( + `${backupsCmd} createBackupWithEncryptionKey ${INSTANCE_ID} ${DATABASE_ID} ${ENCRYPTED_BACKUP_ID} ${PROJECT_ID} ${key.name}`, + ); + assert.match( + output, + new RegExp(`Backup (.+)${ENCRYPTED_BACKUP_ID} of size`), + ); + assert.include(output, `using encryption key ${key.name}`); + }); + + // copy_backup + it('should create a copy of a backup', async () => { + const sourceBackupPath = `projects/${PROJECT_ID}/instances/${INSTANCE_ID}/backups/${BACKUP_ID}`; + const output = execSync( + `node archived/backups-copy.js ${INSTANCE_ID} ${COPY_BACKUP_ID} ${sourceBackupPath} ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp(`(.*)Backup copy(.*)${COPY_BACKUP_ID} of size(.*)`), + ); + }); + + // cancel_backup + it('should cancel a backup of the database', async () => { + const output = execSync( + `${backupsCmd} cancelBackup ${INSTANCE_ID} ${DATABASE_ID} ${CANCELLED_BACKUP_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Backup cancelled./); + }); + + // get_backups + it('should list backups in the instance', async () => { + const output = execSync( + `${backupsCmd} getBackups ${INSTANCE_ID} ${DATABASE_ID} ${BACKUP_ID} ${PROJECT_ID}`, + ); + assert.include(output, 'All backups:'); + assert.include(output, 'Backups matching backup name:'); + assert.include(output, 'Backups expiring within 30 days:'); + assert.include(output, 'Backups matching database name:'); + assert.include(output, 'Backups filtered by size:'); + assert.include(output, 'Ready backups filtered by create time:'); + assert.include(output, 'Get backups paginated:'); + const count = (output.match(new RegExp(`${BACKUP_ID}`, 'g')) || []).length; + assert.equal(count, 14); + }); + + // list_backup_operations + it('should list backup operations in the instance', async () => { + const output = execSync( + `${backupsCmd} getBackupOperations ${INSTANCE_ID} ${DATABASE_ID} ${BACKUP_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Create Backup Operations:/); + assert.match( + output, + new RegExp(`Backup (.+)${BACKUP_ID} (.+) is 100% complete`), + ); + assert.match(output, /Copy Backup Operations:/); + assert.match( + output, + new RegExp(`Backup (.+)${COPY_BACKUP_ID} (.+) is 100% complete`), + ); + }); + + // update_backup_expire_time + it('should update the expire time of a backup', async () => { + const output = execSync( + `${backupsCmd} updateBackup ${INSTANCE_ID} ${BACKUP_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Expire time updated./); + }); + + // restore_backup + it('should restore database from a backup', async function () { + // Restoring a backup can be a slow operation so the test may timeout and + // we'll have to retry. + this.retries(5); + // Delay the start of the test, if this is a retry. + await delay(this.test); + + const output = execSync( + `${backupsCmd} restoreBackup ${INSTANCE_ID} ${RESTORE_DATABASE_ID} ${BACKUP_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Database restored from backup./); + assert.match( + output, + new RegExp( + `Database (.+) was restored to ${RESTORE_DATABASE_ID} from backup ` + + `(.+)${BACKUP_ID} with version time (.+)`, + ), + ); + }); + + // restore_backup_with_encryption_key + it('should restore database from a backup using an encryption key', async function () { + // Restoring a backup can be a slow operation so the test may timeout and + // we'll have to retry. + this.retries(5); + // Delay the start of the test, if this is a retry. + await delay(this.test); + + const key = await getCryptoKey(); + + const output = execSync( + `${backupsCmd} restoreBackupWithEncryptionKey ${INSTANCE_ID} ${ENCRYPTED_RESTORE_DATABASE_ID} ${ENCRYPTED_BACKUP_ID} ${PROJECT_ID} ${key.name}`, + ); + assert.match(output, /Database restored from backup./); + assert.match( + output, + new RegExp( + `Database (.+) was restored to ${ENCRYPTED_RESTORE_DATABASE_ID} from backup ` + + `(.+)${ENCRYPTED_BACKUP_ID} using encryption key ${key.name}`, + ), + ); + }); + + // list_database_operations + it('should list database operations in the instance', async () => { + const output = execSync( + `${backupsCmd} getDatabaseOperations ${INSTANCE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Optimize Database Operations:/); + assert.match( + output, + new RegExp( + `Database (.+)${RESTORE_DATABASE_ID} restored from backup is (\\d+)% ` + + 'optimized', + ), + ); + }); + + // delete_backup + it('should delete a backup', async () => { + function sleep(timeMillis) { + return new Promise(resolve => setTimeout(resolve, timeMillis)); + } + + // 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); + } + + const output = execSync( + `${backupsCmd} deleteBackup ${INSTANCE_ID} ${RESTORE_DATABASE_ID} ${BACKUP_ID} ${PROJECT_ID}`, + ); + 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( + `${schemaCmd} createDatabaseWithVersionRetentionPeriod "${INSTANCE_ID}" "${VERSION_RETENTION_DATABASE_ID}" ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + `Waiting for operation on ${VERSION_RETENTION_DATABASE_ID} to complete...`, + ), + ); + assert.match( + output, + new RegExp( + `Created database ${VERSION_RETENTION_DATABASE_ID} with version retention period.`, + ), + ); + assert.include(output, 'Version retention period: 1d'); + 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('sequence', () => { + before(async () => { + const instance = spanner.instance(INSTANCE_ID); + const database = instance.database(SEQUENCE_DATABASE_ID); + const [, operation_seq] = await database.create(); + await operation_seq.promise(); + }); + + after(async () => { + await spanner + .instance(INSTANCE_ID) + .database(SEQUENCE_DATABASE_ID) + .delete(); + }); + + // create_sequence + it('should create a sequence', async () => { + const output = execSync( + `node archived/sequence-create.js "${INSTANCE_ID}" "${SEQUENCE_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'), + ); + }); + + // alter_sequence + it('should alter a sequence', async () => { + const output = execSync( + `node archived/sequence-alter.js "${INSTANCE_ID}" "${SEQUENCE_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'), + ); + }); + + // drop_sequence + it('should drop a sequence', async () => { + const output = execSync( + `node archived/sequence-drop.js "${INSTANCE_ID}" "${SEQUENCE_DATABASE_ID}" ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + 'Altered Customers table to drop DEFAULT from CustomerId column and dropped the Seq sequence.', + ), + ); + }); + }); + + 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 archived/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 archived/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 archived/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 archived/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 archived/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 archived/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 archived/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 archived/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}.`, + ), + ); + }); + + // list_databases + it('should list databases on the instance', async () => { + const output = execSync( + `node archived/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 archived/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'); + }); + }); + + 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 archived/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 archived/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_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('Successfully executed 1 postgreSQL statements using DML'), + ); + }); + + // pg_dml_batch + it('should execute a batch of DML statements on a Spanner PostgreSQL database', async () => { + const output = execSync( + `node pg-dml-batch.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + 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('SingerId: 1, FirstName: Alice, LastName: Henderson'), + ); + }); + + // pg_dml_update + it('should update a table using parameterized queries on a Spanner PostgreSQL database.', async () => { + const output = execSync( + `node pg-dml-getting-started-update.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + 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 archived/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 archived/pg-index-create-storing.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, new RegExp('Added the AlbumsByAlbumTitle index.')); + }); + + // 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('Author ORDER BY FirstName DESC NULLS LAST'), + ); + }); + + // 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 archived/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_jsonb_insert_data + it('should insert pg jsonb data', async () => { + const output = execSync( + `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('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( + `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.')); + }); + + // 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 pg-datatypes-casting.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}`, + ); + 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, + new RegExp('Successfully inserted 1 record into the Singers table'), + ); + assert.match(output, new RegExp('Virginia Watson')); + }); + + // pg_dml_returning_update + it('should update records using DML Returning in a Spanner PostgreSQL database', async () => { + const output = execSync( + `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 archived/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 archived/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 () => { + const output = execSync( + `node archived/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.', + ), + ); + }); + + // directed_read_options + it('should run read-only transaction with directed read options set', async () => { + const output = execSync( + `node directed-reads.js ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + console.log(output); + assert.match( + output, + new RegExp( + 'SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold your Peace', + ), + ); + assert.match( + output, + new RegExp( + 'Successfully executed read-only transaction with directedReadOptions', + ), + ); + }); + }); +}); diff --git a/handwritten/spanner/samples/system-test/spanner.test.js b/handwritten/spanner/samples/system-test/spanner.test.js new file mode 100644 index 00000000000..a7f93857f22 --- /dev/null +++ b/handwritten/spanner/samples/system-test/spanner.test.js @@ -0,0 +1,2523 @@ +// Copyright 2024 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. + +'use strict'; + +const {Spanner} = require('@google-cloud/spanner'); +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 batchCmd = 'node batch.js'; +const crudCmd = 'node crud.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 transactionTimeoutCommand = 'node transaction-timeout.js'; +const statementTimeoutCommand = 'node statement-timeout.js'; +const requestTagCommand = 'node request-tag.js'; +const timestampCmd = 'node timestamp.js'; +const structCmd = 'node struct.js'; +const dmlCmd = 'node dml.js'; +const batchWriteCmd = 'node batch-write.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 createInstanceWithoutDefaultBackupSchedulesCommand = + 'node create-instance-without-default-backup-schedules.js'; +const updateInstanceDefaultBackupScheduleTypeCommand = + 'node update-instance-default-backup-schedule-type.js'; +const alterTableWithForeignKeyDeleteCascadeCommand = + 'node table-alter-with-foreign-key-delete-cascade.js'; +const dropForeignKeyConstraintDeleteCascaseCommand = + 'node table-drop-foreign-key-constraint-delete-cascade.js'; +const traceObservabilityCommand = 'node observability-traces.js'; + +const CURRENT_TIME = Math.round(Date.now() / 1000).toString(); +const PROJECT_ID = process.env.GCLOUD_PROJECT; +const PREFIX = 'test-instance'; +const INSTANCE_ID = + process.env.SPANNERTEST_INSTANCE || `${PREFIX}-${CURRENT_TIME}`; +const MULTI_REGION_INSTANCE_ID = + process.env.SPANNERTEST_MR_INSTANCE || `${PREFIX}-mr-${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 BASE_INSTANCE_CONFIG_ID = 'regional-us-central1'; +const INSTANCE_ALREADY_EXISTS = !!process.env.SPANNERTEST_INSTANCE; +const DATABASE_ID = `test-database-${CURRENT_TIME}`; +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 PROTO_DATABASE_ID = `test-db${CURRENT_TIME}-proto1`; +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`; +const CANCELLED_BACKUP_ID = `test-backup-${CURRENT_TIME}-c`; +const LOCATION_ID = 'regional-us-central1'; +const MULTI_REGION_LOCATION_ID = 'nam3'; +const PG_LOCATION_ID = 'regional-us-west2'; +const KEY_LOCATION_ID1 = 'us-central1'; +const KEY_LOCATION_ID2 = 'us-east1'; +const KEY_LOCATION_ID3 = 'us-east4'; +const KEY_RING_ID = 'test-key-ring-node'; +const KEY_ID = 'test-key'; +const DEFAULT_LEADER = 'us-central1'; +const DEFAULT_LEADER_2 = 'us-east1'; + +const spanner = new Spanner({ + projectId: PROJECT_ID, +}); +const LABEL = 'node-sample-tests'; +const GAX_OPTIONS = { + retry: { + retryCodes: [4, 8, 14], + backoffSettings: { + initialRetryDelayMillis: 1000, + retryDelayMultiplier: 1.3, + maxRetryDelayMillis: 32000, + initialRpcTimeoutMillis: 60000, + rpcTimeoutMultiplier: 1, + maxRpcTimeoutMillis: 60000, + totalTimeoutMillis: 600000, + }, + }, +}; + +const delay = async test => { + const retries = test.currentRetry(); + // No retry on the first failure. + if (retries === 0) return; + // See: https://cloud.google.com/storage/docs/exponential-backoff + const ms = Math.pow(2, retries) + Math.random() * 1000; + return new Promise(done => { + console.info(`retrying "${test.title}" in ${ms}ms`); + setTimeout(done, ms); + }); +}; + +async function deleteStaleInstances() { + let [instances] = await spanner.getInstances({ + filter: `(labels.${LABEL}:true) OR (labels.cloud_spanner_samples:true)`, + }); + const old = new Date(); + old.setHours(old.getHours() - 4); + + instances = instances.filter(instance => { + return ( + instance.metadata.labels['created'] && + new Date(parseInt(instance.metadata.labels['created']) * 1000) < old + ); + }); + const limit = pLimit(5); + await Promise.all( + instances.map(instance => + limit(() => setTimeout(deleteInstance, delay, instance)), + ), + ); +} + +async function deleteInstance(instance) { + const [backups] = await instance.getBackups(); + await Promise.all(backups.map(backup => backup.delete(GAX_OPTIONS))); + return instance.delete(GAX_OPTIONS); +} + +async function getCryptoKey(key_location) { + const NOT_FOUND = 5; + + // Instantiates a client. + const client = new KeyManagementServiceClient(); + + // Build the parent key ring name. + const keyRingName = client.keyRingPath(PROJECT_ID, key_location, KEY_RING_ID); + + // Get key ring. + try { + await client.getKeyRing({name: keyRingName}); + } catch (err) { + // Create key ring if it doesn't exist. + if (err.code === NOT_FOUND) { + // Build the parent location name. + const locationName = client.locationPath(PROJECT_ID, key_location); + await client.createKeyRing({ + parent: locationName, + keyRingId: KEY_RING_ID, + }); + } else { + throw err; + } + } + + // Get key. + try { + // Build the key name + const keyName = client.cryptoKeyPath( + PROJECT_ID, + key_location, + KEY_RING_ID, + KEY_ID, + ); + const [key] = await client.getCryptoKey({ + name: keyName, + }); + return key; + } catch (err) { + // Create key if it doesn't exist. + if (err.code === NOT_FOUND) { + const [key] = await client.createCryptoKey({ + parent: keyRingName, + cryptoKeyId: KEY_ID, + cryptoKey: { + purpose: 'ENCRYPT_DECRYPT', + versionTemplate: { + algorithm: 'GOOGLE_SYMMETRIC_ENCRYPTION', + }, + }, + }); + return key; + } else { + throw err; + } + } +} + +describe('Autogenerated Admin Clients', () => { + const instance = spanner.instance(INSTANCE_ID); + + before(async () => { + await deleteStaleInstances(); + + if (!INSTANCE_ALREADY_EXISTS) { + const [, operation] = await instance.create({ + config: LOCATION_ID, + nodes: 1, + edition: 'ENTERPRISE_PLUS', + labels: { + [LABEL]: 'true', + created: CURRENT_TIME, + }, + gaxOptions: GAX_OPTIONS, + }); + return operation.promise(); + } else { + console.log( + `Not creating temp instance, using + ${instance.formattedName_}...`, + ); + } + }); + + after(async () => { + const instance = spanner.instance(INSTANCE_ID); + + if (!INSTANCE_ALREADY_EXISTS) { + // Make sure all backups are deleted before an instance can be deleted. + await Promise.all([ + instance.backup(BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(ENCRYPTED_BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(COPY_BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(CANCELLED_BACKUP_ID).delete(GAX_OPTIONS), + ]); + await instance.delete(GAX_OPTIONS); + } else { + await Promise.all([ + instance.database(DATABASE_ID).delete(), + instance.database(PG_DATABASE_ID).delete(), + instance.database(RESTORE_DATABASE_ID).delete(), + instance.database(ENCRYPTED_RESTORE_DATABASE_ID).delete(), + instance.backup(BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(COPY_BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(ENCRYPTED_BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(CANCELLED_BACKUP_ID).delete(GAX_OPTIONS), + ]); + } + await spanner.instance(SAMPLE_INSTANCE_ID).delete(GAX_OPTIONS); + }); + + describe('instance', () => { + afterEach(async () => { + const sample_instance = spanner.instance(SAMPLE_INSTANCE_ID); + await sample_instance.delete(); + }); + + // create_and_update_instance + it('should create and update an example instance with spanner editions', async () => { + const createInstanceOutput = execSync( + `${instanceCmd} createInstance "${SAMPLE_INSTANCE_ID}" ${PROJECT_ID}`, + ); + assert.match( + createInstanceOutput, + new RegExp( + `Waiting for operation on ${SAMPLE_INSTANCE_ID} to complete...`, + ), + ); + assert.match( + createInstanceOutput, + new RegExp(`Created instance ${SAMPLE_INSTANCE_ID}.`), + ); + + const updateInstanceOutput = execSync( + `node instance-update "${SAMPLE_INSTANCE_ID}" ${PROJECT_ID}`, + ); + assert.match( + updateInstanceOutput, + new RegExp( + `Waiting for operation on ${SAMPLE_INSTANCE_ID} to complete...`, + ), + ); + assert.match( + updateInstanceOutput, + new RegExp(`Updated instance ${SAMPLE_INSTANCE_ID}.`), + ); + assert.match( + updateInstanceOutput, + new RegExp( + `Instance ${SAMPLE_INSTANCE_ID} has been updated with the ENTERPRISE edition.`, + ), + ); + }); + + // create_and_update_instance_default_backup_schedule_type + it('should create an example instance without default backup schedule type and update the instance to have it', async () => { + const createInstanceOutput = execSync( + `${createInstanceWithoutDefaultBackupSchedulesCommand} "${SAMPLE_INSTANCE_ID}" ${PROJECT_ID}`, + ); + assert.match( + createInstanceOutput, + new RegExp( + `Created instance ${SAMPLE_INSTANCE_ID} without default backup schedules.`, + ), + ); + + const updateInstanceOutput = execSync( + `${updateInstanceDefaultBackupScheduleTypeCommand} "${SAMPLE_INSTANCE_ID}" ${PROJECT_ID}`, + ); + assert.match( + updateInstanceOutput, + new RegExp( + `Instance ${SAMPLE_INSTANCE_ID} has been updated with the AUTOMATIC default backup schedule type.`, + ), + ); + }); + + // create_instance_with_processing_units + it('should create an example instance with processing units', async () => { + const output = execSync( + `${instanceCmd} createInstanceWithProcessingUnits "${SAMPLE_INSTANCE_ID}" ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + `Waiting for operation on ${SAMPLE_INSTANCE_ID} to complete...`, + ), + ); + assert.match( + output, + new RegExp(`Created instance ${SAMPLE_INSTANCE_ID}.`), + ); + assert.match( + output, + new RegExp(`Instance ${SAMPLE_INSTANCE_ID} has 500 processing units.`), + ); + }); + + // create_instance_with_autoscaling_config + it('should create an example instance with autoscaling config', async () => { + const output = execSync( + `node instance-with-autoscaling-config.js "${SAMPLE_INSTANCE_ID}" ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + `Waiting for operation on ${SAMPLE_INSTANCE_ID} to complete...`, + ), + ); + assert.match( + output, + new RegExp(`Created instance ${SAMPLE_INSTANCE_ID}.`), + ); + assert.match( + output, + new RegExp( + `Autoscaling configurations of ${SAMPLE_INSTANCE_ID} are: ` + + '\n' + + 'Min nodes: 1 ' + + 'nodes.' + + '\n' + + 'Max nodes: 2' + + ' nodes.' + + '\n' + + 'High priority cpu utilization percent: 65.' + + '\n' + + 'Storage utilization percent: 95.', + ), + ); + }); + + // create_instance_with_asymmetric_autoscaling_config + it('should create an example instance with autoscaling config and asymmetric Autoscaling Options', async () => { + const output = execSync( + `node instance-with-asymmetric-autoscaling-config.js "${SAMPLE_INSTANCE_ID}" ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + `Waiting for operation on ${SAMPLE_INSTANCE_ID} to complete...`, + ), + ); + assert.match( + output, + new RegExp(`Created instance ${SAMPLE_INSTANCE_ID}.`), + ); + assert.match( + output, + new RegExp( + `Autoscaling configurations of ${SAMPLE_INSTANCE_ID} are: ` + + '\n' + + 'Min nodes: 1 ' + + 'nodes.' + + '\n' + + 'Max nodes: 2' + + ' nodes.' + + '\n' + + 'High priority cpu utilization percent: 65.' + + '\n' + + 'Storage utilization percent: 95.' + + '\n' + + 'Asymmetric Autoscaling Options: europe-west1, europe-west4, asia-east1', + ), + ); + }); + }); + + // check that base instance was created + it('should have created an instance', async () => { + const [exists] = await instance.exists(); + assert.strictEqual( + exists, + true, + 'The main instance was not created successfully!', + ); + }); + + // create_database + it('should create an example database', async () => { + const output = execSync( + `${schemaCmd} createDatabase "${INSTANCE_ID}" "${DATABASE_ID}" ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp(`Waiting for creation of ${DATABASE_ID} to complete...`), + ); + assert.match( + output, + new RegExp(`Created database ${DATABASE_ID} on instance ${INSTANCE_ID}.`), + ); + }); + + // update_database + it('should set database metadata', async () => { + const output = execSync( + `node database-update.js ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + `Waiting for update operation for ${DATABASE_ID} to complete...`, + ), + ); + assert.match(output, new RegExp(`Updated database ${DATABASE_ID}.`)); + // cleanup + const [operation] = await instance + .database(DATABASE_ID) + .setMetadata({enableDropProtection: false}); + await operation.promise(); + }); + + describe('encrypted database', () => { + after(async () => { + const instance = spanner.instance(INSTANCE_ID); + const encrypted_database = instance.database(ENCRYPTED_DATABASE_ID); + await encrypted_database.delete(); + }); + + // create_database_with_encryption_key + it('should create a database with an encryption key', async () => { + const key = await getCryptoKey(KEY_LOCATION_ID1); + + const output = execSync( + `${schemaCmd} createDatabaseWithEncryptionKey "${INSTANCE_ID}" "${ENCRYPTED_DATABASE_ID}" ${PROJECT_ID} "${key.name}"`, + ); + assert.match( + output, + new RegExp( + `Waiting for operation on ${ENCRYPTED_DATABASE_ID} to complete...`, + ), + ); + assert.match( + output, + new RegExp( + `Created database ${ENCRYPTED_DATABASE_ID} on instance ${INSTANCE_ID}.`, + ), + ); + assert.match( + output, + new RegExp(`Database encrypted with key ${key.name}.`), + ); + }); + }); + + 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 quickstart ${PROJECT_ID} ${INSTANCE_ID} ${DATABASE_ID}`, + ); + assert.match(output, /Query: \d+ found./); + }); + }); + + // insert_data + it('should insert rows into an example table', async () => { + const output = execSync( + `${crudCmd} insert ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + 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( + `${schemaCmd} addColumn ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Waiting for operation to complete\.\.\./); + assert.match(output, /Added the MarketingBudget column\./); + }); + + // update_data + it('should update existing rows in an example table', async () => { + const output = execSync( + `${crudCmd} update ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + 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( + `${schemaCmd} queryNewColumn ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /SingerId: 1, AlbumId: 1, MarketingBudget: 100000/); + assert.match(output, /SingerId: 2, AlbumId: 2, MarketingBudget: 500000/); + }); + + // create_index + it('should create an index in an example table', async () => { + const output = execSync( + `node index-create ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Waiting for operation to complete\.\.\./); + assert.match(output, /Added the AlbumsByAlbumTitle index\./); + }); + + // create_storing_index + it('should create a storing index in an example table', async function () { + this.retries(5); + // Delay the start of the test, if this is a retry. + await delay(this.test); + + const output = execSync( + `node index-create-storing ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Waiting for operation to complete\.\.\./); + assert.match(output, /Added the AlbumsByAlbumTitle2 index\./); + }); + + // query_data_with_index + it('should query an example table with an index and return matching rows', async () => { + const output = execSync( + `node index-query-data ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /AlbumId: 2, AlbumTitle: Go, Go, Go, MarketingBudget:/, + ); + assert.notMatch( + output, + /AlbumId: 1, AlbumTitle: Total Junk, MarketingBudget:/, + ); + }); + + it('should respect query boundaries when querying an example table with an index', async () => { + const output = execSync( + `node index-query-data ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID} "Ardvark" "Zoo"`, + ); + assert.match( + output, + /AlbumId: 1, AlbumTitle: Total Junk, MarketingBudget:/, + ); + assert.match( + output, + /AlbumId: 2, AlbumTitle: Go, Go, Go, MarketingBudget:/, + ); + }); + + // read_data_with_index + it('should read an example table with an index', async () => { + const output = execSync( + `node index-read-data ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /AlbumId: 1, AlbumTitle: Total Junk/); + }); + + // 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/); + }); + + // spanner_create_client_with_query_options + it('should use query options from a database reference', async () => { + const output = execSync( + `${queryOptionsCmd} databaseWithQueryOptions ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget:/, + ); + }); + + // spanner_query_with_query_options + it('should use query options on request', async () => { + const output = execSync( + `${queryOptionsCmd} queryWithQueryOptions ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget:/, + ); + }); + + // query with RPC priority for run command + it('should use RPC priority from request options for run command', async () => { + const output = execSync( + `${rpcPriorityRunCommand} ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /Successfully fetched \d rows using low RPC priority\./, + ); + assert.match( + output, + /AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget:/, + ); + }); + + // 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')); + }); + + // batch_write + it('should perform CRUD operations using batch write', async () => { + const output = execSync( + `${batchWriteCmd} ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ).toString(); + + const successRegex = + /Mutation group indexes [\d,]+ have been applied with commit timestamp \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z/; + const failureRegex = + /Mutation group indexes [\d,]+, could not be applied with error code \d+, and error message .+/; + + const successMatch = successRegex.test(output); + const errorMatch = failureRegex.test(output); + + if (successMatch || errorMatch) { + assert.include(output, 'Request completed successfully'); + } else { + assert.ifError(output); + } + }); + + // 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}`, + ); + assert.match(output, /Inserted data./); + }); + + // query_with_array_parameter + it('should use an ARRAY query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithArray ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /VenueId: 19, VenueName: Venue 19, AvailableDate: 2020-11-01/, + ); + assert.match( + output, + /VenueId: 42, VenueName: Venue 42, AvailableDate: 2020-10-01/, + ); + }); + + // query_with_bool_parameter + it('should use a BOOL query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithBool ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /VenueId: 19, VenueName: Venue 19, OutdoorVenue: true/, + ); + }); + + // query_with_bytes_parameter + it('should use a BYTES query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithBytes ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /VenueId: 4, VenueName: Venue 4/); + }); + + // query_with_date_parameter + it('should use a DATE query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithDate ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /VenueId: 4, VenueName: Venue 4, LastContactDate: 2018-09-02/, + ); + assert.match( + output, + /VenueId: 42, VenueName: Venue 42, LastContactDate: 2018-10-01/, + ); + }); + + // query_with_float_parameter + it('should use a FLOAT64 query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithFloat ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + /VenueId: 4, VenueName: Venue 4, PopularityScore: 0.8/, + ); + assert.match( + output, + /VenueId: 19, VenueName: Venue 19, PopularityScore: 0.9/, + ); + }); + + // query_with_int_parameter + it('should use a INT64 query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithInt ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /VenueId: 19, VenueName: Venue 19, Capacity: 6300/); + assert.match(output, /VenueId: 42, VenueName: Venue 42, Capacity: 3000/); + }); + + // query_with_string_parameter + it('should use a STRING query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithString ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /VenueId: 42, VenueName: Venue 42/); + }); + + // query_with_timestamp_parameter + it('should use a TIMESTAMP query parameter to query record from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithTimestamp ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /VenueId: 4, VenueName: Venue 4, LastUpdateTime:/); + assert.match(output, /VenueId: 19, VenueName: Venue 19, LastUpdateTime:/); + assert.match(output, /VenueId: 42, VenueName: Venue 42, LastUpdateTime:/); + }); + + // add_numeric_column + it('should add a Revenue column to Venues example table', async () => { + const output = execSync( + `${datatypesCmd} addNumericColumn "${INSTANCE_ID}" "${DATABASE_ID}" ${PROJECT_ID}`, + ); + + assert.include( + output, + `Waiting for operation on ${DATABASE_ID} to complete...`, + ); + assert.include( + output, + `Added Revenue column to Venues table in database ${DATABASE_ID}.`, + ); + }); + + // update_data_with_numeric + it('should update rows in Venues example table to add data in Revenue column', async () => { + const output = execSync( + `${datatypesCmd} updateWithNumericData ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Updated data./); + }); + + // query_with_numeric_parameter + it('should use a NUMERIC query parameter to query records from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithNumericParameter ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + 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'); + }); + + // read_write_transaction with transaction timeout + it('should execute a read/write transaction with a transaction timeout of 60 seconds', async () => { + const output = execSync( + `${transactionTimeoutCommand} ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.include(output, '1 record inserted.'); + }); + + // read_write_transaction with statement timeout + it('should execute a read/write transaction with a statement timeout of 60 seconds', async () => { + const output = execSync( + `${statementTimeoutCommand} ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.include(output, '1 record inserted.'); + }); + + // add_json_column + it('should add a VenueDetails column to Venues example table', async () => { + const output = execSync( + `${datatypesCmd} addJsonColumn "${INSTANCE_ID}" "${DATABASE_ID}" ${PROJECT_ID}`, + ); + + assert.include( + output, + `Waiting for operation on ${DATABASE_ID} to complete...`, + ); + assert.include( + output, + `Added VenueDetails column to Venues table in database ${DATABASE_ID}.`, + ); + }); + + // update_data_with_json + it('should update rows in Venues example table to add data in VenueDetails column', async () => { + const output = execSync( + `${datatypesCmd} updateWithJsonData ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Updated data./); + }); + + // query_with_json_parameter + it('should use a JSON query parameter to query records from the Venues example table', async () => { + const output = execSync( + `${datatypesCmd} queryWithJsonParameter ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /VenueId: 19, Details: {"open":true,"rating":9}/); + }); + + // add_and_drop_new_database_role + it('should add and drop new database roles', async () => { + const output = execSync( + `node add-and-drop-new-database-role.js ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, new RegExp('Waiting for operation to complete...')); + assert.match( + output, + new RegExp('Created roles child and parent and granted privileges'), + ); + assert.match( + output, + new RegExp('Revoked privileges and dropped role child'), + ); + }); + + // 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( + `node get-database-roles.js ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + `Role: projects/${PROJECT_ID}/instances/${INSTANCE_ID}/databases/${DATABASE_ID}/databaseRoles/public`, + ), + ); + }); + + it('shoud create a full backup schedule', async () => { + const output = execSync( + `node create-full-backup-schedule.js ${PROJECT_ID} ${INSTANCE_ID} ${DATABASE_ID} full-backup-schedule`, + ); + assert.match(output, new RegExp('Created full backup schedule')); + assert.match( + output, + new RegExp( + `projects/${PROJECT_ID}/instances/${INSTANCE_ID}/databases/${DATABASE_ID}/backupSchedules/full-backup-schedule`, + ), + ); + }); + + it('shoud create an incremental backup schedule', async () => { + const output = execSync( + `node create-incremental-backup-schedule.js ${PROJECT_ID} ${INSTANCE_ID} ${DATABASE_ID} incremental-backup-schedule`, + ); + assert.match(output, new RegExp('Created incremental backup schedule')); + assert.match( + output, + new RegExp( + `projects/${PROJECT_ID}/instances/${INSTANCE_ID}/databases/${DATABASE_ID}/backupSchedules/incremental-backup-schedule`, + ), + ); + }); + + it('shoud list backup schedules', async () => { + const output = execSync( + `node list-backup-schedules.js ${PROJECT_ID} ${INSTANCE_ID} ${DATABASE_ID}`, + ); + assert.match( + output, + new RegExp( + `projects/${PROJECT_ID}/instances/${INSTANCE_ID}/databases/${DATABASE_ID}/backupSchedules/full-backup-schedule`, + ), + ); + assert.match( + output, + new RegExp( + `projects/${PROJECT_ID}/instances/${INSTANCE_ID}/databases/${DATABASE_ID}/backupSchedules/incremental-backup-schedule`, + ), + ); + }); + + it('shoud get a backup schedule', async () => { + const output = execSync( + `node get-backup-schedule.js ${PROJECT_ID} ${INSTANCE_ID} ${DATABASE_ID} full-backup-schedule`, + ); + assert.match( + output, + new RegExp( + `projects/${PROJECT_ID}/instances/${INSTANCE_ID}/databases/${DATABASE_ID}/backupSchedules/full-backup-schedule`, + ), + ); + }); + + it('shoud update a backup schedule', async () => { + const output = execSync( + `node update-backup-schedule.js ${PROJECT_ID} ${INSTANCE_ID} ${DATABASE_ID} full-backup-schedule`, + ); + assert.match(output, new RegExp('Updated backup schedule')); + assert.match( + output, + new RegExp( + `projects/${PROJECT_ID}/instances/${INSTANCE_ID}/databases/${DATABASE_ID}/backupSchedules/full-backup-schedule`, + ), + ); + }); + + it('shoud delete a backup schedule', async () => { + const output = execSync( + `node delete-backup-schedule.js ${PROJECT_ID} ${INSTANCE_ID} ${DATABASE_ID} full-backup-schedule`, + ); + assert.match(output, new RegExp('Deleted backup schedule')); + }); + + // create_backup + it('should create a backup of the database', async () => { + const instance = spanner.instance(INSTANCE_ID); + const database = instance.database(DATABASE_ID); + const query = { + sql: 'SELECT CURRENT_TIMESTAMP() as Timestamp', + }; + const [rows] = await database.run(query); + const versionTime = rows[0].toJSON().Timestamp.toISOString(); + + const output = execSync( + `${backupsCmd} createBackup ${INSTANCE_ID} ${DATABASE_ID} ${BACKUP_ID} ${PROJECT_ID} ${versionTime}`, + ); + assert.match(output, new RegExp(`Backup (.+)${BACKUP_ID} of size`)); + }); + + // create_backup_with_encryption_key + it('should create an encrypted backup of the database', async () => { + const key = await getCryptoKey(KEY_LOCATION_ID1); + + const output = execSync( + `${backupsCmd} createBackupWithEncryptionKey ${INSTANCE_ID} ${DATABASE_ID} ${ENCRYPTED_BACKUP_ID} ${PROJECT_ID} ${key.name}`, + ); + assert.match( + output, + new RegExp(`Backup (.+)${ENCRYPTED_BACKUP_ID} of size`), + ); + assert.include(output, `using encryption key ${key.name}`); + }); + + // copy_backup + it('should create a copy of a backup', async () => { + const sourceBackupPath = `projects/${PROJECT_ID}/instances/${INSTANCE_ID}/backups/${BACKUP_ID}`; + const output = execSync( + `node backups-copy.js ${INSTANCE_ID} ${COPY_BACKUP_ID} ${sourceBackupPath} ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp(`(.*)Backup copy(.*)${COPY_BACKUP_ID} of size(.*)`), + ); + }); + + // cancel_backup + it('should cancel a backup of the database', async () => { + const output = execSync( + `${backupsCmd} cancelBackup ${INSTANCE_ID} ${DATABASE_ID} ${CANCELLED_BACKUP_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Backup cancelled./); + }); + + // get_backups + it('should list backups in the instance', async () => { + const output = execSync( + `${backupsCmd} getBackups ${INSTANCE_ID} ${DATABASE_ID} ${BACKUP_ID} ${PROJECT_ID}`, + ); + assert.include(output, 'All backups:'); + assert.include(output, 'Backups matching backup name:'); + assert.include(output, 'Backups expiring within 30 days:'); + assert.include(output, 'Backups matching database name:'); + assert.include(output, 'Backups filtered by size:'); + assert.include(output, 'Ready backups filtered by create time:'); + assert.include(output, 'Get backups paginated:'); + const count = (output.match(new RegExp(`${BACKUP_ID}`, 'g')) || []).length; + assert.equal(count, 14); + }); + + // list_backup_operations + it('should list backup operations in the instance', async () => { + const output = execSync( + `${backupsCmd} getBackupOperations ${INSTANCE_ID} ${DATABASE_ID} ${BACKUP_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Create Backup Operations:/); + assert.match( + output, + new RegExp(`Backup (.+)${BACKUP_ID} (.+) is 100% complete`), + ); + assert.match(output, /Copy Backup Operations:/); + assert.match( + output, + new RegExp(`Backup (.+)${COPY_BACKUP_ID} (.+) is 100% complete`), + ); + }); + + // update_backup_expire_time + it('should update the expire time of a backup', async () => { + const output = execSync( + `${backupsCmd} updateBackup ${INSTANCE_ID} ${BACKUP_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Expire time updated./); + }); + + // restore_backup + it('should restore database from a backup', async function () { + // Restoring a backup can be a slow operation so the test may timeout and + // we'll have to retry. + this.retries(5); + // Delay the start of the test, if this is a retry. + await delay(this.test); + + const output = execSync( + `${backupsCmd} restoreBackup ${INSTANCE_ID} ${RESTORE_DATABASE_ID} ${BACKUP_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Database restored from backup./); + assert.match( + output, + new RegExp( + `Database (.+) was restored to ${RESTORE_DATABASE_ID} from backup ` + + `(.+)${BACKUP_ID} with version time (.+)`, + ), + ); + }); + + // restore_backup_with_encryption_key + it('should restore database from a backup using an encryption key', async function () { + // Restoring a backup can be a slow operation so the test may timeout and + // we'll have to retry. + this.retries(5); + // Delay the start of the test, if this is a retry. + await delay(this.test); + + const key = await getCryptoKey(KEY_LOCATION_ID1); + + const output = execSync( + `${backupsCmd} restoreBackupWithEncryptionKey ${INSTANCE_ID} ${ENCRYPTED_RESTORE_DATABASE_ID} ${ENCRYPTED_BACKUP_ID} ${PROJECT_ID} ${key.name}`, + ); + assert.match(output, /Database restored from backup./); + assert.match( + output, + new RegExp( + `Database (.+) was restored to ${ENCRYPTED_RESTORE_DATABASE_ID} from backup ` + + `(.+)${ENCRYPTED_BACKUP_ID} using encryption key ${key.name}`, + ), + ); + }); + + // list_database_operations + it('should list database operations in the instance', async () => { + const output = execSync( + `${backupsCmd} getDatabaseOperations ${INSTANCE_ID} ${PROJECT_ID}`, + ); + assert.match(output, /Optimize Database Operations:/); + assert.match( + output, + new RegExp( + `Database (.+)${RESTORE_DATABASE_ID} restored from backup is (\\d+)% ` + + 'optimized', + ), + ); + }); + + // delete_backup + it('should delete a backup', async () => { + function sleep(timeMillis) { + return new Promise(resolve => setTimeout(resolve, timeMillis)); + } + + // 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); + } + + const output = execSync( + `${backupsCmd} deleteBackup ${INSTANCE_ID} ${BACKUP_ID} ${PROJECT_ID}`, + ); + 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( + `${schemaCmd} createDatabaseWithVersionRetentionPeriod "${INSTANCE_ID}" "${VERSION_RETENTION_DATABASE_ID}" ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + `Waiting for operation on ${VERSION_RETENTION_DATABASE_ID} to complete...`, + ), + ); + assert.match( + output, + new RegExp( + `Created database ${VERSION_RETENTION_DATABASE_ID} with version retention period.`, + ), + ); + assert.include(output, 'Version retention period: 1d'); + 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('observability', () => { + it('traces', () => { + const output = execSync( + `${traceObservabilityCommand} ${PROJECT_ID} ${INSTANCE_ID} ${DATABASE_ID}`, + ); + assert.match(output, /Query: \d+ found./); + }); + }); + + 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', () => { + before(async () => { + const instance = spanner.instance(INSTANCE_ID); + const database = instance.database(SEQUENCE_DATABASE_ID); + const [, operation_seq] = await database.create(); + await operation_seq.promise(); + }); + + after(async () => { + await spanner + .instance(INSTANCE_ID) + .database(SEQUENCE_DATABASE_ID) + .delete(); + }); + + // create_sequence + it('should create a sequence', async () => { + const output = execSync( + `node sequence-create.js "${INSTANCE_ID}" "${SEQUENCE_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'), + ); + }); + + // alter_sequence + it('should alter a sequence', async () => { + const output = execSync( + `node sequence-alter.js "${INSTANCE_ID}" "${SEQUENCE_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'), + ); + }); + + // drop_sequence + it('should drop a sequence', async () => { + const output = execSync( + `node sequence-drop.js "${INSTANCE_ID}" "${SEQUENCE_DATABASE_ID}" ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + 'Altered Customers table to drop DEFAULT from CustomerId column and dropped the Seq sequence.', + ), + ); + }); + }); + + describe('instance partition', () => { + before(async () => { + const instance = spanner.instance(SAMPLE_INSTANCE_ID); + const [, operation] = await instance.create({ + config: 'regional-us-central1', + nodes: 1, + edition: 'ENTERPRISE_PLUS', + displayName: 'Instance partitions 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_partition + it('should create an instance partition', async () => { + const output = execSync( + `node instance-partition-create.js "${SAMPLE_INSTANCE_ID}" "my-instance-partition" "${PROJECT_ID}"`, + ); + assert.match( + output, + new RegExp( + 'Waiting for operation on my-instance-partition to complete...', + ), + ); + assert.match( + output, + new RegExp('Created instance partition my-instance-partition.'), + ); + }); + }); + + 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_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('Successfully executed 1 postgreSQL statements using DML'), + ); + }); + + // pg_dml_batch + it('should execute a batch of DML statements on a Spanner PostgreSQL database', async () => { + const output = execSync( + `node pg-dml-batch.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + 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('SingerId: 1, FirstName: Alice, LastName: Henderson'), + ); + }); + + // pg_dml_update + it('should update a table using parameterized queries on a Spanner PostgreSQL database.', async () => { + const output = execSync( + `node pg-dml-getting-started-update.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match( + output, + 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( + `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_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('Author ORDER BY FirstName DESC NULLS LAST'), + ); + }); + + // 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_jsonb_insert_data + it('should insert pg jsonb data', async () => { + const output = execSync( + `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('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( + `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.')); + }); + + // 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 pg-datatypes-casting.js ${SAMPLE_INSTANCE_ID} ${PG_DATABASE_ID} ${PROJECT_ID}`, + ); + 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, + new RegExp('Successfully inserted 1 record into the Singers table'), + ); + assert.match(output, new RegExp('Virginia Watson')); + }); + + // pg_dml_returning_update + it('should update records using DML Returning in a Spanner PostgreSQL database', async () => { + const output = execSync( + `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( + '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 () => { + 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.', + ), + ); + }); + + // directed_read_options + it('should run read-only transaction with directed read options set', async () => { + const output = execSync( + `node directed-reads.js ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + console.log(output); + assert.match( + output, + new RegExp( + 'SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold your Peace', + ), + ); + assert.match( + output, + new RegExp( + 'Successfully executed read-only transaction with directedReadOptions', + ), + ); + }); + }); + + describe('proto columns', () => { + before(async () => { + // Setup database for Proto columns + const databaseAdminClient = spanner.getDatabaseAdminClient(); + const [operation] = await databaseAdminClient.createDatabase({ + createStatement: 'CREATE DATABASE `' + PROTO_DATABASE_ID + '`', + extraStatements: [ + ` + CREATE TABLE Singers ( + SingerId INT64 NOT NULL, + FirstName STRING(1024), + LastName STRING(1024), + ) PRIMARY KEY (SingerId)`, + `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`, + ], + parent: databaseAdminClient.instancePath(PROJECT_ID, INSTANCE_ID), + }); + + console.log( + `Waiting for creation of ${PROTO_DATABASE_ID} to complete...`, + ); + await operation.promise(); + console.log( + `Created database ${PROTO_DATABASE_ID} on instance ${INSTANCE_ID}.`, + ); + + // Insert seed data into the database tables + execSync( + `${crudCmd} insert ${INSTANCE_ID} ${PROTO_DATABASE_ID} ${PROJECT_ID}`, + ); + }); + + after(async () => { + await spanner.instance(INSTANCE_ID).database(PROTO_DATABASE_ID).delete(); + }); + + it('should add proto message and enum columns', async () => { + const output = execSync( + `node proto-type-add-column.js "${INSTANCE_ID}" "${PROTO_DATABASE_ID}" ${PROJECT_ID}`, + ); + assert.match( + output, + new RegExp( + `Altered table "Singers" on database ${PROTO_DATABASE_ID} on instance ${INSTANCE_ID} with proto descriptors.`, + ), + ); + }); + + it('update data with proto message and enum columns', async () => { + const output = execSync( + `node proto-update-data.js "${INSTANCE_ID}" "${PROTO_DATABASE_ID}" ${PROJECT_ID}`, + ); + assert.match(output, new RegExp('Data updated')); + }); + + it('update data with proto message and enum columns using DML', async () => { + const output = execSync( + `node proto-update-data-dml.js "${INSTANCE_ID}" "${PROTO_DATABASE_ID}" ${PROJECT_ID}`, + ); + assert.include(output, '1 record updated.'); + }); + + it('query data with proto message and enum columns', async () => { + const output = execSync( + `node proto-query-data.js "${INSTANCE_ID}" "${PROTO_DATABASE_ID}" ${PROJECT_ID}`, + ); + assert.match(output, new RegExp('SingerId: 2')); + }); + }); + + describe('encrypted database and backups with multiple KMS keys', () => { + const MR_CMEK_DB = `test-mr-${CURRENT_TIME}-db`; + const MR_CMEK_BACKUP = `test-mr-${CURRENT_TIME}-backup`; + const MR_CMEK_COPIED = `test-mr-${CURRENT_TIME}-copied`; + const MR_CMEK_RESTORED = `test-mr-${CURRENT_TIME}-restored`; + let instance_already_exists = false; + let key1, key2, key3; + before(async () => { + // Create multiple KMS keys covering `nam3`. + key1 = await getCryptoKey(KEY_LOCATION_ID1); + key2 = await getCryptoKey(KEY_LOCATION_ID2); + key3 = await getCryptoKey(KEY_LOCATION_ID3); + + const multi_region_instance = spanner.instance(MULTI_REGION_INSTANCE_ID); + [instance_already_exists] = await multi_region_instance.exists(); + if (!instance_already_exists) { + const [, operation] = await multi_region_instance.create({ + config: MULTI_REGION_LOCATION_ID, + nodes: 1, + labels: { + [LABEL]: 'true', + created: CURRENT_TIME, + }, + gaxOptions: GAX_OPTIONS, + }); + await operation.promise(); + console.log( + `Created temp instance, using + ${multi_region_instance.formattedName_}...`, + ); + } else { + console.log( + `Not creating temp instance, using + ${multi_region_instance.formattedName_}...`, + ); + } + }); + + after(async () => { + const instance = spanner.instance(MULTI_REGION_INSTANCE_ID); + const restored_db = instance.database(MR_CMEK_RESTORED); + function sleep(timeMillis) { + return new Promise(resolve => setTimeout(resolve, timeMillis)); + } + // Backup cannot be deleted when restored db is not in READY_OPTIMIZING state. + while ((await restored_db.getState()) === 'READY_OPTIMIZING') { + await sleep(1000); + } + await Promise.all([ + instance.database(MR_CMEK_DB).delete(GAX_OPTIONS), + instance.database(MR_CMEK_RESTORED).delete(), + instance.backup(MR_CMEK_BACKUP).delete(GAX_OPTIONS), + instance.backup(MR_CMEK_COPIED).delete(GAX_OPTIONS), + ]); + if (!instance_already_exists) { + await spanner.instance(MULTI_REGION_INSTANCE_ID).delete(GAX_OPTIONS); + } + }); + + it('should create a database with multiple KMS keys', async () => { + const output = execSync( + `node database-create-with-multiple-kms-keys.js \ + "${MULTI_REGION_INSTANCE_ID}" \ + "${MR_CMEK_DB}" \ + "${PROJECT_ID}" \ + "${key1.name},${key2.name},${key3.name}"`, + ); + assert.match( + output, + new RegExp(`Waiting for operation on ${MR_CMEK_DB} to complete...`), + ); + assert.match( + output, + new RegExp( + `Created database ${MR_CMEK_DB} on instance ${MULTI_REGION_INSTANCE_ID}.`, + ), + ); + assert.match(output, new RegExp('Database encrypted with keys')); + }); + + it('should create backup with multiple KMS keys', async () => { + const output = execSync( + `node backups-create-with-multiple-kms-keys.js \ + ${MULTI_REGION_INSTANCE_ID} \ + ${MR_CMEK_DB} \ + ${MR_CMEK_BACKUP} \ + ${PROJECT_ID} \ + "${key1.name},${key2.name},${key3.name}"`, + ); + assert.match(output, new RegExp(`Backup (.+)${MR_CMEK_BACKUP} of size`)); + assert.include(output, 'using encryption key'); + }); + + it('should copy backup with multiple KMS keys', async () => { + const sourceBackupPath = `projects/${PROJECT_ID}/instances/${MULTI_REGION_INSTANCE_ID}/backups/${MR_CMEK_BACKUP}`; + const output = execSync( + `node backups-copy-with-multiple-kms-keys.js \ + ${MULTI_REGION_INSTANCE_ID} \ + ${MR_CMEK_COPIED} \ + ${sourceBackupPath} \ + ${PROJECT_ID} \ + "${key1.name},${key2.name},${key3.name}"`, + ); + assert.match( + output, + new RegExp(`(.*)Backup copy(.*)${MR_CMEK_COPIED} of size(.*)`), + ); + }); + + it('should restore backup with multiple KMS keys', async function () { + // Restoring a backup can be a slow operation so the test may timeout and + // we'll have to retry. + this.retries(5); + // Delay the start of the test, if this is a retry. + await delay(this.test); + + const output = execSync( + `node backups-restore-with-multiple-kms-keys.js \ + ${MULTI_REGION_INSTANCE_ID} \ + ${MR_CMEK_RESTORED} \ + ${MR_CMEK_BACKUP} \ + ${PROJECT_ID} \ + "${key1.name},${key2.name},${key3.name}"`, + ); + assert.match(output, /Database restored from backup./); + assert.match( + output, + new RegExp( + `Database (.+) was restored to ${MR_CMEK_RESTORED} from backup ` + + `(.+)${MR_CMEK_BACKUP}.`, + ), + ); + }); + + // add split points, enable when drop database automatically reclaims quota + it.skip('should add split points', async () => { + const output = execSync( + `node database-add-split-points.js ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`, + ); + assert.match(output, new RegExp('Added Split Points')); + }); + }); +}); diff --git a/handwritten/spanner/samples/table-alter-with-foreign-key-delete-cascade.js b/handwritten/spanner/samples/table-alter-with-foreign-key-delete-cascade.js new file mode 100644 index 00000000000..382b918ecf4 --- /dev/null +++ b/handwritten/spanner/samples/table-alter-with-foreign-key-delete-cascade.js @@ -0,0 +1,70 @@ +// Copyright 2024 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] + + /** + * 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'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function alterTableWithForeignKeyDeleteCascade() { + const request = [ + `ALTER TABLE ShoppingCarts + ADD CONSTRAINT FKShoppingCartsCustomerName + FOREIGN KEY (CustomerName) + REFERENCES Customers(CustomerName) + ON DELETE CASCADE`, + ]; + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + 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/handwritten/spanner/samples/table-create-with-foreign-key-delete-cascade.js b/handwritten/spanner/samples/table-create-with-foreign-key-delete-cascade.js new file mode 100644 index 00000000000..93c1791bf97 --- /dev/null +++ b/handwritten/spanner/samples/table-create-with-foreign-key-delete-cascade.js @@ -0,0 +1,78 @@ +// Copyright 2024 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] + + /** + * 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'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function createTableWithForeignKeyDeleteCascade() { + const request = [ + `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 [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + 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/handwritten/spanner/samples/table-drop-foreign-key-constraint-delete-cascade.js b/handwritten/spanner/samples/table-drop-foreign-key-constraint-delete-cascade.js new file mode 100644 index 00000000000..05a2880c363 --- /dev/null +++ b/handwritten/spanner/samples/table-drop-foreign-key-constraint-delete-cascade.js @@ -0,0 +1,70 @@ +// Copyright 2024 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] + + /** + * 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'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + async function dropForeignKeyConstraintDeleteCascade() { + const request = [ + `ALTER TABLE ShoppingCarts + DROP CONSTRAINT FKShoppingCartsCustomerName`, + ]; + + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + 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/handwritten/spanner/samples/timestamp.js b/handwritten/spanner/samples/timestamp.js new file mode 100644 index 00000000000..2453b167ba7 --- /dev/null +++ b/handwritten/spanner/samples/timestamp.js @@ -0,0 +1,434 @@ +// Copyright 2018 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. + +'use strict'; + +async function createTableWithTimestamp(instanceId, databaseId, projectId) { + // [START spanner_create_table_with_timestamp_column] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they + // must be converted to strings before being inserted as INT64s + const request = [ + `CREATE TABLE Performances ( + SingerId INT64 NOT NULL, + VenueId INT64 NOT NULL, + EventDate DATE, + Revenue INT64, + LastUpdateTime TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true) + ) PRIMARY KEY (SingerId, VenueId, EventDate), + INTERLEAVE IN PARENT Singers ON DELETE CASCADE`, + ]; + + // Creates a table in an existing database + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log(`Waiting for operation on ${databaseId} to complete...`); + + await operation.promise(); + + console.log(`Created table Performances in database ${databaseId}.`); + // [END spanner_create_table_with_timestamp_column] +} + +async function insertWithTimestamp(instanceId, databaseId, projectId) { + // [START spanner_insert_data_with_timestamp_column] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Instantiate Spanner table objects + const performancesTable = database.table('Performances'); + + const data = [ + { + SingerId: '1', + VenueId: '4', + EventDate: '2017-10-05', + Revenue: '11000', + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + SingerId: '1', + VenueId: '19', + EventDate: '2017-11-02', + Revenue: '15000', + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + SingerId: '2', + VenueId: '42', + EventDate: '2017-12-23', + Revenue: '7000', + LastUpdateTime: 'spanner.commit_timestamp()', + }, + ]; + + // Inserts rows into the Singers table + // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so + // they must be converted to strings before being inserted as INT64s + try { + await performancesTable.insert(data); + console.log('Inserted data.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished + database.close(); + } + // [END spanner_insert_data_with_timestamp_column] +} + +async function queryTableWithTimestamp(instanceId, databaseId, projectId) { + // [START spanner_query_new_table_with_timestamp_column] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: `SELECT SingerId, VenueId, EventDate, Revenue, LastUpdateTime + FROM Performances + ORDER BY LastUpdateTime DESC`, + }; + + // Queries rows from the Performances table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + console.log( + `SingerId: ${json.SingerId}, VenueId: ${json.VenueId}, EventDate: ${json.EventDate}, Revenue: ${json.Revenue}, LastUpdateTime: ${json.LastUpdateTime}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished + database.close(); + } + // [END spanner_query_new_table_with_timestamp_column] +} + +async function addTimestampColumn(instanceId, databaseId, projectId) { + // [START spanner_add_timestamp_column] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner Database Admin Client object + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + const request = [ + `ALTER TABLE Albums ADD COLUMN LastUpdateTime TIMESTAMP OPTIONS + (allow_commit_timestamp=true)`, + ]; + + // Adds a new commit timestamp column to the Albums table + try { + const [operation] = await databaseAdminClient.updateDatabaseDdl({ + database: databaseAdminClient.databasePath( + projectId, + instanceId, + databaseId, + ), + statements: request, + }); + + console.log('Waiting for operation to complete...'); + + await operation.promise(); + + console.log( + 'Added LastUpdateTime as a commit timestamp column in Albums table.', + ); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the spanner client when finished. + // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient. + spanner.close(); + } + // [END spanner_add_timestamp_column] +} + +async function updateWithTimestamp(instanceId, databaseId, projectId) { + // [START spanner_update_data_with_timestamp_column] + // [START_EXCLUDE] + // This sample uses the `MarketingBudget` column. You can add the column + // by running the `schema.js addColumn` sample or by running this DDL statement against + // your database: + // ALTER TABLE Albums ADD COLUMN MarketingBudget INT64 + // + // In addition this update expects the `LastUpdateTime` column + // added by running the `timestamp.js addTimestampColumn` sample + // or applying the DDL statement: + // ALTER TABLE Albums ADD COLUMN + // LastUpdateTime TIMESTAMP OPTIONS (allow_commit_timestamp=true) + // [END_EXCLUDE] + + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Update a row in the Albums table + // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they + // must be converted to strings before being inserted as INT64s + const albumsTable = database.table('Albums'); + + const data = [ + { + SingerId: '1', + AlbumId: '1', + MarketingBudget: '1000000', + LastUpdateTime: 'spanner.commit_timestamp()', + }, + { + SingerId: '2', + AlbumId: '2', + MarketingBudget: '750000', + LastUpdateTime: 'spanner.commit_timestamp()', + }, + ]; + + try { + await albumsTable.update(data); + console.log('Updated data.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished + database.close(); + } + // [END spanner_update_data_with_timestamp_column] +} + +async function queryWithTimestamp(instanceId, databaseId, projectId) { + // [START spanner_query_data_with_timestamp_column] + // [START_EXCLUDE] + // This sample uses the `MarketingBudget` column. You can add the column + // by running the `schema.js addColumn` sample or by running this DDL statement against + // your database: + // ALTER TABLE Albums ADD COLUMN MarketingBudget INT64 + // + // In addition this query expects the `LastUpdateTime` column + // added by running the `timestamp.js addTimestampColumn` sample + // or applying the DDL statement: + // ALTER TABLE Albums ADD COLUMN + // LastUpdateTime TIMESTAMP OPTIONS (allow_commit_timestamp=true) + // [END_EXCLUDE] + + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const query = { + sql: `SELECT SingerId, AlbumId, MarketingBudget, LastUpdateTime + FROM Albums ORDER BY LastUpdateTime DESC`, + }; + + // Queries rows from the Albums table + try { + const [rows] = await database.run(query); + + rows.forEach(row => { + const json = row.toJSON(); + + console.log( + `SingerId: ${json.SingerId}, AlbumId: ${ + json.AlbumId + }, MarketingBudget: ${ + json.MarketingBudget ? json.MarketingBudget : null + }, LastUpdateTime: ${json.LastUpdateTime}`, + ); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished + database.close(); + } + // [END spanner_query_data_with_timestamp_column] +} + +require('yargs') + .demand(1) + .command( + 'createTableWithTimestamp ', + 'Creates an example table with a commit timestamp column in a Cloud Spanner database.', + {}, + opts => + createTableWithTimestamp( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'insertWithTimestamp ', + 'Inserts new rows of data including commit timestamps into an example Cloud Spanner table.', + {}, + opts => + insertWithTimestamp(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryTableWithTimestamp ', + 'Executes a read-only SQL query against an example Cloud Spanner table created with a commit timestamp column.', + {}, + opts => + queryTableWithTimestamp( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .command( + 'addTimestampColumn ', + 'Adds a example commit timestamp column to an existing example Cloud Spanner table.', + {}, + opts => + addTimestampColumn(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'updateWithTimestamp ', + 'Modifies existing rows of data in an example Cloud Spanner table with a commit timestamp column..', + {}, + opts => + updateWithTimestamp(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'queryWithTimestamp ', + `Executes a read-only SQL query against an example Cloud Spanner table with an additional + column (LastUpdateTime) added by addTimestampColumn.`, + {}, + opts => + queryWithTimestamp(opts.instanceName, opts.databaseName, opts.projectId), + ) + .example( + 'node $0 createTableWithTimestamp "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 insertWithTimestamp "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 queryTableWithTimestamp "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 addTimestampColumn "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 updateWithTimestamp "my-instance" "my-database" "my-project-id"', + ) + .example( + 'node $0 queryWithTimestamp "my-instance" "my-database" "my-project-id"', + ) + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/transaction-tag.js b/handwritten/spanner/samples/transaction-tag.js new file mode 100644 index 00000000000..4a9c857c514 --- /dev/null +++ b/handwritten/spanner/samples/transaction-tag.js @@ -0,0 +1,93 @@ +/** + * Copyright 2021 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: Executes a read/write transaction with transaction and request tags +// usage: node transaction-tag.js + +'use strict'; + +function main(instanceId, databaseId, projectId) { + // [START spanner_set_transaction_tag] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function transactionTag() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Run a transaction with a transaction tag that will automatically be + // included with each request in the transaction. + try { + await database.runTransactionAsync( + {requestOptions: {transactionTag: 'app=cart,env=dev'}}, + async tx => { + // Set the request tag to "app=concert,env=dev,action=update". + // This request tag will only be set on this request. + await tx.runUpdate({ + sql: 'UPDATE Venues SET Capacity = DIV(Capacity, 4) WHERE OutdoorVenue = false', + requestOptions: {requestTag: 'app=concert,env=dev,action=update'}, + }); + console.log('Updated capacity of all indoor venues to 1/4.'); + + await tx.runUpdate({ + sql: `INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime) + VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())`, + params: { + venueId: 81, + venueName: 'Venue 81', + capacity: 1440, + outdoorVenue: true, + }, + types: { + venueId: {type: 'int64'}, + venueName: {type: 'string'}, + capacity: {type: 'int64'}, + outdoorVenue: {type: 'bool'}, + }, + requestOptions: {requestTag: 'app=concert,env=dev,action=update'}, + }); + console.log('Inserted new outdoor venue'); + + await tx.commit(); + }, + ); + } catch (err) { + console.error('ERROR:', err); + } finally { + await database.close(); + } + } + transactionTag(); + // [END spanner_set_transaction_tag] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/transaction-timeout.js b/handwritten/spanner/samples/transaction-timeout.js new file mode 100644 index 00000000000..e51c9411330 --- /dev/null +++ b/handwritten/spanner/samples/transaction-timeout.js @@ -0,0 +1,77 @@ +/** + * Copyright 2025 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: Executes a read/write transaction with transaction timeout +// usage: node transaction-timeout.js + +'use strict'; + +function main(instanceId, databaseId, projectId) { + // [START spanner_transaction_timeout] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + async function executeTransactionWithTimeout() { + // Gets a reference to a Cloud Spanner instance and database. + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const options = { + timeout: 60000, // 60 seconds timeout + }; + + try { + await database.runTransactionAsync(options, async tx => { + const [results] = await tx.run( + 'SELECT SingerId, FirstName, LastName FROM Singers ORDER BY LastName, FirstName', + ); + results.forEach(result => { + console.log( + `${result[0].name}: ${result[0].value.value}, ${result[1].name}: ${result[1].value}, ${result[2].name}: ${result[2].value}`, + ); + }); + const sql = + "INSERT INTO Singers (SingerId, FirstName, LastName) VALUES (100, 'George', 'Washington')"; + const [rowCount] = await tx.runUpdate(sql); + console.log(`${rowCount} record inserted.`); + await tx.commit(); + }); + } catch (err) { + console.error('ERROR:', err); + } finally { + await database.close(); + } + } + executeTransactionWithTimeout(); + // [END spanner_transaction_timeout] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/transaction.js b/handwritten/spanner/samples/transaction.js new file mode 100644 index 00000000000..b72d9d3a395 --- /dev/null +++ b/handwritten/spanner/samples/transaction.js @@ -0,0 +1,225 @@ +// Copyright 2016 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. + +'use strict'; + +function readOnlyTransaction(instanceId, databaseId, projectId) { + // [START spanner_read_only_transaction] + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + // Gets a transaction object that captures the database state + // at a specific point in time + database.getSnapshot(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + const queryOne = 'SELECT SingerId, AlbumId, AlbumTitle FROM Albums'; + + try { + // Read #1, using SQL + const [qOneRows] = await transaction.run(queryOne); + + qOneRows.forEach(row => { + const json = row.toJSON(); + console.log( + `SingerId: ${json.SingerId}, AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`, + ); + }); + + const queryTwo = { + columns: ['SingerId', 'AlbumId', 'AlbumTitle'], + }; + + // Read #2, using the `read` method. Even if changes occur + // in-between the reads, the transaction ensures that both + // return the same data. + const [qTwoRows] = await transaction.read('Albums', queryTwo); + + qTwoRows.forEach(row => { + const json = row.toJSON(); + console.log( + `SingerId: ${json.SingerId}, AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`, + ); + }); + + console.log('Successfully executed read-only transaction.'); + } catch (err) { + console.error('ERROR:', err); + } finally { + transaction.end(); + // Close the database when finished. + await database.close(); + } + }); + // [END spanner_read_only_transaction] +} + +function readWriteTransaction(instanceId, databaseId, projectId) { + // [START spanner_read_write_transaction] + // This sample transfers 200,000 from the MarketingBudget field + // of the second Album to the first Album, as long as the second + // Album has enough money in its budget. Make sure to run the + // addColumn and updateData samples first (in that order). + + // 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'; + // const databaseId = 'my-database'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + // Gets a reference to a Cloud Spanner instance and database + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + + const transferAmount = 200000; + + database.runTransaction(async (err, transaction) => { + if (err) { + console.error(err); + return; + } + let firstBudget, secondBudget; + const queryOne = { + columns: ['MarketingBudget'], + keys: [[2, 2]], // SingerId: 2, AlbumId: 2 + }; + + const queryTwo = { + columns: ['MarketingBudget'], + keys: [[1, 1]], // SingerId: 1, AlbumId: 1 + }; + + Promise.all([ + // Reads the second album's budget + transaction.read('Albums', queryOne).then(results => { + // Gets second album's budget + const rows = results[0].map(row => row.toJSON()); + secondBudget = rows[0].MarketingBudget; + console.log(`The second album's marketing budget: ${secondBudget}`); + + // Makes sure the second album's budget is large enough + if (secondBudget < transferAmount) { + throw new Error( + `The second album's budget (${secondBudget}) is less than the transfer amount (${transferAmount}).`, + ); + } + }), + + // Reads the first album's budget + transaction.read('Albums', queryTwo).then(results => { + // Gets first album's budget + const rows = results[0].map(row => row.toJSON()); + firstBudget = rows[0].MarketingBudget; + console.log(`The first album's marketing budget: ${firstBudget}`); + }), + ]) + .then(() => { + console.log(firstBudget, secondBudget); + // Transfers the budgets between the albums + firstBudget += transferAmount; + secondBudget -= transferAmount; + + console.log(firstBudget, secondBudget); + + // Updates the database + // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they + // must be converted (back) to strings before being inserted as INT64s. + transaction.update('Albums', [ + { + SingerId: '1', + AlbumId: '1', + MarketingBudget: firstBudget.toString(), + }, + { + SingerId: '2', + AlbumId: '2', + MarketingBudget: secondBudget.toString(), + }, + ]); + }) + .then(() => { + // Commits the transaction and send the changes to the database + return transaction.commit(); + }) + .then(() => { + console.log( + `Successfully executed read-write transaction to transfer ${transferAmount} from Album 2 to Album 1.`, + ); + }) + .catch(err => { + console.error('ERROR:', err); + }) + .then(() => { + transaction.end(); + // Closes the database when finished + return database.close(); + }); + }); + // [END spanner_read_write_transaction] +} + +require('yargs') + .demand(1) + .command( + 'readOnly ', + 'Execute a read-only transaction on an example Cloud Spanner table.', + {}, + opts => + readOnlyTransaction(opts.instanceName, opts.databaseName, opts.projectId), + ) + .command( + 'readWrite ', + 'Execute a read-write transaction on an example Cloud Spanner table.', + {}, + opts => + readWriteTransaction( + opts.instanceName, + opts.databaseName, + opts.projectId, + ), + ) + .example('node $0 readOnly "my-instance" "my-database" "my-project-id"') + .example('node $0 readWrite "my-instance" "my-database" "my-project-id"') + .wrap(120) + .recommendCommands() + .epilogue('For more information, see https://cloud.google.com/spanner/docs') + .strict() + .help().argv; diff --git a/handwritten/spanner/samples/update-backup-schedule.js b/handwritten/spanner/samples/update-backup-schedule.js new file mode 100644 index 00000000000..41e3fcbcb2c --- /dev/null +++ b/handwritten/spanner/samples/update-backup-schedule.js @@ -0,0 +1,90 @@ +/** + * Copyright 2024 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: Updates a backup schedule +// usage: node update-backup-schedule.js + +'use strict'; + +function main( + projectId = 'my-project-id', + instanceId = 'my-instance-id', + databaseId = 'my-database-id', + scheduleId = 'my-schedule-id', +) { + async function updateBackupSchedule() { + // [START spanner_update_backup_schedule] + // Import the Google Cloud client library for Spanner. + const {Spanner} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance-id'; + // const databaseId = 'my-database-id'; + // const scheduleId = 'my-schedule-id'; + + // Create a Spanner database admin client. + const spanner = new Spanner({projectId}); + const client = spanner.getDatabaseAdminClient(); + + try { + // Update the schedule to create backups daily at 3:45 PM, using the + // database's encryption config, and retained for 48 hours. + const [response] = await client.updateBackupSchedule({ + backupSchedule: { + name: client.backupSchedulePath( + projectId, + instanceId, + databaseId, + scheduleId, + ), + spec: { + cronSpec: { + text: '45 15 * * *', + }, + }, + retentionDuration: { + seconds: 172800, + }, + encryptionConfig: { + encryptionType: 'USE_DATABASE_ENCRYPTION', + }, + }, + updateMask: { + paths: [ + 'spec.cron_spec.text', + 'retention_duration', + 'encryption_config', + ], + }, + }); + console.log('Updated backup schedule:', response); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_update_backup_schedule] + } + + updateBackupSchedule(); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/handwritten/spanner/samples/update-instance-default-backup-schedule-type.js b/handwritten/spanner/samples/update-instance-default-backup-schedule-type.js new file mode 100644 index 00000000000..cd222a56bc7 --- /dev/null +++ b/handwritten/spanner/samples/update-instance-default-backup-schedule-type.js @@ -0,0 +1,74 @@ +/** + * Copyright 2024 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: Updates an instance. +// usage: node instance-update.js + +'use strict'; + +function main(instanceId, projectId) { + async function updateInstanceDefaultBackupScheduleType() { + // [START spanner_update_instance_default_backup_schedule_type] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + const instanceAdminClient = await spanner.getInstanceAdminClient(); + + // Updates an instance + try { + const [operation] = await instanceAdminClient.updateInstance({ + instance: { + name: instanceAdminClient.instancePath(projectId, instanceId), + defaultBackupScheduleType: + protos.google.spanner.admin.instance.v1.Instance + .DefaultBackupScheduleType.AUTOMATIC, // optional + }, + // Field mask specifying fields that should get updated in an Instance + fieldMask: (protos.google.protobuf.FieldMask = { + paths: ['default_backup_schedule_type'], + }), + }); + + await operation.promise(); + const [metadata] = await instanceAdminClient.getInstance({ + name: instanceAdminClient.instancePath(projectId, instanceId), + }); + console.log( + `Instance ${instanceId} has been updated with the ${metadata.defaultBackupScheduleType}` + + ' default backup schedule type.', + ); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_update_instance_default_backup_schedule_type] + } + updateInstanceDefaultBackupScheduleType(); +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2));