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/CHANGELOG.md b/CHANGELOG.md index 6208de826..34546cc7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://www.npmjs.com/package/nodejs-spanner?activeTab=versions +## [7.13.0](https://github.com/googleapis/nodejs-spanner/compare/v7.12.0...v7.13.0) (2024-08-09) + + +### Features + +* **spanner:** Add support for Cloud Spanner Incremental Backups ([#2085](https://github.com/googleapis/nodejs-spanner/issues/2085)) ([33b9645](https://github.com/googleapis/nodejs-spanner/commit/33b9645d6096e0d77d30fab6aadf5d92da973a67)) + + +### Bug Fixes + +* Unhandled exception error catch ([#2091](https://github.com/googleapis/nodejs-spanner/issues/2091)) ([e277752](https://github.com/googleapis/nodejs-spanner/commit/e277752fad961908e37e37d88d7b6a61d61a078e)) + ## [7.12.0](https://github.com/googleapis/nodejs-spanner/compare/v7.11.0...v7.12.0) (2024-08-02) diff --git a/package.json b/package.json index cbab89390..cc9293d9b 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.12.0", + "version": "7.13.0", "license": "Apache-2.0", "author": "Google Inc.", "engines": { diff --git a/protos/google/spanner/admin/database/v1/backup.proto b/protos/google/spanner/admin/database/v1/backup.proto index f684a4c60..842ab0ff1 100644 --- a/protos/google/spanner/admin/database/v1/backup.proto +++ b/protos/google/spanner/admin/database/v1/backup.proto @@ -103,6 +103,24 @@ message Backup { // Output only. Size of the backup in bytes. int64 size_bytes = 5 [(google.api.field_behavior) = OUTPUT_ONLY]; + // Output only. The number of bytes that will be freed by deleting this + // backup. This value will be zero if, for example, this backup is part of an + // incremental backup chain and younger backups in the chain require that we + // keep its data. For backups not in an incremental backup chain, this is + // always the size of the backup. This value may change if backups on the same + // chain get created, deleted or expired. + int64 freeable_size_bytes = 15 [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Output only. For a backup in an incremental backup chain, this is the + // storage space needed to keep the data that has changed since the previous + // backup. For all other backups, this is always the size of the backup. This + // value may change if backups on the same chain get deleted or expired. + // + // This field can be used to calculate the total storage space used by a set + // of backups. For example, the total space used by all backups of a database + // can be computed by summing up this field. + int64 exclusive_size_bytes = 16 [(google.api.field_behavior) = OUTPUT_ONLY]; + // Output only. The current state of the backup. State state = 6 [(google.api.field_behavior) = OUTPUT_ONLY]; @@ -168,6 +186,23 @@ message Backup { // single backup schedule URI associated with creating this backup. repeated string backup_schedules = 14 [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Output only. Populated only for backups in an incremental backup chain. + // Backups share the same chain id if and only if they belong to the same + // incremental backup chain. Use this field to determine which backups are + // part of the same incremental backup chain. The ordering of backups in the + // chain can be determined by ordering the backup `version_time`. + string incremental_backup_chain_id = 17 + [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Output only. Data deleted at a time older than this is guaranteed not to be + // retained in order to support this backup. For a backup in an incremental + // backup chain, this is the version time of the oldest backup that exists or + // ever existed in the chain. For all other backups, this is the version time + // of the backup. This field can be used to understand what data is being + // retained by the backup system. + google.protobuf.Timestamp oldest_version_time = 18 + [(google.api.field_behavior) = OUTPUT_ONLY]; } // The request for @@ -705,3 +740,11 @@ message CopyBackupEncryptionConfig { // A full backup stores the entire contents of the database at a given // version time. message FullBackupSpec {} + +// The specification for incremental backup chains. +// An incremental backup stores the delta of changes between a previous +// backup and the database contents at a given version time. An +// incremental backup chain consists of a full backup and zero or more +// successive incremental backups. The first backup created for an +// incremental backup chain is always a full backup. +message IncrementalBackupSpec {} diff --git a/protos/google/spanner/admin/database/v1/backup_schedule.proto b/protos/google/spanner/admin/database/v1/backup_schedule.proto index 9ef4587f8..c9b5e7e3f 100644 --- a/protos/google/spanner/admin/database/v1/backup_schedule.proto +++ b/protos/google/spanner/admin/database/v1/backup_schedule.proto @@ -83,6 +83,9 @@ message BackupSchedule { oneof backup_type_spec { // The schedule creates only full backups. FullBackupSpec full_backup_spec = 7; + + // The schedule creates incremental backup chains. + IncrementalBackupSpec incremental_backup_spec = 8; } // Output only. The timestamp at which the schedule was last updated. diff --git a/protos/protos.d.ts b/protos/protos.d.ts index faebebfe6..f91afd2af 100644 --- a/protos/protos.d.ts +++ b/protos/protos.d.ts @@ -6549,6 +6549,12 @@ export namespace google { /** Backup sizeBytes */ sizeBytes?: (number|Long|string|null); + /** Backup freeableSizeBytes */ + freeableSizeBytes?: (number|Long|string|null); + + /** Backup exclusiveSizeBytes */ + exclusiveSizeBytes?: (number|Long|string|null); + /** Backup state */ state?: (google.spanner.admin.database.v1.Backup.State|keyof typeof google.spanner.admin.database.v1.Backup.State|null); @@ -6572,6 +6578,12 @@ export namespace google { /** Backup backupSchedules */ backupSchedules?: (string[]|null); + + /** Backup incrementalBackupChainId */ + incrementalBackupChainId?: (string|null); + + /** Backup oldestVersionTime */ + oldestVersionTime?: (google.protobuf.ITimestamp|null); } /** Represents a Backup. */ @@ -6601,6 +6613,12 @@ export namespace google { /** Backup sizeBytes. */ public sizeBytes: (number|Long|string); + /** Backup freeableSizeBytes. */ + public freeableSizeBytes: (number|Long|string); + + /** Backup exclusiveSizeBytes. */ + public exclusiveSizeBytes: (number|Long|string); + /** Backup state. */ public state: (google.spanner.admin.database.v1.Backup.State|keyof typeof google.spanner.admin.database.v1.Backup.State); @@ -6625,6 +6643,12 @@ export namespace google { /** Backup backupSchedules. */ public backupSchedules: string[]; + /** Backup incrementalBackupChainId. */ + public incrementalBackupChainId: string; + + /** Backup oldestVersionTime. */ + public oldestVersionTime?: (google.protobuf.ITimestamp|null); + /** * Creates a new Backup instance using the specified properties. * @param [properties] Properties to set @@ -8358,6 +8382,97 @@ export namespace google { public static getTypeUrl(typeUrlPrefix?: string): string; } + /** Properties of an IncrementalBackupSpec. */ + interface IIncrementalBackupSpec { + } + + /** Represents an IncrementalBackupSpec. */ + class IncrementalBackupSpec implements IIncrementalBackupSpec { + + /** + * Constructs a new IncrementalBackupSpec. + * @param [properties] Properties to set + */ + constructor(properties?: google.spanner.admin.database.v1.IIncrementalBackupSpec); + + /** + * Creates a new IncrementalBackupSpec instance using the specified properties. + * @param [properties] Properties to set + * @returns IncrementalBackupSpec instance + */ + public static create(properties?: google.spanner.admin.database.v1.IIncrementalBackupSpec): google.spanner.admin.database.v1.IncrementalBackupSpec; + + /** + * Encodes the specified IncrementalBackupSpec message. Does not implicitly {@link google.spanner.admin.database.v1.IncrementalBackupSpec.verify|verify} messages. + * @param message IncrementalBackupSpec message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: google.spanner.admin.database.v1.IIncrementalBackupSpec, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified IncrementalBackupSpec message, length delimited. Does not implicitly {@link google.spanner.admin.database.v1.IncrementalBackupSpec.verify|verify} messages. + * @param message IncrementalBackupSpec message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: google.spanner.admin.database.v1.IIncrementalBackupSpec, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an IncrementalBackupSpec message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns IncrementalBackupSpec + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): google.spanner.admin.database.v1.IncrementalBackupSpec; + + /** + * Decodes an IncrementalBackupSpec message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns IncrementalBackupSpec + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): google.spanner.admin.database.v1.IncrementalBackupSpec; + + /** + * Verifies an IncrementalBackupSpec message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates an IncrementalBackupSpec message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns IncrementalBackupSpec + */ + public static fromObject(object: { [k: string]: any }): google.spanner.admin.database.v1.IncrementalBackupSpec; + + /** + * Creates a plain object from an IncrementalBackupSpec message. Also converts values to other types if specified. + * @param message IncrementalBackupSpec + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: google.spanner.admin.database.v1.IncrementalBackupSpec, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this IncrementalBackupSpec to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for IncrementalBackupSpec + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + /** Properties of an OperationProgress. */ interface IOperationProgress { @@ -8814,6 +8929,9 @@ export namespace google { /** BackupSchedule fullBackupSpec */ fullBackupSpec?: (google.spanner.admin.database.v1.IFullBackupSpec|null); + /** BackupSchedule incrementalBackupSpec */ + incrementalBackupSpec?: (google.spanner.admin.database.v1.IIncrementalBackupSpec|null); + /** BackupSchedule updateTime */ updateTime?: (google.protobuf.ITimestamp|null); } @@ -8842,11 +8960,14 @@ export namespace google { /** BackupSchedule fullBackupSpec. */ public fullBackupSpec?: (google.spanner.admin.database.v1.IFullBackupSpec|null); + /** BackupSchedule incrementalBackupSpec. */ + public incrementalBackupSpec?: (google.spanner.admin.database.v1.IIncrementalBackupSpec|null); + /** BackupSchedule updateTime. */ public updateTime?: (google.protobuf.ITimestamp|null); /** BackupSchedule backupTypeSpec. */ - public backupTypeSpec?: "fullBackupSpec"; + public backupTypeSpec?: ("fullBackupSpec"|"incrementalBackupSpec"); /** * Creates a new BackupSchedule instance using the specified properties. diff --git a/protos/protos.js b/protos/protos.js index 320809a01..e32b07c36 100644 --- a/protos/protos.js +++ b/protos/protos.js @@ -17695,6 +17695,8 @@ * @property {string|null} [name] Backup name * @property {google.protobuf.ITimestamp|null} [createTime] Backup createTime * @property {number|Long|null} [sizeBytes] Backup sizeBytes + * @property {number|Long|null} [freeableSizeBytes] Backup freeableSizeBytes + * @property {number|Long|null} [exclusiveSizeBytes] Backup exclusiveSizeBytes * @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 @@ -17703,6 +17705,8 @@ * @property {Array.|null} [referencingBackups] Backup referencingBackups * @property {google.protobuf.ITimestamp|null} [maxExpireTime] Backup maxExpireTime * @property {Array.|null} [backupSchedules] Backup backupSchedules + * @property {string|null} [incrementalBackupChainId] Backup incrementalBackupChainId + * @property {google.protobuf.ITimestamp|null} [oldestVersionTime] Backup oldestVersionTime */ /** @@ -17772,6 +17776,22 @@ */ Backup.prototype.sizeBytes = $util.Long ? $util.Long.fromBits(0,0,false) : 0; + /** + * Backup freeableSizeBytes. + * @member {number|Long} freeableSizeBytes + * @memberof google.spanner.admin.database.v1.Backup + * @instance + */ + Backup.prototype.freeableSizeBytes = $util.Long ? $util.Long.fromBits(0,0,false) : 0; + + /** + * Backup exclusiveSizeBytes. + * @member {number|Long} exclusiveSizeBytes + * @memberof google.spanner.admin.database.v1.Backup + * @instance + */ + Backup.prototype.exclusiveSizeBytes = $util.Long ? $util.Long.fromBits(0,0,false) : 0; + /** * Backup state. * @member {google.spanner.admin.database.v1.Backup.State} state @@ -17836,6 +17856,22 @@ */ Backup.prototype.backupSchedules = $util.emptyArray; + /** + * Backup incrementalBackupChainId. + * @member {string} incrementalBackupChainId + * @memberof google.spanner.admin.database.v1.Backup + * @instance + */ + Backup.prototype.incrementalBackupChainId = ""; + + /** + * Backup oldestVersionTime. + * @member {google.protobuf.ITimestamp|null|undefined} oldestVersionTime + * @memberof google.spanner.admin.database.v1.Backup + * @instance + */ + Backup.prototype.oldestVersionTime = null; + /** * Creates a new Backup instance using the specified properties. * @function create @@ -17892,6 +17928,14 @@ if (message.backupSchedules != null && message.backupSchedules.length) for (var i = 0; i < message.backupSchedules.length; ++i) writer.uint32(/* id 14, wireType 2 =*/114).string(message.backupSchedules[i]); + if (message.freeableSizeBytes != null && Object.hasOwnProperty.call(message, "freeableSizeBytes")) + writer.uint32(/* id 15, wireType 0 =*/120).int64(message.freeableSizeBytes); + if (message.exclusiveSizeBytes != null && Object.hasOwnProperty.call(message, "exclusiveSizeBytes")) + writer.uint32(/* id 16, wireType 0 =*/128).int64(message.exclusiveSizeBytes); + if (message.incrementalBackupChainId != null && Object.hasOwnProperty.call(message, "incrementalBackupChainId")) + writer.uint32(/* id 17, wireType 2 =*/138).string(message.incrementalBackupChainId); + if (message.oldestVersionTime != null && Object.hasOwnProperty.call(message, "oldestVersionTime")) + $root.google.protobuf.Timestamp.encode(message.oldestVersionTime, writer.uint32(/* id 18, wireType 2 =*/146).fork()).ldelim(); return writer; }; @@ -17950,6 +17994,14 @@ message.sizeBytes = reader.int64(); break; } + case 15: { + message.freeableSizeBytes = reader.int64(); + break; + } + case 16: { + message.exclusiveSizeBytes = reader.int64(); + break; + } case 6: { message.state = reader.int32(); break; @@ -17990,6 +18042,14 @@ message.backupSchedules.push(reader.string()); break; } + case 17: { + message.incrementalBackupChainId = reader.string(); + break; + } + case 18: { + message.oldestVersionTime = $root.google.protobuf.Timestamp.decode(reader, reader.uint32()); + break; + } default: reader.skipType(tag & 7); break; @@ -18049,6 +18109,12 @@ if (message.sizeBytes != null && message.hasOwnProperty("sizeBytes")) if (!$util.isInteger(message.sizeBytes) && !(message.sizeBytes && $util.isInteger(message.sizeBytes.low) && $util.isInteger(message.sizeBytes.high))) return "sizeBytes: integer|Long expected"; + if (message.freeableSizeBytes != null && message.hasOwnProperty("freeableSizeBytes")) + if (!$util.isInteger(message.freeableSizeBytes) && !(message.freeableSizeBytes && $util.isInteger(message.freeableSizeBytes.low) && $util.isInteger(message.freeableSizeBytes.high))) + return "freeableSizeBytes: integer|Long expected"; + if (message.exclusiveSizeBytes != null && message.hasOwnProperty("exclusiveSizeBytes")) + if (!$util.isInteger(message.exclusiveSizeBytes) && !(message.exclusiveSizeBytes && $util.isInteger(message.exclusiveSizeBytes.low) && $util.isInteger(message.exclusiveSizeBytes.high))) + return "exclusiveSizeBytes: integer|Long expected"; if (message.state != null && message.hasOwnProperty("state")) switch (message.state) { default: @@ -18107,6 +18173,14 @@ if (!$util.isString(message.backupSchedules[i])) return "backupSchedules: string[] expected"; } + if (message.incrementalBackupChainId != null && message.hasOwnProperty("incrementalBackupChainId")) + if (!$util.isString(message.incrementalBackupChainId)) + return "incrementalBackupChainId: string expected"; + if (message.oldestVersionTime != null && message.hasOwnProperty("oldestVersionTime")) { + var error = $root.google.protobuf.Timestamp.verify(message.oldestVersionTime); + if (error) + return "oldestVersionTime." + error; + } return null; }; @@ -18150,6 +18224,24 @@ message.sizeBytes = object.sizeBytes; else if (typeof object.sizeBytes === "object") message.sizeBytes = new $util.LongBits(object.sizeBytes.low >>> 0, object.sizeBytes.high >>> 0).toNumber(); + if (object.freeableSizeBytes != null) + if ($util.Long) + (message.freeableSizeBytes = $util.Long.fromValue(object.freeableSizeBytes)).unsigned = false; + else if (typeof object.freeableSizeBytes === "string") + message.freeableSizeBytes = parseInt(object.freeableSizeBytes, 10); + else if (typeof object.freeableSizeBytes === "number") + message.freeableSizeBytes = object.freeableSizeBytes; + else if (typeof object.freeableSizeBytes === "object") + message.freeableSizeBytes = new $util.LongBits(object.freeableSizeBytes.low >>> 0, object.freeableSizeBytes.high >>> 0).toNumber(); + if (object.exclusiveSizeBytes != null) + if ($util.Long) + (message.exclusiveSizeBytes = $util.Long.fromValue(object.exclusiveSizeBytes)).unsigned = false; + else if (typeof object.exclusiveSizeBytes === "string") + message.exclusiveSizeBytes = parseInt(object.exclusiveSizeBytes, 10); + else if (typeof object.exclusiveSizeBytes === "number") + message.exclusiveSizeBytes = object.exclusiveSizeBytes; + else if (typeof object.exclusiveSizeBytes === "object") + message.exclusiveSizeBytes = new $util.LongBits(object.exclusiveSizeBytes.low >>> 0, object.exclusiveSizeBytes.high >>> 0).toNumber(); switch (object.state) { default: if (typeof object.state === "number") { @@ -18231,6 +18323,13 @@ for (var i = 0; i < object.backupSchedules.length; ++i) message.backupSchedules[i] = String(object.backupSchedules[i]); } + if (object.incrementalBackupChainId != null) + message.incrementalBackupChainId = String(object.incrementalBackupChainId); + if (object.oldestVersionTime != null) { + if (typeof object.oldestVersionTime !== "object") + throw TypeError(".google.spanner.admin.database.v1.Backup.oldestVersionTime: object expected"); + message.oldestVersionTime = $root.google.protobuf.Timestamp.fromObject(object.oldestVersionTime); + } return message; }; @@ -18268,6 +18367,18 @@ object.versionTime = null; object.databaseDialect = options.enums === String ? "DATABASE_DIALECT_UNSPECIFIED" : 0; object.maxExpireTime = null; + if ($util.Long) { + var long = new $util.Long(0, 0, false); + object.freeableSizeBytes = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; + } else + object.freeableSizeBytes = options.longs === String ? "0" : 0; + if ($util.Long) { + var long = new $util.Long(0, 0, false); + object.exclusiveSizeBytes = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; + } else + object.exclusiveSizeBytes = options.longs === String ? "0" : 0; + object.incrementalBackupChainId = ""; + object.oldestVersionTime = null; } if (message.name != null && message.hasOwnProperty("name")) object.name = message.name; @@ -18312,6 +18423,20 @@ for (var j = 0; j < message.backupSchedules.length; ++j) object.backupSchedules[j] = message.backupSchedules[j]; } + if (message.freeableSizeBytes != null && message.hasOwnProperty("freeableSizeBytes")) + if (typeof message.freeableSizeBytes === "number") + object.freeableSizeBytes = options.longs === String ? String(message.freeableSizeBytes) : message.freeableSizeBytes; + else + object.freeableSizeBytes = options.longs === String ? $util.Long.prototype.toString.call(message.freeableSizeBytes) : options.longs === Number ? new $util.LongBits(message.freeableSizeBytes.low >>> 0, message.freeableSizeBytes.high >>> 0).toNumber() : message.freeableSizeBytes; + if (message.exclusiveSizeBytes != null && message.hasOwnProperty("exclusiveSizeBytes")) + if (typeof message.exclusiveSizeBytes === "number") + object.exclusiveSizeBytes = options.longs === String ? String(message.exclusiveSizeBytes) : message.exclusiveSizeBytes; + else + object.exclusiveSizeBytes = options.longs === String ? $util.Long.prototype.toString.call(message.exclusiveSizeBytes) : options.longs === Number ? new $util.LongBits(message.exclusiveSizeBytes.low >>> 0, message.exclusiveSizeBytes.high >>> 0).toNumber() : message.exclusiveSizeBytes; + if (message.incrementalBackupChainId != null && message.hasOwnProperty("incrementalBackupChainId")) + object.incrementalBackupChainId = message.incrementalBackupChainId; + if (message.oldestVersionTime != null && message.hasOwnProperty("oldestVersionTime")) + object.oldestVersionTime = $root.google.protobuf.Timestamp.toObject(message.oldestVersionTime, options); return object; }; @@ -22286,6 +22411,181 @@ return FullBackupSpec; })(); + v1.IncrementalBackupSpec = (function() { + + /** + * Properties of an IncrementalBackupSpec. + * @memberof google.spanner.admin.database.v1 + * @interface IIncrementalBackupSpec + */ + + /** + * Constructs a new IncrementalBackupSpec. + * @memberof google.spanner.admin.database.v1 + * @classdesc Represents an IncrementalBackupSpec. + * @implements IIncrementalBackupSpec + * @constructor + * @param {google.spanner.admin.database.v1.IIncrementalBackupSpec=} [properties] Properties to set + */ + function IncrementalBackupSpec(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Creates a new IncrementalBackupSpec instance using the specified properties. + * @function create + * @memberof google.spanner.admin.database.v1.IncrementalBackupSpec + * @static + * @param {google.spanner.admin.database.v1.IIncrementalBackupSpec=} [properties] Properties to set + * @returns {google.spanner.admin.database.v1.IncrementalBackupSpec} IncrementalBackupSpec instance + */ + IncrementalBackupSpec.create = function create(properties) { + return new IncrementalBackupSpec(properties); + }; + + /** + * Encodes the specified IncrementalBackupSpec message. Does not implicitly {@link google.spanner.admin.database.v1.IncrementalBackupSpec.verify|verify} messages. + * @function encode + * @memberof google.spanner.admin.database.v1.IncrementalBackupSpec + * @static + * @param {google.spanner.admin.database.v1.IIncrementalBackupSpec} message IncrementalBackupSpec message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + IncrementalBackupSpec.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + return writer; + }; + + /** + * Encodes the specified IncrementalBackupSpec message, length delimited. Does not implicitly {@link google.spanner.admin.database.v1.IncrementalBackupSpec.verify|verify} messages. + * @function encodeDelimited + * @memberof google.spanner.admin.database.v1.IncrementalBackupSpec + * @static + * @param {google.spanner.admin.database.v1.IIncrementalBackupSpec} message IncrementalBackupSpec message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + IncrementalBackupSpec.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes an IncrementalBackupSpec message from the specified reader or buffer. + * @function decode + * @memberof google.spanner.admin.database.v1.IncrementalBackupSpec + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {google.spanner.admin.database.v1.IncrementalBackupSpec} IncrementalBackupSpec + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + IncrementalBackupSpec.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.google.spanner.admin.database.v1.IncrementalBackupSpec(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes an IncrementalBackupSpec message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof google.spanner.admin.database.v1.IncrementalBackupSpec + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {google.spanner.admin.database.v1.IncrementalBackupSpec} IncrementalBackupSpec + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + IncrementalBackupSpec.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies an IncrementalBackupSpec message. + * @function verify + * @memberof google.spanner.admin.database.v1.IncrementalBackupSpec + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + IncrementalBackupSpec.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + return null; + }; + + /** + * Creates an IncrementalBackupSpec message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof google.spanner.admin.database.v1.IncrementalBackupSpec + * @static + * @param {Object.} object Plain object + * @returns {google.spanner.admin.database.v1.IncrementalBackupSpec} IncrementalBackupSpec + */ + IncrementalBackupSpec.fromObject = function fromObject(object) { + if (object instanceof $root.google.spanner.admin.database.v1.IncrementalBackupSpec) + return object; + return new $root.google.spanner.admin.database.v1.IncrementalBackupSpec(); + }; + + /** + * Creates a plain object from an IncrementalBackupSpec message. Also converts values to other types if specified. + * @function toObject + * @memberof google.spanner.admin.database.v1.IncrementalBackupSpec + * @static + * @param {google.spanner.admin.database.v1.IncrementalBackupSpec} message IncrementalBackupSpec + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + IncrementalBackupSpec.toObject = function toObject() { + return {}; + }; + + /** + * Converts this IncrementalBackupSpec to JSON. + * @function toJSON + * @memberof google.spanner.admin.database.v1.IncrementalBackupSpec + * @instance + * @returns {Object.} JSON object + */ + IncrementalBackupSpec.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for IncrementalBackupSpec + * @function getTypeUrl + * @memberof google.spanner.admin.database.v1.IncrementalBackupSpec + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + IncrementalBackupSpec.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/google.spanner.admin.database.v1.IncrementalBackupSpec"; + }; + + return IncrementalBackupSpec; + })(); + v1.OperationProgress = (function() { /** @@ -23338,6 +23638,7 @@ * @property {google.protobuf.IDuration|null} [retentionDuration] BackupSchedule retentionDuration * @property {google.spanner.admin.database.v1.ICreateBackupEncryptionConfig|null} [encryptionConfig] BackupSchedule encryptionConfig * @property {google.spanner.admin.database.v1.IFullBackupSpec|null} [fullBackupSpec] BackupSchedule fullBackupSpec + * @property {google.spanner.admin.database.v1.IIncrementalBackupSpec|null} [incrementalBackupSpec] BackupSchedule incrementalBackupSpec * @property {google.protobuf.ITimestamp|null} [updateTime] BackupSchedule updateTime */ @@ -23396,6 +23697,14 @@ */ BackupSchedule.prototype.fullBackupSpec = null; + /** + * BackupSchedule incrementalBackupSpec. + * @member {google.spanner.admin.database.v1.IIncrementalBackupSpec|null|undefined} incrementalBackupSpec + * @memberof google.spanner.admin.database.v1.BackupSchedule + * @instance + */ + BackupSchedule.prototype.incrementalBackupSpec = null; + /** * BackupSchedule updateTime. * @member {google.protobuf.ITimestamp|null|undefined} updateTime @@ -23409,12 +23718,12 @@ /** * BackupSchedule backupTypeSpec. - * @member {"fullBackupSpec"|undefined} backupTypeSpec + * @member {"fullBackupSpec"|"incrementalBackupSpec"|undefined} backupTypeSpec * @memberof google.spanner.admin.database.v1.BackupSchedule * @instance */ Object.defineProperty(BackupSchedule.prototype, "backupTypeSpec", { - get: $util.oneOfGetter($oneOfFields = ["fullBackupSpec"]), + get: $util.oneOfGetter($oneOfFields = ["fullBackupSpec", "incrementalBackupSpec"]), set: $util.oneOfSetter($oneOfFields) }); @@ -23452,6 +23761,8 @@ $root.google.spanner.admin.database.v1.BackupScheduleSpec.encode(message.spec, writer.uint32(/* id 6, wireType 2 =*/50).fork()).ldelim(); if (message.fullBackupSpec != null && Object.hasOwnProperty.call(message, "fullBackupSpec")) $root.google.spanner.admin.database.v1.FullBackupSpec.encode(message.fullBackupSpec, writer.uint32(/* id 7, wireType 2 =*/58).fork()).ldelim(); + if (message.incrementalBackupSpec != null && Object.hasOwnProperty.call(message, "incrementalBackupSpec")) + $root.google.spanner.admin.database.v1.IncrementalBackupSpec.encode(message.incrementalBackupSpec, writer.uint32(/* id 8, wireType 2 =*/66).fork()).ldelim(); if (message.updateTime != null && Object.hasOwnProperty.call(message, "updateTime")) $root.google.protobuf.Timestamp.encode(message.updateTime, writer.uint32(/* id 9, wireType 2 =*/74).fork()).ldelim(); return writer; @@ -23508,6 +23819,10 @@ message.fullBackupSpec = $root.google.spanner.admin.database.v1.FullBackupSpec.decode(reader, reader.uint32()); break; } + case 8: { + message.incrementalBackupSpec = $root.google.spanner.admin.database.v1.IncrementalBackupSpec.decode(reader, reader.uint32()); + break; + } case 9: { message.updateTime = $root.google.protobuf.Timestamp.decode(reader, reader.uint32()); break; @@ -23574,6 +23889,16 @@ return "fullBackupSpec." + error; } } + if (message.incrementalBackupSpec != null && message.hasOwnProperty("incrementalBackupSpec")) { + if (properties.backupTypeSpec === 1) + return "backupTypeSpec: multiple values"; + properties.backupTypeSpec = 1; + { + var error = $root.google.spanner.admin.database.v1.IncrementalBackupSpec.verify(message.incrementalBackupSpec); + if (error) + return "incrementalBackupSpec." + error; + } + } if (message.updateTime != null && message.hasOwnProperty("updateTime")) { var error = $root.google.protobuf.Timestamp.verify(message.updateTime); if (error) @@ -23616,6 +23941,11 @@ throw TypeError(".google.spanner.admin.database.v1.BackupSchedule.fullBackupSpec: object expected"); message.fullBackupSpec = $root.google.spanner.admin.database.v1.FullBackupSpec.fromObject(object.fullBackupSpec); } + if (object.incrementalBackupSpec != null) { + if (typeof object.incrementalBackupSpec !== "object") + throw TypeError(".google.spanner.admin.database.v1.BackupSchedule.incrementalBackupSpec: object expected"); + message.incrementalBackupSpec = $root.google.spanner.admin.database.v1.IncrementalBackupSpec.fromObject(object.incrementalBackupSpec); + } if (object.updateTime != null) { if (typeof object.updateTime !== "object") throw TypeError(".google.spanner.admin.database.v1.BackupSchedule.updateTime: object expected"); @@ -23657,6 +23987,11 @@ if (options.oneofs) object.backupTypeSpec = "fullBackupSpec"; } + if (message.incrementalBackupSpec != null && message.hasOwnProperty("incrementalBackupSpec")) { + object.incrementalBackupSpec = $root.google.spanner.admin.database.v1.IncrementalBackupSpec.toObject(message.incrementalBackupSpec, options); + if (options.oneofs) + object.backupTypeSpec = "incrementalBackupSpec"; + } if (message.updateTime != null && message.hasOwnProperty("updateTime")) object.updateTime = $root.google.protobuf.Timestamp.toObject(message.updateTime, options); return object; diff --git a/protos/protos.json b/protos/protos.json index e4a6dddac..f344ee884 100644 --- a/protos/protos.json +++ b/protos/protos.json @@ -1667,6 +1667,20 @@ "(google.api.field_behavior)": "OUTPUT_ONLY" } }, + "freeableSizeBytes": { + "type": "int64", + "id": 15, + "options": { + "(google.api.field_behavior)": "OUTPUT_ONLY" + } + }, + "exclusiveSizeBytes": { + "type": "int64", + "id": 16, + "options": { + "(google.api.field_behavior)": "OUTPUT_ONLY" + } + }, "state": { "type": "State", "id": 6, @@ -1728,6 +1742,20 @@ "options": { "(google.api.field_behavior)": "OUTPUT_ONLY" } + }, + "incrementalBackupChainId": { + "type": "string", + "id": 17, + "options": { + "(google.api.field_behavior)": "OUTPUT_ONLY" + } + }, + "oldestVersionTime": { + "type": "google.protobuf.Timestamp", + "id": 18, + "options": { + "(google.api.field_behavior)": "OUTPUT_ONLY" + } } }, "nested": { @@ -2087,6 +2115,9 @@ "FullBackupSpec": { "fields": {} }, + "IncrementalBackupSpec": { + "fields": {} + }, "OperationProgress": { "fields": { "progressPercent": { @@ -2189,7 +2220,8 @@ "oneofs": { "backupTypeSpec": { "oneof": [ - "fullBackupSpec" + "fullBackupSpec", + "incrementalBackupSpec" ] } }, @@ -2226,6 +2258,10 @@ "type": "FullBackupSpec", "id": 7 }, + "incrementalBackupSpec": { + "type": "IncrementalBackupSpec", + "id": 8 + }, "updateTime": { "type": "google.protobuf.Timestamp", "id": 9, diff --git a/samples/package.json b/samples/package.json index d0712247f..c092bb019 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.12.0", + "@google-cloud/spanner": "^7.13.0", "yargs": "^17.0.0", "protobufjs": "^7.0.0" }, diff --git a/src/transaction.ts b/src/transaction.ts index 5617b4a3c..2e2420c04 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -712,8 +712,16 @@ export class Snapshot extends EventEmitter { this._update(response.metadata!.transaction); } }) - .on('error', () => { - if (!this.id && this._useInRunner) { + .on('error', err => { + const isServiceError = err && typeof err === 'object' && 'code' in err; + if ( + !this.id && + this._useInRunner && + !( + isServiceError && + (err as grpc.ServiceError).code === grpc.status.ABORTED + ) + ) { this.begin(); } }); @@ -1219,8 +1227,16 @@ export class Snapshot extends EventEmitter { this._update(response.metadata!.transaction); } }) - .on('error', () => { - if (!this.id && this._useInRunner) { + .on('error', err => { + const isServiceError = err && typeof err === 'object' && 'code' in err; + if ( + !this.id && + this._useInRunner && + !( + isServiceError && + (err as grpc.ServiceError).code === grpc.status.ABORTED + ) + ) { this.begin(); } }); @@ -1437,6 +1453,7 @@ export class Snapshot extends EventEmitter { this._waitingRequests.push(() => { makeRequest(resumeToken) .on('data', chunk => streamProxy.emit('data', chunk)) + .on('error', err => streamProxy.emit('error', err)) .on('end', () => streamProxy.emit('end')); }); diff --git a/system-test/spanner.ts b/system-test/spanner.ts index e647919a7..750ea0231 100644 --- a/system-test/spanner.ts +++ b/system-test/spanner.ts @@ -141,6 +141,23 @@ describe('Spanner', () => { gaxOptions: GAX_OPTIONS, }); await googleSqlOperation1.promise(); + + const [pg_database, postgreSqlOperation] = await PG_DATABASE.create({ + databaseDialect: Spanner.POSTGRESQL, + gaxOptions: GAX_OPTIONS, + }); + await postgreSqlOperation.promise(); + const schema = [ + ` + CREATE TABLE ${TABLE_NAME} ( + SingerId VARCHAR(1024) NOT NULL, + Name VARCHAR(1024), + PRIMARY KEY (SingerId) + );`, + ]; + const [postgreSqlOperationUpdateDDL] = + await pg_database.updateSchema(schema); + await postgreSqlOperationUpdateDDL.promise(); } else { // Reading proto descriptor file const protoDescriptor = fs @@ -167,6 +184,7 @@ describe('Spanner', () => { await googleSqlOperation1.promise(); } RESOURCES_TO_CLEAN.push(DATABASE); + RESOURCES_TO_CLEAN.push(PG_DATABASE); const [, googleSqlOperation2] = await DATABASE_DROP_PROTECTION.create({ schema: ` @@ -461,6 +479,35 @@ describe('Spanner', () => { ` ); await googleSqlOperationUpdateDDL.promise(); + // TODO: add column Float32Value DOUBLE PRECISION and FLOAT32Array DOUBLE PRECISION[] while using float32 feature. + const [postgreSqlOperationUpdateDDL] = await PG_DATABASE.updateSchema( + ` + CREATE TABLE ${TABLE_NAME} + ( + "Key" VARCHAR NOT NULL PRIMARY KEY, + "BytesValue" BYTEA, + "BoolValue" BOOL, + "FloatValue" DOUBLE PRECISION, + "IntValue" BIGINT, + "NumericValue" NUMERIC, + "StringValue" VARCHAR, + "TimestampValue" TIMESTAMPTZ, + "DateValue" DATE, + "JsonbValue" JSONB, + "BytesArray" BYTEA[], + "BoolArray" BOOL[], + "FloatArray" DOUBLE PRECISION[], + "IntArray" BIGINT[], + "NumericArray" NUMERIC[], + "StringArray" VARCHAR[], + "TimestampArray" TIMESTAMPTZ[], + "DateArray" DATE[], + "JsonbArray" JSONB[], + "CommitTimestamp" SPANNER.COMMIT_TIMESTAMP + ); + ` + ); + await postgreSqlOperationUpdateDDL.promise(); } else { // TODO: add column Float32Value FLOAT32 and FLOAT32Array Array while using float32 feature. const [googleSqlOperationUpdateDDL] = await DATABASE.updateSchema( @@ -586,10 +633,7 @@ describe('Spanner', () => { differentlyOrderedRows(done, Spanner.GOOGLE_STANDARD_SQL); }); - it('POSTGRESQL should allow differently-ordered rows}', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should allow differently-ordered rows}', done => { differentlyOrderedRows(done, Spanner.POSTGRESQL); }); }); @@ -698,10 +742,7 @@ describe('Spanner', () => { booleanInsert(done, Spanner.GOOGLE_STANDARD_SQL, true); }); - it('POSTGRESQL should write boolean values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write boolean values', done => { booleanInsert(done, Spanner.POSTGRESQL, true); }); @@ -709,10 +750,7 @@ describe('Spanner', () => { booleanInsert(done, Spanner.GOOGLE_STANDARD_SQL, null); }); - it('POSTGRESQL should write null boolean values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null boolean values', done => { booleanInsert(done, Spanner.POSTGRESQL, null); }); @@ -724,10 +762,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should write empty boolean array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write empty boolean array values', done => { insert({BoolArray: []}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().BoolArray, []); @@ -743,10 +778,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should write null boolean array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null boolean array values', done => { insert({BoolArray: [null]}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().BoolArray, [null]); @@ -766,10 +798,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write boolean array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write boolean array values', done => { insert({BoolArray: [true, false]}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().BoolArray, [true, false]); @@ -794,10 +823,7 @@ describe('Spanner', () => { int64Insert(done, Spanner.GOOGLE_STANDARD_SQL, Spanner.int(1234)); }); - it('POSTGRESQL should write int64 values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write int64 values', done => { int64Insert(done, Spanner.POSTGRESQL, Spanner.int(1234)); }); @@ -805,10 +831,7 @@ describe('Spanner', () => { int64Insert(done, Spanner.GOOGLE_STANDARD_SQL, null); }); - it('POSTGRESQL should write null int64 values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null int64 values', done => { int64Insert(done, Spanner.POSTGRESQL, null); }); @@ -830,10 +853,7 @@ describe('Spanner', () => { int64OutOfBounds(done, Spanner.GOOGLE_STANDARD_SQL); }); - it('POSTGRESQL should throw for of bounds integers', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should throw for of bounds integers', done => { int64OutOfBounds(done, Spanner.POSTGRESQL); }); @@ -853,10 +873,7 @@ describe('Spanner', () => { int64WrapOutOfBounds(done, Spanner.GOOGLE_STANDARD_SQL); }); - it('POSTGRESQL should optionally wrap out of bounds integers', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should optionally wrap out of bounds integers', done => { int64WrapOutOfBounds(done, Spanner.POSTGRESQL); }); @@ -868,10 +885,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should write empty in64 array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write empty in64 array values', done => { insert({IntArray: []}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().IntArray, []); @@ -887,10 +901,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should write null int64 array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null int64 array values', done => { insert({IntArray: [null]}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().IntArray, [null]); @@ -908,10 +919,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should write int64 array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write int64 array values', done => { const values = [1, 2, 3]; insert({IntArray: values}, Spanner.POSTGRESQL, (err, row) => { @@ -923,10 +931,7 @@ describe('Spanner', () => { }); describe('oids', () => { - it('POSTGRESQL should read non-null pgOid values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should read non-null pgOid values', done => { PG_DATABASE.run('SELECT 123::oid', (err, rows) => { assert.ifError(err); let queriedValue = rows[0][0].value; @@ -938,10 +943,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should read null pgOid values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should read null pgOid values', done => { PG_DATABASE.run('SELECT null::oid', (err, rows) => { assert.ifError(err); let queriedValue = rows[0][0].value; @@ -977,10 +979,7 @@ describe('Spanner', () => { float32Insert(done, Spanner.GOOGLE_STANDARD_SQL, 8.2); }); - it('POSTGRESQL should write float32 values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write float32 values', done => { float32Insert(done, Spanner.POSTGRESQL, 8.2); }); @@ -988,10 +987,7 @@ describe('Spanner', () => { float32Insert(done, Spanner.GOOGLE_STANDARD_SQL, null); }); - it('POSTGRESQL should write null float32 values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null float32 values', done => { float32Insert(done, Spanner.POSTGRESQL, null); }); @@ -999,10 +995,7 @@ describe('Spanner', () => { float32Insert(done, Spanner.GOOGLE_STANDARD_SQL, Spanner.float32(8)); }); - it('POSTGRESQL should accept a Float object with an Int-like value', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should accept a Float object with an Int-like value', done => { float32Insert(done, Spanner.POSTGRESQL, Spanner.float32(8)); }); @@ -1010,10 +1003,7 @@ describe('Spanner', () => { float32Insert(done, Spanner.GOOGLE_STANDARD_SQL, Infinity); }); - it('POSTGRESQL should handle Infinity', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should handle Infinity', done => { float32Insert(done, Spanner.POSTGRESQL, Infinity); }); @@ -1021,10 +1011,7 @@ describe('Spanner', () => { float32Insert(done, Spanner.GOOGLE_STANDARD_SQL, -Infinity); }); - it('POSTGRESQL should handle -Infinity', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should handle -Infinity', done => { float32Insert(done, Spanner.POSTGRESQL, -Infinity); }); @@ -1032,10 +1019,7 @@ describe('Spanner', () => { float32Insert(done, Spanner.GOOGLE_STANDARD_SQL, NaN); }); - it('POSTGRESQL should handle NaN', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should handle NaN', done => { float32Insert(done, Spanner.POSTGRESQL, NaN); }); @@ -1047,10 +1031,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should write empty float32 array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write empty float32 array values', done => { insert({Float32Array: []}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().Float32Array, []); @@ -1070,10 +1051,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write null float32 array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null float32 array values', done => { insert({Float32Array: [null]}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().Float32Array, [null]); @@ -1097,10 +1075,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write float32 array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write float32 array values', done => { const values = [1.2, 2.3, 3.4]; insert({Float32Array: values}, Spanner.POSTGRESQL, (err, row) => { @@ -1127,10 +1102,7 @@ describe('Spanner', () => { float64Insert(done, Spanner.GOOGLE_STANDARD_SQL, 8.2); }); - it('POSTGRESQL should write float64 values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write float64 values', done => { float64Insert(done, Spanner.POSTGRESQL, 8.2); }); @@ -1138,10 +1110,7 @@ describe('Spanner', () => { float64Insert(done, Spanner.GOOGLE_STANDARD_SQL, null); }); - it('POSTGRESQL should write null float64 values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null float64 values', done => { float64Insert(done, Spanner.POSTGRESQL, null); }); @@ -1149,10 +1118,7 @@ describe('Spanner', () => { float64Insert(done, Spanner.GOOGLE_STANDARD_SQL, Spanner.float(8)); }); - it('POSTGRESQL should accept a Float object with an Int-like value', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should accept a Float object with an Int-like value', done => { float64Insert(done, Spanner.POSTGRESQL, Spanner.float(8)); }); @@ -1160,10 +1126,7 @@ describe('Spanner', () => { float64Insert(done, Spanner.GOOGLE_STANDARD_SQL, Infinity); }); - it('POSTGRESQL should handle Infinity', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should handle Infinity', done => { float64Insert(done, Spanner.POSTGRESQL, Infinity); }); @@ -1171,10 +1134,7 @@ describe('Spanner', () => { float64Insert(done, Spanner.GOOGLE_STANDARD_SQL, -Infinity); }); - it('POSTGRESQL should handle -Infinity', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should handle -Infinity', done => { float64Insert(done, Spanner.POSTGRESQL, -Infinity); }); @@ -1182,10 +1142,7 @@ describe('Spanner', () => { float64Insert(done, Spanner.GOOGLE_STANDARD_SQL, NaN); }); - it('POSTGRESQL should handle NaN', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should handle NaN', done => { float64Insert(done, Spanner.POSTGRESQL, NaN); }); @@ -1197,10 +1154,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should write empty float64 array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write empty float64 array values', done => { insert({FloatArray: []}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().FloatArray, []); @@ -1220,10 +1174,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write null float64 array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null float64 array values', done => { insert({FloatArray: [null]}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().FloatArray, [null]); @@ -1245,10 +1196,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write float64 array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write float64 array values', done => { const values = [1.2, 2.3, 3.4]; insert({FloatArray: values}, Spanner.POSTGRESQL, (err, row) => { @@ -1276,10 +1224,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write numeric values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write numeric values', done => { numericInsert( done, Spanner.POSTGRESQL, @@ -1291,17 +1236,11 @@ describe('Spanner', () => { numericInsert(done, Spanner.GOOGLE_STANDARD_SQL, null); }); - it('POSTGRESQL should write null numeric values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null numeric values', done => { numericInsert(done, Spanner.POSTGRESQL, null); }); - it('POSTGRESQL should bind NaN', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind NaN', done => { numericInsert(done, Spanner.POSTGRESQL, Spanner.pgNumeric('NaN')); }); @@ -1320,10 +1259,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should throw for out of bounds values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should throw for out of bounds values', done => { numericInsertOutOfBounds( done, Spanner.POSTGRESQL, @@ -1339,10 +1275,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should write empty numeric array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write empty numeric array values', done => { insert({NumericArray: []}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().NumericArray, []); @@ -1362,10 +1295,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write null numeric array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null numeric array values', done => { insert({NumericArray: [null]}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().NumericArray, [null]); @@ -1391,10 +1321,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write numeric array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write numeric array values', done => { const values = [ Spanner.pgNumeric('-99999999999999999999999999999.999999999'), Spanner.pgNumeric('3.141592653'), @@ -1422,10 +1349,7 @@ describe('Spanner', () => { stringInsert(done, Spanner.GOOGLE_STANDARD_SQL, 'abc'); }); - it('POSTGRESQL should write string values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write string values', done => { stringInsert(done, Spanner.POSTGRESQL, 'abc'); }); @@ -1433,10 +1357,7 @@ describe('Spanner', () => { stringInsert(done, Spanner.GOOGLE_STANDARD_SQL, null); }); - it('POSTGRESQL should write null string values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null string values', done => { stringInsert(done, Spanner.POSTGRESQL, null); }); @@ -1448,10 +1369,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should write empty string array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write empty string array values', done => { insert({StringArray: []}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().StringArray, []); @@ -1471,10 +1389,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write null string array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null string array values', done => { insert({StringArray: [null]}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().StringArray, [null]); @@ -1494,10 +1409,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write string array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write string array values', done => { insert( {StringArray: ['abc', 'def']}, Spanner.POSTGRESQL, @@ -1523,10 +1435,7 @@ describe('Spanner', () => { bytesInsert(done, Spanner.GOOGLE_STANDARD_SQL, Buffer.from('abc')); }); - it('POSTGRESQL should write bytes values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write bytes values', done => { bytesInsert(done, Spanner.POSTGRESQL, Buffer.from('abc')); }); @@ -1534,10 +1443,7 @@ describe('Spanner', () => { bytesInsert(done, Spanner.GOOGLE_STANDARD_SQL, null); }); - it('POSTGRESQL should write null bytes values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null bytes values', done => { bytesInsert(done, Spanner.POSTGRESQL, null); }); @@ -1549,10 +1455,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should write empty bytes array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write empty bytes array values', done => { insert({BytesArray: []}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().BytesArray, []); @@ -1572,10 +1475,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write null bytes array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null bytes array values', done => { insert({BytesArray: [null]}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().BytesArray, [null]); @@ -1597,10 +1497,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write bytes array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write bytes array values', done => { const values = [Buffer.from('a'), Buffer.from('b')]; insert({BytesArray: values}, Spanner.POSTGRESQL, (err, row) => { @@ -1689,10 +1586,7 @@ describe('Spanner', () => { timestampInsert(done, Spanner.GOOGLE_STANDARD_SQL); }); - it('POSTGRESQL should write timestamp values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write timestamp values', done => { timestampInsert(done, Spanner.POSTGRESQL); }); @@ -1708,10 +1602,7 @@ describe('Spanner', () => { timestampInsertNull(done, Spanner.GOOGLE_STANDARD_SQL); }); - it('POSTGRESQL should write null timestamp values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null timestamp values', done => { timestampInsertNull(done, Spanner.POSTGRESQL); }); @@ -1727,10 +1618,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write empty timestamp array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write empty timestamp array values', done => { insert({TimestampArray: []}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().TimestampArray, []); @@ -1750,10 +1638,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write null timestamp array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null timestamp array values', done => { insert({TimestampArray: [null]}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().TimestampArray, [null]); @@ -1775,10 +1660,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should write timestamp array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write timestamp array values', done => { const values = [Spanner.timestamp(), Spanner.timestamp('3-3-1933')]; insert({TimestampArray: values}, Spanner.POSTGRESQL, (err, row) => { @@ -1805,10 +1687,7 @@ describe('Spanner', () => { dateInsert(done, Spanner.GOOGLE_STANDARD_SQL); }); - it('POSTGRESQL should write date values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write date values', done => { dateInsert(done, Spanner.POSTGRESQL); }); @@ -1824,10 +1703,7 @@ describe('Spanner', () => { dateInsertNull(done, Spanner.GOOGLE_STANDARD_SQL); }); - it('POSTGRESQL should write null date values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null date values', done => { dateInsertNull(done, Spanner.POSTGRESQL); }); @@ -1839,10 +1715,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should write empty date array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write empty date array values', done => { insert({DateArray: []}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().DateArray, []); @@ -1858,10 +1731,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should write null date array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write null date array values', done => { insert({DateArray: [null]}, Spanner.POSTGRESQL, (err, row) => { assert.ifError(err); assert.deepStrictEqual(row.toJSON().DateArray, [null]); @@ -1880,10 +1750,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should write date array values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should write date array values', done => { const values = [Spanner.date(), Spanner.date('3-3-1933')]; insert({DateArray: values}, Spanner.POSTGRESQL, (err, row) => { @@ -2186,10 +2053,7 @@ describe('Spanner', () => { commitTimestamp(done, Spanner.GOOGLE_STANDARD_SQL); }); - it('POSTGRESQL should accept the commit timestamp placeholder', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should accept the commit timestamp placeholder', done => { commitTimestamp(done, Spanner.POSTGRESQL); }); }); @@ -2205,10 +2069,7 @@ describe('Spanner', () => { incorrectValueType(done, googleSqlTable); }); - it('POSTGRESQL should throw an error for incorrect value types', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should throw an error for incorrect value types', done => { incorrectValueType(done, postgreSqlTable); }); }); @@ -2513,10 +2374,7 @@ describe('Spanner', () => { autoCreateDatabase(done, 'database'); }); - it('POSTGRESQL should auto create a database', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should auto create a database', done => { autoCreateDatabase(done, 'pg-db'); }); @@ -2541,10 +2399,7 @@ describe('Spanner', () => { createDatabase(done, DATABASE, 'GOOGLE_STANDARD_SQL'); }); - it('POSTGRESQL should have created the database', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should have created the database', done => { createDatabase(done, PG_DATABASE, 'POSTGRESQL'); }); @@ -2645,10 +2500,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should create a table', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should create a table', done => { const createTableStatement = ` CREATE TABLE ${TABLE_NAME} ( SingerId BIGINT NOT NULL, @@ -4175,23 +4027,21 @@ describe('Spanner', () => { ); await onPromiseOperationComplete(googleSqlCreateTable); - if (!IS_EMULATOR_ENABLED) { - // TODO: Add column "Float32" DOUBLE PRECISION while using float32 feature. - const postgreSqlCreateTable = await postgreSqlTable.create( - `CREATE TABLE ${TABLE_NAME} - ( - "SingerId" VARCHAR(1024) NOT NULL PRIMARY KEY, - "Name" VARCHAR(1024), - "Float" DOUBLE PRECISION, - "Int" BIGINT, - "Info" BYTEA, - "Created" TIMESTAMPTZ, - "HasGear" BOOL - )`, - GAX_OPTIONS - ); - await onPromiseOperationComplete(postgreSqlCreateTable); - } + // TODO: Add column "Float32" DOUBLE PRECISION while using float32 feature. + const postgreSqlCreateTable = await postgreSqlTable.create( + `CREATE TABLE ${TABLE_NAME} + ( + "SingerId" VARCHAR(1024) NOT NULL PRIMARY KEY, + "Name" VARCHAR(1024), + "Float" DOUBLE PRECISION, + "Int" BIGINT, + "Info" BYTEA, + "Created" TIMESTAMPTZ, + "HasGear" BOOL + )`, + GAX_OPTIONS + ); + await onPromiseOperationComplete(postgreSqlCreateTable); }); const nonExistentTable = (done, database) => { @@ -4212,10 +4062,7 @@ describe('Spanner', () => { nonExistentTable(done, DATABASE); }); - it('POSTGRESQL should throw an error for non-existent tables', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should throw an error for non-existent tables', done => { nonExistentTable(done, PG_DATABASE); }); @@ -4236,10 +4083,7 @@ describe('Spanner', () => { nonExistentColumn(done, googleSqlTable); }); - it('POSTGRESQL should throw an error for non-existent columns', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should throw an error for non-existent columns', done => { nonExistentColumn(done, postgreSqlTable); }); @@ -4287,10 +4131,7 @@ describe('Spanner', () => { readRowsStream(done, googleSqlTable); }); - it('POSTGRESQL should read rows as a stream', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should read rows as a stream', done => { readRowsStream(done, postgreSqlTable); }); @@ -4334,10 +4175,7 @@ describe('Spanner', () => { automaticallyConvertToJson(done, googleSqlTable); }); - it('POSTGRESQL should automatically convert to JSON', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should automatically convert to JSON', done => { automaticallyConvertToJson(done, postgreSqlTable); }); @@ -4378,10 +4216,7 @@ describe('Spanner', () => { automaticallyConvertToJsonWithOptions(done, googleSqlTable); }); - it('POSTGRESQL should automatically convert to JSON with options', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should automatically convert to JSON with options', done => { automaticallyConvertToJsonWithOptions(done, postgreSqlTable); }); @@ -4424,10 +4259,7 @@ describe('Spanner', () => { insertAndDeleteSingleRow(done, googleSqlTable); }); - it('POSTGRESQL should insert and delete a row', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should insert and delete a row', done => { insertAndDeleteSingleRow(done, postgreSqlTable); }); @@ -4479,10 +4311,7 @@ describe('Spanner', () => { insertAndDeleteMultipleRows(done, googleSqlTable); }); - it('POSTGRESQL should insert and delete multiple rows', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should insert and delete multiple rows', done => { insertAndDeleteMultipleRows(done, postgreSqlTable); }); @@ -4553,10 +4382,7 @@ describe('Spanner', () => { insertAndDeleteMultipleCompositeKeyRows(DATABASE, createTableStatement); }); - it('POSTGRESQL should insert and delete multiple composite key rows', function () { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should insert and delete multiple composite key rows', () => { const createTableStatement = ` CREATE TABLE SingersComposite ( "SingerId" BIGINT NOT NULL, @@ -4619,10 +4445,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should should insert and query multiple rows', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should should insert and query multiple rows', done => { insertAndQueryMultipleRows( done, PG_DATABASE, @@ -4671,10 +4494,7 @@ describe('Spanner', () => { insertThenReplaceRow(done, googleSqlTable); }); - it('POSTGRESQL should insert then replace a row', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should insert then replace a row', done => { insertThenReplaceRow(done, postgreSqlTable); }); @@ -4719,10 +4539,7 @@ describe('Spanner', () => { insertThenUpdateRow(done, googleSqlTable); }); - it('POSTGRESQL should insert then replace a row', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should insert then replace a row', done => { insertThenUpdateRow(done, postgreSqlTable); }); @@ -4769,9 +4586,7 @@ describe('Spanner', () => { before(async () => { await googleSqlTable.insert(GOOGLE_SQL_INSERT_ROW); - if (!IS_EMULATOR_ENABLED) { - await postgreSqlTable.insert(POSTGRESQL_INSERT_ROW); - } + await postgreSqlTable.insert(POSTGRESQL_INSERT_ROW); }); const queryCallbackMode = (done, database, query, EXPECTED_ROW) => { @@ -4794,10 +4609,7 @@ describe('Spanner', () => { queryCallbackMode(done, DATABASE, query, GOOGLE_SQL_EXPECTED_ROW); }); - it('POSTGRESQL should query in callback mode', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should query in callback mode', done => { const query = { sql: `SELECT * FROM ${TABLE_NAME} WHERE "SingerId"=$1`, params: {p1: ID}, @@ -4828,10 +4640,7 @@ describe('Spanner', () => { queryPromiseMode(done, DATABASE, query, GOOGLE_SQL_EXPECTED_ROW); }); - it('POSTGRESQL should query in promise mode', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should query in promise mode', done => { const query = { sql: `SELECT * FROM ${TABLE_NAME} WHERE "SingerId"=$1`, params: {p1: ID}, @@ -4866,10 +4675,7 @@ describe('Spanner', () => { queryStreamMode(done, DATABASE, query, GOOGLE_SQL_EXPECTED_ROW); }); - it('POSTGRESQL should query in stream mode', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should query in stream mode', done => { const query = { sql: `SELECT * FROM ${TABLE_NAME} WHERE "SingerId"=$1`, params: {p1: ID}, @@ -4969,10 +4775,7 @@ describe('Spanner', () => { DATABASE.run('SELECT 1', done); }); - it('POSTGRESQL should allow "SELECT 1" queries', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should allow "SELECT 1" queries', done => { PG_DATABASE.run('SELECT 1', done); }); @@ -4999,10 +4802,7 @@ describe('Spanner', () => { assert.strictEqual(metadata.rowType!.fields![9].name, 'HasGear'); }); - it('POSTGRESQL should return metadata', async function () { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should return metadata', async () => { const [rows, , metadata] = await PG_DATABASE.run({ sql: `SELECT * FROM ${TABLE_NAME} WHERE "SingerId"=$1`, params: {p1: ID}, @@ -5033,10 +4833,7 @@ describe('Spanner', () => { invalidQueries(done, DATABASE); }); - it('POSTGRESQL should allow "SELECT 1" queries', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should allow "SELECT 1" queries', done => { invalidQueries(done, PG_DATABASE); }); @@ -5099,10 +4896,7 @@ describe('Spanner', () => { booleanQuery(done, DATABASE, query, true); }); - it('POSTGRESQL should bind the value', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind the value', done => { const query = { sql: 'SELECT $1', params: { @@ -5125,10 +4919,7 @@ describe('Spanner', () => { booleanQuery(done, DATABASE, query, null); }); - it('POSTGRESQL should allow for null values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should allow for null values', done => { const query = { sql: 'SELECT $1', params: { @@ -5226,10 +5017,7 @@ describe('Spanner', () => { int64Query(done, DATABASE, query, '1234'); }); - it('POSTGRESQL should bind the value', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind the value', done => { const query = { sql: 'SELECT $1', params: { @@ -5252,10 +5040,7 @@ describe('Spanner', () => { int64Query(done, DATABASE, query, null); }); - it('POSTGRESQL should allow for null values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should allow for null values', done => { const query = { sql: 'SELECT $1', params: { @@ -5351,10 +5136,7 @@ describe('Spanner', () => { }); }; - it('POSTGRESQL should bind the value', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind the value', done => { const query = { sql: 'SELECT $1', params: { @@ -5367,10 +5149,7 @@ describe('Spanner', () => { oidQuery(done, PG_DATABASE, query, '1234'); }); - it('POSTGRESQL should allow for null values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should allow for null values', done => { const query = { sql: 'SELECT $1', params: { @@ -5441,10 +5220,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should bind the value when param type float32 is used', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind the value when param type float32 is used', done => { const query = { sql: 'SELECT $1', params: { @@ -5457,10 +5233,7 @@ describe('Spanner', () => { float32Query(done, PG_DATABASE, query, 2.2); }); - it('POSTGRESQL should bind the value when Spanner.float32 is used', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind the value when Spanner.float32 is used', done => { const query = { sql: 'SELECT $1', params: { @@ -5483,10 +5256,7 @@ describe('Spanner', () => { float32Query(done, DATABASE, query, null); }); - it('POSTGRESQL should allow for null values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should allow for null values', done => { const query = { sql: 'SELECT $1', params: { @@ -5592,10 +5362,7 @@ describe('Spanner', () => { float32Query(done, DATABASE, query, 'Infinity'); }); - it('POSTGRESQL should bind Infinity', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind Infinity', done => { const query = { sql: 'SELECT $1', params: { @@ -5621,10 +5388,7 @@ describe('Spanner', () => { float32Query(done, DATABASE, query, '-Infinity'); }); - it('POSTGRESQL should bind -Infinity', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind -Infinity', done => { const query = { sql: 'SELECT $1', params: { @@ -5650,10 +5414,7 @@ describe('Spanner', () => { float32Query(done, DATABASE, query, 'NaN'); }); - it('POSTGRESQL should bind NaN', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind NaN', done => { const query = { sql: 'SELECT $1', params: { @@ -5721,10 +5482,7 @@ describe('Spanner', () => { float64Query(done, DATABASE, query, 2.2); }); - it('POSTGRESQL should bind the value', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind the value', done => { const query = { sql: 'SELECT $1', params: { @@ -5747,10 +5505,7 @@ describe('Spanner', () => { float64Query(done, DATABASE, query, null); }); - it('POSTGRESQL should allow for null values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should allow for null values', done => { const query = { sql: 'SELECT $1', params: { @@ -5842,10 +5597,7 @@ describe('Spanner', () => { float64Query(done, DATABASE, query, 'Infinity'); }); - it('POSTGRESQL should bind Infinity', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind Infinity', done => { const query = { sql: 'SELECT $1', params: { @@ -5865,10 +5617,7 @@ describe('Spanner', () => { float64Query(done, DATABASE, query, '-Infinity'); }); - it('POSTGRESQL should bind -Infinity', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind -Infinity', done => { const query = { sql: 'SELECT $1', params: { @@ -5888,10 +5637,7 @@ describe('Spanner', () => { float64Query(done, DATABASE, query, 'NaN'); }); - it('POSTGRESQL should bind NaN', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind NaN', done => { const query = { sql: 'SELECT $1', params: { @@ -5946,10 +5692,7 @@ describe('Spanner', () => { stringQuery(done, DATABASE, query, 'abc'); }); - it('POSTGRESQL should bind the value', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind the value', done => { const query = { sql: 'SELECT $1', params: { @@ -5972,10 +5715,7 @@ describe('Spanner', () => { stringQuery(done, DATABASE, query, null); }); - it('POSTGRESQL should allow for null values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should allow for null values', done => { const query = { sql: 'SELECT $1', params: { @@ -6069,10 +5809,7 @@ describe('Spanner', () => { bytesQuery(done, DATABASE, query, Buffer.from('abc')); }); - it('POSTGRESQL should bind the value', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind the value', done => { const query = { sql: 'SELECT $1', params: { @@ -6095,10 +5832,7 @@ describe('Spanner', () => { bytesQuery(done, DATABASE, query, null); }); - it('POSTGRESQL should allow for null values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should allow for null values', done => { const query = { sql: 'SELECT $1', params: { @@ -6193,10 +5927,7 @@ describe('Spanner', () => { timestampQuery(done, DATABASE, query, timestamp); }); - it('POSTGRESQL should bind the value', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind the value', done => { const timestamp = Spanner.timestamp(); const query = { sql: 'SELECT $1', @@ -6220,10 +5951,7 @@ describe('Spanner', () => { timestampQuery(done, DATABASE, query, null); }); - it('POSTGRESQL should allow for null values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should allow for null values', done => { const query = { sql: 'SELECT $1', params: { @@ -6327,10 +6055,7 @@ describe('Spanner', () => { dateQuery(done, DATABASE, query, date); }); - it('POSTGRESQL should bind the value', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should bind the value', done => { const date = Spanner.date(); const query = { sql: 'SELECT $1', @@ -6354,10 +6079,7 @@ describe('Spanner', () => { dateQuery(done, DATABASE, query, null); }); - it('POSTGRESQL should allow for null values', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should allow for null values', done => { const query = { sql: 'SELECT $1', params: { @@ -6861,18 +6583,16 @@ describe('Spanner', () => { await onPromiseOperationComplete(googleSqlCreateTable); await googleSqlTable.insert(googleSqlExpectedRow); - if (!IS_EMULATOR_ENABLED) { - const postgreSqlCreateTable = await postgreSqlTable.create( - `CREATE TABLE ${TABLE_NAME} ( - "Key" VARCHAR NOT NULL PRIMARY KEY, - "StringValue" VARCHAR, - "BytesValue" BYTEA - )`, - GAX_OPTIONS - ); - await onPromiseOperationComplete(postgreSqlCreateTable); - await postgreSqlTable.insert(postgreSqlExpectedRow); - } + const postgreSqlCreateTable = await postgreSqlTable.create( + `CREATE TABLE ${TABLE_NAME} ( + "Key" VARCHAR NOT NULL PRIMARY KEY, + "StringValue" VARCHAR, + "BytesValue" BYTEA + )`, + GAX_OPTIONS + ); + await onPromiseOperationComplete(postgreSqlCreateTable); + await postgreSqlTable.insert(postgreSqlExpectedRow); }); it('GOOGLE_STANDARD_SQL should read large datasets', done => { @@ -6919,10 +6639,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should read large datasets', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should read large datasets', done => { postgreSqlTable.read( { keys: [postgreSqlExpectedRow.Key], @@ -6989,10 +6706,7 @@ describe('Spanner', () => { }); }); - it('POSTGRESQL should query large datasets', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should query large datasets', done => { const query = { sql: 'SELECT * FROM ' + postgreSqlTable.name + ' WHERE "Key" = $1', params: { @@ -7060,10 +6774,7 @@ describe('Spanner', () => { updateRow(done, googleSqlTable); }); - it('POSTGRESQL should update a row', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should update a row', done => { updateRow(done, postgreSqlTable); }); @@ -7089,10 +6800,7 @@ describe('Spanner', () => { insertRow(done, googleSqlTable); }); - it('POSTGRESQL should update a row', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should update a row', done => { insertRow(done, postgreSqlTable); }); }); @@ -7128,21 +6836,19 @@ describe('Spanner', () => { } await googleSqlTable.insert(data); - if (!IS_EMULATOR_ENABLED) { - const postgreSqlCreateTable = await postgreSqlTable.create( - ` - CREATE TABLE ${TABLE_NAME} ( - "Key" VARCHAR NOT NULL PRIMARY KEY, - "StringValue" VARCHAR - )`, - GAX_OPTIONS - ); - await onPromiseOperationComplete(postgreSqlCreateTable); - const postgreSqlCreateIndex = await PG_DATABASE.updateSchema(` - CREATE INDEX ReadByValue ON ${TABLE_NAME}("StringValue")`); - await onPromiseOperationComplete(postgreSqlCreateIndex); - await postgreSqlTable.insert(data); - } + const postgreSqlCreateTable = await postgreSqlTable.create( + ` + CREATE TABLE ${TABLE_NAME} ( + "Key" VARCHAR NOT NULL PRIMARY KEY, + "StringValue" VARCHAR + )`, + GAX_OPTIONS + ); + await onPromiseOperationComplete(postgreSqlCreateTable); + const postgreSqlCreateIndex = await PG_DATABASE.updateSchema(` + CREATE INDEX ReadByValue ON ${TABLE_NAME}("StringValue")`); + await onPromiseOperationComplete(postgreSqlCreateIndex); + await postgreSqlTable.insert(data); }); // all of these tests require testing with and without an index, @@ -7369,10 +7075,7 @@ describe('Spanner', () => { }); }); - it(`POSTGRESQL ${test.test}`, function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it(`POSTGRESQL ${test.test}`, done => { postgreSqlTable.read(test.query as ReadRequest, (err, rows) => { test.assertions(err, rows); done(); @@ -7417,10 +7120,7 @@ describe('Spanner', () => { readUsingIndex(done, test, googleSqlTable); }); - it(`POSTGRESQL ${test.test}` + ' with an index', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it(`POSTGRESQL ${test.test}` + ' with an index', done => { readUsingIndex(done, test, postgreSqlTable); }); }); @@ -7458,10 +7158,7 @@ describe('Spanner', () => { readInvalidTable(done, DATABASE); }); - it('POSTGRESQL should read over invalid table fails', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should read over invalid table fails', done => { readInvalidTable(done, PG_DATABASE); }); @@ -7481,10 +7178,7 @@ describe('Spanner', () => { readInvalidColumn(done, googleSqlTable); }); - it('POSTGRESQL should read over invalid column fails', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should read over invalid column fails', done => { readInvalidColumn(done, postgreSqlTable); }); @@ -7720,12 +7414,10 @@ describe('Spanner', () => { ); await insertRecords(googleSqlTable, googleSqlRecords); - if (!IS_EMULATOR_ENABLED) { - await onPromiseOperationComplete( - await postgreSqlTable.create(postgreSqlSchema, GAX_OPTIONS) - ); - await insertRecords(postgreSqlTable, postgreSqlRecords); - } + await onPromiseOperationComplete( + await postgreSqlTable.create(postgreSqlSchema, GAX_OPTIONS) + ); + await insertRecords(postgreSqlTable, postgreSqlRecords); }); describe('snapshots', () => { @@ -7751,10 +7443,7 @@ describe('Spanner', () => { readOnlyTransaction(done, DATABASE, googleSqlRecords); }); - it('POSTGRESQL should run a read only transaction', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should run a read only transaction', done => { readOnlyTransaction(done, PG_DATABASE, postgreSqlRecords); }); @@ -7786,10 +7475,7 @@ describe('Spanner', () => { readKeysFromTable(done, DATABASE, googleSqlTable, googleSqlRecords); }); - it('POSTGRESQL should read keys from a table', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should read keys from a table', done => { readKeysFromTable( done, PG_DATABASE, @@ -7826,10 +7512,7 @@ describe('Spanner', () => { acceptReadTimestamp(done, DATABASE, googleSqlRecords); }); - it('POSTGRESQL should accept a read timestamp', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should accept a read timestamp', done => { acceptReadTimestamp(done, PG_DATABASE, postgreSqlRecords); }); @@ -7853,10 +7536,7 @@ describe('Spanner', () => { acceptMinTimestamp(done, DATABASE, googleSqlRecords); }); - it('POSTGRESQL should accept a min timestamp', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should accept a min timestamp', done => { acceptMinTimestamp(done, PG_DATABASE, postgreSqlRecords); }); @@ -7894,10 +7574,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should accept an exact staleness', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should accept an exact staleness', done => { acceptExactStaleness( done, PG_DATABASE, @@ -7926,10 +7603,7 @@ describe('Spanner', () => { acceptMaxStaleness(done, DATABASE, googleSqlRecords); }); - it('POSTGRESQL should accept a max staleness', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should accept a max staleness', done => { acceptMaxStaleness(done, PG_DATABASE, postgreSqlRecords); }); @@ -7984,10 +7658,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should do a strong read with concurrent updates', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should do a strong read with concurrent updates', done => { strongReadWithConcurrentUpdates( done, PG_DATABASE, @@ -8050,10 +7721,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should do a strong read with concurrent updates', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should do a strong read with concurrent updates', done => { exactReadWithConcurrentUpdates( done, PG_DATABASE, @@ -8111,10 +7779,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should read with staleness & concurrent updates', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should read with staleness & concurrent updates', done => { readWithStalenessAndConcurrentUpdates( done, PG_DATABASE, @@ -8158,34 +7823,26 @@ describe('Spanner', () => { }); describe('dml', () => { - before(done => { - const postgresUpdateDmlDatabase = () => { - PG_DATABASE.runTransaction((err, transaction) => { - assert.ifError(err); - - transaction!.runUpdate( - { - sql: - 'INSERT INTO ' + - TABLE_NAME + - ' ("Key", "StringValue") VALUES($1, $2)', - params: { - p1: 'k999', - p2: 'abc', - }, + before(async () => { + const psqlTransaction = await PG_DATABASE.runTransactionAsync( + async transaction => { + await transaction!.runUpdate({ + sql: + 'INSERT INTO ' + + TABLE_NAME + + ' ("Key", "StringValue") VALUES($1, $2)', + params: { + p1: 'k999', + p2: 'abc', }, - err => { - assert.ifError(err); - transaction!.commit(done); - } - ); - }); - }; - DATABASE.runTransaction((err, transaction) => { - assert.ifError(err); + }); + await transaction!.commit(); + } + ); - transaction!.runUpdate( - { + const gsqlTransaction = DATABASE.runTransactionAsync( + async transaction => { + await transaction!.runUpdate({ sql: 'INSERT INTO ' + TABLE_NAME + @@ -8194,17 +7851,12 @@ describe('Spanner', () => { key: 'k999', str: 'abc', }, - }, - err => { - assert.ifError(err); - if (!IS_EMULATOR_ENABLED) { - transaction!.commit(postgresUpdateDmlDatabase); - } else { - transaction!.commit(done); - } - } - ); - }); + }); + await transaction!.commit(); + } + ); + + return Promise.all([psqlTransaction, gsqlTransaction]); }); const rowCountRunUpdate = (done, database, query) => { @@ -8232,10 +7884,7 @@ describe('Spanner', () => { rowCountRunUpdate(done, DATABASE, query); }); - it('POSTGRESQL should return rowCount from runUpdate', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should return rowCount from runUpdate', done => { const query = { sql: 'UPDATE ' + TABLE_NAME + ' SET "StringValue" = $1 WHERE "Key" = $2', @@ -8275,10 +7924,7 @@ describe('Spanner', () => { rowCountRun(done, DATABASE, query); }); - it('POSTGRESQL should return rowCount from run', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should return rowCount from run', done => { const query = { sql: 'UPDATE ' + TABLE_NAME + ' SET "StringValue" = $1 WHERE "Key" = $2', @@ -8355,10 +8001,7 @@ describe('Spanner', () => { multipleDmlOnTxn(done, DATABASE, insertQuery, updateQuery, selectQuery); }); - it('POSTGRESQL should exec multiple dml statements on the same txn', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should exec multiple dml statements on the same txn', done => { const key = 'k1000'; const str = 'abcd'; const num = 11; @@ -8428,10 +8071,7 @@ describe('Spanner', () => { dmlChangesInQueryResults(done, DATABASE, updateQuery, selectQuery); }); - it('POSTGRESQL should show dml changes in query results', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should show dml changes in query results', done => { const key = 'k999'; const str = 'abcd'; const updateQuery = { @@ -8488,10 +8128,7 @@ describe('Spanner', () => { rollbackDmlStatement(done, DATABASE, updateQuery, selectQuery); }); - it('POSTGRESQL should rollback a dml statement', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should rollback a dml statement', done => { const key = 'k999'; const str = 'abcd'; const updateQuery = { @@ -8552,10 +8189,7 @@ describe('Spanner', () => { handleDmlAndInsert(done, DATABASE, insertQuery, selectQuery); }); - it('POSTGRESQL should handle using both dml and insert methods', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should handle using both dml and insert methods', done => { const str = 'dml+mutation'; const insertQuery = { sql: @@ -9003,10 +8637,7 @@ describe('Spanner', () => { await executeSingleStatement(DATABASE, googleSqlInsert); }); - it('POSTGRESQL should execute a single statement', async function () { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should execute a single statement', async () => { await executeSingleStatement(PG_DATABASE, postgreSqlInsert); }); @@ -9035,10 +8666,7 @@ describe('Spanner', () => { await noStatementError(DATABASE); }); - it('POSTGRESQL should return an error when no statements are supplied', async function () { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should return an error when no statements are supplied', async () => { await noStatementError(PG_DATABASE); }); @@ -9060,10 +8688,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should run multiple statements that depend on each other', async function () { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should run multiple statements that depend on each other', async () => { await multipleDependingStatements( PG_DATABASE, postgreSqlInsert, @@ -9086,10 +8711,7 @@ describe('Spanner', () => { await runAfterRunUpdate(DATABASE, googleSqlInsert, googleSqlUpdate); }); - it('POSTGRESQL should run after a runUpdate call', async function () { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should run after a runUpdate call', async () => { await runAfterRunUpdate(PG_DATABASE, postgreSqlInsert, posgreSqlUpdate); }); @@ -9108,10 +8730,7 @@ describe('Spanner', () => { await runBeforeRunUpdate(DATABASE, googleSqlInsert, googleSqlUpdate); }); - it('POSTGRESQL should run before a runUpdate call', async function () { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should run before a runUpdate call', async () => { await runBeforeRunUpdate( PG_DATABASE, postgreSqlInsert, @@ -9155,10 +8774,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should stop executing statements if an error occurs', async function () { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should stop executing statements if an error occurs', async () => { await stopExecutingStatementsIfError( PG_DATABASE, postgreSqlInsert, @@ -9197,10 +8813,7 @@ describe('Spanner', () => { ); }); - it('POSTGRESQL should ignore any additional statement errors', async function () { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should ignore any additional statement errors', async () => { await ignoreAdditionalStatementErrors( PG_DATABASE, postgreSqlInsert, @@ -9264,10 +8877,7 @@ describe('Spanner', () => { } }); - it('POSTGRESQL should throw an error for mismatched columns', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should throw an error for mismatched columns', done => { mismatchedColumnError(done, PG_DATABASE, postgreSqlTable); }); @@ -9288,10 +8898,7 @@ describe('Spanner', () => { commitTransaction(done, DATABASE, googleSqlTable); }); - it('POSTGRESQL should commit a transaction', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should commit a transaction', done => { commitTransaction(done, PG_DATABASE, postgreSqlTable); }); @@ -9310,10 +8917,7 @@ describe('Spanner', () => { rollbackTransaction(done, DATABASE); }); - it('POSTGRESQL should rollback a transaction', function (done) { - if (IS_EMULATOR_ENABLED) { - this.skip(); - } + it('POSTGRESQL should rollback a transaction', done => { rollbackTransaction(done, PG_DATABASE); }); diff --git a/test/mockserver/mockspanner.ts b/test/mockserver/mockspanner.ts index 464027b7d..9e0c06996 100644 --- a/test/mockserver/mockspanner.ts +++ b/test/mockserver/mockspanner.ts @@ -540,6 +540,7 @@ export class MockSpanner { call.request!.transaction.id }`; if (this.abortedTransactions.has(fullTransactionId)) { + call.sendMetadata(new Metadata()); call.emit( 'error', MockSpanner.createTransactionAbortedError(`${fullTransactionId}`) @@ -556,6 +557,7 @@ export class MockSpanner { call.request!.transaction.begin ); if (txn instanceof Error) { + call.sendMetadata(new Metadata()); call.emit('error', txn); call.end(); return; @@ -593,6 +595,7 @@ export class MockSpanner { index ); if (streamErr) { + call.sendMetadata(new Metadata()); call.emit('error', streamErr); break; } @@ -610,6 +613,7 @@ export class MockSpanner { 1 ); if (streamErr) { + call.sendMetadata(new Metadata()); call.emit('error', streamErr); break; } diff --git a/test/spanner.ts b/test/spanner.ts index 4acb15fdf..b9c2ca682 100644 --- a/test/spanner.ts +++ b/test/spanner.ts @@ -1314,6 +1314,68 @@ describe('Spanner with mock server', () => { ); }); + it('should retry UNAVAILABLE during streaming with txn ID from inline begin response with parallel queries', async () => { + const err = { + message: 'Temporary unavailable', + code: grpc.status.UNAVAILABLE, + streamIndex: index, + } as MockError; + spannerMock.setExecutionTime( + spannerMock.executeStreamingSql, + SimulatedExecutionTime.ofError(err) + ); + const database = newTestDatabase(); + + await database.runTransactionAsync(async tx => { + const [rows1, rows2] = await Promise.all([ + tx!.run(selectSql), + tx!.run(selectSql), + ]); + assert.equal(rows1.length, 3); + assert.equal(rows2.length, 3); + await tx.commit(); + }); + await database.close(); + + const requests = spannerMock + .getRequests() + .filter(val => (val as v1.ExecuteSqlRequest).sql) + .map(req => req as v1.ExecuteSqlRequest); + assert.strictEqual(requests.length, 3); + assert.ok( + requests[0].transaction?.begin!.readWrite, + 'inline txn is not set.' + ); + assert.ok( + requests[1].transaction!.id, + 'Transaction ID is not used for retries.' + ); + assert.ok( + requests[1].resumeToken, + 'Resume token is not set for the retried' + ); + const commitRequests = spannerMock + .getRequests() + .filter(val => (val as v1.CommitRequest).mutations) + .map(req => req as v1.CommitRequest); + assert.strictEqual(commitRequests.length, 1); + assert.deepStrictEqual( + requests[1].transaction!.id, + requests[2].transaction!.id + ); + assert.deepStrictEqual( + requests[1].transaction!.id, + commitRequests[0].transactionId + ); + const beginTxnRequests = spannerMock + .getRequests() + .filter( + val => (val as v1.BeginTransactionRequest).options?.readWrite + ) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequests.length, 0); + }); + it('should not retry non-retryable error during streaming', async () => { const database = newTestDatabase(); const err = { @@ -2890,6 +2952,59 @@ describe('Spanner with mock server', () => { }); }); + it('should retry on aborted when running parallel query', async () => { + let attempts = 0; + const database = newTestDatabase(); + const rowCount = await database.runTransactionAsync( + (transaction): Promise => { + if (!attempts) { + spannerMock.abortTransaction(transaction); + } + attempts++; + return Promise.all([ + transaction!.run(selectSql), + transaction!.run(selectSql), + ]).then(([rows1, rows2]) => { + assert.strictEqual(rows1.length, 3); + assert.strictEqual(rows2.length, 3); + return transaction.commit().then(() => rows1.length + rows2.length); + }); + } + ); + assert.strictEqual(rowCount, 6); + assert.strictEqual(attempts, 2); + const requests = spannerMock + .getRequests() + .filter(val => { + return (val as v1.ExecuteSqlRequest).sql === selectSql; + }) + .map(req => req as v1.ExecuteSqlRequest); + + // First request will fail and second blocked request will get discarded, once Abort error is received. + assert.strictEqual(requests.length, 3); + assert.ok( + requests[0].transaction?.begin!.readWrite, + 'Inline txn is not set in request.' + ); + requests.slice(1, 3).forEach((request, index) => { + assert.ok( + request.transaction!.id, + `Transaction ID is not used for retries. ${index}.` + ); + }); + const beginTxnRequest = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequest.length, 1); + const commitRequests = spannerMock + .getRequests() + .filter(val => (val as v1.CommitRequest).mutations) + .map(req => req as v1.CommitRequest); + assert.strictEqual(commitRequests.length, 1); + await database.close(); + }); + it('should retry on aborted update statement', async () => { let attempts = 0; const database = newTestDatabase(); @@ -3211,6 +3326,19 @@ describe('Spanner with mock server', () => { assert.ok(!beginTxnRequest, 'beginTransaction was called'); }); + it('should catch an exception error during invalid queries while using inline begin transaction', async () => { + const database = newTestDatabase(); + await database.runTransactionAsync(async tx => { + try { + await Promise.all([tx!.run(selectSql), tx!.run(invalidSql)]); + await tx.commit(); + } catch (err) { + assert(err, 'Expected an error to be thrown'); + assert.match((err as Error).message, /Table FOO not found/); + } + }); + }); + it('should apply blind writes only once', async () => { const database = newTestDatabase(); let attempts = 0; @@ -3243,10 +3371,11 @@ describe('Spanner with mock server', () => { }) as v1.ExecuteSqlRequest; assert.ok(secondExecuteSqlRequest.transaction?.id); // Verify that we have a BeginTransaction request for the retry. - const beginTxnRequest = spannerMock.getRequests().find(val => { - return (val as v1.BeginTransactionRequest).options?.readWrite; - }) as v1.BeginTransactionRequest; - assert.ok(beginTxnRequest, 'beginTransaction was called'); + const beginTxnRequests = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequests.length, 1); // Verify that we have a single Commit request, and that the Commit request contains only one mutation. assert.strictEqual( 1, @@ -3541,11 +3670,31 @@ describe('Spanner with mock server', () => { await tx.commit(); }); await database.close(); + const beginTxnRequest = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequest.length, 1); + }); - const beginTxnRequest = spannerMock.getRequests().find(val => { - return (val as v1.BeginTransactionRequest).options?.readWrite; - }) as v1.BeginTransactionRequest; - assert.ok(beginTxnRequest, 'beginTransaction was called'); + it('should use beginTransaction on retry for parallel queries', async () => { + const database = newTestDatabase(); + let attempts = 0; + await database.runTransactionAsync(async tx => { + await Promise.all([tx!.run(selectSql), tx!.run(selectSql)]); + if (!attempts) { + spannerMock.abortTransaction(tx); + } + attempts++; + await Promise.all([tx!.run(insertSql), tx!.run(insertSql)]); + await tx.commit(); + }); + await database.close(); + const beginTxnRequest = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequest.length, 1); }); it('should use beginTransaction on retry with excludeTxnFromChangeStreams', async () => { @@ -3565,12 +3714,13 @@ describe('Spanner with mock server', () => { ); 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'); + const beginTxnRequest = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequest.length, 1); assert.strictEqual( - beginTxnRequest.options?.excludeTxnFromChangeStreams, + beginTxnRequest[0].options?.excludeTxnFromChangeStreams, true ); }); @@ -3589,12 +3739,13 @@ describe('Spanner with mock server', () => { }); 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'); + const beginTxnRequest = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequest.length, 1); assert.strictEqual( - beginTxnRequest.options!.readWrite!.readLockMode, + beginTxnRequest[0].options!.readWrite!.readLockMode, 'OPTIMISTIC' ); }); @@ -3616,10 +3767,11 @@ describe('Spanner with mock server', () => { }); 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'); + const beginTxnRequest = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequest.length, 1); }); it('should use beginTransaction on retry for unknown reason with excludeTxnFromChangeStreams', async () => { @@ -3644,12 +3796,13 @@ describe('Spanner with mock server', () => { ); 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'); + const beginTxnRequest = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequest.length, 1); assert.strictEqual( - beginTxnRequest.options?.excludeTxnFromChangeStreams, + beginTxnRequest[0].options?.excludeTxnFromChangeStreams, true ); }); @@ -3671,10 +3824,11 @@ describe('Spanner with mock server', () => { }); 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'); + const beginTxnRequest = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequest.length, 1); }); it('should use beginTransaction for streaming on retry for unknown reason with excludeTxnFromChangeStreams', async () => { @@ -3699,12 +3853,13 @@ describe('Spanner with mock server', () => { ); 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'); + const beginTxnRequest = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequest.length, 1); assert.strictEqual( - beginTxnRequest.options?.excludeTxnFromChangeStreams, + beginTxnRequest[0].options?.excludeTxnFromChangeStreams, true ); }); @@ -3769,10 +3924,11 @@ describe('Spanner with mock server', () => { }); 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'); + const beginTxnRequest = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequest.length, 1); }); it('should run begin transaction on blind commit with excludeTxnFromChangeStreams', async () => { @@ -3788,11 +3944,13 @@ describe('Spanner with mock server', () => { ); await database.close(); - const beginTxnRequest = spannerMock.getRequests().find(val => { - return (val as v1.BeginTransactionRequest).options?.readWrite; - }) as v1.BeginTransactionRequest; + const beginTxnRequest = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequest.length, 1); assert.strictEqual( - beginTxnRequest.options?.excludeTxnFromChangeStreams, + beginTxnRequest[0].options?.excludeTxnFromChangeStreams, true ); }); @@ -3841,12 +3999,13 @@ describe('Spanner with mock server', () => { } ); } catch (e) { - const beginTxnRequest = spannerMock.getRequests().find(val => { - return (val as v1.BeginTransactionRequest).options?.readWrite; - }) as v1.BeginTransactionRequest; - + const beginTxnRequest = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequest.length, 1); assert.strictEqual( - beginTxnRequest.options?.excludeTxnFromChangeStreams, + beginTxnRequest[0].options?.excludeTxnFromChangeStreams, true ); assert.strictEqual( @@ -3897,11 +4056,13 @@ describe('Spanner with mock server', () => { excludeTxnFromChangeStreams: true, } ); - const beginTxnRequest = spannerMock.getRequests().find(val => { - return (val as v1.BeginTransactionRequest).options?.readWrite; - }) as v1.BeginTransactionRequest; + const beginTxnRequest = spannerMock + .getRequests() + .filter(val => (val as v1.BeginTransactionRequest).options?.readWrite) + .map(req => req as v1.BeginTransactionRequest); + assert.deepStrictEqual(beginTxnRequest.length, 1); assert.strictEqual( - beginTxnRequest.options?.excludeTxnFromChangeStreams, + beginTxnRequest[0].options?.excludeTxnFromChangeStreams, true ); await database.close();