From a4623c560c16fa1f37a06cb57a5e47a1d6759d27 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 17:09:17 -0700 Subject: [PATCH 1/8] feat: add `RESOURCE_EXHAUSTED` to the list of retryable error codes (#2032) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add `RESOURCE_EXHAUSTED` to the list of retryable error codes PiperOrigin-RevId: 628281023 Source-Link: https://github.com/googleapis/googleapis/commit/60536a2a263b6d33b0b1adb5b10c10e34ccf4528 Source-Link: https://github.com/googleapis/googleapis-gen/commit/c5cfd5b956f9eadff54096c9f1c8a57ab01db294 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiYzVjZmQ1Yjk1NmY5ZWFkZmY1NDA5NmM5ZjFjOGE1N2FiMDFkYjI5NCJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .gitignore | 1 - src/v1/spanner_client_config.json | 29 +++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 14050d4e4..d4f03a0df 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,3 @@ system-test/*key.json .DS_Store package-lock.json __pycache__ -.vscode \ No newline at end of file diff --git a/src/v1/spanner_client_config.json b/src/v1/spanner_client_config.json index f1ed7096b..3d5086946 100644 --- a/src/v1/spanner_client_config.json +++ b/src/v1/spanner_client_config.json @@ -7,7 +7,8 @@ "DEADLINE_EXCEEDED", "UNAVAILABLE" ], - "unavailable": [ + "resource_exhausted_unavailable": [ + "RESOURCE_EXHAUSTED", "UNAVAILABLE" ] }, @@ -34,32 +35,32 @@ "methods": { "CreateSession": { "timeout_millis": 30000, - "retry_codes_name": "unavailable", + "retry_codes_name": "resource_exhausted_unavailable", "retry_params_name": "9442ca297df43f7314712e1a19d003838e738a45" }, "BatchCreateSessions": { "timeout_millis": 60000, - "retry_codes_name": "unavailable", + "retry_codes_name": "resource_exhausted_unavailable", "retry_params_name": "9442ca297df43f7314712e1a19d003838e738a45" }, "GetSession": { "timeout_millis": 30000, - "retry_codes_name": "unavailable", + "retry_codes_name": "resource_exhausted_unavailable", "retry_params_name": "9442ca297df43f7314712e1a19d003838e738a45" }, "ListSessions": { "timeout_millis": 3600000, - "retry_codes_name": "unavailable", + "retry_codes_name": "resource_exhausted_unavailable", "retry_params_name": "9442ca297df43f7314712e1a19d003838e738a45" }, "DeleteSession": { "timeout_millis": 30000, - "retry_codes_name": "unavailable", + "retry_codes_name": "resource_exhausted_unavailable", "retry_params_name": "9442ca297df43f7314712e1a19d003838e738a45" }, "ExecuteSql": { "timeout_millis": 30000, - "retry_codes_name": "unavailable", + "retry_codes_name": "resource_exhausted_unavailable", "retry_params_name": "9442ca297df43f7314712e1a19d003838e738a45" }, "ExecuteStreamingSql": { @@ -69,12 +70,12 @@ }, "ExecuteBatchDml": { "timeout_millis": 30000, - "retry_codes_name": "unavailable", + "retry_codes_name": "resource_exhausted_unavailable", "retry_params_name": "9442ca297df43f7314712e1a19d003838e738a45" }, "Read": { "timeout_millis": 30000, - "retry_codes_name": "unavailable", + "retry_codes_name": "resource_exhausted_unavailable", "retry_params_name": "9442ca297df43f7314712e1a19d003838e738a45" }, "StreamingRead": { @@ -84,27 +85,27 @@ }, "BeginTransaction": { "timeout_millis": 30000, - "retry_codes_name": "unavailable", + "retry_codes_name": "resource_exhausted_unavailable", "retry_params_name": "9442ca297df43f7314712e1a19d003838e738a45" }, "Commit": { "timeout_millis": 3600000, - "retry_codes_name": "unavailable", + "retry_codes_name": "resource_exhausted_unavailable", "retry_params_name": "9442ca297df43f7314712e1a19d003838e738a45" }, "Rollback": { "timeout_millis": 30000, - "retry_codes_name": "unavailable", + "retry_codes_name": "resource_exhausted_unavailable", "retry_params_name": "9442ca297df43f7314712e1a19d003838e738a45" }, "PartitionQuery": { "timeout_millis": 30000, - "retry_codes_name": "unavailable", + "retry_codes_name": "resource_exhausted_unavailable", "retry_params_name": "9442ca297df43f7314712e1a19d003838e738a45" }, "PartitionRead": { "timeout_millis": 30000, - "retry_codes_name": "unavailable", + "retry_codes_name": "resource_exhausted_unavailable", "retry_params_name": "9442ca297df43f7314712e1a19d003838e738a45" }, "BatchWrite": { From d86c1b0c21c7c95e3110221b3ca6ff9ff3b4a088 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 4 May 2024 13:24:16 +0200 Subject: [PATCH 2/8] fix(deps): update dependency google-gax to v4.3.3 (#2038) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [google-gax](https://togithub.com/googleapis/gax-nodejs) ([source](https://togithub.com/googleapis/gax-nodejs/tree/HEAD/gax)) | [`4.3.2` -> `4.3.3`](https://renovatebot.com/diffs/npm/google-gax/4.3.2/4.3.3) | [![age](https://developer.mend.io/api/mc/badges/age/npm/google-gax/4.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/google-gax/4.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/google-gax/4.3.2/4.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/google-gax/4.3.2/4.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
googleapis/gax-nodejs (google-gax) ### [`v4.3.3`](https://togithub.com/googleapis/gax-nodejs/blob/HEAD/gax/CHANGELOG.md#433-2024-04-19) [Compare Source](https://togithub.com/googleapis/gax-nodejs/compare/google-gax-v4.3.2...google-gax-v4.3.3) ##### Bug Fixes - don't retry server streaming calls if retryCodes is the empty array ([#​1578](https://togithub.com/googleapis/gax-nodejs/issues/1578)) ([150a683](https://togithub.com/googleapis/gax-nodejs/commit/150a683514fe8c310d03d5f1a82a80160438effb)) - update minimum grpc-js to 1.10.3 to skip potentially problematic 1.10.2 ([#​1576](https://togithub.com/googleapis/gax-nodejs/issues/1576)) ([43e6fc0](https://togithub.com/googleapis/gax-nodejs/commit/43e6fc09c05ece5279ff8f28fc646285260673ce))
--- ### Configuration 📅 **Schedule**: Branch creation - "after 9am and before 3pm" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/nodejs-spanner). --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b8cdca08..c6ed8b666 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "events-intercept": "^2.0.0", "extend": "^3.0.2", "google-auth-library": "^9.0.0", - "google-gax": "4.3.2", + "google-gax": "4.3.3", "grpc-gcp": "^1.0.0", "is": "^3.2.1", "lodash.snakecase": "^4.1.1", From 81fa610895fe709cbb7429896493a67407a6343c Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 10 May 2024 11:04:42 +0530 Subject: [PATCH 3/8] fix!: An existing method `UpdateVehicleLocation` is removed from service `VehicleService` (#2037) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Add support for multi region encryption config docs: fix linting for several doc comments PiperOrigin-RevId: 630422337 Source-Link: https://github.com/googleapis/googleapis/commit/65db386b43905c561686b58344c5b620a10ed808 Source-Link: https://github.com/googleapis/googleapis-gen/commit/b798ca9f56e2ad3e0d14982b68b6724d1c3d62b5 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiYjc5OGNhOWY1NmUyYWQzZTBkMTQ5ODJiNjhiNjcyNGQxYzNkNjJiNSJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * fix!: An existing method `UpdateVehicleLocation` is removed from service `VehicleService` fix!: An existing method `SearchFuzzedVehicles` is removed from service `VehicleService` fix!: An existing message `UpdateVehicleLocationRequest` is removed PiperOrigin-RevId: 631557549 Source-Link: https://github.com/googleapis/googleapis/commit/3d50414a7ff3f0b8ffe8ad7858257396e4f18131 Source-Link: https://github.com/googleapis/googleapis-gen/commit/5ce63d4e636a975175bde2d16c15e70dd5a81ff4 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiNWNlNjNkNGU2MzZhOTc1MTc1YmRlMmQxNmMxNWU3MGRkNWE4MWZmNCJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot Co-authored-by: surbhigarg92 --- .../spanner/admin/database/v1/backup.proto | 299 ++++++++++------- .../spanner/admin/database/v1/common.proto | 40 ++- .../database/v1/spanner_database_admin.proto | 266 ++++++++++------ .../spanner/admin/instance/v1/common.proto | 2 +- .../spanner/executor/v1/cloud_executor.proto | 2 +- .../google/spanner/v1/commit_response.proto | 2 +- protos/google/spanner/v1/keys.proto | 2 +- protos/google/spanner/v1/mutation.proto | 2 +- protos/google/spanner/v1/query_plan.proto | 2 +- protos/google/spanner/v1/result_set.proto | 2 +- protos/google/spanner/v1/spanner.proto | 2 +- protos/google/spanner/v1/transaction.proto | 2 +- protos/google/spanner/v1/type.proto | 2 +- protos/protos.d.ts | 30 ++ protos/protos.js | 204 ++++++++++++ protos/protos.json | 43 +++ src/v1/database_admin_client.ts | 301 ++++++++++-------- 17 files changed, 848 insertions(+), 355 deletions(-) diff --git a/protos/google/spanner/admin/database/v1/backup.proto b/protos/google/spanner/admin/database/v1/backup.proto index fce69a2f3..bb8ef4d55 100644 --- a/protos/google/spanner/admin/database/v1/backup.proto +++ b/protos/google/spanner/admin/database/v1/backup.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// 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. @@ -51,14 +51,14 @@ message Backup { READY = 2; } - // Required for the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] operation. - // Name of the database from which this backup was - // created. This needs to be in the same instance as the backup. - // Values are of the form + // Required for the + // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] + // operation. Name of the database from which this backup was created. This + // needs to be in the same instance as the backup. Values are of the form // `projects//instances//databases/`. string database = 2 [(google.api.resource_reference) = { - type: "spanner.googleapis.com/Database" - }]; + type: "spanner.googleapis.com/Database" + }]; // The backup will contain an externally consistent copy of the database at // the timestamp specified by `version_time`. If `version_time` is not @@ -66,7 +66,8 @@ message Backup { // backup. google.protobuf.Timestamp version_time = 9; - // Required for the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] + // Required for the + // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] // operation. The expiration time of the backup, with microseconds // granularity that must be at least 6 hours and at most 366 days // from the time the CreateBackup request is processed. Once the `expire_time` @@ -74,8 +75,11 @@ message Backup { // Spanner to free the resources used by the backup. google.protobuf.Timestamp expire_time = 3; - // Output only for the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] operation. - // Required for the [UpdateBackup][google.spanner.admin.database.v1.DatabaseAdmin.UpdateBackup] operation. + // Output only for the + // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] + // operation. Required for the + // [UpdateBackup][google.spanner.admin.database.v1.DatabaseAdmin.UpdateBackup] + // operation. // // A globally unique identifier for the backup which cannot be // changed. Values are of the form @@ -89,10 +93,12 @@ message Backup { // `projects//instances/`. string name = 1; - // Output only. The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] + // Output only. The time the + // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] // request is received. If the request does not specify `version_time`, the // `version_time` of the backup will be equivalent to the `create_time`. - google.protobuf.Timestamp create_time = 4 [(google.api.field_behavior) = OUTPUT_ONLY]; + google.protobuf.Timestamp create_time = 4 + [(google.api.field_behavior) = OUTPUT_ONLY]; // Output only. Size of the backup in bytes. int64 size_bytes = 5 [(google.api.field_behavior) = OUTPUT_ONLY]; @@ -115,10 +121,21 @@ message Backup { ]; // Output only. The encryption information for the backup. - EncryptionInfo encryption_info = 8 [(google.api.field_behavior) = OUTPUT_ONLY]; + EncryptionInfo encryption_info = 8 + [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Output only. The encryption information for the backup, whether it is + // protected by one or more KMS keys. The information includes all Cloud + // KMS key versions used to encrypt the backup. The `encryption_status' field + // inside of each `EncryptionInfo` is not populated. At least one of the key + // versions must be available for the backup to be restored. If a key version + // is revoked in the middle of a restore, the restore behavior is undefined. + repeated EncryptionInfo encryption_information = 13 + [(google.api.field_behavior) = OUTPUT_ONLY]; // Output only. The database dialect information for the backup. - DatabaseDialect database_dialect = 10 [(google.api.field_behavior) = OUTPUT_ONLY]; + DatabaseDialect database_dialect = 10 + [(google.api.field_behavior) = OUTPUT_ONLY]; // Output only. The names of the destination backups being created by copying // this source backup. The backup names are of the form @@ -129,9 +146,7 @@ message Backup { // destination backup is deleted), the reference to the backup is removed. repeated string referencing_backups = 11 [ (google.api.field_behavior) = OUTPUT_ONLY, - (google.api.resource_reference) = { - type: "spanner.googleapis.com/Backup" - } + (google.api.resource_reference) = { type: "spanner.googleapis.com/Backup" } ]; // Output only. The max allowed expiration time of the backup, with @@ -139,10 +154,12 @@ message Backup { // multiple APIs: CreateBackup, UpdateBackup, CopyBackup. When updating or // copying an existing backup, the expiration time specified must be // less than `Backup.max_expire_time`. - google.protobuf.Timestamp max_expire_time = 12 [(google.api.field_behavior) = OUTPUT_ONLY]; + google.protobuf.Timestamp max_expire_time = 12 + [(google.api.field_behavior) = OUTPUT_ONLY]; } -// The request for [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]. +// The request for +// [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]. message CreateBackupRequest { // Required. The name of the instance in which the backup will be // created. This must be the same instance that contains the database the @@ -165,29 +182,31 @@ message CreateBackupRequest { // Required. The backup to create. Backup backup = 3 [(google.api.field_behavior) = REQUIRED]; - // Optional. The encryption configuration used to encrypt the backup. If this field is - // not specified, the backup will use the same - // encryption configuration as the database by default, namely - // [encryption_type][google.spanner.admin.database.v1.CreateBackupEncryptionConfig.encryption_type] = - // `USE_DATABASE_ENCRYPTION`. - CreateBackupEncryptionConfig encryption_config = 4 [(google.api.field_behavior) = OPTIONAL]; + // Optional. The encryption configuration used to encrypt the backup. If this + // field is not specified, the backup will use the same encryption + // configuration as the database by default, namely + // [encryption_type][google.spanner.admin.database.v1.CreateBackupEncryptionConfig.encryption_type] + // = `USE_DATABASE_ENCRYPTION`. + CreateBackupEncryptionConfig encryption_config = 4 + [(google.api.field_behavior) = OPTIONAL]; } // Metadata type for the operation returned by // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup]. message CreateBackupMetadata { // The name of the backup being created. - string name = 1 [(google.api.resource_reference) = { - type: "spanner.googleapis.com/Backup" - }]; + string name = 1 [ + (google.api.resource_reference) = { type: "spanner.googleapis.com/Backup" } + ]; // The name of the database the backup is created from. string database = 2 [(google.api.resource_reference) = { - type: "spanner.googleapis.com/Database" - }]; + type: "spanner.googleapis.com/Database" + }]; // The progress of the - // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] operation. + // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] + // operation. OperationProgress progress = 3; // The time at which cancellation of this operation was received. @@ -205,10 +224,11 @@ message CreateBackupMetadata { google.protobuf.Timestamp cancel_time = 4; } -// The request for [CopyBackup][google.spanner.admin.database.v1.DatabaseAdmin.CopyBackup]. +// The request for +// [CopyBackup][google.spanner.admin.database.v1.DatabaseAdmin.CopyBackup]. message CopyBackupRequest { - // Required. The name of the destination instance that will contain the backup copy. - // Values are of the form: `projects//instances/`. + // Required. The name of the destination instance that will contain the backup + // copy. Values are of the form: `projects//instances/`. string parent = 1 [ (google.api.field_behavior) = REQUIRED, (google.api.resource_reference) = { @@ -229,9 +249,7 @@ message CopyBackupRequest { // `projects//instances//backups/`. string source_backup = 3 [ (google.api.field_behavior) = REQUIRED, - (google.api.resource_reference) = { - type: "spanner.googleapis.com/Backup" - } + (google.api.resource_reference) = { type: "spanner.googleapis.com/Backup" } ]; // Required. The expiration time of the backup in microsecond granularity. @@ -239,35 +257,38 @@ message CopyBackupRequest { // from the `create_time` of the source backup. Once the `expire_time` has // passed, the backup is eligible to be automatically deleted by Cloud Spanner // to free the resources used by the backup. - google.protobuf.Timestamp expire_time = 4 [(google.api.field_behavior) = REQUIRED]; - - // Optional. The encryption configuration used to encrypt the backup. If this field is - // not specified, the backup will use the same - // encryption configuration as the source backup by default, namely - // [encryption_type][google.spanner.admin.database.v1.CopyBackupEncryptionConfig.encryption_type] = - // `USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION`. - CopyBackupEncryptionConfig encryption_config = 5 [(google.api.field_behavior) = OPTIONAL]; + google.protobuf.Timestamp expire_time = 4 + [(google.api.field_behavior) = REQUIRED]; + + // Optional. The encryption configuration used to encrypt the backup. If this + // field is not specified, the backup will use the same encryption + // configuration as the source backup by default, namely + // [encryption_type][google.spanner.admin.database.v1.CopyBackupEncryptionConfig.encryption_type] + // = `USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION`. + CopyBackupEncryptionConfig encryption_config = 5 + [(google.api.field_behavior) = OPTIONAL]; } -// Metadata type for the google.longrunning.Operation returned by +// Metadata type for the operation returned by // [CopyBackup][google.spanner.admin.database.v1.DatabaseAdmin.CopyBackup]. message CopyBackupMetadata { // The name of the backup being created through the copy operation. // Values are of the form // `projects//instances//backups/`. - string name = 1 [(google.api.resource_reference) = { - type: "spanner.googleapis.com/Backup" - }]; + string name = 1 [ + (google.api.resource_reference) = { type: "spanner.googleapis.com/Backup" } + ]; // The name of the source backup that is being copied. // Values are of the form // `projects//instances//backups/`. - string source_backup = 2 [(google.api.resource_reference) = { - type: "spanner.googleapis.com/Backup" - }]; + string source_backup = 2 [ + (google.api.resource_reference) = { type: "spanner.googleapis.com/Backup" } + ]; // The progress of the - // [CopyBackup][google.spanner.admin.database.v1.DatabaseAdmin.CopyBackup] operation. + // [CopyBackup][google.spanner.admin.database.v1.DatabaseAdmin.CopyBackup] + // operation. OperationProgress progress = 3; // The time at which cancellation of CopyBackup operation was received. @@ -285,7 +306,8 @@ message CopyBackupMetadata { google.protobuf.Timestamp cancel_time = 4; } -// The request for [UpdateBackup][google.spanner.admin.database.v1.DatabaseAdmin.UpdateBackup]. +// The request for +// [UpdateBackup][google.spanner.admin.database.v1.DatabaseAdmin.UpdateBackup]. message UpdateBackupRequest { // Required. The backup to update. `backup.name`, and the fields to be updated // as specified by `update_mask` are required. Other fields are ignored. @@ -298,36 +320,36 @@ message UpdateBackupRequest { // resource, not to the request message. The field mask must always be // specified; this prevents any future fields from being erased accidentally // by clients that do not know about them. - google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = REQUIRED]; + google.protobuf.FieldMask update_mask = 2 + [(google.api.field_behavior) = REQUIRED]; } -// The request for [GetBackup][google.spanner.admin.database.v1.DatabaseAdmin.GetBackup]. +// The request for +// [GetBackup][google.spanner.admin.database.v1.DatabaseAdmin.GetBackup]. message GetBackupRequest { // Required. Name of the backup. // Values are of the form // `projects//instances//backups/`. string name = 1 [ (google.api.field_behavior) = REQUIRED, - (google.api.resource_reference) = { - type: "spanner.googleapis.com/Backup" - } + (google.api.resource_reference) = { type: "spanner.googleapis.com/Backup" } ]; } -// The request for [DeleteBackup][google.spanner.admin.database.v1.DatabaseAdmin.DeleteBackup]. +// The request for +// [DeleteBackup][google.spanner.admin.database.v1.DatabaseAdmin.DeleteBackup]. message DeleteBackupRequest { // Required. Name of the backup to delete. // Values are of the form // `projects//instances//backups/`. string name = 1 [ (google.api.field_behavior) = REQUIRED, - (google.api.resource_reference) = { - type: "spanner.googleapis.com/Backup" - } + (google.api.resource_reference) = { type: "spanner.googleapis.com/Backup" } ]; } -// The request for [ListBackups][google.spanner.admin.database.v1.DatabaseAdmin.ListBackups]. +// The request for +// [ListBackups][google.spanner.admin.database.v1.DatabaseAdmin.ListBackups]. message ListBackupsRequest { // Required. The instance to list backups from. Values are of the // form `projects//instances/`. @@ -346,7 +368,9 @@ message ListBackupsRequest { // must be one of: `<`, `>`, `<=`, `>=`, `!=`, `=`, or `:`. // Colon `:` is the contains operator. Filter rules are not case sensitive. // - // The following fields in the [Backup][google.spanner.admin.database.v1.Backup] are eligible for filtering: + // The following fields in the + // [Backup][google.spanner.admin.database.v1.Backup] are eligible for + // filtering: // // * `name` // * `database` @@ -380,21 +404,23 @@ message ListBackupsRequest { int32 page_size = 3; // If non-empty, `page_token` should contain a - // [next_page_token][google.spanner.admin.database.v1.ListBackupsResponse.next_page_token] from a - // previous [ListBackupsResponse][google.spanner.admin.database.v1.ListBackupsResponse] to the same `parent` and with the same - // `filter`. + // [next_page_token][google.spanner.admin.database.v1.ListBackupsResponse.next_page_token] + // from a previous + // [ListBackupsResponse][google.spanner.admin.database.v1.ListBackupsResponse] + // to the same `parent` and with the same `filter`. string page_token = 4; } -// The response for [ListBackups][google.spanner.admin.database.v1.DatabaseAdmin.ListBackups]. +// The response for +// [ListBackups][google.spanner.admin.database.v1.DatabaseAdmin.ListBackups]. message ListBackupsResponse { // The list of matching backups. Backups returned are ordered by `create_time` // in descending order, starting from the most recent `create_time`. repeated Backup backups = 1; // `next_page_token` can be sent in a subsequent - // [ListBackups][google.spanner.admin.database.v1.DatabaseAdmin.ListBackups] call to fetch more - // of the matching backups. + // [ListBackups][google.spanner.admin.database.v1.DatabaseAdmin.ListBackups] + // call to fetch more of the matching backups. string next_page_token = 2; } @@ -424,7 +450,9 @@ message ListBackupOperationsRequest { // * `name` - The name of the long-running operation // * `done` - False if the operation is in progress, else true. // * `metadata.@type` - the type of metadata. For example, the type string - // for [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata] is + // for + // [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata] + // is // `type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata`. // * `metadata.` - any field in metadata.value. // `metadata.@type` must be specified first if filtering on metadata @@ -442,14 +470,15 @@ message ListBackupOperationsRequest { // * `done:true` - The operation is complete. // * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ // `metadata.database:prod` - Returns operations where: - // * The operation's metadata type is [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. - // * The database the backup was taken from has a name containing the - // string "prod". + // * The operation's metadata type is + // [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. + // * The source database name of backup contains the string "prod". // * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ // `(metadata.name:howl) AND` \ // `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ // `(error:*)` - Returns operations where: - // * The operation's metadata type is [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. + // * The operation's metadata type is + // [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. // * The backup name contains the string "howl". // * The operation started before 2018-03-28T14:50:00Z. // * The operation resulted in an error. @@ -457,9 +486,9 @@ message ListBackupOperationsRequest { // `(metadata.source_backup:test) AND` \ // `(metadata.progress.start_time < \"2022-01-18T14:50:00Z\") AND` \ // `(error:*)` - Returns operations where: - // * The operation's metadata type is [CopyBackupMetadata][google.spanner.admin.database.v1.CopyBackupMetadata]. - // * The source backup of the copied backup name contains the string - // "test". + // * The operation's metadata type is + // [CopyBackupMetadata][google.spanner.admin.database.v1.CopyBackupMetadata]. + // * The source backup name contains the string "test". // * The operation started before 2022-01-18T14:50:00Z. // * The operation resulted in an error. // * `((metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ @@ -469,12 +498,13 @@ message ListBackupOperationsRequest { // `(metadata.source_backup:test_bkp)) AND` \ // `(error:*)` - Returns operations where: // * The operation's metadata matches either of criteria: - // * The operation's metadata type is [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata] AND the - // database the backup was taken from has name containing string + // * The operation's metadata type is + // [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata] + // AND the source database name of the backup contains the string // "test_db" - // * The operation's metadata type is [CopyBackupMetadata][google.spanner.admin.database.v1.CopyBackupMetadata] AND the - // backup the backup was copied from has name containing string - // "test_bkp" + // * The operation's metadata type is + // [CopyBackupMetadata][google.spanner.admin.database.v1.CopyBackupMetadata] + // AND the source backup name contains the string "test_bkp" // * The operation resulted in an error. string filter = 2; @@ -484,8 +514,9 @@ message ListBackupOperationsRequest { // If non-empty, `page_token` should contain a // [next_page_token][google.spanner.admin.database.v1.ListBackupOperationsResponse.next_page_token] - // from a previous [ListBackupOperationsResponse][google.spanner.admin.database.v1.ListBackupOperationsResponse] to the - // same `parent` and with the same `filter`. + // from a previous + // [ListBackupOperationsResponse][google.spanner.admin.database.v1.ListBackupOperationsResponse] + // to the same `parent` and with the same `filter`. string page_token = 4; } @@ -512,25 +543,26 @@ message ListBackupOperationsResponse { // Information about a backup. message BackupInfo { // Name of the backup. - string backup = 1 [(google.api.resource_reference) = { - type: "spanner.googleapis.com/Backup" - }]; + string backup = 1 [ + (google.api.resource_reference) = { type: "spanner.googleapis.com/Backup" } + ]; // The backup contains an externally consistent copy of `source_database` at // the timestamp specified by `version_time`. If the - // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request did not specify - // `version_time`, the `version_time` of the backup is equivalent to the - // `create_time`. + // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] + // request did not specify `version_time`, the `version_time` of the backup is + // equivalent to the `create_time`. google.protobuf.Timestamp version_time = 4; - // The time the [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] request was - // received. + // The time the + // [CreateBackup][google.spanner.admin.database.v1.DatabaseAdmin.CreateBackup] + // request was received. google.protobuf.Timestamp create_time = 2; // Name of the database the backup was created from. string source_database = 3 [(google.api.resource_reference) = { - type: "spanner.googleapis.com/Database" - }]; + type: "spanner.googleapis.com/Database" + }]; } // Encryption configuration for the backup to create. @@ -542,9 +574,10 @@ message CreateBackupEncryptionConfig { // Use the same encryption configuration as the database. This is the // default option when - // [encryption_config][google.spanner.admin.database.v1.CreateBackupEncryptionConfig] is empty. - // For example, if the database is using `Customer_Managed_Encryption`, the - // backup will be using the same Cloud KMS key as the database. + // [encryption_config][google.spanner.admin.database.v1.CreateBackupEncryptionConfig] + // is empty. For example, if the database is using + // `Customer_Managed_Encryption`, the backup will be using the same Cloud + // KMS key as the database. USE_DATABASE_ENCRYPTION = 1; // Use Google default encryption. @@ -560,8 +593,8 @@ message CreateBackupEncryptionConfig { // Optional. The Cloud KMS key that will be used to protect the backup. // This field should be set only when - // [encryption_type][google.spanner.admin.database.v1.CreateBackupEncryptionConfig.encryption_type] is - // `CUSTOMER_MANAGED_ENCRYPTION`. Values are of the form + // [encryption_type][google.spanner.admin.database.v1.CreateBackupEncryptionConfig.encryption_type] + // is `CUSTOMER_MANAGED_ENCRYPTION`. Values are of the form // `projects//locations//keyRings//cryptoKeys/`. string kms_key_name = 2 [ (google.api.field_behavior) = OPTIONAL, @@ -569,6 +602,28 @@ message CreateBackupEncryptionConfig { type: "cloudkms.googleapis.com/CryptoKey" } ]; + + // Optional. Specifies the KMS configuration for the one or more keys used to + // protect the backup. Values are of the form + // `projects//locations//keyRings//cryptoKeys/`. + // + // The keys referenced by kms_key_names must fully cover all + // regions of the backup's instance configuration. Some examples: + // * For single region instance configs, specify a single regional + // location KMS key. + // * For multi-regional instance configs of type GOOGLE_MANAGED, + // either specify a multi-regional location KMS key or multiple regional + // location KMS keys that cover all regions in the instance config. + // * For an instance config of type USER_MANAGED, please specify only + // regional location KMS keys to cover each region in the instance config. + // Multi-regional location KMS keys are not supported for USER_MANAGED + // instance configs. + repeated string kms_key_names = 3 [ + (google.api.field_behavior) = OPTIONAL, + (google.api.resource_reference) = { + type: "cloudkms.googleapis.com/CryptoKey" + } + ]; } // Encryption configuration for the copied backup. @@ -578,17 +633,20 @@ message CopyBackupEncryptionConfig { // Unspecified. Do not use. ENCRYPTION_TYPE_UNSPECIFIED = 0; - // This is the default option for [CopyBackup][google.spanner.admin.database.v1.DatabaseAdmin.CopyBackup] - // when [encryption_config][google.spanner.admin.database.v1.CopyBackupEncryptionConfig] is not specified. - // For example, if the source backup is using `Customer_Managed_Encryption`, - // the backup will be using the same Cloud KMS key as the source backup. + // This is the default option for + // [CopyBackup][google.spanner.admin.database.v1.DatabaseAdmin.CopyBackup] + // when + // [encryption_config][google.spanner.admin.database.v1.CopyBackupEncryptionConfig] + // is not specified. For example, if the source backup is using + // `Customer_Managed_Encryption`, the backup will be using the same Cloud + // KMS key as the source backup. USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION = 1; // Use Google default encryption. GOOGLE_DEFAULT_ENCRYPTION = 2; - // Use customer managed encryption. If specified, `kms_key_name` - // must contain a valid Cloud KMS key. + // Use customer managed encryption. If specified, either `kms_key_name` or + // `kms_key_names` must contain valid Cloud KMS key(s). CUSTOMER_MANAGED_ENCRYPTION = 3; } @@ -597,8 +655,8 @@ message CopyBackupEncryptionConfig { // Optional. The Cloud KMS key that will be used to protect the backup. // This field should be set only when - // [encryption_type][google.spanner.admin.database.v1.CopyBackupEncryptionConfig.encryption_type] is - // `CUSTOMER_MANAGED_ENCRYPTION`. Values are of the form + // [encryption_type][google.spanner.admin.database.v1.CopyBackupEncryptionConfig.encryption_type] + // is `CUSTOMER_MANAGED_ENCRYPTION`. Values are of the form // `projects//locations//keyRings//cryptoKeys/`. string kms_key_name = 2 [ (google.api.field_behavior) = OPTIONAL, @@ -606,4 +664,27 @@ message CopyBackupEncryptionConfig { type: "cloudkms.googleapis.com/CryptoKey" } ]; + + // Optional. Specifies the KMS configuration for the one or more keys used to + // protect the backup. Values are of the form + // `projects//locations//keyRings//cryptoKeys/`. + // Kms keys specified can be in any order. + // + // The keys referenced by kms_key_names must fully cover all + // regions of the backup's instance configuration. Some examples: + // * For single region instance configs, specify a single regional + // location KMS key. + // * For multi-regional instance configs of type GOOGLE_MANAGED, + // either specify a multi-regional location KMS key or multiple regional + // location KMS keys that cover all regions in the instance config. + // * For an instance config of type USER_MANAGED, please specify only + // regional location KMS keys to cover each region in the instance config. + // Multi-regional location KMS keys are not supported for USER_MANAGED + // instance configs. + repeated string kms_key_names = 3 [ + (google.api.field_behavior) = OPTIONAL, + (google.api.resource_reference) = { + type: "cloudkms.googleapis.com/CryptoKey" + } + ]; } diff --git a/protos/google/spanner/admin/database/v1/common.proto b/protos/google/spanner/admin/database/v1/common.proto index 32d7519e3..a91012306 100644 --- a/protos/google/spanner/admin/database/v1/common.proto +++ b/protos/google/spanner/admin/database/v1/common.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// 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. @@ -58,8 +58,27 @@ message EncryptionConfig { // the database. Values are of the form // `projects//locations//keyRings//cryptoKeys/`. string kms_key_name = 2 [(google.api.resource_reference) = { - type: "cloudkms.googleapis.com/CryptoKey" - }]; + type: "cloudkms.googleapis.com/CryptoKey" + }]; + + // Specifies the KMS configuration for the one or more keys used to encrypt + // the database. Values are of the form + // `projects//locations//keyRings//cryptoKeys/`. + // + // The keys referenced by kms_key_names must fully cover all + // regions of the database instance configuration. Some examples: + // * For single region database instance configs, specify a single regional + // location KMS key. + // * For multi-regional database instance configs of type GOOGLE_MANAGED, + // either specify a multi-regional location KMS key or multiple regional + // location KMS keys that cover all regions in the instance config. + // * For a database instance config of type USER_MANAGED, please specify only + // regional location KMS keys to cover each region in the instance config. + // Multi-regional location KMS keys are not supported for USER_MANAGED + // instance configs. + repeated string kms_key_names = 3 [(google.api.resource_reference) = { + type: "cloudkms.googleapis.com/CryptoKey" + }]; } // Encryption information for a Cloud Spanner database or backup. @@ -83,13 +102,14 @@ message EncryptionInfo { // Output only. The type of encryption. Type encryption_type = 3 [(google.api.field_behavior) = OUTPUT_ONLY]; - // Output only. If present, the status of a recent encrypt/decrypt call on underlying data - // for this database or backup. Regardless of status, data is always encrypted - // at rest. - google.rpc.Status encryption_status = 4 [(google.api.field_behavior) = OUTPUT_ONLY]; + // Output only. If present, the status of a recent encrypt/decrypt call on + // underlying data for this database or backup. Regardless of status, data is + // always encrypted at rest. + google.rpc.Status encryption_status = 4 + [(google.api.field_behavior) = OUTPUT_ONLY]; - // Output only. A Cloud KMS key version that is being used to protect the database or - // backup. + // Output only. A Cloud KMS key version that is being used to protect the + // database or backup. string kms_key_version = 2 [ (google.api.field_behavior) = OUTPUT_ONLY, (google.api.resource_reference) = { @@ -104,7 +124,7 @@ enum DatabaseDialect { // GOOGLE_STANDARD_SQL dialect. DATABASE_DIALECT_UNSPECIFIED = 0; - // Google standard SQL. + // GoogleSQL supported SQL. GOOGLE_STANDARD_SQL = 1; // PostgreSQL supported SQL. diff --git a/protos/google/spanner/admin/database/v1/spanner_database_admin.proto b/protos/google/spanner/admin/database/v1/spanner_database_admin.proto index a522c08c1..0d4e26170 100644 --- a/protos/google/spanner/admin/database/v1/spanner_database_admin.proto +++ b/protos/google/spanner/admin/database/v1/spanner_database_admin.proto @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC +// 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. @@ -46,7 +46,7 @@ option (google.api.resource_definition) = { // The Cloud Spanner Database Admin API can be used to: // * create, drop, and list databases // * update the schema of pre-existing databases -// * create, delete and list backups for a database +// * create, delete, copy and list backups for a database // * restore a database from an existing backup service DatabaseAdmin { option (google.api.default_host) = "spanner.googleapis.com"; @@ -67,10 +67,11 @@ service DatabaseAdmin { // have a name of the format `/operations/` and // can be used to track preparation of the database. The // [metadata][google.longrunning.Operation.metadata] field type is - // [CreateDatabaseMetadata][google.spanner.admin.database.v1.CreateDatabaseMetadata]. The - // [response][google.longrunning.Operation.response] field type is + // [CreateDatabaseMetadata][google.spanner.admin.database.v1.CreateDatabaseMetadata]. + // The [response][google.longrunning.Operation.response] field type is // [Database][google.spanner.admin.database.v1.Database], if successful. - rpc CreateDatabase(CreateDatabaseRequest) returns (google.longrunning.Operation) { + rpc CreateDatabase(CreateDatabaseRequest) + returns (google.longrunning.Operation) { option (google.api.http) = { post: "/v1/{parent=projects/*/instances/*}/databases" body: "*" @@ -145,8 +146,10 @@ service DatabaseAdmin { // the format `/operations/` and can be used to // track execution of the schema change(s). The // [metadata][google.longrunning.Operation.metadata] field type is - // [UpdateDatabaseDdlMetadata][google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata]. The operation has no response. - rpc UpdateDatabaseDdl(UpdateDatabaseDdlRequest) returns (google.longrunning.Operation) { + // [UpdateDatabaseDdlMetadata][google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata]. + // The operation has no response. + rpc UpdateDatabaseDdl(UpdateDatabaseDdlRequest) + returns (google.longrunning.Operation) { option (google.api.http) = { patch: "/v1/{database=projects/*/instances/*/databases/*}/ddl" body: "*" @@ -187,7 +190,8 @@ service DatabaseAdmin { // permission on [resource][google.iam.v1.SetIamPolicyRequest.resource]. // For backups, authorization requires `spanner.backups.setIamPolicy` // permission on [resource][google.iam.v1.SetIamPolicyRequest.resource]. - rpc SetIamPolicy(google.iam.v1.SetIamPolicyRequest) returns (google.iam.v1.Policy) { + rpc SetIamPolicy(google.iam.v1.SetIamPolicyRequest) + returns (google.iam.v1.Policy) { option (google.api.http) = { post: "/v1/{resource=projects/*/instances/*/databases/*}:setIamPolicy" body: "*" @@ -207,7 +211,8 @@ service DatabaseAdmin { // [resource][google.iam.v1.GetIamPolicyRequest.resource]. // For backups, authorization requires `spanner.backups.getIamPolicy` // permission on [resource][google.iam.v1.GetIamPolicyRequest.resource]. - rpc GetIamPolicy(google.iam.v1.GetIamPolicyRequest) returns (google.iam.v1.Policy) { + rpc GetIamPolicy(google.iam.v1.GetIamPolicyRequest) + returns (google.iam.v1.Policy) { option (google.api.http) = { post: "/v1/{resource=projects/*/instances/*/databases/*}:getIamPolicy" body: "*" @@ -229,7 +234,8 @@ service DatabaseAdmin { // Calling this method on a backup that does not exist will // result in a NOT_FOUND error if the user has // `spanner.backups.list` permission on the containing instance. - rpc TestIamPermissions(google.iam.v1.TestIamPermissionsRequest) returns (google.iam.v1.TestIamPermissionsResponse) { + rpc TestIamPermissions(google.iam.v1.TestIamPermissionsRequest) + returns (google.iam.v1.TestIamPermissionsResponse) { option (google.api.http) = { post: "/v1/{resource=projects/*/instances/*/databases/*}:testIamPermissions" body: "*" @@ -251,12 +257,12 @@ service DatabaseAdmin { // `projects//instances//backups//operations/` // and can be used to track creation of the backup. The // [metadata][google.longrunning.Operation.metadata] field type is - // [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. The - // [response][google.longrunning.Operation.response] field type is - // [Backup][google.spanner.admin.database.v1.Backup], if successful. Cancelling the returned operation will stop the - // creation and delete the backup. - // There can be only one pending backup creation per database. Backup creation - // of different databases can run concurrently. + // [CreateBackupMetadata][google.spanner.admin.database.v1.CreateBackupMetadata]. + // The [response][google.longrunning.Operation.response] field type is + // [Backup][google.spanner.admin.database.v1.Backup], if successful. + // Cancelling the returned operation will stop the creation and delete the + // backup. There can be only one pending backup creation per database. Backup + // creation of different databases can run concurrently. rpc CreateBackup(CreateBackupRequest) returns (google.longrunning.Operation) { option (google.api.http) = { post: "/v1/{parent=projects/*/instances/*}/backups" @@ -278,22 +284,25 @@ service DatabaseAdmin { // The [metadata][google.longrunning.Operation.metadata] field type is // [CopyBackupMetadata][google.spanner.admin.database.v1.CopyBackupMetadata]. // The [response][google.longrunning.Operation.response] field type is - // [Backup][google.spanner.admin.database.v1.Backup], if successful. Cancelling the returned operation will stop the - // copying and delete the backup. - // Concurrent CopyBackup requests can run on the same source backup. + // [Backup][google.spanner.admin.database.v1.Backup], if successful. + // Cancelling the returned operation will stop the copying and delete the + // destination backup. Concurrent CopyBackup requests can run on the same + // source backup. rpc CopyBackup(CopyBackupRequest) returns (google.longrunning.Operation) { option (google.api.http) = { post: "/v1/{parent=projects/*/instances/*}/backups:copy" body: "*" }; - option (google.api.method_signature) = "parent,backup_id,source_backup,expire_time"; + option (google.api.method_signature) = + "parent,backup_id,source_backup,expire_time"; option (google.longrunning.operation_info) = { response_type: "google.spanner.admin.database.v1.Backup" metadata_type: "google.spanner.admin.database.v1.CopyBackupMetadata" }; } - // Gets metadata on a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. + // Gets metadata on a pending or completed + // [Backup][google.spanner.admin.database.v1.Backup]. rpc GetBackup(GetBackupRequest) returns (Backup) { option (google.api.http) = { get: "/v1/{name=projects/*/instances/*/backups/*}" @@ -301,7 +310,8 @@ service DatabaseAdmin { option (google.api.method_signature) = "name"; } - // Updates a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. + // Updates a pending or completed + // [Backup][google.spanner.admin.database.v1.Backup]. rpc UpdateBackup(UpdateBackupRequest) returns (Backup) { option (google.api.http) = { patch: "/v1/{backup.name=projects/*/instances/*/backups/*}" @@ -310,7 +320,8 @@ service DatabaseAdmin { option (google.api.method_signature) = "backup,update_mask"; } - // Deletes a pending or completed [Backup][google.spanner.admin.database.v1.Backup]. + // Deletes a pending or completed + // [Backup][google.spanner.admin.database.v1.Backup]. rpc DeleteBackup(DeleteBackupRequest) returns (google.protobuf.Empty) { option (google.api.http) = { delete: "/v1/{name=projects/*/instances/*/backups/*}" @@ -345,7 +356,8 @@ service DatabaseAdmin { // Once the restore operation completes, a new restore operation can be // initiated, without waiting for the optimize operation associated with the // first restore to complete. - rpc RestoreDatabase(RestoreDatabaseRequest) returns (google.longrunning.Operation) { + rpc RestoreDatabase(RestoreDatabaseRequest) + returns (google.longrunning.Operation) { option (google.api.http) = { post: "/v1/{parent=projects/*/instances/*}/databases:restore" body: "*" @@ -365,7 +377,8 @@ service DatabaseAdmin { // `metadata.type_url` describes the type of the metadata. Operations returned // include those that have completed/failed/canceled within the last 7 days, // and pending operations. - rpc ListDatabaseOperations(ListDatabaseOperationsRequest) returns (ListDatabaseOperationsResponse) { + rpc ListDatabaseOperations(ListDatabaseOperationsRequest) + returns (ListDatabaseOperationsResponse) { option (google.api.http) = { get: "/v1/{parent=projects/*/instances/*}/databaseOperations" }; @@ -382,7 +395,8 @@ service DatabaseAdmin { // and pending operations. Operations returned are ordered by // `operation.metadata.value.progress.start_time` in descending order starting // from the most recently started operation. - rpc ListBackupOperations(ListBackupOperationsRequest) returns (ListBackupOperationsResponse) { + rpc ListBackupOperations(ListBackupOperationsRequest) + returns (ListBackupOperationsResponse) { option (google.api.http) = { get: "/v1/{parent=projects/*/instances/*}/backupOperations" }; @@ -390,7 +404,8 @@ service DatabaseAdmin { } // Lists Cloud Spanner database roles. - rpc ListDatabaseRoles(ListDatabaseRolesRequest) returns (ListDatabaseRolesResponse) { + rpc ListDatabaseRoles(ListDatabaseRolesRequest) + returns (ListDatabaseRolesResponse) { option (google.api.http) = { get: "/v1/{parent=projects/*/instances/*/databases/*}/databaseRoles" }; @@ -452,7 +467,8 @@ message Database { State state = 2 [(google.api.field_behavior) = OUTPUT_ONLY]; // Output only. If exists, the time at which the database creation started. - google.protobuf.Timestamp create_time = 3 [(google.api.field_behavior) = OUTPUT_ONLY]; + google.protobuf.Timestamp create_time = 3 + [(google.api.field_behavior) = OUTPUT_ONLY]; // Output only. Applicable only for restored databases. Contains information // about the restore source. @@ -462,32 +478,37 @@ message Database { // field contains the encryption configuration for the database. // For databases that are using Google default or other types of encryption, // this field is empty. - EncryptionConfig encryption_config = 5 [(google.api.field_behavior) = OUTPUT_ONLY]; + EncryptionConfig encryption_config = 5 + [(google.api.field_behavior) = OUTPUT_ONLY]; // Output only. For databases that are using customer managed encryption, this // field contains the encryption information for the database, such as - // encryption state and the Cloud KMS key versions that are in use. + // all Cloud KMS key versions that are in use. The `encryption_status' field + // inside of each `EncryptionInfo` is not populated. // // For databases that are using Google default or other types of encryption, // this field is empty. // // This field is propagated lazily from the backend. There might be a delay // from when a key version is being used and when it appears in this field. - repeated EncryptionInfo encryption_info = 8 [(google.api.field_behavior) = OUTPUT_ONLY]; + repeated EncryptionInfo encryption_info = 8 + [(google.api.field_behavior) = OUTPUT_ONLY]; // Output only. The period in which Cloud Spanner retains all versions of data // for the database. This is the same as the value of version_retention_period // database option set using - // [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl]. Defaults to 1 hour, - // if not set. - string version_retention_period = 6 [(google.api.field_behavior) = OUTPUT_ONLY]; + // [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl]. + // Defaults to 1 hour, if not set. + string version_retention_period = 6 + [(google.api.field_behavior) = OUTPUT_ONLY]; // Output only. Earliest timestamp at which older versions of the data can be // read. This value is continuously updated by Cloud Spanner and becomes stale // the moment it is queried. If you are using this value to recover data, make // sure to account for the time from the moment when the value is queried to // the moment when you initiate the recovery. - google.protobuf.Timestamp earliest_version_time = 7 [(google.api.field_behavior) = OUTPUT_ONLY]; + google.protobuf.Timestamp earliest_version_time = 7 + [(google.api.field_behavior) = OUTPUT_ONLY]; // Output only. The read-write region which contains the database's leader // replicas. @@ -498,10 +519,13 @@ message Database { string default_leader = 9 [(google.api.field_behavior) = OUTPUT_ONLY]; // Output only. The dialect of the Cloud Spanner Database. - DatabaseDialect database_dialect = 10 [(google.api.field_behavior) = OUTPUT_ONLY]; + DatabaseDialect database_dialect = 10 + [(google.api.field_behavior) = OUTPUT_ONLY]; // Whether drop protection is enabled for this database. Defaults to false, - // if not set. + // if not set. For more details, please see how to [prevent accidental + // database + // deletion](https://cloud.google.com/spanner/docs/prevent-database-deletion). bool enable_drop_protection = 11; // Output only. If true, the database is being updated. If false, there are no @@ -509,7 +533,8 @@ message Database { bool reconciling = 12 [(google.api.field_behavior) = OUTPUT_ONLY]; } -// The request for [ListDatabases][google.spanner.admin.database.v1.DatabaseAdmin.ListDatabases]. +// The request for +// [ListDatabases][google.spanner.admin.database.v1.DatabaseAdmin.ListDatabases]. message ListDatabasesRequest { // Required. The instance whose databases should be listed. // Values are of the form `projects//instances/`. @@ -525,23 +550,26 @@ message ListDatabasesRequest { int32 page_size = 3; // If non-empty, `page_token` should contain a - // [next_page_token][google.spanner.admin.database.v1.ListDatabasesResponse.next_page_token] from a - // previous [ListDatabasesResponse][google.spanner.admin.database.v1.ListDatabasesResponse]. + // [next_page_token][google.spanner.admin.database.v1.ListDatabasesResponse.next_page_token] + // from a previous + // [ListDatabasesResponse][google.spanner.admin.database.v1.ListDatabasesResponse]. string page_token = 4; } -// The response for [ListDatabases][google.spanner.admin.database.v1.DatabaseAdmin.ListDatabases]. +// The response for +// [ListDatabases][google.spanner.admin.database.v1.DatabaseAdmin.ListDatabases]. message ListDatabasesResponse { // Databases that matched the request. repeated Database databases = 1; // `next_page_token` can be sent in a subsequent - // [ListDatabases][google.spanner.admin.database.v1.DatabaseAdmin.ListDatabases] call to fetch more - // of the matching databases. + // [ListDatabases][google.spanner.admin.database.v1.DatabaseAdmin.ListDatabases] + // call to fetch more of the matching databases. string next_page_token = 2; } -// The request for [CreateDatabase][google.spanner.admin.database.v1.DatabaseAdmin.CreateDatabase]. +// The request for +// [CreateDatabase][google.spanner.admin.database.v1.DatabaseAdmin.CreateDatabase]. message CreateDatabaseRequest { // Required. The name of the instance that will serve the new database. // Values are of the form `projects//instances/`. @@ -565,10 +593,11 @@ message CreateDatabaseRequest { // if there is an error in any statement, the database is not created. repeated string extra_statements = 3 [(google.api.field_behavior) = OPTIONAL]; - // Optional. The encryption configuration for the database. If this field is not - // specified, Cloud Spanner will encrypt/decrypt all data at rest using + // Optional. The encryption configuration for the database. If this field is + // not specified, Cloud Spanner will encrypt/decrypt all data at rest using // Google default encryption. - EncryptionConfig encryption_config = 4 [(google.api.field_behavior) = OPTIONAL]; + EncryptionConfig encryption_config = 4 + [(google.api.field_behavior) = OPTIONAL]; // Optional. The dialect of the Cloud Spanner Database. DatabaseDialect database_dialect = 5 [(google.api.field_behavior) = OPTIONAL]; @@ -596,11 +625,12 @@ message CreateDatabaseRequest { message CreateDatabaseMetadata { // The database being created. string database = 1 [(google.api.resource_reference) = { - type: "spanner.googleapis.com/Database" - }]; + type: "spanner.googleapis.com/Database" + }]; } -// The request for [GetDatabase][google.spanner.admin.database.v1.DatabaseAdmin.GetDatabase]. +// The request for +// [GetDatabase][google.spanner.admin.database.v1.DatabaseAdmin.GetDatabase]. message GetDatabaseRequest { // Required. The name of the requested database. Values are of the form // `projects//instances//databases/`. @@ -657,8 +687,8 @@ message UpdateDatabaseMetadata { // Each batch of statements is assigned a name which can be used with // the [Operations][google.longrunning.Operations] API to monitor // progress. See the -// [operation_id][google.spanner.admin.database.v1.UpdateDatabaseDdlRequest.operation_id] field for more -// details. +// [operation_id][google.spanner.admin.database.v1.UpdateDatabaseDdlRequest.operation_id] +// field for more details. message UpdateDatabaseDdlRequest { // Required. The database to update. string database = 1 [ @@ -678,18 +708,20 @@ message UpdateDatabaseDdlRequest { // // Specifying an explicit operation ID simplifies determining // whether the statements were executed in the event that the - // [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl] call is replayed, - // or the return value is otherwise lost: the [database][google.spanner.admin.database.v1.UpdateDatabaseDdlRequest.database] and - // `operation_id` fields can be combined to form the + // [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl] + // call is replayed, or the return value is otherwise lost: the + // [database][google.spanner.admin.database.v1.UpdateDatabaseDdlRequest.database] + // and `operation_id` fields can be combined to form the // [name][google.longrunning.Operation.name] of the resulting - // [longrunning.Operation][google.longrunning.Operation]: `/operations/`. + // [longrunning.Operation][google.longrunning.Operation]: + // `/operations/`. // // `operation_id` should be unique within the database, and must be // a valid identifier: `[a-z][a-z0-9_]*`. Note that // automatically-generated operation IDs always begin with an // underscore. If the named operation already exists, - // [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl] returns - // `ALREADY_EXISTS`. + // [UpdateDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl] + // returns `ALREADY_EXISTS`. string operation_id = 3; // Optional. Proto descriptors used by CREATE/ALTER PROTO BUNDLE statements. @@ -735,8 +767,8 @@ message DdlStatementActionInfo { message UpdateDatabaseDdlMetadata { // The database being modified. string database = 1 [(google.api.resource_reference) = { - type: "spanner.googleapis.com/Database" - }]; + type: "spanner.googleapis.com/Database" + }]; // For an update this list contains all the statements. For an // individual statement, this list contains only that statement. @@ -766,7 +798,8 @@ message UpdateDatabaseDdlMetadata { repeated DdlStatementActionInfo actions = 6; } -// The request for [DropDatabase][google.spanner.admin.database.v1.DatabaseAdmin.DropDatabase]. +// The request for +// [DropDatabase][google.spanner.admin.database.v1.DatabaseAdmin.DropDatabase]. message DropDatabaseRequest { // Required. The database to be dropped. string database = 1 [ @@ -777,7 +810,8 @@ message DropDatabaseRequest { ]; } -// The request for [GetDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.GetDatabaseDdl]. +// The request for +// [GetDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.GetDatabaseDdl]. message GetDatabaseDdlRequest { // Required. The database whose schema we wish to get. // Values are of the form @@ -790,7 +824,8 @@ message GetDatabaseDdlRequest { ]; } -// The response for [GetDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.GetDatabaseDdl]. +// The response for +// [GetDatabaseDdl][google.spanner.admin.database.v1.DatabaseAdmin.GetDatabaseDdl]. message GetDatabaseDdlResponse { // A list of formatted DDL statements defining the schema of the database // specified in the request. @@ -830,7 +865,9 @@ message ListDatabaseOperationsRequest { // * `name` - The name of the long-running operation // * `done` - False if the operation is in progress, else true. // * `metadata.@type` - the type of metadata. For example, the type string - // for [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata] is + // for + // [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata] + // is // `type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata`. // * `metadata.` - any field in metadata.value. // `metadata.@type` must be specified first, if filtering on metadata @@ -852,7 +889,8 @@ message ListDatabaseOperationsRequest { // `(metadata.name:restored_howl) AND` \ // `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ // `(error:*)` - Return operations where: - // * The operation's metadata type is [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. + // * The operation's metadata type is + // [RestoreDatabaseMetadata][google.spanner.admin.database.v1.RestoreDatabaseMetadata]. // * The database is restored from a backup. // * The backup name contains "backup_howl". // * The restored database's name contains "restored_howl". @@ -866,8 +904,9 @@ message ListDatabaseOperationsRequest { // If non-empty, `page_token` should contain a // [next_page_token][google.spanner.admin.database.v1.ListDatabaseOperationsResponse.next_page_token] - // from a previous [ListDatabaseOperationsResponse][google.spanner.admin.database.v1.ListDatabaseOperationsResponse] to the - // same `parent` and with the same `filter`. + // from a previous + // [ListDatabaseOperationsResponse][google.spanner.admin.database.v1.ListDatabaseOperationsResponse] + // to the same `parent` and with the same `filter`. string page_token = 4; } @@ -913,17 +952,18 @@ message RestoreDatabaseRequest { // Name of the backup from which to restore. Values are of the form // `projects//instances//backups/`. string backup = 3 [(google.api.resource_reference) = { - type: "spanner.googleapis.com/Backup" - }]; + type: "spanner.googleapis.com/Backup" + }]; } - // Optional. An encryption configuration describing the encryption type and key - // resources in Cloud KMS used to encrypt/decrypt the database to restore to. - // If this field is not specified, the restored database will use - // the same encryption configuration as the backup by default, namely - // [encryption_type][google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.encryption_type] = - // `USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION`. - RestoreDatabaseEncryptionConfig encryption_config = 4 [(google.api.field_behavior) = OPTIONAL]; + // Optional. An encryption configuration describing the encryption type and + // key resources in Cloud KMS used to encrypt/decrypt the database to restore + // to. If this field is not specified, the restored database will use the same + // encryption configuration as the backup by default, namely + // [encryption_type][google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.encryption_type] + // = `USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION`. + RestoreDatabaseEncryptionConfig encryption_config = 4 + [(google.api.field_behavior) = OPTIONAL]; } // Encryption configuration for the restored database. @@ -934,7 +974,8 @@ message RestoreDatabaseEncryptionConfig { ENCRYPTION_TYPE_UNSPECIFIED = 0; // This is the default option when - // [encryption_config][google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig] is not specified. + // [encryption_config][google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig] + // is not specified. USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION = 1; // Use Google default encryption. @@ -948,10 +989,10 @@ message RestoreDatabaseEncryptionConfig { // Required. The encryption type of the restored database. EncryptionType encryption_type = 1 [(google.api.field_behavior) = REQUIRED]; - // Optional. The Cloud KMS key that will be used to encrypt/decrypt the restored - // database. This field should be set only when - // [encryption_type][google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.encryption_type] is - // `CUSTOMER_MANAGED_ENCRYPTION`. Values are of the form + // Optional. The Cloud KMS key that will be used to encrypt/decrypt the + // restored database. This field should be set only when + // [encryption_type][google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.encryption_type] + // is `CUSTOMER_MANAGED_ENCRYPTION`. Values are of the form // `projects//locations//keyRings//cryptoKeys/`. string kms_key_name = 2 [ (google.api.field_behavior) = OPTIONAL, @@ -959,6 +1000,28 @@ message RestoreDatabaseEncryptionConfig { type: "cloudkms.googleapis.com/CryptoKey" } ]; + + // Optional. Specifies the KMS configuration for the one or more keys used to + // encrypt the database. Values are of the form + // `projects//locations//keyRings//cryptoKeys/`. + // + // The keys referenced by kms_key_names must fully cover all + // regions of the database instance configuration. Some examples: + // * For single region database instance configs, specify a single regional + // location KMS key. + // * For multi-regional database instance configs of type GOOGLE_MANAGED, + // either specify a multi-regional location KMS key or multiple regional + // location KMS keys that cover all regions in the instance config. + // * For a database instance config of type USER_MANAGED, please specify only + // regional location KMS keys to cover each region in the instance config. + // Multi-regional location KMS keys are not supported for USER_MANAGED + // instance configs. + repeated string kms_key_names = 3 [ + (google.api.field_behavior) = OPTIONAL, + (google.api.resource_reference) = { + type: "cloudkms.googleapis.com/CryptoKey" + } + ]; } // Metadata type for the long-running operation returned by @@ -966,14 +1029,15 @@ message RestoreDatabaseEncryptionConfig { message RestoreDatabaseMetadata { // Name of the database being created and restored to. string name = 1 [(google.api.resource_reference) = { - type: "spanner.googleapis.com/Database" - }]; + type: "spanner.googleapis.com/Database" + }]; // The type of the restore source. RestoreSourceType source_type = 2; // Information about the source used to restore the database, as specified by - // `source` in [RestoreDatabaseRequest][google.spanner.admin.database.v1.RestoreDatabaseRequest]. + // `source` in + // [RestoreDatabaseRequest][google.spanner.admin.database.v1.RestoreDatabaseRequest]. oneof source_info { // Information about the backup used to restore the database. BackupInfo backup_info = 3; @@ -994,7 +1058,8 @@ message RestoreDatabaseMetadata { // operation completed despite cancellation. On successful cancellation, // the operation is not deleted; instead, it becomes an operation with // an [Operation.error][google.longrunning.Operation.error] value with a - // [google.rpc.Status.code][google.rpc.Status.code] of 1, corresponding to `Code.CANCELLED`. + // [google.rpc.Status.code][google.rpc.Status.code] of 1, corresponding to + // `Code.CANCELLED`. google.protobuf.Timestamp cancel_time = 5; // If exists, the name of the long-running operation that will be used to @@ -1004,10 +1069,10 @@ message RestoreDatabaseMetadata { // `projects//instances//databases//operations/` // where the is the name of database being created and restored to. // The metadata type of the long-running operation is - // [OptimizeRestoredDatabaseMetadata][google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata]. This long-running operation will be - // automatically created by the system after the RestoreDatabase long-running - // operation completes successfully. This operation will not be created if the - // restore was not successful. + // [OptimizeRestoredDatabaseMetadata][google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata]. + // This long-running operation will be automatically created by the system + // after the RestoreDatabase long-running operation completes successfully. + // This operation will not be created if the restore was not successful. string optimize_database_operation_name = 6; } @@ -1018,8 +1083,8 @@ message RestoreDatabaseMetadata { message OptimizeRestoredDatabaseMetadata { // Name of the restored database being optimized. string name = 1 [(google.api.resource_reference) = { - type: "spanner.googleapis.com/Database" - }]; + type: "spanner.googleapis.com/Database" + }]; // The progress of the post-restore optimizations. OperationProgress progress = 2; @@ -1042,18 +1107,17 @@ message DatabaseRole { }; // Required. The name of the database role. Values are of the form - // `projects//instances//databases//databaseRoles/ - // {role}`, where `` is as specified in the `CREATE ROLE` - // DDL statement. This name can be passed to Get/Set IAMPolicy methods to - // identify the database role. + // `projects//instances//databases//databaseRoles/` + // where `` is as specified in the `CREATE ROLE` DDL statement. string name = 1 [(google.api.field_behavior) = REQUIRED]; } -// The request for [ListDatabaseRoles][google.spanner.admin.database.v1.DatabaseAdmin.ListDatabaseRoles]. +// The request for +// [ListDatabaseRoles][google.spanner.admin.database.v1.DatabaseAdmin.ListDatabaseRoles]. message ListDatabaseRolesRequest { // Required. The database whose roles should be listed. // Values are of the form - // `projects//instances//databases//databaseRoles`. + // `projects//instances//databases/`. string parent = 1 [ (google.api.field_behavior) = REQUIRED, (google.api.resource_reference) = { @@ -1066,12 +1130,14 @@ message ListDatabaseRolesRequest { int32 page_size = 2; // If non-empty, `page_token` should contain a - // [next_page_token][google.spanner.admin.database.v1.ListDatabaseRolesResponse.next_page_token] from a - // previous [ListDatabaseRolesResponse][google.spanner.admin.database.v1.ListDatabaseRolesResponse]. + // [next_page_token][google.spanner.admin.database.v1.ListDatabaseRolesResponse.next_page_token] + // from a previous + // [ListDatabaseRolesResponse][google.spanner.admin.database.v1.ListDatabaseRolesResponse]. string page_token = 3; } -// The response for [ListDatabaseRoles][google.spanner.admin.database.v1.DatabaseAdmin.ListDatabaseRoles]. +// The response for +// [ListDatabaseRoles][google.spanner.admin.database.v1.DatabaseAdmin.ListDatabaseRoles]. message ListDatabaseRolesResponse { // Database roles that matched the request. repeated DatabaseRole database_roles = 1; diff --git a/protos/google/spanner/admin/instance/v1/common.proto b/protos/google/spanner/admin/instance/v1/common.proto index ab6293acf..075b45fea 100644 --- a/protos/google/spanner/admin/instance/v1/common.proto +++ b/protos/google/spanner/admin/instance/v1/common.proto @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC +// 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. diff --git a/protos/google/spanner/executor/v1/cloud_executor.proto b/protos/google/spanner/executor/v1/cloud_executor.proto index 3ad36e3ee..73474a650 100644 --- a/protos/google/spanner/executor/v1/cloud_executor.proto +++ b/protos/google/spanner/executor/v1/cloud_executor.proto @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC +// 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. diff --git a/protos/google/spanner/v1/commit_response.proto b/protos/google/spanner/v1/commit_response.proto index 436a002b8..d44aad63b 100644 --- a/protos/google/spanner/v1/commit_response.proto +++ b/protos/google/spanner/v1/commit_response.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// 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. diff --git a/protos/google/spanner/v1/keys.proto b/protos/google/spanner/v1/keys.proto index 8fb4757f5..82f073b96 100644 --- a/protos/google/spanner/v1/keys.proto +++ b/protos/google/spanner/v1/keys.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// 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. diff --git a/protos/google/spanner/v1/mutation.proto b/protos/google/spanner/v1/mutation.proto index cced61f33..7fbf93f8a 100644 --- a/protos/google/spanner/v1/mutation.proto +++ b/protos/google/spanner/v1/mutation.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// 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. diff --git a/protos/google/spanner/v1/query_plan.proto b/protos/google/spanner/v1/query_plan.proto index c0903bdd7..ba18055e3 100644 --- a/protos/google/spanner/v1/query_plan.proto +++ b/protos/google/spanner/v1/query_plan.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// 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. diff --git a/protos/google/spanner/v1/result_set.proto b/protos/google/spanner/v1/result_set.proto index cfa5719c4..f392c1d7d 100644 --- a/protos/google/spanner/v1/result_set.proto +++ b/protos/google/spanner/v1/result_set.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// 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. diff --git a/protos/google/spanner/v1/spanner.proto b/protos/google/spanner/v1/spanner.proto index 440ebf785..25c67f155 100644 --- a/protos/google/spanner/v1/spanner.proto +++ b/protos/google/spanner/v1/spanner.proto @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC +// 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. diff --git a/protos/google/spanner/v1/transaction.proto b/protos/google/spanner/v1/transaction.proto index e3f22ee3c..8af513d15 100644 --- a/protos/google/spanner/v1/transaction.proto +++ b/protos/google/spanner/v1/transaction.proto @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC +// 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. diff --git a/protos/google/spanner/v1/type.proto b/protos/google/spanner/v1/type.proto index 8e28fa7fd..4e77106c9 100644 --- a/protos/google/spanner/v1/type.proto +++ b/protos/google/spanner/v1/type.proto @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC +// 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. diff --git a/protos/protos.d.ts b/protos/protos.d.ts index 98bfcb679..0003e5dd4 100644 --- a/protos/protos.d.ts +++ b/protos/protos.d.ts @@ -6558,6 +6558,9 @@ export namespace google { /** Backup encryptionInfo */ encryptionInfo?: (google.spanner.admin.database.v1.IEncryptionInfo|null); + /** Backup encryptionInformation */ + encryptionInformation?: (google.spanner.admin.database.v1.IEncryptionInfo[]|null); + /** Backup databaseDialect */ databaseDialect?: (google.spanner.admin.database.v1.DatabaseDialect|keyof typeof google.spanner.admin.database.v1.DatabaseDialect|null); @@ -6604,6 +6607,9 @@ export namespace google { /** Backup encryptionInfo. */ public encryptionInfo?: (google.spanner.admin.database.v1.IEncryptionInfo|null); + /** Backup encryptionInformation. */ + public encryptionInformation: google.spanner.admin.database.v1.IEncryptionInfo[]; + /** Backup databaseDialect. */ public databaseDialect: (google.spanner.admin.database.v1.DatabaseDialect|keyof typeof google.spanner.admin.database.v1.DatabaseDialect); @@ -8023,6 +8029,9 @@ export namespace google { /** CreateBackupEncryptionConfig kmsKeyName */ kmsKeyName?: (string|null); + + /** CreateBackupEncryptionConfig kmsKeyNames */ + kmsKeyNames?: (string[]|null); } /** Represents a CreateBackupEncryptionConfig. */ @@ -8040,6 +8049,9 @@ export namespace google { /** CreateBackupEncryptionConfig kmsKeyName. */ public kmsKeyName: string; + /** CreateBackupEncryptionConfig kmsKeyNames. */ + public kmsKeyNames: string[]; + /** * Creates a new CreateBackupEncryptionConfig instance using the specified properties. * @param [properties] Properties to set @@ -8137,6 +8149,9 @@ export namespace google { /** CopyBackupEncryptionConfig kmsKeyName */ kmsKeyName?: (string|null); + + /** CopyBackupEncryptionConfig kmsKeyNames */ + kmsKeyNames?: (string[]|null); } /** Represents a CopyBackupEncryptionConfig. */ @@ -8154,6 +8169,9 @@ export namespace google { /** CopyBackupEncryptionConfig kmsKeyName. */ public kmsKeyName: string; + /** CopyBackupEncryptionConfig kmsKeyNames. */ + public kmsKeyNames: string[]; + /** * Creates a new CopyBackupEncryptionConfig instance using the specified properties. * @param [properties] Properties to set @@ -8357,6 +8375,9 @@ export namespace google { /** EncryptionConfig kmsKeyName */ kmsKeyName?: (string|null); + + /** EncryptionConfig kmsKeyNames */ + kmsKeyNames?: (string[]|null); } /** Represents an EncryptionConfig. */ @@ -8371,6 +8392,9 @@ export namespace google { /** EncryptionConfig kmsKeyName. */ public kmsKeyName: string; + /** EncryptionConfig kmsKeyNames. */ + public kmsKeyNames: string[]; + /** * Creates a new EncryptionConfig instance using the specified properties. * @param [properties] Properties to set @@ -11036,6 +11060,9 @@ export namespace google { /** RestoreDatabaseEncryptionConfig kmsKeyName */ kmsKeyName?: (string|null); + + /** RestoreDatabaseEncryptionConfig kmsKeyNames */ + kmsKeyNames?: (string[]|null); } /** Represents a RestoreDatabaseEncryptionConfig. */ @@ -11053,6 +11080,9 @@ export namespace google { /** RestoreDatabaseEncryptionConfig kmsKeyName. */ public kmsKeyName: string; + /** RestoreDatabaseEncryptionConfig kmsKeyNames. */ + public kmsKeyNames: string[]; + /** * Creates a new RestoreDatabaseEncryptionConfig instance using the specified properties. * @param [properties] Properties to set diff --git a/protos/protos.js b/protos/protos.js index feb8d0198..239a3074d 100644 --- a/protos/protos.js +++ b/protos/protos.js @@ -17698,6 +17698,7 @@ * @property {google.spanner.admin.database.v1.Backup.State|null} [state] Backup state * @property {Array.|null} [referencingDatabases] Backup referencingDatabases * @property {google.spanner.admin.database.v1.IEncryptionInfo|null} [encryptionInfo] Backup encryptionInfo + * @property {Array.|null} [encryptionInformation] Backup encryptionInformation * @property {google.spanner.admin.database.v1.DatabaseDialect|null} [databaseDialect] Backup databaseDialect * @property {Array.|null} [referencingBackups] Backup referencingBackups * @property {google.protobuf.ITimestamp|null} [maxExpireTime] Backup maxExpireTime @@ -17713,6 +17714,7 @@ */ function Backup(properties) { this.referencingDatabases = []; + this.encryptionInformation = []; this.referencingBackups = []; if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) @@ -17792,6 +17794,14 @@ */ Backup.prototype.encryptionInfo = null; + /** + * Backup encryptionInformation. + * @member {Array.} encryptionInformation + * @memberof google.spanner.admin.database.v1.Backup + * @instance + */ + Backup.prototype.encryptionInformation = $util.emptyArray; + /** * Backup databaseDialect. * @member {google.spanner.admin.database.v1.DatabaseDialect} databaseDialect @@ -17866,6 +17876,9 @@ writer.uint32(/* id 11, wireType 2 =*/90).string(message.referencingBackups[i]); if (message.maxExpireTime != null && Object.hasOwnProperty.call(message, "maxExpireTime")) $root.google.protobuf.Timestamp.encode(message.maxExpireTime, writer.uint32(/* id 12, wireType 2 =*/98).fork()).ldelim(); + if (message.encryptionInformation != null && message.encryptionInformation.length) + for (var i = 0; i < message.encryptionInformation.length; ++i) + $root.google.spanner.admin.database.v1.EncryptionInfo.encode(message.encryptionInformation[i], writer.uint32(/* id 13, wireType 2 =*/106).fork()).ldelim(); return writer; }; @@ -17938,6 +17951,12 @@ message.encryptionInfo = $root.google.spanner.admin.database.v1.EncryptionInfo.decode(reader, reader.uint32()); break; } + case 13: { + if (!(message.encryptionInformation && message.encryptionInformation.length)) + message.encryptionInformation = []; + message.encryptionInformation.push($root.google.spanner.admin.database.v1.EncryptionInfo.decode(reader, reader.uint32())); + break; + } case 10: { message.databaseDialect = reader.int32(); break; @@ -18032,6 +18051,15 @@ if (error) return "encryptionInfo." + error; } + if (message.encryptionInformation != null && message.hasOwnProperty("encryptionInformation")) { + if (!Array.isArray(message.encryptionInformation)) + return "encryptionInformation: array expected"; + for (var i = 0; i < message.encryptionInformation.length; ++i) { + var error = $root.google.spanner.admin.database.v1.EncryptionInfo.verify(message.encryptionInformation[i]); + if (error) + return "encryptionInformation." + error; + } + } if (message.databaseDialect != null && message.hasOwnProperty("databaseDialect")) switch (message.databaseDialect) { default: @@ -18128,6 +18156,16 @@ throw TypeError(".google.spanner.admin.database.v1.Backup.encryptionInfo: object expected"); message.encryptionInfo = $root.google.spanner.admin.database.v1.EncryptionInfo.fromObject(object.encryptionInfo); } + if (object.encryptionInformation) { + if (!Array.isArray(object.encryptionInformation)) + throw TypeError(".google.spanner.admin.database.v1.Backup.encryptionInformation: array expected"); + message.encryptionInformation = []; + for (var i = 0; i < object.encryptionInformation.length; ++i) { + if (typeof object.encryptionInformation[i] !== "object") + throw TypeError(".google.spanner.admin.database.v1.Backup.encryptionInformation: object expected"); + message.encryptionInformation[i] = $root.google.spanner.admin.database.v1.EncryptionInfo.fromObject(object.encryptionInformation[i]); + } + } switch (object.databaseDialect) { default: if (typeof object.databaseDialect === "number") { @@ -18179,6 +18217,7 @@ if (options.arrays || options.defaults) { object.referencingDatabases = []; object.referencingBackups = []; + object.encryptionInformation = []; } if (options.defaults) { object.name = ""; @@ -18229,6 +18268,11 @@ } if (message.maxExpireTime != null && message.hasOwnProperty("maxExpireTime")) object.maxExpireTime = $root.google.protobuf.Timestamp.toObject(message.maxExpireTime, options); + if (message.encryptionInformation && message.encryptionInformation.length) { + object.encryptionInformation = []; + for (var j = 0; j < message.encryptionInformation.length; ++j) + object.encryptionInformation[j] = $root.google.spanner.admin.database.v1.EncryptionInfo.toObject(message.encryptionInformation[j], options); + } return object; }; @@ -21408,6 +21452,7 @@ * @interface ICreateBackupEncryptionConfig * @property {google.spanner.admin.database.v1.CreateBackupEncryptionConfig.EncryptionType|null} [encryptionType] CreateBackupEncryptionConfig encryptionType * @property {string|null} [kmsKeyName] CreateBackupEncryptionConfig kmsKeyName + * @property {Array.|null} [kmsKeyNames] CreateBackupEncryptionConfig kmsKeyNames */ /** @@ -21419,6 +21464,7 @@ * @param {google.spanner.admin.database.v1.ICreateBackupEncryptionConfig=} [properties] Properties to set */ function CreateBackupEncryptionConfig(properties) { + this.kmsKeyNames = []; if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) @@ -21441,6 +21487,14 @@ */ CreateBackupEncryptionConfig.prototype.kmsKeyName = ""; + /** + * CreateBackupEncryptionConfig kmsKeyNames. + * @member {Array.} kmsKeyNames + * @memberof google.spanner.admin.database.v1.CreateBackupEncryptionConfig + * @instance + */ + CreateBackupEncryptionConfig.prototype.kmsKeyNames = $util.emptyArray; + /** * Creates a new CreateBackupEncryptionConfig instance using the specified properties. * @function create @@ -21469,6 +21523,9 @@ writer.uint32(/* id 1, wireType 0 =*/8).int32(message.encryptionType); if (message.kmsKeyName != null && Object.hasOwnProperty.call(message, "kmsKeyName")) writer.uint32(/* id 2, wireType 2 =*/18).string(message.kmsKeyName); + if (message.kmsKeyNames != null && message.kmsKeyNames.length) + for (var i = 0; i < message.kmsKeyNames.length; ++i) + writer.uint32(/* id 3, wireType 2 =*/26).string(message.kmsKeyNames[i]); return writer; }; @@ -21511,6 +21568,12 @@ message.kmsKeyName = reader.string(); break; } + case 3: { + if (!(message.kmsKeyNames && message.kmsKeyNames.length)) + message.kmsKeyNames = []; + message.kmsKeyNames.push(reader.string()); + break; + } default: reader.skipType(tag & 7); break; @@ -21559,6 +21622,13 @@ if (message.kmsKeyName != null && message.hasOwnProperty("kmsKeyName")) if (!$util.isString(message.kmsKeyName)) return "kmsKeyName: string expected"; + if (message.kmsKeyNames != null && message.hasOwnProperty("kmsKeyNames")) { + if (!Array.isArray(message.kmsKeyNames)) + return "kmsKeyNames: array expected"; + for (var i = 0; i < message.kmsKeyNames.length; ++i) + if (!$util.isString(message.kmsKeyNames[i])) + return "kmsKeyNames: string[] expected"; + } return null; }; @@ -21600,6 +21670,13 @@ } if (object.kmsKeyName != null) message.kmsKeyName = String(object.kmsKeyName); + if (object.kmsKeyNames) { + if (!Array.isArray(object.kmsKeyNames)) + throw TypeError(".google.spanner.admin.database.v1.CreateBackupEncryptionConfig.kmsKeyNames: array expected"); + message.kmsKeyNames = []; + for (var i = 0; i < object.kmsKeyNames.length; ++i) + message.kmsKeyNames[i] = String(object.kmsKeyNames[i]); + } return message; }; @@ -21616,6 +21693,8 @@ if (!options) options = {}; var object = {}; + if (options.arrays || options.defaults) + object.kmsKeyNames = []; if (options.defaults) { object.encryptionType = options.enums === String ? "ENCRYPTION_TYPE_UNSPECIFIED" : 0; object.kmsKeyName = ""; @@ -21624,6 +21703,11 @@ object.encryptionType = options.enums === String ? $root.google.spanner.admin.database.v1.CreateBackupEncryptionConfig.EncryptionType[message.encryptionType] === undefined ? message.encryptionType : $root.google.spanner.admin.database.v1.CreateBackupEncryptionConfig.EncryptionType[message.encryptionType] : message.encryptionType; if (message.kmsKeyName != null && message.hasOwnProperty("kmsKeyName")) object.kmsKeyName = message.kmsKeyName; + if (message.kmsKeyNames && message.kmsKeyNames.length) { + object.kmsKeyNames = []; + for (var j = 0; j < message.kmsKeyNames.length; ++j) + object.kmsKeyNames[j] = message.kmsKeyNames[j]; + } return object; }; @@ -21682,6 +21766,7 @@ * @interface ICopyBackupEncryptionConfig * @property {google.spanner.admin.database.v1.CopyBackupEncryptionConfig.EncryptionType|null} [encryptionType] CopyBackupEncryptionConfig encryptionType * @property {string|null} [kmsKeyName] CopyBackupEncryptionConfig kmsKeyName + * @property {Array.|null} [kmsKeyNames] CopyBackupEncryptionConfig kmsKeyNames */ /** @@ -21693,6 +21778,7 @@ * @param {google.spanner.admin.database.v1.ICopyBackupEncryptionConfig=} [properties] Properties to set */ function CopyBackupEncryptionConfig(properties) { + this.kmsKeyNames = []; if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) @@ -21715,6 +21801,14 @@ */ CopyBackupEncryptionConfig.prototype.kmsKeyName = ""; + /** + * CopyBackupEncryptionConfig kmsKeyNames. + * @member {Array.} kmsKeyNames + * @memberof google.spanner.admin.database.v1.CopyBackupEncryptionConfig + * @instance + */ + CopyBackupEncryptionConfig.prototype.kmsKeyNames = $util.emptyArray; + /** * Creates a new CopyBackupEncryptionConfig instance using the specified properties. * @function create @@ -21743,6 +21837,9 @@ writer.uint32(/* id 1, wireType 0 =*/8).int32(message.encryptionType); if (message.kmsKeyName != null && Object.hasOwnProperty.call(message, "kmsKeyName")) writer.uint32(/* id 2, wireType 2 =*/18).string(message.kmsKeyName); + if (message.kmsKeyNames != null && message.kmsKeyNames.length) + for (var i = 0; i < message.kmsKeyNames.length; ++i) + writer.uint32(/* id 3, wireType 2 =*/26).string(message.kmsKeyNames[i]); return writer; }; @@ -21785,6 +21882,12 @@ message.kmsKeyName = reader.string(); break; } + case 3: { + if (!(message.kmsKeyNames && message.kmsKeyNames.length)) + message.kmsKeyNames = []; + message.kmsKeyNames.push(reader.string()); + break; + } default: reader.skipType(tag & 7); break; @@ -21833,6 +21936,13 @@ if (message.kmsKeyName != null && message.hasOwnProperty("kmsKeyName")) if (!$util.isString(message.kmsKeyName)) return "kmsKeyName: string expected"; + if (message.kmsKeyNames != null && message.hasOwnProperty("kmsKeyNames")) { + if (!Array.isArray(message.kmsKeyNames)) + return "kmsKeyNames: array expected"; + for (var i = 0; i < message.kmsKeyNames.length; ++i) + if (!$util.isString(message.kmsKeyNames[i])) + return "kmsKeyNames: string[] expected"; + } return null; }; @@ -21874,6 +21984,13 @@ } if (object.kmsKeyName != null) message.kmsKeyName = String(object.kmsKeyName); + if (object.kmsKeyNames) { + if (!Array.isArray(object.kmsKeyNames)) + throw TypeError(".google.spanner.admin.database.v1.CopyBackupEncryptionConfig.kmsKeyNames: array expected"); + message.kmsKeyNames = []; + for (var i = 0; i < object.kmsKeyNames.length; ++i) + message.kmsKeyNames[i] = String(object.kmsKeyNames[i]); + } return message; }; @@ -21890,6 +22007,8 @@ if (!options) options = {}; var object = {}; + if (options.arrays || options.defaults) + object.kmsKeyNames = []; if (options.defaults) { object.encryptionType = options.enums === String ? "ENCRYPTION_TYPE_UNSPECIFIED" : 0; object.kmsKeyName = ""; @@ -21898,6 +22017,11 @@ object.encryptionType = options.enums === String ? $root.google.spanner.admin.database.v1.CopyBackupEncryptionConfig.EncryptionType[message.encryptionType] === undefined ? message.encryptionType : $root.google.spanner.admin.database.v1.CopyBackupEncryptionConfig.EncryptionType[message.encryptionType] : message.encryptionType; if (message.kmsKeyName != null && message.hasOwnProperty("kmsKeyName")) object.kmsKeyName = message.kmsKeyName; + if (message.kmsKeyNames && message.kmsKeyNames.length) { + object.kmsKeyNames = []; + for (var j = 0; j < message.kmsKeyNames.length; ++j) + object.kmsKeyNames[j] = message.kmsKeyNames[j]; + } return object; }; @@ -22215,6 +22339,7 @@ * @memberof google.spanner.admin.database.v1 * @interface IEncryptionConfig * @property {string|null} [kmsKeyName] EncryptionConfig kmsKeyName + * @property {Array.|null} [kmsKeyNames] EncryptionConfig kmsKeyNames */ /** @@ -22226,6 +22351,7 @@ * @param {google.spanner.admin.database.v1.IEncryptionConfig=} [properties] Properties to set */ function EncryptionConfig(properties) { + this.kmsKeyNames = []; if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) @@ -22240,6 +22366,14 @@ */ EncryptionConfig.prototype.kmsKeyName = ""; + /** + * EncryptionConfig kmsKeyNames. + * @member {Array.} kmsKeyNames + * @memberof google.spanner.admin.database.v1.EncryptionConfig + * @instance + */ + EncryptionConfig.prototype.kmsKeyNames = $util.emptyArray; + /** * Creates a new EncryptionConfig instance using the specified properties. * @function create @@ -22266,6 +22400,9 @@ writer = $Writer.create(); if (message.kmsKeyName != null && Object.hasOwnProperty.call(message, "kmsKeyName")) writer.uint32(/* id 2, wireType 2 =*/18).string(message.kmsKeyName); + if (message.kmsKeyNames != null && message.kmsKeyNames.length) + for (var i = 0; i < message.kmsKeyNames.length; ++i) + writer.uint32(/* id 3, wireType 2 =*/26).string(message.kmsKeyNames[i]); return writer; }; @@ -22304,6 +22441,12 @@ message.kmsKeyName = reader.string(); break; } + case 3: { + if (!(message.kmsKeyNames && message.kmsKeyNames.length)) + message.kmsKeyNames = []; + message.kmsKeyNames.push(reader.string()); + break; + } default: reader.skipType(tag & 7); break; @@ -22342,6 +22485,13 @@ if (message.kmsKeyName != null && message.hasOwnProperty("kmsKeyName")) if (!$util.isString(message.kmsKeyName)) return "kmsKeyName: string expected"; + if (message.kmsKeyNames != null && message.hasOwnProperty("kmsKeyNames")) { + if (!Array.isArray(message.kmsKeyNames)) + return "kmsKeyNames: array expected"; + for (var i = 0; i < message.kmsKeyNames.length; ++i) + if (!$util.isString(message.kmsKeyNames[i])) + return "kmsKeyNames: string[] expected"; + } return null; }; @@ -22359,6 +22509,13 @@ var message = new $root.google.spanner.admin.database.v1.EncryptionConfig(); if (object.kmsKeyName != null) message.kmsKeyName = String(object.kmsKeyName); + if (object.kmsKeyNames) { + if (!Array.isArray(object.kmsKeyNames)) + throw TypeError(".google.spanner.admin.database.v1.EncryptionConfig.kmsKeyNames: array expected"); + message.kmsKeyNames = []; + for (var i = 0; i < object.kmsKeyNames.length; ++i) + message.kmsKeyNames[i] = String(object.kmsKeyNames[i]); + } return message; }; @@ -22375,10 +22532,17 @@ if (!options) options = {}; var object = {}; + if (options.arrays || options.defaults) + object.kmsKeyNames = []; if (options.defaults) object.kmsKeyName = ""; if (message.kmsKeyName != null && message.hasOwnProperty("kmsKeyName")) object.kmsKeyName = message.kmsKeyName; + if (message.kmsKeyNames && message.kmsKeyNames.length) { + object.kmsKeyNames = []; + for (var j = 0; j < message.kmsKeyNames.length; ++j) + object.kmsKeyNames[j] = message.kmsKeyNames[j]; + } return object; }; @@ -28487,6 +28651,7 @@ * @interface IRestoreDatabaseEncryptionConfig * @property {google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.EncryptionType|null} [encryptionType] RestoreDatabaseEncryptionConfig encryptionType * @property {string|null} [kmsKeyName] RestoreDatabaseEncryptionConfig kmsKeyName + * @property {Array.|null} [kmsKeyNames] RestoreDatabaseEncryptionConfig kmsKeyNames */ /** @@ -28498,6 +28663,7 @@ * @param {google.spanner.admin.database.v1.IRestoreDatabaseEncryptionConfig=} [properties] Properties to set */ function RestoreDatabaseEncryptionConfig(properties) { + this.kmsKeyNames = []; if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) @@ -28520,6 +28686,14 @@ */ RestoreDatabaseEncryptionConfig.prototype.kmsKeyName = ""; + /** + * RestoreDatabaseEncryptionConfig kmsKeyNames. + * @member {Array.} kmsKeyNames + * @memberof google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig + * @instance + */ + RestoreDatabaseEncryptionConfig.prototype.kmsKeyNames = $util.emptyArray; + /** * Creates a new RestoreDatabaseEncryptionConfig instance using the specified properties. * @function create @@ -28548,6 +28722,9 @@ writer.uint32(/* id 1, wireType 0 =*/8).int32(message.encryptionType); if (message.kmsKeyName != null && Object.hasOwnProperty.call(message, "kmsKeyName")) writer.uint32(/* id 2, wireType 2 =*/18).string(message.kmsKeyName); + if (message.kmsKeyNames != null && message.kmsKeyNames.length) + for (var i = 0; i < message.kmsKeyNames.length; ++i) + writer.uint32(/* id 3, wireType 2 =*/26).string(message.kmsKeyNames[i]); return writer; }; @@ -28590,6 +28767,12 @@ message.kmsKeyName = reader.string(); break; } + case 3: { + if (!(message.kmsKeyNames && message.kmsKeyNames.length)) + message.kmsKeyNames = []; + message.kmsKeyNames.push(reader.string()); + break; + } default: reader.skipType(tag & 7); break; @@ -28638,6 +28821,13 @@ if (message.kmsKeyName != null && message.hasOwnProperty("kmsKeyName")) if (!$util.isString(message.kmsKeyName)) return "kmsKeyName: string expected"; + if (message.kmsKeyNames != null && message.hasOwnProperty("kmsKeyNames")) { + if (!Array.isArray(message.kmsKeyNames)) + return "kmsKeyNames: array expected"; + for (var i = 0; i < message.kmsKeyNames.length; ++i) + if (!$util.isString(message.kmsKeyNames[i])) + return "kmsKeyNames: string[] expected"; + } return null; }; @@ -28679,6 +28869,13 @@ } if (object.kmsKeyName != null) message.kmsKeyName = String(object.kmsKeyName); + if (object.kmsKeyNames) { + if (!Array.isArray(object.kmsKeyNames)) + throw TypeError(".google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.kmsKeyNames: array expected"); + message.kmsKeyNames = []; + for (var i = 0; i < object.kmsKeyNames.length; ++i) + message.kmsKeyNames[i] = String(object.kmsKeyNames[i]); + } return message; }; @@ -28695,6 +28892,8 @@ if (!options) options = {}; var object = {}; + if (options.arrays || options.defaults) + object.kmsKeyNames = []; if (options.defaults) { object.encryptionType = options.enums === String ? "ENCRYPTION_TYPE_UNSPECIFIED" : 0; object.kmsKeyName = ""; @@ -28703,6 +28902,11 @@ object.encryptionType = options.enums === String ? $root.google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.EncryptionType[message.encryptionType] === undefined ? message.encryptionType : $root.google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.EncryptionType[message.encryptionType] : message.encryptionType; if (message.kmsKeyName != null && message.hasOwnProperty("kmsKeyName")) object.kmsKeyName = message.kmsKeyName; + if (message.kmsKeyNames && message.kmsKeyNames.length) { + object.kmsKeyNames = []; + for (var j = 0; j < message.kmsKeyNames.length; ++j) + object.kmsKeyNames[j] = message.kmsKeyNames[j]; + } return object; }; diff --git a/protos/protos.json b/protos/protos.json index 2c8db5995..f1daeaffc 100644 --- a/protos/protos.json +++ b/protos/protos.json @@ -1690,6 +1690,14 @@ "(google.api.field_behavior)": "OUTPUT_ONLY" } }, + "encryptionInformation": { + "rule": "repeated", + "type": "EncryptionInfo", + "id": 13, + "options": { + "(google.api.field_behavior)": "OUTPUT_ONLY" + } + }, "databaseDialect": { "type": "DatabaseDialect", "id": 10, @@ -2008,6 +2016,15 @@ "(google.api.field_behavior)": "OPTIONAL", "(google.api.resource_reference).type": "cloudkms.googleapis.com/CryptoKey" } + }, + "kmsKeyNames": { + "rule": "repeated", + "type": "string", + "id": 3, + "options": { + "(google.api.field_behavior)": "OPTIONAL", + "(google.api.resource_reference).type": "cloudkms.googleapis.com/CryptoKey" + } } }, "nested": { @@ -2037,6 +2054,15 @@ "(google.api.field_behavior)": "OPTIONAL", "(google.api.resource_reference).type": "cloudkms.googleapis.com/CryptoKey" } + }, + "kmsKeyNames": { + "rule": "repeated", + "type": "string", + "id": 3, + "options": { + "(google.api.field_behavior)": "OPTIONAL", + "(google.api.resource_reference).type": "cloudkms.googleapis.com/CryptoKey" + } } }, "nested": { @@ -2074,6 +2100,14 @@ "options": { "(google.api.resource_reference).type": "cloudkms.googleapis.com/CryptoKey" } + }, + "kmsKeyNames": { + "rule": "repeated", + "type": "string", + "id": 3, + "options": { + "(google.api.resource_reference).type": "cloudkms.googleapis.com/CryptoKey" + } } } }, @@ -3052,6 +3086,15 @@ "(google.api.field_behavior)": "OPTIONAL", "(google.api.resource_reference).type": "cloudkms.googleapis.com/CryptoKey" } + }, + "kmsKeyNames": { + "rule": "repeated", + "type": "string", + "id": 3, + "options": { + "(google.api.field_behavior)": "OPTIONAL", + "(google.api.resource_reference).type": "cloudkms.googleapis.com/CryptoKey" + } } }, "nested": { diff --git a/src/v1/database_admin_client.ts b/src/v1/database_admin_client.ts index a4e26200a..3fae88a1d 100644 --- a/src/v1/database_admin_client.ts +++ b/src/v1/database_admin_client.ts @@ -46,7 +46,7 @@ const version = require('../../../package.json').version; * The Cloud Spanner Database Admin API can be used to: * * create, drop, and list databases * * update the schema of pre-existing databases - * * create, delete and list backups for a database + * * create, delete, copy and list backups for a database * * restore a database from an existing backup * @class * @memberof v1 @@ -1139,7 +1139,8 @@ export class DatabaseAdminClient { return this.innerApiCalls.testIamPermissions(request, options, callback); } /** - * Gets metadata on a pending or completed {@link protos.google.spanner.admin.database.v1.Backup|Backup}. + * Gets metadata on a pending or completed + * {@link protos.google.spanner.admin.database.v1.Backup|Backup}. * * @param {Object} request * The request object that will be sent. @@ -1229,7 +1230,8 @@ export class DatabaseAdminClient { return this.innerApiCalls.getBackup(request, options, callback); } /** - * Updates a pending or completed {@link protos.google.spanner.admin.database.v1.Backup|Backup}. + * Updates a pending or completed + * {@link protos.google.spanner.admin.database.v1.Backup|Backup}. * * @param {Object} request * The request object that will be sent. @@ -1326,7 +1328,8 @@ export class DatabaseAdminClient { return this.innerApiCalls.updateBackup(request, options, callback); } /** - * Deletes a pending or completed {@link protos.google.spanner.admin.database.v1.Backup|Backup}. + * Deletes a pending or completed + * {@link protos.google.spanner.admin.database.v1.Backup|Backup}. * * @param {Object} request * The request object that will be sent. @@ -1422,8 +1425,8 @@ export class DatabaseAdminClient { * have a name of the format `/operations/` and * can be used to track preparation of the database. The * {@link protos.google.longrunning.Operation.metadata|metadata} field type is - * {@link protos.google.spanner.admin.database.v1.CreateDatabaseMetadata|CreateDatabaseMetadata}. The - * {@link protos.google.longrunning.Operation.response|response} field type is + * {@link protos.google.spanner.admin.database.v1.CreateDatabaseMetadata|CreateDatabaseMetadata}. + * The {@link protos.google.longrunning.Operation.response|response} field type is * {@link protos.google.spanner.admin.database.v1.Database|Database}, if successful. * * @param {Object} request @@ -1443,8 +1446,8 @@ export class DatabaseAdminClient { * statements execute atomically with the creation of the database: * if there is an error in any statement, the database is not created. * @param {google.spanner.admin.database.v1.EncryptionConfig} [request.encryptionConfig] - * Optional. The encryption configuration for the database. If this field is not - * specified, Cloud Spanner will encrypt/decrypt all data at rest using + * Optional. The encryption configuration for the database. If this field is + * not specified, Cloud Spanner will encrypt/decrypt all data at rest using * Google default encryption. * @param {google.spanner.admin.database.v1.DatabaseDialect} [request.databaseDialect] * Optional. The dialect of the Cloud Spanner Database. @@ -1768,7 +1771,8 @@ export class DatabaseAdminClient { * the format `/operations/` and can be used to * track execution of the schema change(s). The * {@link protos.google.longrunning.Operation.metadata|metadata} field type is - * {@link protos.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata|UpdateDatabaseDdlMetadata}. The operation has no response. + * {@link protos.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata|UpdateDatabaseDdlMetadata}. + * The operation has no response. * * @param {Object} request * The request object that will be sent. @@ -1784,18 +1788,20 @@ export class DatabaseAdminClient { * * Specifying an explicit operation ID simplifies determining * whether the statements were executed in the event that the - * {@link protos.google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl|UpdateDatabaseDdl} call is replayed, - * or the return value is otherwise lost: the {@link protos.google.spanner.admin.database.v1.UpdateDatabaseDdlRequest.database|database} and - * `operation_id` fields can be combined to form the + * {@link protos.google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl|UpdateDatabaseDdl} + * call is replayed, or the return value is otherwise lost: the + * {@link protos.google.spanner.admin.database.v1.UpdateDatabaseDdlRequest.database|database} + * and `operation_id` fields can be combined to form the * {@link protos.google.longrunning.Operation.name|name} of the resulting - * {@link protos.google.longrunning.Operation|longrunning.Operation}: `/operations/`. + * {@link protos.google.longrunning.Operation|longrunning.Operation}: + * `/operations/`. * * `operation_id` should be unique within the database, and must be * a valid identifier: `{@link protos.a-z0-9_|a-z}*`. Note that * automatically-generated operation IDs always begin with an * underscore. If the named operation already exists, - * {@link protos.google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl|UpdateDatabaseDdl} returns - * `ALREADY_EXISTS`. + * {@link protos.google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl|UpdateDatabaseDdl} + * returns `ALREADY_EXISTS`. * @param {Buffer} [request.protoDescriptors] * Optional. Proto descriptors used by CREATE/ALTER PROTO BUNDLE statements. * Contains a protobuf-serialized @@ -1943,12 +1949,12 @@ export class DatabaseAdminClient { * `projects//instances//backups//operations/` * and can be used to track creation of the backup. The * {@link protos.google.longrunning.Operation.metadata|metadata} field type is - * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. The - * {@link protos.google.longrunning.Operation.response|response} field type is - * {@link protos.google.spanner.admin.database.v1.Backup|Backup}, if successful. Cancelling the returned operation will stop the - * creation and delete the backup. - * There can be only one pending backup creation per database. Backup creation - * of different databases can run concurrently. + * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. + * The {@link protos.google.longrunning.Operation.response|response} field type is + * {@link protos.google.spanner.admin.database.v1.Backup|Backup}, if successful. + * Cancelling the returned operation will stop the creation and delete the + * backup. There can be only one pending backup creation per database. Backup + * creation of different databases can run concurrently. * * @param {Object} request * The request object that will be sent. @@ -1966,11 +1972,11 @@ export class DatabaseAdminClient { * @param {google.spanner.admin.database.v1.Backup} request.backup * Required. The backup to create. * @param {google.spanner.admin.database.v1.CreateBackupEncryptionConfig} [request.encryptionConfig] - * Optional. The encryption configuration used to encrypt the backup. If this field is - * not specified, the backup will use the same - * encryption configuration as the database by default, namely - * {@link protos.google.spanner.admin.database.v1.CreateBackupEncryptionConfig.encryption_type|encryption_type} = - * `USE_DATABASE_ENCRYPTION`. + * Optional. The encryption configuration used to encrypt the backup. If this + * field is not specified, the backup will use the same encryption + * configuration as the database by default, namely + * {@link protos.google.spanner.admin.database.v1.CreateBackupEncryptionConfig.encryption_type|encryption_type} + * = `USE_DATABASE_ENCRYPTION`. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. @@ -2106,15 +2112,16 @@ export class DatabaseAdminClient { * The {@link protos.google.longrunning.Operation.metadata|metadata} field type is * {@link protos.google.spanner.admin.database.v1.CopyBackupMetadata|CopyBackupMetadata}. * The {@link protos.google.longrunning.Operation.response|response} field type is - * {@link protos.google.spanner.admin.database.v1.Backup|Backup}, if successful. Cancelling the returned operation will stop the - * copying and delete the backup. - * Concurrent CopyBackup requests can run on the same source backup. + * {@link protos.google.spanner.admin.database.v1.Backup|Backup}, if successful. + * Cancelling the returned operation will stop the copying and delete the + * destination backup. Concurrent CopyBackup requests can run on the same + * source backup. * * @param {Object} request * The request object that will be sent. * @param {string} request.parent - * Required. The name of the destination instance that will contain the backup copy. - * Values are of the form: `projects//instances/`. + * Required. The name of the destination instance that will contain the backup + * copy. Values are of the form: `projects//instances/`. * @param {string} request.backupId * Required. The id of the backup copy. * The `backup_id` appended to `parent` forms the full backup_uri of the form @@ -2133,11 +2140,11 @@ export class DatabaseAdminClient { * passed, the backup is eligible to be automatically deleted by Cloud Spanner * to free the resources used by the backup. * @param {google.spanner.admin.database.v1.CopyBackupEncryptionConfig} [request.encryptionConfig] - * Optional. The encryption configuration used to encrypt the backup. If this field is - * not specified, the backup will use the same - * encryption configuration as the source backup by default, namely - * {@link protos.google.spanner.admin.database.v1.CopyBackupEncryptionConfig.encryption_type|encryption_type} = - * `USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION`. + * Optional. The encryption configuration used to encrypt the backup. If this + * field is not specified, the backup will use the same encryption + * configuration as the source backup by default, namely + * {@link protos.google.spanner.admin.database.v1.CopyBackupEncryptionConfig.encryption_type|encryption_type} + * = `USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION`. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. @@ -2299,12 +2306,12 @@ export class DatabaseAdminClient { * Name of the backup from which to restore. Values are of the form * `projects//instances//backups/`. * @param {google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig} [request.encryptionConfig] - * Optional. An encryption configuration describing the encryption type and key - * resources in Cloud KMS used to encrypt/decrypt the database to restore to. - * If this field is not specified, the restored database will use - * the same encryption configuration as the backup by default, namely - * {@link protos.google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.encryption_type|encryption_type} = - * `USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION`. + * Optional. An encryption configuration describing the encryption type and + * key resources in Cloud KMS used to encrypt/decrypt the database to restore + * to. If this field is not specified, the restored database will use the same + * encryption configuration as the backup by default, namely + * {@link protos.google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.encryption_type|encryption_type} + * = `USE_CONFIG_DEFAULT_OR_BACKUP_ENCRYPTION`. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. @@ -2443,8 +2450,9 @@ export class DatabaseAdminClient { * defaults to the server's maximum allowed page size. * @param {string} request.pageToken * If non-empty, `page_token` should contain a - * {@link protos.google.spanner.admin.database.v1.ListDatabasesResponse.next_page_token|next_page_token} from a - * previous {@link protos.google.spanner.admin.database.v1.ListDatabasesResponse|ListDatabasesResponse}. + * {@link protos.google.spanner.admin.database.v1.ListDatabasesResponse.next_page_token|next_page_token} + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListDatabasesResponse|ListDatabasesResponse}. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. @@ -2544,8 +2552,9 @@ export class DatabaseAdminClient { * defaults to the server's maximum allowed page size. * @param {string} request.pageToken * If non-empty, `page_token` should contain a - * {@link protos.google.spanner.admin.database.v1.ListDatabasesResponse.next_page_token|next_page_token} from a - * previous {@link protos.google.spanner.admin.database.v1.ListDatabasesResponse|ListDatabasesResponse}. + * {@link protos.google.spanner.admin.database.v1.ListDatabasesResponse.next_page_token|next_page_token} + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListDatabasesResponse|ListDatabasesResponse}. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Stream} @@ -2593,8 +2602,9 @@ export class DatabaseAdminClient { * defaults to the server's maximum allowed page size. * @param {string} request.pageToken * If non-empty, `page_token` should contain a - * {@link protos.google.spanner.admin.database.v1.ListDatabasesResponse.next_page_token|next_page_token} from a - * previous {@link protos.google.spanner.admin.database.v1.ListDatabasesResponse|ListDatabasesResponse}. + * {@link protos.google.spanner.admin.database.v1.ListDatabasesResponse.next_page_token|next_page_token} + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListDatabasesResponse|ListDatabasesResponse}. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Object} @@ -2645,7 +2655,9 @@ export class DatabaseAdminClient { * must be one of: `<`, `>`, `<=`, `>=`, `!=`, `=`, or `:`. * Colon `:` is the contains operator. Filter rules are not case sensitive. * - * The following fields in the {@link protos.google.spanner.admin.database.v1.Backup|Backup} are eligible for filtering: + * The following fields in the + * {@link protos.google.spanner.admin.database.v1.Backup|Backup} are eligible for + * filtering: * * * `name` * * `database` @@ -2677,9 +2689,10 @@ export class DatabaseAdminClient { * less, defaults to the server's maximum allowed page size. * @param {string} request.pageToken * If non-empty, `page_token` should contain a - * {@link protos.google.spanner.admin.database.v1.ListBackupsResponse.next_page_token|next_page_token} from a - * previous {@link protos.google.spanner.admin.database.v1.ListBackupsResponse|ListBackupsResponse} to the same `parent` and with the same - * `filter`. + * {@link protos.google.spanner.admin.database.v1.ListBackupsResponse.next_page_token|next_page_token} + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListBackupsResponse|ListBackupsResponse} + * to the same `parent` and with the same `filter`. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. @@ -2783,7 +2796,9 @@ export class DatabaseAdminClient { * must be one of: `<`, `>`, `<=`, `>=`, `!=`, `=`, or `:`. * Colon `:` is the contains operator. Filter rules are not case sensitive. * - * The following fields in the {@link protos.google.spanner.admin.database.v1.Backup|Backup} are eligible for filtering: + * The following fields in the + * {@link protos.google.spanner.admin.database.v1.Backup|Backup} are eligible for + * filtering: * * * `name` * * `database` @@ -2815,9 +2830,10 @@ export class DatabaseAdminClient { * less, defaults to the server's maximum allowed page size. * @param {string} request.pageToken * If non-empty, `page_token` should contain a - * {@link protos.google.spanner.admin.database.v1.ListBackupsResponse.next_page_token|next_page_token} from a - * previous {@link protos.google.spanner.admin.database.v1.ListBackupsResponse|ListBackupsResponse} to the same `parent` and with the same - * `filter`. + * {@link protos.google.spanner.admin.database.v1.ListBackupsResponse.next_page_token|next_page_token} + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListBackupsResponse|ListBackupsResponse} + * to the same `parent` and with the same `filter`. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Stream} @@ -2869,7 +2885,9 @@ export class DatabaseAdminClient { * must be one of: `<`, `>`, `<=`, `>=`, `!=`, `=`, or `:`. * Colon `:` is the contains operator. Filter rules are not case sensitive. * - * The following fields in the {@link protos.google.spanner.admin.database.v1.Backup|Backup} are eligible for filtering: + * The following fields in the + * {@link protos.google.spanner.admin.database.v1.Backup|Backup} are eligible for + * filtering: * * * `name` * * `database` @@ -2901,9 +2919,10 @@ export class DatabaseAdminClient { * less, defaults to the server's maximum allowed page size. * @param {string} request.pageToken * If non-empty, `page_token` should contain a - * {@link protos.google.spanner.admin.database.v1.ListBackupsResponse.next_page_token|next_page_token} from a - * previous {@link protos.google.spanner.admin.database.v1.ListBackupsResponse|ListBackupsResponse} to the same `parent` and with the same - * `filter`. + * {@link protos.google.spanner.admin.database.v1.ListBackupsResponse.next_page_token|next_page_token} + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListBackupsResponse|ListBackupsResponse} + * to the same `parent` and with the same `filter`. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Object} @@ -2965,7 +2984,9 @@ export class DatabaseAdminClient { * * `name` - The name of the long-running operation * * `done` - False if the operation is in progress, else true. * * `metadata.@type` - the type of metadata. For example, the type string - * for {@link protos.google.spanner.admin.database.v1.RestoreDatabaseMetadata|RestoreDatabaseMetadata} is + * for + * {@link protos.google.spanner.admin.database.v1.RestoreDatabaseMetadata|RestoreDatabaseMetadata} + * is * `type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata`. * * `metadata.` - any field in metadata.value. * `metadata.@type` must be specified first, if filtering on metadata @@ -2987,7 +3008,8 @@ export class DatabaseAdminClient { * `(metadata.name:restored_howl) AND` \ * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Return operations where: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.RestoreDatabaseMetadata|RestoreDatabaseMetadata}. + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.RestoreDatabaseMetadata|RestoreDatabaseMetadata}. * * The database is restored from a backup. * * The backup name contains "backup_howl". * * The restored database's name contains "restored_howl". @@ -2999,8 +3021,9 @@ export class DatabaseAdminClient { * @param {string} request.pageToken * If non-empty, `page_token` should contain a * {@link protos.google.spanner.admin.database.v1.ListDatabaseOperationsResponse.next_page_token|next_page_token} - * from a previous {@link protos.google.spanner.admin.database.v1.ListDatabaseOperationsResponse|ListDatabaseOperationsResponse} to the - * same `parent` and with the same `filter`. + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListDatabaseOperationsResponse|ListDatabaseOperationsResponse} + * to the same `parent` and with the same `filter`. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. @@ -3114,7 +3137,9 @@ export class DatabaseAdminClient { * * `name` - The name of the long-running operation * * `done` - False if the operation is in progress, else true. * * `metadata.@type` - the type of metadata. For example, the type string - * for {@link protos.google.spanner.admin.database.v1.RestoreDatabaseMetadata|RestoreDatabaseMetadata} is + * for + * {@link protos.google.spanner.admin.database.v1.RestoreDatabaseMetadata|RestoreDatabaseMetadata} + * is * `type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata`. * * `metadata.` - any field in metadata.value. * `metadata.@type` must be specified first, if filtering on metadata @@ -3136,7 +3161,8 @@ export class DatabaseAdminClient { * `(metadata.name:restored_howl) AND` \ * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Return operations where: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.RestoreDatabaseMetadata|RestoreDatabaseMetadata}. + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.RestoreDatabaseMetadata|RestoreDatabaseMetadata}. * * The database is restored from a backup. * * The backup name contains "backup_howl". * * The restored database's name contains "restored_howl". @@ -3148,8 +3174,9 @@ export class DatabaseAdminClient { * @param {string} request.pageToken * If non-empty, `page_token` should contain a * {@link protos.google.spanner.admin.database.v1.ListDatabaseOperationsResponse.next_page_token|next_page_token} - * from a previous {@link protos.google.spanner.admin.database.v1.ListDatabaseOperationsResponse|ListDatabaseOperationsResponse} to the - * same `parent` and with the same `filter`. + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListDatabaseOperationsResponse|ListDatabaseOperationsResponse} + * to the same `parent` and with the same `filter`. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Stream} @@ -3207,7 +3234,9 @@ export class DatabaseAdminClient { * * `name` - The name of the long-running operation * * `done` - False if the operation is in progress, else true. * * `metadata.@type` - the type of metadata. For example, the type string - * for {@link protos.google.spanner.admin.database.v1.RestoreDatabaseMetadata|RestoreDatabaseMetadata} is + * for + * {@link protos.google.spanner.admin.database.v1.RestoreDatabaseMetadata|RestoreDatabaseMetadata} + * is * `type.googleapis.com/google.spanner.admin.database.v1.RestoreDatabaseMetadata`. * * `metadata.` - any field in metadata.value. * `metadata.@type` must be specified first, if filtering on metadata @@ -3229,7 +3258,8 @@ export class DatabaseAdminClient { * `(metadata.name:restored_howl) AND` \ * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Return operations where: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.RestoreDatabaseMetadata|RestoreDatabaseMetadata}. + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.RestoreDatabaseMetadata|RestoreDatabaseMetadata}. * * The database is restored from a backup. * * The backup name contains "backup_howl". * * The restored database's name contains "restored_howl". @@ -3241,8 +3271,9 @@ export class DatabaseAdminClient { * @param {string} request.pageToken * If non-empty, `page_token` should contain a * {@link protos.google.spanner.admin.database.v1.ListDatabaseOperationsResponse.next_page_token|next_page_token} - * from a previous {@link protos.google.spanner.admin.database.v1.ListDatabaseOperationsResponse|ListDatabaseOperationsResponse} to the - * same `parent` and with the same `filter`. + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListDatabaseOperationsResponse|ListDatabaseOperationsResponse} + * to the same `parent` and with the same `filter`. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Object} @@ -3306,7 +3337,9 @@ export class DatabaseAdminClient { * * `name` - The name of the long-running operation * * `done` - False if the operation is in progress, else true. * * `metadata.@type` - the type of metadata. For example, the type string - * for {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata} is + * for + * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata} + * is * `type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata`. * * `metadata.` - any field in metadata.value. * `metadata.@type` must be specified first if filtering on metadata @@ -3324,14 +3357,15 @@ export class DatabaseAdminClient { * * `done:true` - The operation is complete. * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ * `metadata.database:prod` - Returns operations where: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. - * * The database the backup was taken from has a name containing the - * string "prod". + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. + * * The source database name of backup contains the string "prod". * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ * `(metadata.name:howl) AND` \ * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. * * The backup name contains the string "howl". * * The operation started before 2018-03-28T14:50:00Z. * * The operation resulted in an error. @@ -3339,9 +3373,9 @@ export class DatabaseAdminClient { * `(metadata.source_backup:test) AND` \ * `(metadata.progress.start_time < \"2022-01-18T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CopyBackupMetadata|CopyBackupMetadata}. - * * The source backup of the copied backup name contains the string - * "test". + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CopyBackupMetadata|CopyBackupMetadata}. + * * The source backup name contains the string "test". * * The operation started before 2022-01-18T14:50:00Z. * * The operation resulted in an error. * * `((metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ @@ -3351,12 +3385,13 @@ export class DatabaseAdminClient { * `(metadata.source_backup:test_bkp)) AND` \ * `(error:*)` - Returns operations where: * * The operation's metadata matches either of criteria: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata} AND the - * database the backup was taken from has name containing string + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata} + * AND the source database name of the backup contains the string * "test_db" - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CopyBackupMetadata|CopyBackupMetadata} AND the - * backup the backup was copied from has name containing string - * "test_bkp" + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CopyBackupMetadata|CopyBackupMetadata} + * AND the source backup name contains the string "test_bkp" * * The operation resulted in an error. * @param {number} request.pageSize * Number of operations to be returned in the response. If 0 or @@ -3364,8 +3399,9 @@ export class DatabaseAdminClient { * @param {string} request.pageToken * If non-empty, `page_token` should contain a * {@link protos.google.spanner.admin.database.v1.ListBackupOperationsResponse.next_page_token|next_page_token} - * from a previous {@link protos.google.spanner.admin.database.v1.ListBackupOperationsResponse|ListBackupOperationsResponse} to the - * same `parent` and with the same `filter`. + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListBackupOperationsResponse|ListBackupOperationsResponse} + * to the same `parent` and with the same `filter`. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. @@ -3475,7 +3511,9 @@ export class DatabaseAdminClient { * * `name` - The name of the long-running operation * * `done` - False if the operation is in progress, else true. * * `metadata.@type` - the type of metadata. For example, the type string - * for {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata} is + * for + * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata} + * is * `type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata`. * * `metadata.` - any field in metadata.value. * `metadata.@type` must be specified first if filtering on metadata @@ -3493,14 +3531,15 @@ export class DatabaseAdminClient { * * `done:true` - The operation is complete. * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ * `metadata.database:prod` - Returns operations where: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. - * * The database the backup was taken from has a name containing the - * string "prod". + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. + * * The source database name of backup contains the string "prod". * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ * `(metadata.name:howl) AND` \ * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. * * The backup name contains the string "howl". * * The operation started before 2018-03-28T14:50:00Z. * * The operation resulted in an error. @@ -3508,9 +3547,9 @@ export class DatabaseAdminClient { * `(metadata.source_backup:test) AND` \ * `(metadata.progress.start_time < \"2022-01-18T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CopyBackupMetadata|CopyBackupMetadata}. - * * The source backup of the copied backup name contains the string - * "test". + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CopyBackupMetadata|CopyBackupMetadata}. + * * The source backup name contains the string "test". * * The operation started before 2022-01-18T14:50:00Z. * * The operation resulted in an error. * * `((metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ @@ -3520,12 +3559,13 @@ export class DatabaseAdminClient { * `(metadata.source_backup:test_bkp)) AND` \ * `(error:*)` - Returns operations where: * * The operation's metadata matches either of criteria: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata} AND the - * database the backup was taken from has name containing string + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata} + * AND the source database name of the backup contains the string * "test_db" - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CopyBackupMetadata|CopyBackupMetadata} AND the - * backup the backup was copied from has name containing string - * "test_bkp" + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CopyBackupMetadata|CopyBackupMetadata} + * AND the source backup name contains the string "test_bkp" * * The operation resulted in an error. * @param {number} request.pageSize * Number of operations to be returned in the response. If 0 or @@ -3533,8 +3573,9 @@ export class DatabaseAdminClient { * @param {string} request.pageToken * If non-empty, `page_token` should contain a * {@link protos.google.spanner.admin.database.v1.ListBackupOperationsResponse.next_page_token|next_page_token} - * from a previous {@link protos.google.spanner.admin.database.v1.ListBackupOperationsResponse|ListBackupOperationsResponse} to the - * same `parent` and with the same `filter`. + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListBackupOperationsResponse|ListBackupOperationsResponse} + * to the same `parent` and with the same `filter`. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Stream} @@ -3592,7 +3633,9 @@ export class DatabaseAdminClient { * * `name` - The name of the long-running operation * * `done` - False if the operation is in progress, else true. * * `metadata.@type` - the type of metadata. For example, the type string - * for {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata} is + * for + * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata} + * is * `type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata`. * * `metadata.` - any field in metadata.value. * `metadata.@type` must be specified first if filtering on metadata @@ -3610,14 +3653,15 @@ export class DatabaseAdminClient { * * `done:true` - The operation is complete. * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ * `metadata.database:prod` - Returns operations where: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. - * * The database the backup was taken from has a name containing the - * string "prod". + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. + * * The source database name of backup contains the string "prod". * * `(metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ * `(metadata.name:howl) AND` \ * `(metadata.progress.start_time < \"2018-03-28T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata}. * * The backup name contains the string "howl". * * The operation started before 2018-03-28T14:50:00Z. * * The operation resulted in an error. @@ -3625,9 +3669,9 @@ export class DatabaseAdminClient { * `(metadata.source_backup:test) AND` \ * `(metadata.progress.start_time < \"2022-01-18T14:50:00Z\") AND` \ * `(error:*)` - Returns operations where: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CopyBackupMetadata|CopyBackupMetadata}. - * * The source backup of the copied backup name contains the string - * "test". + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CopyBackupMetadata|CopyBackupMetadata}. + * * The source backup name contains the string "test". * * The operation started before 2022-01-18T14:50:00Z. * * The operation resulted in an error. * * `((metadata.@type=type.googleapis.com/google.spanner.admin.database.v1.CreateBackupMetadata) AND` \ @@ -3637,12 +3681,13 @@ export class DatabaseAdminClient { * `(metadata.source_backup:test_bkp)) AND` \ * `(error:*)` - Returns operations where: * * The operation's metadata matches either of criteria: - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata} AND the - * database the backup was taken from has name containing string + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CreateBackupMetadata|CreateBackupMetadata} + * AND the source database name of the backup contains the string * "test_db" - * * The operation's metadata type is {@link protos.google.spanner.admin.database.v1.CopyBackupMetadata|CopyBackupMetadata} AND the - * backup the backup was copied from has name containing string - * "test_bkp" + * * The operation's metadata type is + * {@link protos.google.spanner.admin.database.v1.CopyBackupMetadata|CopyBackupMetadata} + * AND the source backup name contains the string "test_bkp" * * The operation resulted in an error. * @param {number} request.pageSize * Number of operations to be returned in the response. If 0 or @@ -3650,8 +3695,9 @@ export class DatabaseAdminClient { * @param {string} request.pageToken * If non-empty, `page_token` should contain a * {@link protos.google.spanner.admin.database.v1.ListBackupOperationsResponse.next_page_token|next_page_token} - * from a previous {@link protos.google.spanner.admin.database.v1.ListBackupOperationsResponse|ListBackupOperationsResponse} to the - * same `parent` and with the same `filter`. + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListBackupOperationsResponse|ListBackupOperationsResponse} + * to the same `parent` and with the same `filter`. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Object} @@ -3691,14 +3737,15 @@ export class DatabaseAdminClient { * @param {string} request.parent * Required. The database whose roles should be listed. * Values are of the form - * `projects//instances//databases//databaseRoles`. + * `projects//instances//databases/`. * @param {number} request.pageSize * Number of database roles to be returned in the response. If 0 or less, * defaults to the server's maximum allowed page size. * @param {string} request.pageToken * If non-empty, `page_token` should contain a - * {@link protos.google.spanner.admin.database.v1.ListDatabaseRolesResponse.next_page_token|next_page_token} from a - * previous {@link protos.google.spanner.admin.database.v1.ListDatabaseRolesResponse|ListDatabaseRolesResponse}. + * {@link protos.google.spanner.admin.database.v1.ListDatabaseRolesResponse.next_page_token|next_page_token} + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListDatabaseRolesResponse|ListDatabaseRolesResponse}. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. @@ -3793,14 +3840,15 @@ export class DatabaseAdminClient { * @param {string} request.parent * Required. The database whose roles should be listed. * Values are of the form - * `projects//instances//databases//databaseRoles`. + * `projects//instances//databases/`. * @param {number} request.pageSize * Number of database roles to be returned in the response. If 0 or less, * defaults to the server's maximum allowed page size. * @param {string} request.pageToken * If non-empty, `page_token` should contain a - * {@link protos.google.spanner.admin.database.v1.ListDatabaseRolesResponse.next_page_token|next_page_token} from a - * previous {@link protos.google.spanner.admin.database.v1.ListDatabaseRolesResponse|ListDatabaseRolesResponse}. + * {@link protos.google.spanner.admin.database.v1.ListDatabaseRolesResponse.next_page_token|next_page_token} + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListDatabaseRolesResponse|ListDatabaseRolesResponse}. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Stream} @@ -3843,14 +3891,15 @@ export class DatabaseAdminClient { * @param {string} request.parent * Required. The database whose roles should be listed. * Values are of the form - * `projects//instances//databases//databaseRoles`. + * `projects//instances//databases/`. * @param {number} request.pageSize * Number of database roles to be returned in the response. If 0 or less, * defaults to the server's maximum allowed page size. * @param {string} request.pageToken * If non-empty, `page_token` should contain a - * {@link protos.google.spanner.admin.database.v1.ListDatabaseRolesResponse.next_page_token|next_page_token} from a - * previous {@link protos.google.spanner.admin.database.v1.ListDatabaseRolesResponse|ListDatabaseRolesResponse}. + * {@link protos.google.spanner.admin.database.v1.ListDatabaseRolesResponse.next_page_token|next_page_token} + * from a previous + * {@link protos.google.spanner.admin.database.v1.ListDatabaseRolesResponse|ListDatabaseRolesResponse}. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Object} From ae59c7f957660e08cd5965b5e67694fa1ccc0057 Mon Sep 17 00:00:00 2001 From: Sri Harsha CH <57220027+harshachinta@users.noreply.github.com> Date: Wed, 15 May 2024 11:59:17 +0530 Subject: [PATCH 4/8] feat: add support for Proto columns (#1991) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Support For Proto Column in Spanner (#1829) * feat: Generated Proto Changes Changes need to be reverted. * feat: Implementation for Proto Message & Enum -Adding Logic for Serialization & Deserialization -New Type Codes and utilities * feat: Proto static files and typings and generated descriptors * sample: Adding DML, DQL, DML, table insert & read samples. * style: Lint * test: Adding unit tests * refactor: minor refactoring * refactor: minor refactoring * test: Adding integration tests * docs: Adding docs * test: Adding sample Integration Tests * refactor: Minor refactoring and updating comments/docs. * test: Making test fixes * refactor: Minor refactoring and lint fixes * refactor: Minor refactoring and lint fixes * Updating docs and minor changes. * test: fixing test * refactor: minor changes * refactor: minor refactoring * docs: Updating docs & comments * feat(spanner): fix lint * fix(spanner: lint * fix(spanner): lint * fix(spanner): lint * fix(spanner): lint * feat(spanner): fix db name * feat(spanner): remove it.only * feat(spanner): fix tests lint * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * feat(spanner): remove samples and sample tests * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * feat(spanner): update schema to avoid reserved keyword * feat(spanner): add samples and sample tests * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * feat(spanner): code refactoring --------- Co-authored-by: Gaurav Purohit Co-authored-by: Owl Bot --- README.md | 4 + package.json | 2 +- samples/README.md | 72 +++++ samples/package.json | 3 +- samples/proto-query-data.js | 100 ++++++ samples/proto-type-add-column.js | 90 ++++++ samples/proto-update-data-dml.js | 128 ++++++++ samples/proto-update-data.js | 120 ++++++++ samples/resource/descriptors.pb | Bin 0 -> 251 bytes samples/resource/singer.d.ts | 163 ++++++++++ samples/resource/singer.js | 451 ++++++++++++++++++++++++++++ samples/resource/singer.proto | 31 ++ samples/system-test/spanner.test.js | 76 +++++ src/codec.ts | 172 ++++++++++- src/index.ts | 60 ++++ src/partial-result-stream.ts | 51 +++- src/transaction.ts | 61 +++- system-test/spanner.ts | 275 ++++++++++++++++- test/codec.ts | 172 +++++++++++ test/data/descriptors.pb | Bin 0 -> 251 bytes test/data/singer.d.ts | 163 ++++++++++ test/data/singer.js | 451 ++++++++++++++++++++++++++++ test/data/singer.proto | 31 ++ test/index.ts | 62 ++++ test/transaction.ts | 2 + 25 files changed, 2724 insertions(+), 16 deletions(-) create mode 100644 samples/proto-query-data.js create mode 100644 samples/proto-type-add-column.js create mode 100644 samples/proto-update-data-dml.js create mode 100644 samples/proto-update-data.js create mode 100644 samples/resource/descriptors.pb create mode 100644 samples/resource/singer.d.ts create mode 100644 samples/resource/singer.js create mode 100644 samples/resource/singer.proto create mode 100644 test/data/descriptors.pb create mode 100644 test/data/singer.d.ts create mode 100644 test/data/singer.js create mode 100644 test/data/singer.proto diff --git a/README.md b/README.md index e40782b4e..12878220b 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,10 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-spanner/tre | Alters a sequence in a PostgreSQL database. | [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) | | Creates sequence in PostgreSQL database. | [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) | | Drops a sequence in PostgreSQL database. | [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) | +| Proto-query-data | [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) | +| Creates a new database with a proto column and enum | [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) | +| Proto-update-data-dml | [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) | +| Proto-update-data | [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) | | Queryoptions | [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) | | Quickstart | [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) | | Read data with database role | [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) | diff --git a/package.json b/package.json index c6ed8b666..40edf4761 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "ycsb": "node ./benchmark/ycsb.js run -P ./benchmark/workloada -p table=usertable -p cloudspanner.instance=ycsb-instance -p operationcount=100 -p cloudspanner.database=ycsb", "fix": "gts fix", "clean": "gts clean", - "compile": "tsc -p . && cp -r protos build", + "compile": "tsc -p . && cp -r protos build && cp -r test/data build/test", "prepare": "npm run compile-protos && npm run compile", "pretest": "npm run compile", "presystem-test": "npm run compile", diff --git a/samples/README.md b/samples/README.md index 5e99af7cf..d60b9cdc2 100644 --- a/samples/README.md +++ b/samples/README.md @@ -92,6 +92,10 @@ and automatic, synchronous replication for high availability. * [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) @@ -1455,6 +1459,74 @@ __Usage:__ +### 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). diff --git a/samples/package.json b/samples/package.json index ea6e99d57..993669bdb 100644 --- a/samples/package.json +++ b/samples/package.json @@ -18,7 +18,8 @@ "@google-cloud/kms": "^4.0.0", "@google-cloud/precise-date": "^4.0.0", "@google-cloud/spanner": "^7.7.0", - "yargs": "^17.0.0" + "yargs": "^17.0.0", + "protobufjs": "^7.0.0" }, "devDependencies": { "chai": "^4.2.0", diff --git a/samples/proto-query-data.js b/samples/proto-query-data.js new file mode 100644 index 000000000..3ee51cd34 --- /dev/null +++ b/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 node/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/samples/proto-type-add-column.js b/samples/proto-type-add-column.js new file mode 100644 index 000000000..76c77aa5a --- /dev/null +++ b/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/samples/proto-update-data-dml.js b/samples/proto-update-data-dml.js new file mode 100644 index 000000000..e5f9f7ac8 --- /dev/null +++ b/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 node/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/samples/proto-update-data.js b/samples/proto-update-data.js new file mode 100644 index 000000000..004e5938a --- /dev/null +++ b/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 node/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/samples/resource/descriptors.pb b/samples/resource/descriptors.pb new file mode 100644 index 0000000000000000000000000000000000000000..d4c018f3a3c21b18f68820eeab130d8195064e81 GIT binary patch literal 251 zcmd=3!N|o^oSB!NTBKJ{lwXoBB$ir{m|KvOTC7)GkeHVT6wfU!&P-OC&&b6U3|8ow zmzFOi&BY1P7N40S!KlEf!5qW^5%5eAlI7w`$}B3$h)+o@NtIv%%5nyAf<;__0zwL0 z+>> 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'; + if (message.singerId !== null && message.hasOwnProperty('singerId')) + 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')) + if (!$util.isString(message.birthDate)) + return 'birthDate: string expected'; + if ( + message.nationality !== null && + message.hasOwnProperty('nationality') + ) + if (!$util.isString(message.nationality)) + return 'nationality: string expected'; + if (message.genre !== null && message.hasOwnProperty('genre')) + 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 (options.defaults) { + if ($util.Long) { + var long = new $util.Long(0, 0, false); + object.singerId = + options.longs === String + ? long.toString() + : options.longs === Number + ? long.toNumber() + : long; + } else object.singerId = options.longs === String ? '0' : 0; + object.birthDate = ''; + object.nationality = ''; + object.genre = options.enums === String ? 'POP' : 0; + } + 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 (message.birthDate !== null && message.hasOwnProperty('birthDate')) + object.birthDate = message.birthDate; + if ( + message.nationality !== null && + message.hasOwnProperty('nationality') + ) + object.nationality = message.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; + 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/samples/resource/singer.proto b/samples/resource/singer.proto new file mode 100644 index 000000000..d4e82bfc7 --- /dev/null +++ b/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 = "proto2"; + +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/samples/system-test/spanner.test.js b/samples/system-test/spanner.test.js index 3d18b8493..8ce55029b 100644 --- a/samples/system-test/spanner.test.js +++ b/samples/system-test/spanner.test.js @@ -67,6 +67,7 @@ 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`; @@ -2052,4 +2053,79 @@ describe('Autogenerated Admin Clients', () => { ); }); }); + + 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')); + }); + }); }); diff --git a/src/codec.ts b/src/codec.ts index 53c643200..1c49d8599 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -30,6 +30,40 @@ export interface Field { value: Value; } +export interface IProtoMessageParams { + // Supports proto message serialized binary data as a `Buffer` or pass a + // message object created using the functions generated by protobufjs-cli. + value: object; + // Fully qualified name of the proto representing the message definition + fullName: string; + /** + * Provide a First Class function that includes nested functions named + * "encode" for serialization and "decode" for deserialization of the proto + * message. The function should be sourced from the JS file generated by + * protobufjs-cli for the proto message. + */ + messageFunction?: Function; +} + +export interface IProtoEnumParams { + // Supports proto enum integer constant or pass the enum string + // the functions generated by protobufjs-cli. + value: string | number; + // Fully qualified name of the proto representing the enum definition + fullName: string; + /** + * An object containing enum string to id mapping. + * @example: { POP: 0, JAZZ: 1, FOLK: 2, ROCK: 3 } + * + * The object should be sourced from the JS file generated by + * protobufjs-cli for the proto message. Additionally, please review the + * sample at {@link www.samples.com} for guidance. + * + * ToDo: Update the link + */ + enumObject?: object; +} + export interface Json { [field: string]: Value; } @@ -246,6 +280,83 @@ export class PGNumeric { } } +/** + * @typedef ProtoMessage + * @see Spanner.protoMessage + */ +export class ProtoMessage { + value: Buffer; + fullName: string; + messageFunction?: Function; + + constructor(protoMessageParams: IProtoMessageParams) { + this.fullName = protoMessageParams.fullName; + this.messageFunction = protoMessageParams.messageFunction; + + if (protoMessageParams.value instanceof Buffer) { + this.value = protoMessageParams.value; + } else if (protoMessageParams.messageFunction) { + this.value = protoMessageParams.messageFunction['encode']( + protoMessageParams.value + ).finish(); + } else { + throw new GoogleError(`protoMessageParams cannot be used to construct + the ProtoMessage. Pass the serialized buffer of the + proto message as the value or provide the message object along with the + corresponding messageFunction generated by protobufjs-cli.`); + } + } + + toJSON(): string { + if (this.messageFunction) { + return this.messageFunction['toObject']( + this.messageFunction['decode'](this.value) + ); + } + return this.value.toString(); + } +} + +/** + * @typedef ProtoEnum + * @see Spanner.protoEnum + */ +export class ProtoEnum { + value: string; + fullName: string; + enumObject?: object; + + constructor(protoEnumParams: IProtoEnumParams) { + this.fullName = protoEnumParams.fullName; + this.enumObject = protoEnumParams.enumObject; + + /** + * @code{IProtoEnumParams} can accept either a number or a string as a value so + * converting to string and checking whether it's numeric using regex. + */ + if (/^\d+$/.test(protoEnumParams.value.toString())) { + this.value = protoEnumParams.value.toString(); + } else if ( + protoEnumParams.enumObject && + protoEnumParams.enumObject[protoEnumParams.value] + ) { + this.value = protoEnumParams.enumObject[protoEnumParams.value]; + } else { + throw new GoogleError(`protoEnumParams cannot be used for constructing the + ProtoEnum. Pass the number as the value or provide the enum string + constant as the value along with the corresponding enumObject generated + by protobufjs-cli.`); + } + } + + toJSON(): string { + if (this.enumObject) { + return Object.getPrototypeOf(this.enumObject)[this.value]; + } + return this.value.toString(); + } +} + /** * @typedef PGJsonb * @see Spanner.pgJsonb @@ -367,6 +478,10 @@ function convertValueToJson(value: Value, options: JSONOptions): Value { return value.map(child => convertValueToJson(child, options)); } + if (value instanceof ProtoMessage || value instanceof ProtoEnum) { + return value.toJSON(); + } + return value; } @@ -377,9 +492,14 @@ function convertValueToJson(value: Value, options: JSONOptions): Value { * * @param {*} value Value to decode * @param {object[]} type Value type object. + * @param columnMetadata Optional parameter to deserialize data * @returns {*} */ -function decode(value: Value, type: spannerClient.spanner.v1.Type): Value { +function decode( + value: Value, + type: spannerClient.spanner.v1.Type, + columnMetadata?: object +): Value { if (is.null(value)) { return null; } @@ -392,6 +512,23 @@ function decode(value: Value, type: spannerClient.spanner.v1.Type): Value { case 'BYTES': decoded = Buffer.from(decoded, 'base64'); break; + case spannerClient.spanner.v1.TypeCode.PROTO: + case 'PROTO': + decoded = Buffer.from(decoded, 'base64'); + decoded = new ProtoMessage({ + value: decoded, + fullName: type.protoTypeFqn, + messageFunction: columnMetadata as Function, + }); + break; + case spannerClient.spanner.v1.TypeCode.ENUM: + case 'ENUM': + decoded = new ProtoEnum({ + value: decoded, + fullName: type.protoTypeFqn, + enumObject: columnMetadata as object, + }); + break; case spannerClient.spanner.v1.TypeCode.FLOAT32: case 'FLOAT32': decoded = new Float32(decoded); @@ -449,7 +586,8 @@ function decode(value: Value, type: spannerClient.spanner.v1.Type): Value { decoded = decoded.map(value => { return decode( value, - type.arrayElementType! as spannerClient.spanner.v1.Type + type.arrayElementType! as spannerClient.spanner.v1.Type, + columnMetadata ); }); break; @@ -458,7 +596,8 @@ function decode(value: Value, type: spannerClient.spanner.v1.Type): Value { fields = type.structType!.fields!.map(({name, type}, index) => { const value = decode( (!Array.isArray(decoded) && decoded[name!]) || decoded[index], - type as spannerClient.spanner.v1.Type + type as spannerClient.spanner.v1.Type, + columnMetadata ); return {name, value}; }); @@ -518,6 +657,14 @@ function encodeValue(value: Value): Value { return value.toString('base64'); } + if (value instanceof ProtoMessage) { + return value.value.toString('base64'); + } + + if (value instanceof ProtoEnum) { + return value.value; + } + if (value instanceof Struct) { return Array.from(value).map(field => encodeValue(field.value)); } @@ -560,6 +707,8 @@ const TypeCode: { bytes: 'BYTES', json: 'JSON', jsonb: 'JSON', + proto: 'PROTO', + enum: 'ENUM', array: 'ARRAY', struct: 'STRUCT', }; @@ -574,6 +723,7 @@ export interface Type { type: string; fields?: FieldType[]; child?: Type; + fullName?: string; } interface FieldType extends Type { @@ -595,6 +745,8 @@ interface FieldType extends Type { * - string * - bytes * - json + * - proto + * - enum * - timestamp * - date * - struct @@ -650,6 +802,14 @@ function getType(value: Value): Type { return {type: 'pgOid'}; } + if (value instanceof ProtoMessage) { + return {type: 'proto', fullName: value.fullName}; + } + + if (value instanceof ProtoEnum) { + return {type: 'enum', fullName: value.fullName}; + } + if (is.boolean(value)) { return {type: 'bool'}; } @@ -776,6 +936,10 @@ function createTypeObject( code, } as spannerClient.spanner.v1.Type; + if (code === 'PROTO' || code === 'ENUM') { + type.protoTypeFqn = config.fullName || ''; + } + if (code === 'ARRAY') { type.arrayElementType = codec.createTypeObject(config.child); } @@ -811,6 +975,8 @@ export const codec = { Numeric, PGNumeric, PGJsonb, + ProtoMessage, + ProtoEnum, PGOid, convertFieldsToJson, decode, diff --git a/src/index.ts b/src/index.ts index 62e3fc23d..71370fce2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,6 +34,10 @@ import { PGJsonb, SpannerDate, Struct, + ProtoMessage, + ProtoEnum, + IProtoMessageParams, + IProtoEnumParams, } from './codec'; import {Backup} from './backup'; import {Database} from './database'; @@ -1772,6 +1776,62 @@ class Spanner extends GrpcService { static pgJsonb(value): PGJsonb { return new codec.PGJsonb(value); } + + /** + * @typedef IProtoMessageParams + * @property {object} value Proto Message value as serialized-buffer or message object. + * @property {string} fullName Fully-qualified path name of proto message. + * @property {Function} [messageFunction] Function generated by protobufs containing + * helper methods for deserializing and serializing messages. + */ + /** + * Helper function to get a Cloud Spanner proto Message object. + * + * @param {IProtoMessageParams} params The proto message value params + * @returns {ProtoMessage} + * + * @example + * ``` + * const {Spanner} = require('@google-cloud/spanner'); + * const protoMessage = Spanner.protoMessage({ + * value: singerInfo, + * messageFunction: music.SingerInfo, + * fullName: "examples.spanner.music.SingerInfo" + * }); + * ``` + */ + static protoMessage(params: IProtoMessageParams): ProtoMessage { + return new codec.ProtoMessage(params); + } + + /** + * @typedef IProtoEnumParams + * @property {string | number} value Proto Enum value as a string constant or + * an integer constant. + * @property {string} fullName Fully-qualified path name of proto enum. + * @property {object} [enumObject] An enum object generated by protobufjs-cli. + */ + /** + * Helper function to get a Cloud Spanner proto enum object. + * + * @param {IProtoEnumParams} params The proto enum value params in the format of + * @code{IProtoEnumParams} + * @returns {ProtoEnum} + * + * @example + * ``` + * const {Spanner} = require('@google-cloud/spanner'); + * const protoEnum = Spanner.protoEnum({ + * value: 'ROCK', + * enumObject: music.Genre, + * fullName: "examples.spanner.music.Genre" + * }); + * ``` + */ + static protoEnum(params: IProtoEnumParams): ProtoEnum { + return new codec.ProtoEnum(params); + } + /** * Helper function to get a Cloud Spanner Struct object. * diff --git a/src/partial-result-stream.ts b/src/partial-result-stream.ts index e03539219..616cdc88b 100644 --- a/src/partial-result-stream.ts +++ b/src/partial-result-stream.ts @@ -50,11 +50,52 @@ interface RequestFunction { * that it is not ready for any more data. Increase this value if you * experience 'Stream is still not ready to receive data' errors as a * result of a slow writer in your receiving stream. + * @property {object} [columnsMetadata] An object map that can be used to pass + * additional properties for each column type which can help in deserializing + * the data coming from backend. (Eg: We need to pass Proto Function and Enum + * map to deserialize proto messages and enums, respectively.) */ export interface RowOptions { json?: boolean; jsonOptions?: JSONOptions; maxResumeRetries?: number; + /** + * An object where column names as keys and custom objects as corresponding + * values for deserialization. It's specifically useful for data types like + * protobuf where deserialization logic is on user-specific code. When provided, + * the custom object enables deserialization of backend-received column data. + * If not provided, data remains serialized as buffer for Proto Messages and + * integer for Proto Enums. + * + * @example + * To obtain Proto Messages and Proto Enums as JSON objects, you must supply + * additional metadata. This metadata should include the protobufjs-cli + * generated proto message function and enum object. It encompasses the essential + * logic for proper data deserialization. + * + * Eg: To read data from Proto Columns in json format using DQL, you should pass + * columnsMetadata where key is the name of the column and value is the protobufjs-cli + * generated proto message function and enum object. + * + * const query = { + * sql: `SELECT SingerId, + * FirstName, + * LastName, + * SingerInfo, + * SingerGenre, + * SingerInfoArray, + * SingerGenreArray + * FROM Singers + * WHERE SingerId = 6`, + * columnsMetadata: { + * SingerInfo: music.SingerInfo, + * SingerInfoArray: music.SingerInfo, + * SingerGenre: music.Genre, + * SingerGenreArray: music.Genre, + * }, + * }; + */ + columnsMetadata?: object; } /** @@ -334,7 +375,15 @@ export class PartialResultStream extends Transform implements ResultEvents { private _createRow(values: Value[]): Row { const fields = values.map((value, index) => { const {name, type} = this._fields[index]; - return {name, value: codec.decode(value, type as google.spanner.v1.Type)}; + const columnMetadata = this._options.columnsMetadata?.[name]; + return { + name, + value: codec.decode( + value, + type as google.spanner.v1.Type, + columnMetadata + ), + }; }); Object.defineProperty(fields, 'toJSON', { diff --git a/src/transaction.ts b/src/transaction.ts index 11eb03657..f7773bd96 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -64,6 +64,43 @@ export interface RequestOptions { jsonOptions?: JSONOptions; gaxOptions?: CallOptions; maxResumeRetries?: number; + /** + * An object where column names as keys and custom objects as corresponding + * values for deserialization. This is only needed for proto columns + * where deserialization logic is on user-specific code. When provided, + * the custom object enables deserialization of backend-received column data. + * If not provided, data remains serialized as buffer for Proto Messages and + * integer for Proto Enums. + * + * @example + * To obtain Proto Messages and Proto Enums as JSON objects, you must supply + * additional metadata. This metadata should include the protobufjs-cli + * generated proto message function and enum object. It encompasses the essential + * logic for proper data deserialization. + * + * Eg: To read data from Proto Columns in json format using DQL, you should pass + * columnsMetadata where key is the name of the column and value is the protobufjs-cli + * generated proto message function and enum object. + * + * const query = { + * sql: `SELECT SingerId, + * FirstName, + * LastName, + * SingerInfo, + * SingerGenre, + * SingerInfoArray, + * SingerGenreArray + * FROM Singers + * WHERE SingerId = 6`, + * columnsMetadata: { + * SingerInfo: music.SingerInfo, + * SingerInfoArray: music.SingerInfo, + * SingerGenre: music.Genre, + * SingerGenreArray: music.Genre, + * }, + * }; + */ + columnsMetadata?: object; } export interface CommitOptions { @@ -583,8 +620,14 @@ export class Snapshot extends EventEmitter { table: string, request = {} as ReadRequest ): PartialResultStream { - const {gaxOptions, json, jsonOptions, maxResumeRetries, requestOptions} = - request; + const { + gaxOptions, + json, + jsonOptions, + maxResumeRetries, + requestOptions, + columnsMetadata, + } = request; const keySet = Snapshot.encodeKeySet(request); const transaction: spannerClient.spanner.v1.ITransactionSelector = {}; @@ -610,6 +653,7 @@ export class Snapshot extends EventEmitter { delete request.ranges; delete request.requestOptions; delete request.directedReadOptions; + delete request.columnsMetadata; const reqOpts: spannerClient.spanner.v1.IReadRequest = Object.assign( request, @@ -654,6 +698,7 @@ export class Snapshot extends EventEmitter { json, jsonOptions, maxResumeRetries, + columnsMetadata, }) ?.on('response', response => { if (response.metadata && response.metadata!.transaction && !this.id) { @@ -1077,8 +1122,14 @@ export class Snapshot extends EventEmitter { query.queryOptions ); - const {gaxOptions, json, jsonOptions, maxResumeRetries, requestOptions} = - query; + const { + gaxOptions, + json, + jsonOptions, + maxResumeRetries, + requestOptions, + columnsMetadata, + } = query; let reqOpts; const directedReadOptions = this._getDirectedReadOptions( @@ -1103,6 +1154,7 @@ export class Snapshot extends EventEmitter { delete query.requestOptions; delete query.types; delete query.directedReadOptions; + delete query.columnsMetadata; reqOpts = Object.assign(query, { session: this.session.formattedName_!, @@ -1152,6 +1204,7 @@ export class Snapshot extends EventEmitter { json, jsonOptions, maxResumeRetries, + columnsMetadata, }) .on('response', response => { if (response.metadata && response.metadata!.transaction && !this.id) { diff --git a/system-test/spanner.ts b/system-test/spanner.ts index 041920a77..2a4df13aa 100644 --- a/system-test/spanner.ts +++ b/system-test/spanner.ts @@ -46,6 +46,11 @@ import {google} from '../protos/protos'; import CreateDatabaseMetadata = google.spanner.admin.database.v1.CreateDatabaseMetadata; import CreateBackupMetadata = google.spanner.admin.database.v1.CreateBackupMetadata; import CreateInstanceConfigMetadata = google.spanner.admin.instance.v1.CreateInstanceConfigMetadata; +const singer = require('../test/data/singer'); +const music = singer.examples.spanner.music; +import {util} from 'protobufjs'; +import Long = util.Long; +const fs = require('fs'); const SKIP_BACKUPS = process.env.SKIP_BACKUPS; const SKIP_FGAC_TESTS = (process.env.SKIP_FGAC_TESTS || 'false').toLowerCase(); @@ -124,15 +129,42 @@ describe('Spanner', () => { `Not creating temp instance, using + ${instance.formattedName_}...` ); } - const [, googleSqlOperation1] = await DATABASE.create({ - schema: ` + + if (IS_EMULATOR_ENABLED) { + const [, googleSqlOperation1] = await DATABASE.create({ + schema: ` CREATE TABLE ${TABLE_NAME} ( SingerId STRING(1024) NOT NULL, Name STRING(1024), ) PRIMARY KEY(SingerId)`, - gaxOptions: GAX_OPTIONS, - }); - await googleSqlOperation1.promise(); + gaxOptions: GAX_OPTIONS, + }); + await googleSqlOperation1.promise(); + } else { + // Reading proto descriptor file + const protoDescriptor = fs + .readFileSync('test/data/descriptors.pb') + .toString('base64'); + + const [, googleSqlOperation1] = await DATABASE.create({ + schema: [ + ` + CREATE PROTO BUNDLE ( + examples.spanner.music.SingerInfo, + examples.spanner.music.Genre, + )`, + ` + CREATE TABLE ${TABLE_NAME} ( + SingerId STRING(1024) NOT NULL, + Name STRING(1024), + ) PRIMARY KEY(SingerId)`, + ], + gaxOptions: GAX_OPTIONS, + protoDescriptors: protoDescriptor, + }); + + await googleSqlOperation1.promise(); + } RESOURCES_TO_CLEAN.push(DATABASE); const [, googleSqlOperation2] = await DATABASE_DROP_PROTECTION.create({ @@ -346,7 +378,20 @@ describe('Spanner', () => { const googleSqlTable = DATABASE.table(TABLE_NAME); const postgreSqlTable = PG_DATABASE.table(TABLE_NAME); - function insert(insertData, dialect, callback) { + /** + * + * @param insertData data to insert + * @param dialect sql dialect + * @param callback + * @param columnsMetadataForRead Optional parameter use for read/query for + * deserializing Proto messages and enum + */ + function insert( + insertData, + dialect, + callback, + columnsMetadataForRead?: {} + ) { const id = generateName('id'); insertData.Key = id; @@ -357,6 +402,7 @@ describe('Spanner', () => { params: { id, }, + columnsMetadata: columnsMetadataForRead, }; let database = DATABASE; if (dialect === Spanner.POSTGRESQL) { @@ -430,6 +476,8 @@ describe('Spanner', () => { NumericValue NUMERIC, StringValue STRING( MAX), TimestampValue TIMESTAMP, + ProtoMessageValue examples.spanner.music.SingerInfo, + ProtoEnumValue examples.spanner.music.Genre, BytesArray ARRAY, BoolArray ARRAY, DateArray ARRAY< DATE >, @@ -439,6 +487,8 @@ describe('Spanner', () => { NumericArray ARRAY< NUMERIC >, StringArray ARRAY, TimestampArray ARRAY< TIMESTAMP >, + ProtoMessageArray ARRAY, + ProtoEnumArray ARRAY, CommitTimestamp TIMESTAMP OPTIONS (allow_commit_timestamp= true) ) PRIMARY KEY (Key) ` @@ -1844,6 +1894,219 @@ describe('Spanner', () => { }); }); + describe('protoMessage', () => { + before(async function () { + if (IS_EMULATOR_ENABLED) { + this.skip(); + } + }); + + const protoMessageParams = { + value: music.SingerInfo.create({ + singerId: new Long(1), + genre: music.Genre.POP, + birthDate: 'January', + nationality: 'Country1', + }), + messageFunction: music.SingerInfo, + fullName: 'examples.spanner.music.SingerInfo', + }; + + it('GOOGLE_STANDARD_SQL should write protoMessage values', done => { + const value = Spanner.protoMessage(protoMessageParams); + insert( + {ProtoMessageValue: value}, + Spanner.GOOGLE_STANDARD_SQL, + (err, row) => { + assert.ifError(err); + assert.deepStrictEqual( + row.toJSON().ProtoMessageValue, + music.SingerInfo.toObject(protoMessageParams.value) + ); + done(); + }, + {ProtoMessageValue: music.SingerInfo} + ); + }); + + it('GOOGLE_STANDARD_SQL should write bytes in the protoMessage column', done => { + const value = music.SingerInfo.encode( + protoMessageParams.value + ).finish(); + insert( + {ProtoMessageValue: value}, + Spanner.GOOGLE_STANDARD_SQL, + (err, row) => { + assert.ifError(err); + assert.deepStrictEqual( + row.toJSON().ProtoMessageValue, + value.toString() + ); + done(); + } + ); + }); + + it('GOOGLE_STANDARD_SQL should write null in the protoMessage column', done => { + insert( + {ProtoMessageValue: null}, + Spanner.GOOGLE_STANDARD_SQL, + (err, row) => { + assert.ifError(err); + assert.equal(row.toJSON().ProtoMessageValue, null); + done(); + } + ); + }); + + it('GOOGLE_STANDARD_SQL should write protoMessageArray', done => { + const value = Spanner.protoMessage(protoMessageParams); + insert( + {ProtoMessageArray: [value]}, + Spanner.GOOGLE_STANDARD_SQL, + (err, row) => { + assert.ifError(err); + assert.deepStrictEqual(row.toJSON().ProtoMessageArray, [ + music.SingerInfo.toObject(protoMessageParams.value), + ]); + done(); + }, + {ProtoMessageArray: music.SingerInfo} + ); + }); + + it('GOOGLE_STANDARD_SQL should write bytes array in the protoMessageArray column', done => { + const value = music.SingerInfo.encode( + protoMessageParams.value + ).finish(); + insert( + {ProtoMessageArray: [value]}, + Spanner.GOOGLE_STANDARD_SQL, + (err, row) => { + assert.ifError(err); + assert.deepStrictEqual(row.toJSON().ProtoMessageArray, [ + value.toString(), + ]); + done(); + } + ); + }); + + it('GOOGLE_STANDARD_SQL should write null in the protoMessageArray column', done => { + insert( + {ProtoMessageArray: null}, + Spanner.GOOGLE_STANDARD_SQL, + (err, row) => { + assert.ifError(err); + assert.equal(row.toJSON().ProtoMessageArray, null); + done(); + } + ); + }); + }); + + describe('protoEnum', () => { + before(async function () { + if (IS_EMULATOR_ENABLED) { + this.skip(); + } + }); + + const enumParams = { + value: music.Genre.JAZZ, + enumObject: music.Genre, + fullName: 'examples.spanner.music.Genre', + }; + + it('GOOGLE_STANDARD_SQL should write protoEnum values', done => { + const value = Spanner.protoEnum(enumParams); + insert( + {ProtoEnumValue: value}, + Spanner.GOOGLE_STANDARD_SQL, + (err, row) => { + assert.ifError(err); + assert.deepStrictEqual( + row.toJSON().ProtoEnumValue, + Object.getPrototypeOf(music.Genre)[enumParams.value] + ); + done(); + }, + {ProtoEnumValue: music.Genre} + ); + }); + + it('GOOGLE_STANDARD_SQL should write int in the protoEnum column', done => { + const value = 2; + insert( + {ProtoEnumValue: value}, + Spanner.GOOGLE_STANDARD_SQL, + (err, row) => { + assert.ifError(err); + assert.deepStrictEqual( + row.toJSON().ProtoEnumValue, + value.toString() + ); + done(); + } + ); + }); + + it('GOOGLE_STANDARD_SQL should write null in the protoEnum column', done => { + insert( + {ProtoEnumValue: null}, + Spanner.GOOGLE_STANDARD_SQL, + (err, row) => { + assert.ifError(err); + assert.equal(row.toJSON().ProtoEnumValue, null); + done(); + } + ); + }); + + it('GOOGLE_STANDARD_SQL should write protoEnumArray', done => { + const value = Spanner.protoEnum(enumParams); + insert( + {ProtoEnumArray: [value]}, + Spanner.GOOGLE_STANDARD_SQL, + (err, row) => { + assert.ifError(err); + assert.deepStrictEqual(row.toJSON().ProtoEnumArray, [ + Object.getPrototypeOf(music.Genre)[enumParams.value], + ]); + done(); + }, + {ProtoEnumArray: music.Genre} + ); + }); + + it('GOOGLE_STANDARD_SQL should write int array in the protoEnumArray column', done => { + const value = 3; + insert( + {ProtoEnumArray: [value]}, + Spanner.GOOGLE_STANDARD_SQL, + (err, row) => { + assert.ifError(err); + assert.deepStrictEqual(row.toJSON().ProtoEnumArray, [ + value.toString(), + ]); + done(); + } + ); + }); + + it('GOOGLE_STANDARD_SQL should write null in the protoEnumArray column', done => { + insert( + {ProtoEnumArray: null}, + Spanner.GOOGLE_STANDARD_SQL, + (err, row) => { + assert.ifError(err); + assert.equal(row.toJSON().ProtoEnumArray, null); + done(); + } + ); + }); + }); + describe('jsonb', () => { before(async function () { if (IS_EMULATOR_ENABLED) { diff --git a/test/codec.ts b/test/codec.ts index 73b9f2132..9a925be86 100644 --- a/test/codec.ts +++ b/test/codec.ts @@ -22,6 +22,12 @@ import {Big} from 'big.js'; import {PreciseDate} from '@google-cloud/precise-date'; import {GrpcService} from '../src/common-grpc/service'; import {google} from '../protos/protos'; +import {GoogleError} from 'google-gax'; +import {util} from 'protobufjs'; +import Long = util.Long; +const singer = require('./data/singer'); +const music = singer.examples.spanner.music; +const is = require('is'); describe('codec', () => { let codec; @@ -302,6 +308,100 @@ describe('codec', () => { }); }); + describe('ProtoMessage', () => { + const protoMessageParams = { + value: music.SingerInfo.create({ + singerId: new Long(1), + genre: music.Genre.POP, + birthDate: 'January', + nationality: 'Country1', + }), + messageFunction: music.SingerInfo, + fullName: 'examples.spanner.music.SingerInfo', + }; + + it('should store value as buffer', () => { + const protoMessage = new codec.ProtoMessage(protoMessageParams); + assert(Buffer.isBuffer(protoMessage.value)); + }); + + it('should throw an error when value is not object and protoMessage is not passed', () => { + assert.throws( + () => { + new codec.ProtoMessage({ + value: { + singerId: 1, + genre: music.Genre.POP, + birthDate: 'January', + }, + fullName: 'examples.spanner.music.SingerInfo', + }); + }, + new GoogleError(`protoMessageParams cannot be used to construct + the ProtoMessage. Pass the serialized buffer of the + proto message as the value or provide the message object along with the + corresponding messageFunction generated by protobufjs-cli.`) + ); + }); + + it('toJSON with messageFunction', () => { + assert.deepEqual( + new codec.ProtoMessage(protoMessageParams).toJSON(), + music.SingerInfo.toObject(protoMessageParams.value) + ); + }); + + it('toJSON without messageFunction', () => { + const message = new codec.ProtoMessage({ + value: music.SingerInfo.encode(protoMessageParams.value).finish(), + fullName: 'examples.spanner.music.SingerInfo', + }); + assert.deepEqual(message.toJSON(), message.value.toString()); + }); + }); + + describe('ProtoEnum', () => { + const enumParams = { + value: music.Genre.JAZZ, + enumObject: music.Genre, + fullName: 'examples.spanner.music.Genre', + }; + + it('should store value as string', () => { + const protoEnum = new codec.ProtoEnum(enumParams); + assert(is.string(protoEnum.value)); + }); + + it('should throw an error when value is non numeric string and enumObject is not passed', () => { + assert.throws( + () => { + new codec.ProtoEnum({ + value: 'POP', + fullName: 'examples.spanner.music.Genre', + }); + }, + new GoogleError(`protoEnumParams cannot be used for constructing the + ProtoEnum. Pass the number as the value or provide the enum string + constant as the value along with the corresponding enumObject generated + by protobufjs-cli.`) + ); + }); + + it('toJSON with enumObject', () => { + assert.deepEqual(new codec.ProtoEnum(enumParams).toJSON(), 'JAZZ'); + }); + + it('toJSON without enumObject', () => { + assert.deepEqual( + new codec.ProtoEnum({ + value: music.Genre.JAZZ, + fullName: 'examples.spanner.music.Genre', + }).toJSON(), + 1 + ); + }); + }); + describe('Struct', () => { describe('toJSON', () => { it('should covert the struct to JSON', () => { @@ -544,6 +644,42 @@ describe('codec', () => { assert.deepStrictEqual(decoded, expected); }); + it('should decode ProtoMessage', () => { + const expected = new codec.ProtoMessage({ + value: music.SingerInfo.create({ + singerId: 1, + genre: music.Genre.POP, + birthDate: 'January', + nationality: 'Country1', + }), + messageFunction: music.SingerInfo, + fullName: 'examples.spanner.music.SingerInfo', + }); + const encoded = expected.value.toString('base64'); + + const decoded = codec.decode( + encoded, + { + code: google.spanner.v1.TypeCode.PROTO, + protoTypeFqn: 'examples.spanner.music.SingerInfo', + }, + music.SingerInfo + ); + + assert.deepStrictEqual(decoded, expected); + }); + + it('should decode ProtoEnum', () => { + const expected = Buffer.from('bytes value'); + const encoded = expected.toString('base64'); + + const decoded = codec.decode(encoded, { + code: google.spanner.v1.TypeCode.BYTES, + }); + + assert.deepStrictEqual(decoded, expected); + }); + it.skip('should decode FLOAT32', () => { const value = 'Infinity'; @@ -804,6 +940,42 @@ describe('codec', () => { assert.strictEqual(encoded, value.toString('base64')); }); + it('should encode ProtoMessage', () => { + const genre = music.Genre.ROCK; + const singerInfo = music.SingerInfo.create({ + singerId: 1, + genre: genre, + birthDate: 'January', + nationality: 'Country1', + }); + + const protoMessage = new codec.ProtoMessage({ + value: singerInfo, + messageFunction: music.SingerInfo, + fullName: 'examples.spanner.music.SingerInfo', + }); + + const encoded = codec.encode(protoMessage); + + assert.strictEqual( + encoded, + music.SingerInfo.encode(singerInfo).finish().toString('base64') + ); + }); + + it('should encode ProtoEnum', () => { + const genre = music.Genre.ROCK; + const protoEnum = new codec.ProtoEnum({ + value: genre, + enumObject: music.Genre, + fullName: 'examples.spanner.music.Genre', + }); + + const encoded = codec.encode(protoEnum); + + assert.strictEqual(encoded, genre.toString()); + }); + it('should encode structs', () => { const value = codec.Struct.fromJSON({a: 'b', c: 'd'}); const encoded = codec.encode(value); diff --git a/test/data/descriptors.pb b/test/data/descriptors.pb new file mode 100644 index 0000000000000000000000000000000000000000..d4c018f3a3c21b18f68820eeab130d8195064e81 GIT binary patch literal 251 zcmd=3!N|o^oSB!NTBKJ{lwXoBB$ir{m|KvOTC7)GkeHVT6wfU!&P-OC&&b6U3|8ow zmzFOi&BY1P7N40S!KlEf!5qW^5%5eAlI7w`$}B3$h)+o@NtIv%%5nyAf<;__0zwL0 z+>> 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'; + if (message.singerId !== null && message.hasOwnProperty('singerId')) + 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')) + if (!$util.isString(message.birthDate)) + return 'birthDate: string expected'; + if ( + message.nationality !== null && + message.hasOwnProperty('nationality') + ) + if (!$util.isString(message.nationality)) + return 'nationality: string expected'; + if (message.genre !== null && message.hasOwnProperty('genre')) + 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 (options.defaults) { + if ($util.Long) { + var long = new $util.Long(0, 0, false); + object.singerId = + options.longs === String + ? long.toString() + : options.longs === Number + ? long.toNumber() + : long; + } else object.singerId = options.longs === String ? '0' : 0; + object.birthDate = ''; + object.nationality = ''; + object.genre = options.enums === String ? 'POP' : 0; + } + 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 (message.birthDate !== null && message.hasOwnProperty('birthDate')) + object.birthDate = message.birthDate; + if ( + message.nationality !== null && + message.hasOwnProperty('nationality') + ) + object.nationality = message.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; + 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/test/data/singer.proto b/test/data/singer.proto new file mode 100644 index 000000000..d4e82bfc7 --- /dev/null +++ b/test/data/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 = "proto2"; + +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/test/index.ts b/test/index.ts index dd2b4b7b9..636bb845e 100644 --- a/test/index.ts +++ b/test/index.ts @@ -38,6 +38,8 @@ import { GetInstancesOptions, } from '../src'; import {CLOUD_RESOURCE_HEADER} from '../src/common'; +const singer = require('./data/singer'); +const music = singer.examples.spanner.music; // Verify that CLOUD_RESOURCE_HEADER is set to a correct value. assert.strictEqual(CLOUD_RESOURCE_HEADER, 'google-cloud-resource-prefix'); @@ -619,6 +621,66 @@ describe('Spanner', () => { }); }); + describe('protoMessage', () => { + it('should create a ProtoMessage instance', () => { + const protoMessageParams = { + value: music.SingerInfo.create({ + singerId: 2, + genre: music.Genre.POP, + birthDate: 'January', + }), + messageFunction: music.SingerInfo, + fullName: 'examples.spanner.music.SingerInfo', + }; + + const customValue = { + value: { + singerId: 2, + genre: music.Genre.POP, + birthDate: 'January', + }, + messageFunction: music.SingerInfo, + fullName: 'examples.spanner.music.SingerInfo', + }; + + fakeCodec.ProtoMessage = class { + constructor(value_) { + assert.strictEqual(value_, protoMessageParams); + return customValue; + } + }; + + const protoMessage = Spanner.protoMessage(protoMessageParams); + assert.strictEqual(protoMessage, customValue); + }); + }); + + describe('protoEnum', () => { + it('should create a ProtoEnum instance', () => { + const enumParams = { + value: music.Genre.JAZZ, + enumObject: music.Genre, + fullName: 'examples.spanner.music.Genre', + }; + + const customValue = { + value: music.Genre.JAZZ, + enumObject: music.Genre, + fullName: 'examples.spanner.music.Genre', + }; + + fakeCodec.ProtoEnum = class { + constructor(value_) { + assert.strictEqual(value_, enumParams); + return customValue; + } + }; + + const protoEnum = Spanner.protoEnum(enumParams); + assert.strictEqual(protoEnum, customValue); + }); + }); + describe('createInstance', () => { const NAME = 'instance-name'; let PATH; diff --git a/test/transaction.ts b/test/transaction.ts index 11a8647e0..392370ab7 100644 --- a/test/transaction.ts +++ b/test/transaction.ts @@ -395,6 +395,7 @@ describe('Transaction', () => { json: true, jsonOptions: {a: 'b'}, maxResumeRetries: 10, + columnsMetadata: {column1: {test: 'ss'}, column2: Function}, }; snapshot.createReadStream(TABLE, fakeOptions); @@ -769,6 +770,7 @@ describe('Transaction', () => { json: true, jsonOptions: {a: 'b'}, maxResumeRetries: 10, + columnsMetadata: {column1: {test: 'ss'}, column2: Function}, }; const fakeQuery = Object.assign({}, QUERY, expectedOptions); From f31d7b205d74d4a783f0d5159dd5b62efe968fe6 Mon Sep 17 00:00:00 2001 From: alkatrivedi <58396306+alkatrivedi@users.noreply.github.com> Date: Fri, 17 May 2024 04:53:59 +0000 Subject: [PATCH 5/8] Fix: drop table statement (#2036) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: integration test fix * fix: drop table * fix(deps): update dependency google-gax to v4.3.2 (#2026) * fix(deps): update dependency google-gax to v4.3.2 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot * feat(spanner): adding `EXPECTED_FULFILLMENT_PERIOD` to the indicate instance creation times (with `FULFILLMENT_PERIOD_NORMAL` or `FULFILLMENT_PERIOD_EXTENDED` ENUM) with the extended instance creation time triggered by On-Demand Capacity Feature (#2024) * feat: add several fields to manage state of database encryption update PiperOrigin-RevId: 619289281 Source-Link: https://github.com/googleapis/googleapis/commit/3a7c33486ca758b180c6d11dd4705fa9a22e8576 Source-Link: https://github.com/googleapis/googleapis-gen/commit/6a8c733062d833d11c5245eda50f5108e0e55324 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiNmE4YzczMzA2MmQ4MzNkMTFjNTI0NWVkYTUwZjUxMDhlMGU1NTMyNCJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * feat: Add SessionPoolOptions, SpannerOptions protos in executor protos PiperOrigin-RevId: 621265883 Source-Link: https://github.com/googleapis/googleapis/commit/fed9845c564d6acf8d03beee69b36666c8bd7fa4 Source-Link: https://github.com/googleapis/googleapis-gen/commit/c66a76957e2e16347bc1dd3f4c638223f065ee80 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiYzY2YTc2OTU3ZTJlMTYzNDdiYzFkZDNmNGM2MzgyMjNmMDY1ZWU4MCJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * feat(spanner): adding `EXPECTED_FULFILLMENT_PERIOD` to the indicate instance creation times (with `FULFILLMENT_PERIOD_NORMAL` or `FULFILLMENT_PERIOD_EXTENDED` ENUM) with the extended instance creation time triggered by On-Demand Capacity Feature PiperOrigin-RevId: 621488048 Source-Link: https://github.com/googleapis/googleapis/commit/0aa0992a5430c211a73c9b861d65e1e8a7a91a9e Source-Link: https://github.com/googleapis/googleapis-gen/commit/b8ad4c73a5c05fed8bcfddb931326996c3441791 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiYjhhZDRjNzNhNWMwNWZlZDhiY2ZkZGI5MzEzMjY5OTZjMzQ0MTc5MSJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot Co-authored-by: danieljbruce * feat: optimisticLock option for getTransaction method (#2028) * feat: add option for optimistic lock in getTransaction method * feat: add option for optimistic lock in getTransaction method * add sample for read and write transaction * add sample for read and write transaction * chore: add integration test for the read/write query samples * fix: presubmit error * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * fix: presubmit error * refactor: sample doc * refactor: sample doc * refator: getTransaction method * fix: lint errors * fix: lint errors * refactor: remove logs * fix: presubmit error * fix: lint errors * test: add a unit test for getTransaction * tests: add integration and unit test for getTransaction options properties * fix: lint errors * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * refactor: naming convention for varaible in getTransaction method * refactor: naming convention for varaible in getTransaction method * fix: lint errors * fix: lint error --------- Co-authored-by: Owl Bot * chore(main): release 7.7.0 (#2027) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> * fix: drop table statement * refactor: delete method for drop statement * Modified delete method to use async await * fix: lint errors * test: unit test for delete method * fix: lint errors * refactor: unit test for drop table * refactor: remove unwanted commented lines * fix: lint errors * refactor: remove unwanted logs and refactor docs of the method * refactor * remove sample file * refactor: unit test for delete method * fix: lint error * refactor: remove unused lines * refactor: remove unused lines * refactor: remove unused lines * fix: lint errors * refactor: remove sample file * refactor * fix: lint errors * fix: lint errors * refactor * fix: lint errors * refactor * refactor * refactor --------- Co-authored-by: surbhigarg92 Co-authored-by: Mend Renovate Co-authored-by: Owl Bot Co-authored-by: gcf-owl-bot[bot] <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Co-authored-by: danieljbruce Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- .gitignore | 1 + src/database.ts | 73 +++++++++++++++++++++++++++++- src/table.ts | 38 +++++++++++++--- test/database.ts | 30 +++++++++++++ test/table.ts | 113 ++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 243 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index d4f03a0df..14050d4e4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ system-test/*key.json .DS_Store package-lock.json __pycache__ +.vscode \ No newline at end of file diff --git a/src/database.ts b/src/database.ts index c35f1557e..8c5967604 100644 --- a/src/database.ts +++ b/src/database.ts @@ -133,6 +133,10 @@ export interface SessionPoolConstructor { ): SessionPoolInterface; } +export type GetDatabaseDialectCallback = NormalCallback< + EnumKey +>; + export interface SetIamPolicyRequest { policy: Policy | null; updateMask?: FieldMask | null; @@ -166,7 +170,15 @@ type IDatabaseTranslatedEnum = Omit< typeof databaseAdmin.spanner.admin.database.v1.Database.State >, 'restoreInfo' -> & {restoreInfo?: IRestoreInfoTranslatedEnum | null}; +> & + Omit< + TranslateEnumKeys< + databaseAdmin.spanner.admin.database.v1.IDatabase, + 'databaseDialect', + typeof databaseAdmin.spanner.admin.database.v1.DatabaseDialect + >, + 'restoreInfo' + > & {restoreInfo?: IRestoreInfoTranslatedEnum | null}; /** * IRestoreInfo structure with restore source type enum translated to string form. @@ -312,6 +324,9 @@ class Database extends common.GrpcServiceObject { resourceHeader_: {[k: string]: string}; request: DatabaseRequest; databaseRole?: string | null; + databaseDialect?: EnumKey< + typeof databaseAdmin.spanner.admin.database.v1.DatabaseDialect + > | null; constructor( instance: Instance, name: string, @@ -1476,6 +1491,61 @@ class Database extends common.GrpcServiceObject { return metadata.state || undefined; } + /** + * Retrieves the dialect of the database + * + * @see {@link #getMetadata} + * + * @method Database#getDatabaseDialect + * + * @param {object} [gaxOptions] Request configuration options, + * See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} + * for more details. + * @param {GetDatabaseDialectCallback} [callback] Callback function. + * @returns {Promise | undefined>} + * When resolved, contains the database dialect of the database if the dialect is defined. + * @example + * const {Spanner} = require('@google-cloud/spanner'); + * const spanner = new Spanner(); + * const instance = spanner.instance('my-instance'); + * const database = instance.database('my-database'); + * const dialect = await database.getDatabaseDialect(); + * const isGoogleSQL = (dialect === 'GOOGLE_STANDARD_SQL'); + * const isPostgreSQL = (dialect === 'POSTGRESQL'); + */ + + getDatabaseDialect( + options?: CallOptions + ): Promise< + | EnumKey + | undefined + >; + getDatabaseDialect(callback: GetDatabaseDialectCallback): void; + getDatabaseDialect( + options: CallOptions, + callback: GetDatabaseDialectCallback + ): void; + async getDatabaseDialect( + optionsOrCallback?: CallOptions | GetDatabaseDialectCallback, + cb?: GetDatabaseDialectCallback + ): Promise< + | EnumKey + | undefined + > { + const gaxOptions = + typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; + + if ( + this.databaseDialect === 'DATABASE_DIALECT_UNSPECIFIED' || + this.databaseDialect === null || + this.databaseDialect === undefined + ) { + const [metadata] = await this.getMetadata(gaxOptions); + this.databaseDialect = metadata.databaseDialect; + } + return this.databaseDialect || undefined; + } + /** * @typedef {array} GetSchemaResponse * @property {string[]} 0 An array of database DDL statements. @@ -3429,6 +3499,7 @@ promisifyAll(Database, { 'batchTransaction', 'getRestoreInfo', 'getState', + 'getDatabaseDialect', 'getOperations', 'runTransaction', 'runTransactionAsync', diff --git a/src/table.ts b/src/table.ts index c737bf96b..7bf85bf4b 100644 --- a/src/table.ts +++ b/src/table.ts @@ -68,6 +68,8 @@ export type UpsertRowsCallback = CommitCallback; export type UpsertRowsResponse = CommitResponse; export type UpsertRowsOptions = MutateRowsOptions; +const GOOGLE_STANDARD_SQL = 'GOOGLE_STANDARD_SQL'; +const POSTGRESQL = 'POSTGRESQL'; /** * Create a Table object to interact with a table in a Cloud Spanner * database. @@ -342,11 +344,37 @@ class Table { const callback = typeof gaxOptionsOrCallback === 'function' ? gaxOptionsOrCallback : cb!; - return this.database.updateSchema( - 'DROP TABLE `' + this.name + '`', - gaxOptions, - callback! - ); + const [schema, table] = this.name.includes('.') + ? this.name.split('.') + : [null, this.name]; + + let dropStatement = 'DROP TABLE `' + table + '`'; + + const performDelete = async (): Promise => { + const result = await this.database.getDatabaseDialect(gaxOptions); + + if (result && result.includes(POSTGRESQL)) { + dropStatement = schema + ? `DROP TABLE "${schema}"."${table}"` + : `DROP TABLE "${table}"`; + } else if (result && result.includes(GOOGLE_STANDARD_SQL)) { + dropStatement = schema + ? 'DROP TABLE `' + schema + '`.`' + table + '`' + : dropStatement; + } + + const updateSchemaResult = callback + ? this.database.updateSchema(dropStatement, gaxOptions, callback) + : this.database.updateSchema(dropStatement, gaxOptions); + + return updateSchemaResult as Promise; + }; + + if (!callback) { + return performDelete() as Promise; + } else { + performDelete(); + } } /** * @typedef {array} DeleteRowsResponse diff --git a/test/database.ts b/test/database.ts index bd697cd9e..1a41f8188 100644 --- a/test/database.ts +++ b/test/database.ts @@ -52,6 +52,7 @@ const fakePfy = extend({}, pfy, { 'batchTransaction', 'getRestoreInfo', 'getState', + 'getDatabaseDialect', 'getOperations', 'runTransaction', 'runTransactionAsync', @@ -2795,6 +2796,35 @@ describe('Database', () => { }); }); + describe('getDatabaseDialect', () => { + it('should get database dialect from database metadata', async () => { + database.getMetadata = async () => [ + {databaseDialect: 'GOOGLE_STANDARD_SQL'}, + ]; + const result = await database.getDatabaseDialect(); + assert.strictEqual(result, 'GOOGLE_STANDARD_SQL'); + }); + + it('should accept and pass gaxOptions to getMetadata', async () => { + const options = {}; + database.getMetadata = async gaxOptions => { + assert.strictEqual(gaxOptions, options); + return [{}]; + }; + await database.getDatabaseDialect(options); + }); + + it('should accept callback and return database dialect', done => { + const databaseDialect = 'GOOGLE_STANDARD_SQL'; + database.getMetadata = async () => [{databaseDialect}]; + database.getDatabaseDialect((err, result) => { + assert.ifError(err); + assert.strictEqual(result, databaseDialect); + done(); + }); + }); + }); + describe('getRestoreInfo', () => { it('should get restore info from database metadata', async () => { const restoreInfo = {sourceType: 'BACKUP'}; diff --git a/test/table.ts b/test/table.ts index c61e8b40f..2886c3287 100644 --- a/test/table.ts +++ b/test/table.ts @@ -67,6 +67,7 @@ describe('Table', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let TableCached: any; let table; + let tableWithSchema; let transaction: FakeTransaction; const DATABASE = { @@ -75,6 +76,7 @@ describe('Table', () => { }; const NAME = 'table-name'; + const NAMEWITHSCHEMA = 'schema.' + NAME; before(() => { Table = proxyquire('../src/table.js', { @@ -86,6 +88,7 @@ describe('Table', () => { beforeEach(() => { extend(Table, TableCached); table = new Table(DATABASE, NAME); + tableWithSchema = new Table(DATABASE, NAMEWITHSCHEMA); transaction = new FakeTransaction(); }); @@ -209,26 +212,124 @@ describe('Table', () => { }); describe('delete', () => { - it('should update the schema on the database', () => { - const updateSchemaReturnValue = {}; + it('should update the schema on the database for GoogleSQL using await', async () => { + table.database = { + getDatabaseDialect: () => { + return 'GOOGLE_STANDARD_SQL'; + }, + updateSchema: schema => { + assert.strictEqual(schema, 'DROP TABLE `table-name`'); + }, + }; + + await table.delete(); + }); + + it('should update the schema on the database for GoogleSQL using callbacks', () => { + function callback() {} + table.database = { + getDatabaseDialect: () => { + return 'GOOGLE_STANDARD_SQL'; + }, + updateSchema: (schema, gaxOptions, callback_) => { + assert.strictEqual(schema, 'DROP TABLE `table-name`'); + assert.strictEqual(callback_, callback); + }, + }; + table.delete(callback); + }); + + it('should update the schema on the database for GoogleSQL with schema in the table name using await', async () => { + tableWithSchema.database = { + getDatabaseDialect: () => { + return 'GOOGLE_STANDARD_SQL'; + }, + updateSchema: schema => { + assert.strictEqual(schema, 'DROP TABLE `schema`.`table-name`'); + }, + }; + + await tableWithSchema.delete(); + }); + + it('should update the schema on the database for GoogleSQL with schema in the table name using callbacks', () => { + function callback() {} + tableWithSchema.database = { + getDatabaseDialect: () => { + return 'GOOGLE_STANDARD_SQL'; + }, + updateSchema: (schema, gaxOptions, callback_) => { + assert.strictEqual(schema, 'DROP TABLE `schema`.`table-name`'); + assert.strictEqual(callback_, callback); + }, + }; + tableWithSchema.delete(callback); + }); + + it('should update the schema on the database for PostgresSQL using await', async () => { + table.database = { + getDatabaseDialect: () => { + return 'POSTGRESQL'; + }, + updateSchema: schema => { + assert.strictEqual(schema, `DROP TABLE "${table.name}"`); + }, + }; + await table.delete(); + }); + + it('should update the schema on the database for PostgresSQL using callbacks', () => { function callback() {} table.database = { + getDatabaseDialect: () => { + return 'POSTGRESQL'; + }, updateSchema: (schema, gaxOptions, callback_) => { - assert.strictEqual(schema, 'DROP TABLE `' + table.name + '`'); + assert.strictEqual(schema, 'DROP TABLE "table-name"'); assert.strictEqual(callback_, callback); - return updateSchemaReturnValue; }, }; - const returnValue = table.delete(callback); - assert.strictEqual(returnValue, updateSchemaReturnValue); + table.delete(callback); + }); + + it('should update the schema on the database for PostgresSQL with schema in the table name using await', async () => { + tableWithSchema.database = { + getDatabaseDialect: () => { + return 'POSTGRESQL'; + }, + updateSchema: schema => { + assert.strictEqual(schema, `DROP TABLE "schema"."${table.name}"`); + }, + }; + + await tableWithSchema.delete(); + }); + + it('should update the schema on the database for PostgresSQL with schema in the table name using callbacks', () => { + function callback() {} + tableWithSchema.database = { + getDatabaseDialect: () => { + return 'POSTGRESQL'; + }, + updateSchema: (schema, gaxOptions, callback_) => { + assert.strictEqual(schema, `DROP TABLE "schema"."${table.name}"`); + assert.strictEqual(callback_, callback); + }, + }; + + tableWithSchema.delete(callback); }); it('should accept and pass gaxOptions to updateSchema', done => { const gaxOptions = {}; table.database = { + getDatabaseDialect: gaxOptionsFromTable => { + assert.strictEqual(gaxOptionsFromTable, gaxOptions); + return 'GOOGLE_STANDARD_SQL'; + }, updateSchema: (schema, gaxOptionsFromTable) => { assert.strictEqual(gaxOptionsFromTable, gaxOptions); done(); From 628f4b02f311ad71c4e53fc06440b003c6acfab9 Mon Sep 17 00:00:00 2001 From: Sri Harsha CH <57220027+harshachinta@users.noreply.github.com> Date: Wed, 22 May 2024 16:43:35 +0530 Subject: [PATCH 6/8] chore(spanner): regenerate proto files (#2050) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(spanner): regenerate proto files * chore(spanner): regenerate proto files * feat(spanner): ignore lint on generated proto files * chore(spanner): add README files * chore(spanner): fix lint * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- samples/resource/README.md | 8 +++ samples/resource/descriptors.pb | Bin 251 -> 345 bytes samples/resource/singer.d.ts | 20 ++++-- samples/resource/singer.js | 118 +++++++++++++++++++++++--------- samples/resource/singer.proto | 2 +- test/data/README.md | 8 +++ test/data/descriptors.pb | Bin 251 -> 345 bytes test/data/singer.d.ts | 20 ++++-- test/data/singer.js | 118 +++++++++++++++++++++++--------- test/data/singer.proto | 2 +- 10 files changed, 222 insertions(+), 74 deletions(-) create mode 100644 samples/resource/README.md create mode 100644 test/data/README.md diff --git a/samples/resource/README.md b/samples/resource/README.md new file mode 100644 index 000000000..980e6a3d7 --- /dev/null +++ b/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/samples/resource/descriptors.pb b/samples/resource/descriptors.pb index d4c018f3a3c21b18f68820eeab130d8195064e81..10193075ede57d49885522496fbcce7ad0d64a21 100644 GIT binary patch delta 228 zcmey(c$0~l>l)KUX7&14j9gs7nR)4{MV@(S`9ca@oW)>Xd}fLSqXMG_vj;;E2Sms- zrGt@CNQsLpDYK{~BR(auBvpb5sD#slF^Cf^<^mJ~sZr(P&Py!G%+E{A$tX9Uy@&}pTq`c7y|%IWJ3!8 delta 149 zcmcb~^qY~H>j&dRX7&2{j9gs7nR)4{MV@(S`9jiMoW)>Xd}fLSqXMG_a}Wnaz%xZi zmWwMXv#2B^J|(dvRe}j9%NfK87I6Uz2q|!J=Ovb8=I15mWR_G)FoWfhg@lZ`SkqJU aic%$5fO33BvU;f#cSth@u}&6bGz9?Dc`6wI diff --git a/samples/resource/singer.d.ts b/samples/resource/singer.d.ts index 5a22b21b5..87d1cfc11 100644 --- a/samples/resource/singer.d.ts +++ b/samples/resource/singer.d.ts @@ -44,16 +44,28 @@ export namespace examples { constructor(properties?: examples.spanner.music.ISingerInfo); /** SingerInfo singerId. */ - public singerId: number | Long; + public singerId?: number | Long | null; /** SingerInfo birthDate. */ - public birthDate: string; + public birthDate?: string | null; /** SingerInfo nationality. */ - public nationality: string; + public nationality?: string | null; /** SingerInfo genre. */ - public genre: examples.spanner.music.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. diff --git a/samples/resource/singer.js b/samples/resource/singer.js index 5db7a0432..203791b41 100644 --- a/samples/resource/singer.js +++ b/samples/resource/singer.js @@ -81,37 +81,82 @@ $root.examples = (function () { /** * SingerInfo singerId. - * @member {number|Long} singerId + * @member {number|Long|null|undefined} singerId * @memberof examples.spanner.music.SingerInfo * @instance */ - SingerInfo.prototype.singerId = $util.Long - ? $util.Long.fromBits(0, 0, false) - : 0; + SingerInfo.prototype.singerId = null; /** * SingerInfo birthDate. - * @member {string} birthDate + * @member {string|null|undefined} birthDate * @memberof examples.spanner.music.SingerInfo * @instance */ - SingerInfo.prototype.birthDate = ''; + SingerInfo.prototype.birthDate = null; /** * SingerInfo nationality. - * @member {string} nationality + * @member {string|null|undefined} nationality * @memberof examples.spanner.music.SingerInfo * @instance */ - SingerInfo.prototype.nationality = ''; + SingerInfo.prototype.nationality = null; /** * SingerInfo genre. - * @member {examples.spanner.music.Genre} genre + * @member {examples.spanner.music.Genre|null|undefined} genre * @memberof examples.spanner.music.SingerInfo * @instance */ - SingerInfo.prototype.genre = 0; + 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. @@ -242,7 +287,9 @@ $root.examples = (function () { SingerInfo.verify = function verify(message) { if (typeof message !== 'object' || message === null) return 'object expected'; - if (message.singerId !== null && message.hasOwnProperty('singerId')) + var properties = {}; + if (message.singerId !== null && message.hasOwnProperty('singerId')) { + properties._singerId = 1; if ( !$util.isInteger(message.singerId) && !( @@ -252,16 +299,25 @@ $root.examples = (function () { ) ) return 'singerId: integer|Long expected'; - if (message.birthDate !== null && message.hasOwnProperty('birthDate')) + } + 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')) + } + if (message.genre !== null && message.hasOwnProperty('genre')) { + properties._genre = 1; switch (message.genre) { default: return 'genre: enum value expected'; @@ -271,6 +327,7 @@ $root.examples = (function () { case 3: break; } + } return null; }; @@ -343,21 +400,7 @@ $root.examples = (function () { SingerInfo.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; - if (options.defaults) { - if ($util.Long) { - var long = new $util.Long(0, 0, false); - object.singerId = - options.longs === String - ? long.toString() - : options.longs === Number - ? long.toNumber() - : long; - } else object.singerId = options.longs === String ? '0' : 0; - object.birthDate = ''; - object.nationality = ''; - object.genre = options.enums === String ? 'POP' : 0; - } - if (message.singerId !== null && message.hasOwnProperty('singerId')) + if (message.singerId !== null && message.hasOwnProperty('singerId')) { if (typeof message.singerId === 'number') object.singerId = options.longs === String @@ -373,14 +416,23 @@ $root.examples = (function () { message.singerId.high >>> 0 ).toNumber() : message.singerId; - if (message.birthDate !== null && message.hasOwnProperty('birthDate')) + 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 (message.genre !== null && message.hasOwnProperty('genre')) + 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] === @@ -388,6 +440,8 @@ $root.examples = (function () { ? message.genre : $root.examples.spanner.music.Genre[message.genre] : message.genre; + if (options.oneofs) object._genre = 'genre'; + } return object; }; diff --git a/samples/resource/singer.proto b/samples/resource/singer.proto index d4e82bfc7..adc79d18c 100644 --- a/samples/resource/singer.proto +++ b/samples/resource/singer.proto @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto2"; +syntax = "proto3"; package examples.spanner.music; diff --git a/test/data/README.md b/test/data/README.md new file mode 100644 index 000000000..6de5c0ae0 --- /dev/null +++ b/test/data/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 test/data +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/test/data/descriptors.pb b/test/data/descriptors.pb index d4c018f3a3c21b18f68820eeab130d8195064e81..10193075ede57d49885522496fbcce7ad0d64a21 100644 GIT binary patch delta 228 zcmey(c$0~l>l)KUX7&14j9gs7nR)4{MV@(S`9ca@oW)>Xd}fLSqXMG_vj;;E2Sms- zrGt@CNQsLpDYK{~BR(auBvpb5sD#slF^Cf^<^mJ~sZr(P&Py!G%+E{A$tX9Uy@&}pTq`c7y|%IWJ3!8 delta 149 zcmcb~^qY~H>j&dRX7&2{j9gs7nR)4{MV@(S`9jiMoW)>Xd}fLSqXMG_a}Wnaz%xZi zmWwMXv#2B^J|(dvRe}j9%NfK87I6Uz2q|!J=Ovb8=I15mWR_G)FoWfhg@lZ`SkqJU aic%$5fO33BvU;f#cSth@u}&6bGz9?Dc`6wI diff --git a/test/data/singer.d.ts b/test/data/singer.d.ts index 5a22b21b5..87d1cfc11 100644 --- a/test/data/singer.d.ts +++ b/test/data/singer.d.ts @@ -44,16 +44,28 @@ export namespace examples { constructor(properties?: examples.spanner.music.ISingerInfo); /** SingerInfo singerId. */ - public singerId: number | Long; + public singerId?: number | Long | null; /** SingerInfo birthDate. */ - public birthDate: string; + public birthDate?: string | null; /** SingerInfo nationality. */ - public nationality: string; + public nationality?: string | null; /** SingerInfo genre. */ - public genre: examples.spanner.music.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. diff --git a/test/data/singer.js b/test/data/singer.js index 5db7a0432..203791b41 100644 --- a/test/data/singer.js +++ b/test/data/singer.js @@ -81,37 +81,82 @@ $root.examples = (function () { /** * SingerInfo singerId. - * @member {number|Long} singerId + * @member {number|Long|null|undefined} singerId * @memberof examples.spanner.music.SingerInfo * @instance */ - SingerInfo.prototype.singerId = $util.Long - ? $util.Long.fromBits(0, 0, false) - : 0; + SingerInfo.prototype.singerId = null; /** * SingerInfo birthDate. - * @member {string} birthDate + * @member {string|null|undefined} birthDate * @memberof examples.spanner.music.SingerInfo * @instance */ - SingerInfo.prototype.birthDate = ''; + SingerInfo.prototype.birthDate = null; /** * SingerInfo nationality. - * @member {string} nationality + * @member {string|null|undefined} nationality * @memberof examples.spanner.music.SingerInfo * @instance */ - SingerInfo.prototype.nationality = ''; + SingerInfo.prototype.nationality = null; /** * SingerInfo genre. - * @member {examples.spanner.music.Genre} genre + * @member {examples.spanner.music.Genre|null|undefined} genre * @memberof examples.spanner.music.SingerInfo * @instance */ - SingerInfo.prototype.genre = 0; + 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. @@ -242,7 +287,9 @@ $root.examples = (function () { SingerInfo.verify = function verify(message) { if (typeof message !== 'object' || message === null) return 'object expected'; - if (message.singerId !== null && message.hasOwnProperty('singerId')) + var properties = {}; + if (message.singerId !== null && message.hasOwnProperty('singerId')) { + properties._singerId = 1; if ( !$util.isInteger(message.singerId) && !( @@ -252,16 +299,25 @@ $root.examples = (function () { ) ) return 'singerId: integer|Long expected'; - if (message.birthDate !== null && message.hasOwnProperty('birthDate')) + } + 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')) + } + if (message.genre !== null && message.hasOwnProperty('genre')) { + properties._genre = 1; switch (message.genre) { default: return 'genre: enum value expected'; @@ -271,6 +327,7 @@ $root.examples = (function () { case 3: break; } + } return null; }; @@ -343,21 +400,7 @@ $root.examples = (function () { SingerInfo.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; - if (options.defaults) { - if ($util.Long) { - var long = new $util.Long(0, 0, false); - object.singerId = - options.longs === String - ? long.toString() - : options.longs === Number - ? long.toNumber() - : long; - } else object.singerId = options.longs === String ? '0' : 0; - object.birthDate = ''; - object.nationality = ''; - object.genre = options.enums === String ? 'POP' : 0; - } - if (message.singerId !== null && message.hasOwnProperty('singerId')) + if (message.singerId !== null && message.hasOwnProperty('singerId')) { if (typeof message.singerId === 'number') object.singerId = options.longs === String @@ -373,14 +416,23 @@ $root.examples = (function () { message.singerId.high >>> 0 ).toNumber() : message.singerId; - if (message.birthDate !== null && message.hasOwnProperty('birthDate')) + 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 (message.genre !== null && message.hasOwnProperty('genre')) + 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] === @@ -388,6 +440,8 @@ $root.examples = (function () { ? message.genre : $root.examples.spanner.music.Genre[message.genre] : message.genre; + if (options.oneofs) object._genre = 'genre'; + } return object; }; diff --git a/test/data/singer.proto b/test/data/singer.proto index d4e82bfc7..adc79d18c 100644 --- a/test/data/singer.proto +++ b/test/data/singer.proto @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto2"; +syntax = "proto3"; package examples.spanner.music; From d95cab5abe50cdb56cbc1d6d935aee29526e1096 Mon Sep 17 00:00:00 2001 From: alkatrivedi <58396306+alkatrivedi@users.noreply.github.com> Date: Fri, 24 May 2024 07:30:22 +0000 Subject: [PATCH 7/8] feat(spanner): add support for change streams transaction exclusion option (#2049) Add support for excluding transactions from being recorded in the change streams by passing a new boolean option ExcludeTxnFromChangeStreams in the various write APIs: `runTransaction` `getTransaction` `runPartitionedUpdate` `_mutate` Note: Samples will be added later in separate prs. --- src/database.ts | 26 +++- src/table.ts | 29 +++-- src/transaction-runner.ts | 4 + src/transaction.ts | 23 ++++ test/database.ts | 21 +++- test/spanner.ts | 253 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 343 insertions(+), 13 deletions(-) diff --git a/src/database.ts b/src/database.ts index 8c5967604..c0b5fd12f 100644 --- a/src/database.ts +++ b/src/database.ts @@ -142,6 +142,10 @@ export interface SetIamPolicyRequest { updateMask?: FieldMask | null; } +export interface RunPartitionedUpdateOptions extends ExecuteSqlRequest { + excludeTxnFromChangeStreams?: boolean; +} + export type UpdateSchemaCallback = ResourceCallback< GaxOperation, databaseAdmin.longrunning.IOperation @@ -2092,6 +2096,9 @@ class Database extends common.GrpcServiceObject { if (options.optimisticLock) { transaction!.useOptimisticLock(); } + if (options.excludeTxnFromChangeStreams) { + transaction!.excludeTxnFromChangeStreams(); + } if (!err) { this._releaseOnEnd(session!, transaction!); } @@ -2711,13 +2718,15 @@ class Database extends common.GrpcServiceObject { * @param {RunUpdateCallback} [callback] Callback function. * @returns {Promise} */ - runPartitionedUpdate(query: string | ExecuteSqlRequest): Promise<[number]>; runPartitionedUpdate( - query: string | ExecuteSqlRequest, + query: string | RunPartitionedUpdateOptions + ): Promise<[number]>; + runPartitionedUpdate( + query: string | RunPartitionedUpdateOptions, callback?: RunUpdateCallback ): void; runPartitionedUpdate( - query: string | ExecuteSqlRequest, + query: string | RunPartitionedUpdateOptions, callback?: RunUpdateCallback ): void | Promise<[number]> { this.pool_.getSession((err, session) => { @@ -2732,11 +2741,14 @@ class Database extends common.GrpcServiceObject { _runPartitionedUpdate( session: Session, - query: string | ExecuteSqlRequest, + query: string | RunPartitionedUpdateOptions, callback?: RunUpdateCallback ): void | Promise { const transaction = session.partitionedDml(); + if (typeof query !== 'string' && query.excludeTxnFromChangeStreams) { + transaction.excludeTxnFromChangeStreams(); + } transaction.begin(err => { if (err) { this.pool_.release(session!); @@ -3059,6 +3071,9 @@ class Database extends common.GrpcServiceObject { if (options.optimisticLock) { transaction!.useOptimisticLock(); } + if (options.excludeTxnFromChangeStreams) { + transaction!.excludeTxnFromChangeStreams(); + } const release = this.pool_.release.bind(this.pool_, session!); const runner = new TransactionRunner( @@ -3173,6 +3188,9 @@ class Database extends common.GrpcServiceObject { if (options.optimisticLock) { transaction.useOptimisticLock(); } + if (options.excludeTxnFromChangeStreams) { + transaction.excludeTxnFromChangeStreams(); + } const runner = new AsyncTransactionRunner( session, transaction, diff --git a/src/table.ts b/src/table.ts index 7bf85bf4b..343d4364b 100644 --- a/src/table.ts +++ b/src/table.ts @@ -46,6 +46,7 @@ export type DropTableCallback = UpdateSchemaCallback; interface MutateRowsOptions extends CommitOptions { requestOptions?: Omit; + excludeTxnFromChangeStreams?: boolean; } export type DeleteRowsCallback = CommitCallback; @@ -1073,15 +1074,27 @@ class Table { ): void { const requestOptions = 'requestOptions' in options ? options.requestOptions : {}; - this.database.runTransaction({requestOptions}, (err, transaction) => { - if (err) { - callback(err); - return; - } - transaction![method](this.name, rows as Key[]); - transaction!.commit(options, callback); - }); + const excludeTxnFromChangeStreams = + 'excludeTxnFromChangeStreams' in options + ? options.excludeTxnFromChangeStreams + : false; + + this.database.runTransaction( + { + requestOptions: requestOptions, + excludeTxnFromChangeStreams: excludeTxnFromChangeStreams, + }, + (err, transaction) => { + if (err) { + callback(err); + return; + } + + transaction![method](this.name, rows as Key[]); + transaction!.commit(options, callback); + } + ); } } diff --git a/src/transaction-runner.ts b/src/transaction-runner.ts index 7f697cd86..61d979e8c 100644 --- a/src/transaction-runner.ts +++ b/src/transaction-runner.ts @@ -45,6 +45,7 @@ export interface RunTransactionOptions { timeout?: number; requestOptions?: Pick; optimisticLock?: boolean; + excludeTxnFromChangeStreams?: boolean; } /** @@ -204,6 +205,9 @@ export abstract class Runner { if (this.options.optimisticLock) { transaction.useOptimisticLock(); } + if (this.options.excludeTxnFromChangeStreams) { + transaction.excludeTxnFromChangeStreams(); + } if (this.attempts > 0) { await transaction.begin(); } diff --git a/src/transaction.ts b/src/transaction.ts index f7773bd96..1231a55bf 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -2470,6 +2470,18 @@ export class Transaction extends Dml { useOptimisticLock(): void { this._options.readWrite!.readLockMode = ReadLockMode.OPTIMISTIC; } + + /** + * Use option excludeTxnFromChangeStreams to exclude read/write transactions + * from being tracked in change streams. + * + * Enabling this options to true will effectively disable change stream tracking + * for a specified transaction, allowing read/write transaction to operate without being + * included in change streams. + */ + excludeTxnFromChangeStreams(): void { + this._options.excludeTxnFromChangeStreams = true; + } } /*! Developer Documentation @@ -2503,6 +2515,17 @@ export class PartitionedDml extends Dml { super(session); this._options = {partitionedDml: options}; } + /** + * Use option excludeTxnFromChangeStreams to exclude partitionedDml + * queries from being tracked in change streams. + * + * Enabling this options to true will effectively disable change stream tracking + * for a specified partitionedDml query, allowing write queries to operate + * without being included in change streams. + */ + excludeTxnFromChangeStreams(): void { + this._options.excludeTxnFromChangeStreams = true; + } /** * Execute a DML statement and get the affected row count. Unlike diff --git a/test/database.ts b/test/database.ts index 1a41f8188..b3cdf1465 100644 --- a/test/database.ts +++ b/test/database.ts @@ -2544,7 +2544,8 @@ describe('Database', () => { const [query] = runUpdateStub.lastCall.args; - assert.strictEqual(query, QUERY); + assert.strictEqual(query.sql, QUERY.sql); + assert.deepStrictEqual(query.params, QUERY.params); assert.ok(fakeCallback.calledOnce); }); @@ -2581,6 +2582,24 @@ describe('Database', () => { assert.ok(fakeCallback.calledOnce); }); + it('should accept excludeTxnFromChangeStreams', () => { + const fakeCallback = sandbox.spy(); + + database.runPartitionedUpdate( + { + excludeTxnFromChangeStream: true, + }, + fakeCallback + ); + + const [query] = runUpdateStub.lastCall.args; + + assert.deepStrictEqual(query, { + excludeTxnFromChangeStream: true, + }); + assert.ok(fakeCallback.calledOnce); + }); + it('should ignore directedReadOptions set for client', () => { const fakeCallback = sandbox.spy(); diff --git a/test/spanner.ts b/test/spanner.ts index 293b8bbd2..e2e715710 100644 --- a/test/spanner.ts +++ b/test/spanner.ts @@ -3133,6 +3133,23 @@ describe('Spanner with mock server', () => { assert.strictEqual(request.requestOptions!.requestTag, 'request-tag'); await database.close(); }); + + it('should use excludeTxnFromChangeStreams', async () => { + const database = newTestDatabase(); + await database.runPartitionedUpdate({ + sql: updateSql, + excludeTxnFromChangeStreams: true, + }); + const beginTxnRequest = spannerMock.getRequests().find(val => { + return (val as v1.BeginTransactionRequest).options + ?.excludeTxnFromChangeStreams; + }) as v1.BeginTransactionRequest; + assert.strictEqual( + beginTxnRequest.options?.excludeTxnFromChangeStreams, + true + ); + await database.close(); + }); }); }); @@ -3243,6 +3260,28 @@ describe('Spanner with mock server', () => { assert.strictEqual(commitRequest.mutations.length, 1); }); + it('should apply blind writes only once with excludeTxnFromChangeStreams option', async () => { + const database = newTestDatabase(); + await database.runTransactionAsync( + { + excludeTxnFromChangeStreams: true, + }, + async tx => { + await tx!.insert('foo', {id: 1, value: 'One'}); + await tx.commit(); + } + ); + await database.close(); + + const beginTxnRequest = spannerMock.getRequests().find(val => { + return (val as v1.BeginTransactionRequest).options?.readWrite; + }) as v1.BeginTransactionRequest; + assert.strictEqual( + beginTxnRequest.options?.excludeTxnFromChangeStreams, + true + ); + }); + it('should use optimistic lock for runTransactionAsync', async () => { const database = newTestDatabase(); await database.runTransactionAsync( @@ -3266,6 +3305,29 @@ describe('Spanner with mock server', () => { ); }); + it('should use exclude transaction from change streams for runTransactionAsync', async () => { + const database = newTestDatabase(); + await database.runTransactionAsync( + { + excludeTxnFromChangeStreams: true, + }, + async tx => { + await tx!.run(selectSql); + await tx.commit(); + } + ); + await database.close(); + + const request = spannerMock.getRequests().find(val => { + return (val as v1.ExecuteSqlRequest).sql; + }) as v1.ExecuteSqlRequest; + assert.ok(request, 'no ExecuteSqlRequest found'); + assert.strictEqual( + request.transaction!.begin?.excludeTxnFromChangeStreams, + true + ); + }); + it('should use optimistic lock for runTransaction', done => { const database = newTestDatabase(); database.runTransaction({optimisticLock: true}, async (err, tx) => { @@ -3286,6 +3348,29 @@ describe('Spanner with mock server', () => { }); }); + it('should use exclude transaction from change stream for runTransaction', done => { + const database = newTestDatabase(); + database.runTransaction( + {excludeTxnFromChangeStreams: true}, + async (err, tx) => { + assert.ifError(err); + await tx!.run(selectSql); + await tx!.commit(); + await database.close(); + + const request = spannerMock.getRequests().find(val => { + return (val as v1.ExecuteSqlRequest).sql; + }) as v1.ExecuteSqlRequest; + assert.ok(request, 'no ExecuteSqlRequest found'); + assert.strictEqual( + request.transaction!.begin!.excludeTxnFromChangeStreams, + true + ); + done(); + } + ); + }); + it('should use optimistic lock and transaction tag for getTransaction', async () => { const database = newTestDatabase(); const promise = await database.getTransaction({ @@ -3422,6 +3507,33 @@ describe('Spanner with mock server', () => { assert.ok(beginTxnRequest, 'beginTransaction was called'); }); + it('should use beginTransaction on retry with excludeTxnFromChangeStreams', async () => { + const database = newTestDatabase(); + let attempts = 0; + await database.runTransactionAsync( + {excludeTxnFromChangeStreams: true}, + async tx => { + await tx!.run(selectSql); + if (!attempts) { + spannerMock.abortTransaction(tx); + } + attempts++; + await tx!.run(insertSql); + await tx.commit(); + } + ); + await database.close(); + + const beginTxnRequest = spannerMock.getRequests().find(val => { + return (val as v1.BeginTransactionRequest).options?.readWrite; + }) as v1.BeginTransactionRequest; + assert.ok(beginTxnRequest, 'beginTransaction was called'); + assert.strictEqual( + beginTxnRequest.options?.excludeTxnFromChangeStreams, + true + ); + }); + it('should use beginTransaction on retry with optimistic lock', async () => { const database = newTestDatabase(); let attempts = 0; @@ -3469,6 +3581,38 @@ describe('Spanner with mock server', () => { assert.ok(beginTxnRequest, 'beginTransaction was called'); }); + it('should use beginTransaction on retry for unknown reason with excludeTxnFromChangeStreams', async () => { + const database = newTestDatabase(); + await database.runTransactionAsync( + { + excludeTxnFromChangeStreams: true, + }, + async tx => { + try { + await tx.runUpdate(invalidSql); + assert.fail('missing expected error'); + } catch (e) { + assert.strictEqual( + (e as ServiceError).message, + `${grpc.status.NOT_FOUND} NOT_FOUND: ${fooNotFoundErr.message}` + ); + } + await tx.run(selectSql); + await tx.commit(); + } + ); + await database.close(); + + const beginTxnRequest = spannerMock.getRequests().find(val => { + return (val as v1.BeginTransactionRequest).options?.readWrite; + }) as v1.BeginTransactionRequest; + assert.ok(beginTxnRequest, 'beginTransaction was called'); + assert.strictEqual( + beginTxnRequest.options?.excludeTxnFromChangeStreams, + true + ); + }); + it('should use beginTransaction for streaming on retry for unknown reason', async () => { const database = newTestDatabase(); await database.runTransactionAsync(async tx => { @@ -3492,6 +3636,38 @@ describe('Spanner with mock server', () => { assert.ok(beginTxnRequest, 'beginTransaction was called'); }); + it('should use beginTransaction for streaming on retry for unknown reason with excludeTxnFromChangeStreams', async () => { + const database = newTestDatabase(); + await database.runTransactionAsync( + { + excludeTxnFromChangeStreams: true, + }, + async tx => { + try { + await getRowCountFromStreamingSql(tx!, {sql: invalidSql}); + assert.fail('missing expected error'); + } catch (e) { + assert.strictEqual( + (e as ServiceError).message, + `${grpc.status.NOT_FOUND} NOT_FOUND: ${fooNotFoundErr.message}` + ); + } + await tx.run(selectSql); + await tx.commit(); + } + ); + await database.close(); + + const beginTxnRequest = spannerMock.getRequests().find(val => { + return (val as v1.BeginTransactionRequest).options?.readWrite; + }) as v1.BeginTransactionRequest; + assert.ok(beginTxnRequest, 'beginTransaction was called'); + assert.strictEqual( + beginTxnRequest.options?.excludeTxnFromChangeStreams, + true + ); + }); + it('should fail if beginTransaction fails', async () => { const database = newTestDatabase(); const err = { @@ -3558,6 +3734,28 @@ describe('Spanner with mock server', () => { assert.ok(beginTxnRequest, 'beginTransaction was called'); }); + it('should run begin transaction on blind commit with excludeTxnFromChangeStreams', async () => { + const database = newTestDatabase(); + await database.runTransactionAsync( + { + excludeTxnFromChangeStreams: true, + }, + async tx => { + tx.insert('foo', {id: 1, name: 'One'}); + await tx.commit(); + } + ); + await database.close(); + + const beginTxnRequest = spannerMock.getRequests().find(val => { + return (val as v1.BeginTransactionRequest).options?.readWrite; + }) as v1.BeginTransactionRequest; + assert.strictEqual( + beginTxnRequest.options?.excludeTxnFromChangeStreams, + true + ); + }); + it('should throw error if begin transaction fails on blind commit', async () => { const database = newTestDatabase(); const err = { @@ -3581,6 +3779,43 @@ describe('Spanner with mock server', () => { await database.close(); } }); + + it('should throw error if begin transaction fails on blind commit with excludeTxnFromChangeStreams', async () => { + const database = newTestDatabase(); + const err = { + message: 'Test error', + } as MockError; + spannerMock.setExecutionTime( + spannerMock.beginTransaction, + SimulatedExecutionTime.ofError(err) + ); + try { + await database.runTransactionAsync( + { + excludeTxnFromChangeStreams: true, + }, + async tx => { + tx.insert('foo', {id: 1, name: 'One'}); + await tx.commit(); + } + ); + } catch (e) { + const beginTxnRequest = spannerMock.getRequests().find(val => { + return (val as v1.BeginTransactionRequest).options?.readWrite; + }) as v1.BeginTransactionRequest; + + assert.strictEqual( + beginTxnRequest.options?.excludeTxnFromChangeStreams, + true + ); + assert.strictEqual( + (e as ServiceError).message, + '2 UNKNOWN: Test error' + ); + } finally { + await database.close(); + } + }); }); describe('table', () => { @@ -3613,6 +3848,24 @@ describe('Spanner with mock server', () => { await database.close(); }); + it('should use excludeTxnFromChangeStreams for mutations', async () => { + const database = newTestDatabase(); + await database.table('foo').upsert( + {id: 1, name: 'bar'}, + { + excludeTxnFromChangeStreams: true, + } + ); + const beginTxnRequest = spannerMock.getRequests().find(val => { + return (val as v1.BeginTransactionRequest).options?.readWrite; + }) as v1.BeginTransactionRequest; + assert.strictEqual( + beginTxnRequest.options?.excludeTxnFromChangeStreams, + true + ); + await database.close(); + }); + it('should encode object to JSON', async () => { const database = newTestDatabase(); await database From b99a8a03ac483993b498aa69f5b5868024b2099a Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 11:08:48 +0530 Subject: [PATCH 8/8] chore(main): release 7.8.0 (#2034) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 16 ++++++++++++++++ package.json | 2 +- samples/package.json | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9c7d82c2..86d3f1ef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ [1]: https://www.npmjs.com/package/nodejs-spanner?activeTab=versions +## [7.8.0](https://github.com/googleapis/nodejs-spanner/compare/v7.7.0...v7.8.0) (2024-05-24) + + +### Features + +* Add `RESOURCE_EXHAUSTED` to the list of retryable error codes ([#2032](https://github.com/googleapis/nodejs-spanner/issues/2032)) ([a4623c5](https://github.com/googleapis/nodejs-spanner/commit/a4623c560c16fa1f37a06cb57a5e47a1d6759d27)) +* Add support for multi region encryption config ([81fa610](https://github.com/googleapis/nodejs-spanner/commit/81fa610895fe709cbb7429896493a67407a6343c)) +* Add support for Proto columns ([#1991](https://github.com/googleapis/nodejs-spanner/issues/1991)) ([ae59c7f](https://github.com/googleapis/nodejs-spanner/commit/ae59c7f957660e08cd5965b5e67694fa1ccc0057)) +* **spanner:** Add support for change streams transaction exclusion option ([#2049](https://github.com/googleapis/nodejs-spanner/issues/2049)) ([d95cab5](https://github.com/googleapis/nodejs-spanner/commit/d95cab5abe50cdb56cbc1d6d935aee29526e1096)) + + +### Bug Fixes + +* **deps:** Update dependency google-gax to v4.3.3 ([#2038](https://github.com/googleapis/nodejs-spanner/issues/2038)) ([d86c1b0](https://github.com/googleapis/nodejs-spanner/commit/d86c1b0c21c7c95e3110221b3ca6ff9ff3b4a088)) +* Drop table statement ([#2036](https://github.com/googleapis/nodejs-spanner/issues/2036)) ([f31d7b2](https://github.com/googleapis/nodejs-spanner/commit/f31d7b205d74d4a783f0d5159dd5b62efe968fe6)) + ## [7.7.0](https://github.com/googleapis/nodejs-spanner/compare/v7.6.0...v7.7.0) (2024-04-17) diff --git a/package.json b/package.json index 40edf4761..5b5963e6f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@google-cloud/spanner", "description": "Cloud Spanner Client Library for Node.js", - "version": "7.7.0", + "version": "7.8.0", "license": "Apache-2.0", "author": "Google Inc.", "engines": { diff --git a/samples/package.json b/samples/package.json index 993669bdb..58c78db82 100644 --- a/samples/package.json +++ b/samples/package.json @@ -17,7 +17,7 @@ "dependencies": { "@google-cloud/kms": "^4.0.0", "@google-cloud/precise-date": "^4.0.0", - "@google-cloud/spanner": "^7.7.0", + "@google-cloud/spanner": "^7.8.0", "yargs": "^17.0.0", "protobufjs": "^7.0.0" },