@proteinjs/db-driver-spanner 1.17.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/generated/index.js +1 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/generated/test/index.js +1 -1
- package/dist/generated/test/index.js.map +1 -1
- package/dist/src/SpannerDriver.d.ts +23 -0
- package/dist/src/SpannerDriver.d.ts.map +1 -1
- package/dist/src/SpannerDriver.js +46 -16
- package/dist/src/SpannerDriver.js.map +1 -1
- package/dist/test/DmlRetrySafety.test.d.ts +2 -0
- package/dist/test/DmlRetrySafety.test.d.ts.map +1 -0
- package/dist/test/DmlRetrySafety.test.js +472 -0
- package/dist/test/DmlRetrySafety.test.js.map +1 -0
- package/dist/test/RecordIterator.test.d.ts +2 -0
- package/dist/test/RecordIterator.test.d.ts.map +1 -0
- package/dist/test/RecordIterator.test.js +14 -0
- package/dist/test/RecordIterator.test.js.map +1 -0
- package/dist/test/RunPendingMigrations.test.d.ts +2 -0
- package/dist/test/RunPendingMigrations.test.d.ts.map +1 -0
- package/dist/test/RunPendingMigrations.test.js +354 -0
- package/dist/test/RunPendingMigrations.test.js.map +1 -0
- package/dist/test/SpannerOperationDeadline.test.js +1 -1
- package/dist/test/SpannerOperationDeadline.test.js.map +1 -1
- package/generated/index.ts +1 -1
- package/generated/test/index.ts +1 -1
- package/package.json +6 -6
- package/src/SpannerDriver.ts +51 -9
- package/test/DmlRetrySafety.test.ts +305 -0
- package/test/RecordIterator.test.ts +16 -0
- package/test/RunPendingMigrations.test.ts +201 -0
- package/test/SpannerOperationDeadline.test.ts +1 -1
package/generated/test/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ import '@proteinjs/util';
|
|
|
12
12
|
/** Generate Source Graph */
|
|
13
13
|
|
|
14
14
|
const sourceGraph =
|
|
15
|
-
'{"options":{"directed":true,"multigraph":false,"compound":false},"nodes":[{"v":"@proteinjs/db-driver-spanner/TestDbDriverFactory","value":{"packageName":"@proteinjs/db-driver-spanner","name":"TestDbDriverFactory","filePath":"/home/runner/work/db/db/packages/drivers/spanner/test/config/TestDbDriverFactory.ts","qualifiedName":"@proteinjs/db-driver-spanner/TestDbDriverFactory","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[{"name":"getDbDriver","returnType":{"packageName":"@proteinjs/db","name":"DbDriver","filePath":null,"qualifiedName":"@proteinjs/db/DbDriver","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db","name":"DefaultDbDriverFactory","filePath":null,"qualifiedName":"@proteinjs/db/DefaultDbDriverFactory","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db/DefaultDbDriverFactory"},{"v":"@proteinjs/db-driver-spanner/ServiceVerbsDoc","value":{"packageName":"@proteinjs/db-driver-spanner","name":"ServiceVerbsDoc","filePath":"/home/runner/work/db/db/packages/drivers/spanner/test/util/serviceUpdateVerbsTestTables.ts","qualifiedName":"@proteinjs/db-driver-spanner/ServiceVerbsDoc","properties":[{"name":"title","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"scope","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"members","type":{"packageName":"","name":"ReferenceArray<ServiceVerbsDoc>","filePath":null,"qualifiedName":"/ReferenceArray<ServiceVerbsDoc>","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"body","type":{"packageName":"@proteinjs/db-driver-spanner","name":"ServiceVerbsDocBody","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/ServiceVerbsDocBody","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParents":[{"packageName":"@proteinjs/db","name":"Record","filePath":null,"qualifiedName":"@proteinjs/db/Record","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"sourceType":3}},{"v":"@proteinjs/db/Record"},{"v":"@proteinjs/db-driver-spanner/ServiceVerbsDocTable","value":{"packageName":"@proteinjs/db-driver-spanner","name":"ServiceVerbsDocTable","filePath":"/home/runner/work/db/db/packages/drivers/spanner/test/util/serviceUpdateVerbsTestTables.ts","qualifiedName":"@proteinjs/db-driver-spanner/ServiceVerbsDocTable","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"name","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"columns","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[{"packageName":"@proteinjs/db","name":"Table","filePath":null,"qualifiedName":"@proteinjs/db/Table","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[],"typeParameters":["@proteinjs/db-driver-spanner/ServiceVerbsDoc"],"directParentInterfaces":[],"directParentClasses":[]}],"sourceType":2}},{"v":"@proteinjs/db/Table"},{"v":"@proteinjs/db-driver-spanner/SpannerDriver","value":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerDriver","filePath":"/home/runner/work/db/db/packages/drivers/spanner/src/SpannerDriver.ts","qualifiedName":"@proteinjs/db-driver-spanner/SpannerDriver","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"SPANNER","type":{"packageName":"@google-cloud/spanner","name":"Spanner","filePath":null,"qualifiedName":"@google-cloud/spanner/Spanner","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"SPANNER_INSTANCE","type":{"packageName":"@google-cloud/spanner","name":"Instance","filePath":null,"qualifiedName":"@google-cloud/spanner/Instance","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"SPANNER_DB","type":{"packageName":"@google-cloud/spanner","name":"Database","filePath":null,"qualifiedName":"@google-cloud/spanner/Database","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"LIVENESS_MONITOR","type":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerLivenessMonitor","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/SpannerLivenessMonitor","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"CLIENT_GENERATION","type":null,"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"CONSECUTIVE_DEADLINE_FAILURES","type":null,"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"logger","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"config","type":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerConfig","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/SpannerConfig","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"getTable","type":{"packageName":"","name":"((name: string) => Table<any>) | undefined","filePath":null,"qualifiedName":"/((name: string) => Table<any>) | undefined","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[{"name":"getSpanner","returnType":{"packageName":"@google-cloud/spanner","name":"Spanner","filePath":null,"qualifiedName":"@google-cloud/spanner/Spanner","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"getSpannerInstance","returnType":{"packageName":"@google-cloud/spanner","name":"Instance","filePath":null,"qualifiedName":"@google-cloud/spanner/Instance","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"getSpannerDb","returnType":{"packageName":"@google-cloud/spanner","name":"Database","filePath":null,"qualifiedName":"@google-cloud/spanner/Database","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"getDbName","returnType":null,"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"getTableManager","returnType":{"packageName":"@proteinjs/db","name":"TableManager","filePath":null,"qualifiedName":"@proteinjs/db/TableManager","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"getColumnType","returnType":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"tableName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"columnName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"handleCaseSensitivity","returnType":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"tableName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"columnName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"caseSensitive","type":{"packageName":"","name":"boolean","filePath":null,"qualifiedName":"/boolean","typeParameters":null,"directParents":null}}]},{"name":"createDbIfNotExists","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"dbExists","returnType":{"packageName":"","name":"Promise<boolean>","filePath":null,"qualifiedName":"/Promise<boolean>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"databaseName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"runQuery","returnType":{"packageName":"","name":"Promise<any[]>","filePath":null,"qualifiedName":"/Promise<any[]>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"generateStatement","type":{"packageName":"","name":"(config: DbDriverQueryStatementConfig) => Statement","filePath":null,"qualifiedName":"/(config: DbDriverQueryStatementConfig) => Statement","typeParameters":null,"directParents":null}},{"name":"transaction","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"executeQuery","returnType":{"packageName":"","name":"Promise<any[]>","filePath":null,"qualifiedName":"/Promise<any[]>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"generateStatement","type":{"packageName":"","name":"(config: DbDriverQueryStatementConfig) => Statement","filePath":null,"qualifiedName":"/(config: DbDriverQueryStatementConfig) => Statement","typeParameters":null,"directParents":null}},{"name":"runner","type":{"packageName":"","name":"Database | Transaction","filePath":null,"qualifiedName":"/Database | Transaction","typeParameters":null,"directParents":null}}]},{"name":"runDml","returnType":{"packageName":"","name":"Promise<number>","filePath":null,"qualifiedName":"/Promise<number>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"generateStatement","type":{"packageName":"","name":"(config: DbDriverDmlStatementConfig) => Statement","filePath":null,"qualifiedName":"/(config: DbDriverDmlStatementConfig) => Statement","typeParameters":null,"directParents":null}},{"name":"transaction","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"executeDml","returnType":{"packageName":"","name":"Promise<number>","filePath":null,"qualifiedName":"/Promise<number>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"generateStatement","type":{"packageName":"","name":"(config: DbDriverDmlStatementConfig) => Statement","filePath":null,"qualifiedName":"/(config: DbDriverDmlStatementConfig) => Statement","typeParameters":null,"directParents":null}},{"name":"runner","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"runTransaction","returnType":{"packageName":"","name":"Promise<T>","filePath":null,"qualifiedName":"/Promise<T>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fn","type":{"packageName":"","name":"(transaction: Transaction) => Promise<T>","filePath":null,"qualifiedName":"/(transaction: Transaction) => Promise<T>","typeParameters":null,"directParents":null}}]},{"name":"commit","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"transaction","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"rollbackQuietly","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"transaction","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"withDeadline","returnType":{"packageName":"","name":"Promise<T>","filePath":null,"qualifiedName":"/Promise<T>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"op","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"sql","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"promise","type":{"packageName":"","name":"PromiseLike<T>","filePath":null,"qualifiedName":"/PromiseLike<T>","typeParameters":null,"directParents":null}}]},{"name":"recordOpSuccess","returnType":{"packageName":"","name":"void","filePath":null,"qualifiedName":"/void","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"generation","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null}}]},{"name":"recordDeadlineFailure","returnType":{"packageName":"","name":"void","filePath":null,"qualifiedName":"/void","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"generation","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null}}]},{"name":"recycleClient","returnType":{"packageName":"","name":"void","filePath":null,"qualifiedName":"/void","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"operationDeadlineMs","returnType":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"deadlineFailuresBeforeRecycle","returnType":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"runUpdateSchema","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"sql","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db","name":"DbDriver","filePath":null,"qualifiedName":"@proteinjs/db/DbDriver","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db/DbDriver"},{"v":"@proteinjs/db-driver-spanner/SpannerSchemaMetadata","value":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerSchemaMetadata","filePath":"/home/runner/work/db/db/packages/drivers/spanner/src/SpannerSchemaMetadata.ts","qualifiedName":"@proteinjs/db-driver-spanner/SpannerSchemaMetadata","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[{"name":"tableExists","returnType":{"packageName":"","name":"Promise<boolean>","filePath":null,"qualifiedName":"/Promise<boolean>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getColumnMetadata","returnType":{"packageName":"","name":"Promise<{ [columnName: string]: { type: string; isNullable: boolean } }>","filePath":null,"qualifiedName":"/Promise<{ [columnName: string]: { type: string; isNullable: boolean } }>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getPrimaryKey","returnType":{"packageName":"","name":"Promise<string[]>","filePath":null,"qualifiedName":"/Promise<string[]>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getForeignKeys","returnType":{"packageName":"","name":"Promise<{ [columnName: string]: { referencedTableName: string; referencedColumnName: string } }>","filePath":null,"qualifiedName":"/Promise<{ [columnName: string]: { referencedTableName: string; referencedColumnName: string } }>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getUniqueColumns","returnType":{"packageName":"","name":"Promise<string[]>","filePath":null,"qualifiedName":"/Promise<string[]>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getIndexes","returnType":{"packageName":"","name":"Promise<{ [keyName: string]: string[] }>","filePath":null,"qualifiedName":"/Promise<{ [keyName: string]: string[] }>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[{"packageName":"@proteinjs/db","name":"SchemaMetadata","filePath":null,"qualifiedName":"@proteinjs/db/SchemaMetadata","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[]}],"sourceType":2}},{"v":"@proteinjs/db/SchemaMetadata"},{"v":"@proteinjs/db-driver-spanner/SpannerSchemaOperations","value":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerSchemaOperations","filePath":"/home/runner/work/db/db/packages/drivers/spanner/src/SpannerSchemaOperations.ts","qualifiedName":"@proteinjs/db-driver-spanner/SpannerSchemaOperations","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"logger","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"spannerDriver","type":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerDriver","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/SpannerDriver","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"}],"methods":[{"name":"createTable","returnType":null,"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"alterTable","returnType":null,"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}},{"name":"tableChanges","type":{"packageName":"@proteinjs/db","name":"TableChanges","filePath":null,"qualifiedName":"@proteinjs/db/TableChanges","typeParameters":null,"directParents":null}}]},{"name":"stringWideningStatement","returnType":{"packageName":"","name":"string | null","filePath":null,"qualifiedName":"/string | null","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}},{"name":"change","type":{"packageName":"@proteinjs/db-driver-spanner","name":"{ name: string; newType: string; oldType: string }","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/{ name: string; newType: string; oldType: string }","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db","name":"SchemaOperations","filePath":null,"qualifiedName":"@proteinjs/db/SchemaOperations","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db/SchemaOperations"}],"edges":[{"v":"@proteinjs/db-driver-spanner/TestDbDriverFactory","w":"@proteinjs/db/DefaultDbDriverFactory","value":"implements interface"},{"v":"@proteinjs/db-driver-spanner/ServiceVerbsDoc","w":"@proteinjs/db/Record","value":"extends interface"},{"v":"@proteinjs/db-driver-spanner/ServiceVerbsDocTable","w":"@proteinjs/db/Table","value":"extends class"},{"v":"@proteinjs/db-driver-spanner/SpannerDriver","w":"@proteinjs/db/DbDriver","value":"implements interface"},{"v":"@proteinjs/db-driver-spanner/SpannerSchemaMetadata","w":"@proteinjs/db/SchemaMetadata","value":"extends class"},{"v":"@proteinjs/db-driver-spanner/SpannerSchemaOperations","w":"@proteinjs/db/SchemaOperations","value":"implements interface"}]}';
|
|
15
|
+
'{"options":{"directed":true,"multigraph":false,"compound":false},"nodes":[{"v":"@proteinjs/db-driver-spanner/TestDbDriverFactory","value":{"packageName":"@proteinjs/db-driver-spanner","name":"TestDbDriverFactory","filePath":"/home/runner/work/db/db/packages/drivers/spanner/test/config/TestDbDriverFactory.ts","qualifiedName":"@proteinjs/db-driver-spanner/TestDbDriverFactory","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[{"name":"getDbDriver","returnType":{"packageName":"@proteinjs/db","name":"DbDriver","filePath":null,"qualifiedName":"@proteinjs/db/DbDriver","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db","name":"DefaultDbDriverFactory","filePath":null,"qualifiedName":"@proteinjs/db/DefaultDbDriverFactory","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db/DefaultDbDriverFactory"},{"v":"@proteinjs/db-driver-spanner/ServiceVerbsDoc","value":{"packageName":"@proteinjs/db-driver-spanner","name":"ServiceVerbsDoc","filePath":"/home/runner/work/db/db/packages/drivers/spanner/test/util/serviceUpdateVerbsTestTables.ts","qualifiedName":"@proteinjs/db-driver-spanner/ServiceVerbsDoc","properties":[{"name":"title","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"scope","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"members","type":{"packageName":"","name":"ReferenceArray<ServiceVerbsDoc>","filePath":null,"qualifiedName":"/ReferenceArray<ServiceVerbsDoc>","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"body","type":{"packageName":"@proteinjs/db-driver-spanner","name":"ServiceVerbsDocBody","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/ServiceVerbsDocBody","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParents":[{"packageName":"@proteinjs/db","name":"Record","filePath":null,"qualifiedName":"@proteinjs/db/Record","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"sourceType":3}},{"v":"@proteinjs/db/Record"},{"v":"@proteinjs/db-driver-spanner/ServiceVerbsDocTable","value":{"packageName":"@proteinjs/db-driver-spanner","name":"ServiceVerbsDocTable","filePath":"/home/runner/work/db/db/packages/drivers/spanner/test/util/serviceUpdateVerbsTestTables.ts","qualifiedName":"@proteinjs/db-driver-spanner/ServiceVerbsDocTable","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"name","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"columns","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[{"packageName":"@proteinjs/db","name":"Table","filePath":null,"qualifiedName":"@proteinjs/db/Table","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[],"typeParameters":["@proteinjs/db-driver-spanner/ServiceVerbsDoc"],"directParentInterfaces":[],"directParentClasses":[]}],"sourceType":2}},{"v":"@proteinjs/db/Table"},{"v":"@proteinjs/db-driver-spanner/SpannerDriver","value":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerDriver","filePath":"/home/runner/work/db/db/packages/drivers/spanner/src/SpannerDriver.ts","qualifiedName":"@proteinjs/db-driver-spanner/SpannerDriver","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"SPANNER","type":{"packageName":"@google-cloud/spanner","name":"Spanner","filePath":null,"qualifiedName":"@google-cloud/spanner/Spanner","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"SPANNER_INSTANCE","type":{"packageName":"@google-cloud/spanner","name":"Instance","filePath":null,"qualifiedName":"@google-cloud/spanner/Instance","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"SPANNER_DB","type":{"packageName":"@google-cloud/spanner","name":"Database","filePath":null,"qualifiedName":"@google-cloud/spanner/Database","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"LIVENESS_MONITOR","type":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerLivenessMonitor","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/SpannerLivenessMonitor","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"CLIENT_GENERATION","type":null,"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"CONSECUTIVE_DEADLINE_FAILURES","type":null,"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"logger","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"config","type":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerConfig","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/SpannerConfig","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"getTable","type":{"packageName":"","name":"((name: string) => Table<any>) | undefined","filePath":null,"qualifiedName":"/((name: string) => Table<any>) | undefined","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[{"name":"getSessionPoolStats","returnType":{"packageName":"","name":"SpannerSessionPoolStats | undefined","filePath":null,"qualifiedName":"/SpannerSessionPoolStats | undefined","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"public","parameters":[]},{"name":"getSpanner","returnType":{"packageName":"@google-cloud/spanner","name":"Spanner","filePath":null,"qualifiedName":"@google-cloud/spanner/Spanner","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"getSpannerInstance","returnType":{"packageName":"@google-cloud/spanner","name":"Instance","filePath":null,"qualifiedName":"@google-cloud/spanner/Instance","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"getSpannerDb","returnType":{"packageName":"@google-cloud/spanner","name":"Database","filePath":null,"qualifiedName":"@google-cloud/spanner/Database","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"getDbName","returnType":null,"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"getTableManager","returnType":{"packageName":"@proteinjs/db","name":"TableManager","filePath":null,"qualifiedName":"@proteinjs/db/TableManager","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"getColumnType","returnType":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"tableName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"columnName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"handleCaseSensitivity","returnType":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"tableName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"columnName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"caseSensitive","type":{"packageName":"","name":"boolean","filePath":null,"qualifiedName":"/boolean","typeParameters":null,"directParents":null}}]},{"name":"createDbIfNotExists","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"dbExists","returnType":{"packageName":"","name":"Promise<boolean>","filePath":null,"qualifiedName":"/Promise<boolean>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"databaseName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"runQuery","returnType":{"packageName":"","name":"Promise<any[]>","filePath":null,"qualifiedName":"/Promise<any[]>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"generateStatement","type":{"packageName":"","name":"(config: DbDriverQueryStatementConfig) => Statement","filePath":null,"qualifiedName":"/(config: DbDriverQueryStatementConfig) => Statement","typeParameters":null,"directParents":null}},{"name":"transaction","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"executeQuery","returnType":{"packageName":"","name":"Promise<any[]>","filePath":null,"qualifiedName":"/Promise<any[]>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"generateStatement","type":{"packageName":"","name":"(config: DbDriverQueryStatementConfig) => Statement","filePath":null,"qualifiedName":"/(config: DbDriverQueryStatementConfig) => Statement","typeParameters":null,"directParents":null}},{"name":"runner","type":{"packageName":"","name":"Database | Transaction","filePath":null,"qualifiedName":"/Database | Transaction","typeParameters":null,"directParents":null}}]},{"name":"runDml","returnType":{"packageName":"","name":"Promise<number>","filePath":null,"qualifiedName":"/Promise<number>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"generateStatement","type":{"packageName":"","name":"(config: DbDriverDmlStatementConfig) => Statement","filePath":null,"qualifiedName":"/(config: DbDriverDmlStatementConfig) => Statement","typeParameters":null,"directParents":null}},{"name":"transaction","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"executeDml","returnType":{"packageName":"","name":"Promise<number>","filePath":null,"qualifiedName":"/Promise<number>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"generateStatement","type":{"packageName":"","name":"(config: DbDriverDmlStatementConfig) => Statement","filePath":null,"qualifiedName":"/(config: DbDriverDmlStatementConfig) => Statement","typeParameters":null,"directParents":null}},{"name":"runner","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"runTransaction","returnType":{"packageName":"","name":"Promise<T>","filePath":null,"qualifiedName":"/Promise<T>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fn","type":{"packageName":"","name":"(transaction: Transaction) => Promise<T>","filePath":null,"qualifiedName":"/(transaction: Transaction) => Promise<T>","typeParameters":null,"directParents":null}}]},{"name":"commit","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"transaction","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"rollbackQuietly","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"transaction","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"withDeadline","returnType":{"packageName":"","name":"Promise<T>","filePath":null,"qualifiedName":"/Promise<T>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"op","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"sql","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"promise","type":{"packageName":"","name":"PromiseLike<T>","filePath":null,"qualifiedName":"/PromiseLike<T>","typeParameters":null,"directParents":null}}]},{"name":"recordOpSuccess","returnType":{"packageName":"","name":"void","filePath":null,"qualifiedName":"/void","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"generation","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null}}]},{"name":"recordDeadlineFailure","returnType":{"packageName":"","name":"void","filePath":null,"qualifiedName":"/void","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"generation","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null}}]},{"name":"recycleClient","returnType":{"packageName":"","name":"void","filePath":null,"qualifiedName":"/void","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"dmlGaxOptions","returnType":{"packageName":"@proteinjs/db-driver-spanner","name":"{ timeout: number; retry?: null }","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/{ timeout: number; retry?: null }","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"operationDeadlineMs","returnType":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"deadlineFailuresBeforeRecycle","returnType":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"runUpdateSchema","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"sql","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db","name":"DbDriver","filePath":null,"qualifiedName":"@proteinjs/db/DbDriver","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db/DbDriver"},{"v":"@proteinjs/db-driver-spanner/SpannerSchemaMetadata","value":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerSchemaMetadata","filePath":"/home/runner/work/db/db/packages/drivers/spanner/src/SpannerSchemaMetadata.ts","qualifiedName":"@proteinjs/db-driver-spanner/SpannerSchemaMetadata","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[{"name":"tableExists","returnType":{"packageName":"","name":"Promise<boolean>","filePath":null,"qualifiedName":"/Promise<boolean>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getColumnMetadata","returnType":{"packageName":"","name":"Promise<{ [columnName: string]: { type: string; isNullable: boolean } }>","filePath":null,"qualifiedName":"/Promise<{ [columnName: string]: { type: string; isNullable: boolean } }>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getPrimaryKey","returnType":{"packageName":"","name":"Promise<string[]>","filePath":null,"qualifiedName":"/Promise<string[]>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getForeignKeys","returnType":{"packageName":"","name":"Promise<{ [columnName: string]: { referencedTableName: string; referencedColumnName: string } }>","filePath":null,"qualifiedName":"/Promise<{ [columnName: string]: { referencedTableName: string; referencedColumnName: string } }>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getUniqueColumns","returnType":{"packageName":"","name":"Promise<string[]>","filePath":null,"qualifiedName":"/Promise<string[]>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getIndexes","returnType":{"packageName":"","name":"Promise<{ [keyName: string]: string[] }>","filePath":null,"qualifiedName":"/Promise<{ [keyName: string]: string[] }>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[{"packageName":"@proteinjs/db","name":"SchemaMetadata","filePath":null,"qualifiedName":"@proteinjs/db/SchemaMetadata","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[]}],"sourceType":2}},{"v":"@proteinjs/db/SchemaMetadata"},{"v":"@proteinjs/db-driver-spanner/SpannerSchemaOperations","value":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerSchemaOperations","filePath":"/home/runner/work/db/db/packages/drivers/spanner/src/SpannerSchemaOperations.ts","qualifiedName":"@proteinjs/db-driver-spanner/SpannerSchemaOperations","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"logger","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"spannerDriver","type":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerDriver","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/SpannerDriver","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"}],"methods":[{"name":"createTable","returnType":null,"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"alterTable","returnType":null,"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}},{"name":"tableChanges","type":{"packageName":"@proteinjs/db","name":"TableChanges","filePath":null,"qualifiedName":"@proteinjs/db/TableChanges","typeParameters":null,"directParents":null}}]},{"name":"stringWideningStatement","returnType":{"packageName":"","name":"string | null","filePath":null,"qualifiedName":"/string | null","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}},{"name":"change","type":{"packageName":"@proteinjs/db-driver-spanner","name":"{ name: string; newType: string; oldType: string }","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/{ name: string; newType: string; oldType: string }","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db","name":"SchemaOperations","filePath":null,"qualifiedName":"@proteinjs/db/SchemaOperations","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db/SchemaOperations"}],"edges":[{"v":"@proteinjs/db-driver-spanner/TestDbDriverFactory","w":"@proteinjs/db/DefaultDbDriverFactory","value":"implements interface"},{"v":"@proteinjs/db-driver-spanner/ServiceVerbsDoc","w":"@proteinjs/db/Record","value":"extends interface"},{"v":"@proteinjs/db-driver-spanner/ServiceVerbsDocTable","w":"@proteinjs/db/Table","value":"extends class"},{"v":"@proteinjs/db-driver-spanner/SpannerDriver","w":"@proteinjs/db/DbDriver","value":"implements interface"},{"v":"@proteinjs/db-driver-spanner/SpannerSchemaMetadata","w":"@proteinjs/db/SchemaMetadata","value":"extends class"},{"v":"@proteinjs/db-driver-spanner/SpannerSchemaOperations","w":"@proteinjs/db/SchemaOperations","value":"implements interface"}]}';
|
|
16
16
|
|
|
17
17
|
/** Generate Source Links */
|
|
18
18
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proteinjs/db-driver-spanner",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"main": "./dist/generated/index.js",
|
|
5
5
|
"types": "./dist/generated/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@google-cloud/spanner": "7.5.0",
|
|
45
|
-
"@proteinjs/db": "^1.
|
|
46
|
-
"@proteinjs/db-query": "^1.
|
|
47
|
-
"@proteinjs/db-spanner-common": "^1.3.
|
|
48
|
-
"@proteinjs/db-transaction-context": "^0.5.
|
|
45
|
+
"@proteinjs/db": "^1.31.0",
|
|
46
|
+
"@proteinjs/db-query": "^1.7.0",
|
|
47
|
+
"@proteinjs/db-spanner-common": "^1.3.7",
|
|
48
|
+
"@proteinjs/db-transaction-context": "^0.5.7",
|
|
49
49
|
"@proteinjs/logger": "^1.0.21",
|
|
50
50
|
"@proteinjs/reflection": "^1.1.12",
|
|
51
51
|
"@proteinjs/util": "^1.6.1"
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"ts-jest": "29.1.1",
|
|
64
64
|
"typescript": "5.2.2"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "d84e548336f96b2541c1fbbe6a9ad6074df610c7"
|
|
67
67
|
}
|
package/src/SpannerDriver.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
tableByName,
|
|
9
9
|
} from '@proteinjs/db';
|
|
10
10
|
import { SpannerConfig } from './SpannerConfig';
|
|
11
|
-
import { SpannerLivenessMonitor } from './SpannerLivenessMonitor';
|
|
11
|
+
import { SpannerLivenessMonitor, type SpannerSessionPoolStats } from './SpannerLivenessMonitor';
|
|
12
12
|
import { Logger } from '@proteinjs/logger';
|
|
13
13
|
import { Statement } from '@proteinjs/db-query';
|
|
14
14
|
import { SpannerSchemaOperations } from './SpannerSchemaOperations';
|
|
@@ -40,6 +40,18 @@ export class SpannerDriver implements DbDriver {
|
|
|
40
40
|
this.getTable = getTable;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The session-pool gauge (P4a), read-only — the four numbers that make pool exhaustion
|
|
45
|
+
* observable, for external observers (the ops monitors platform). Undefined until the process
|
|
46
|
+
* has connected a Database (no pool exists to gauge yet).
|
|
47
|
+
*/
|
|
48
|
+
static getSessionPoolStats(): SpannerSessionPoolStats | undefined {
|
|
49
|
+
if (!SpannerDriver.SPANNER_DB) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return SpannerDriver.LIVENESS_MONITOR.poolStats();
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
private getSpanner(): Spanner {
|
|
44
56
|
if (!SpannerDriver.SPANNER) {
|
|
45
57
|
// gRPC channel keepalive (2026-07-11 flow-hang investigation): a call in flight on a dead
|
|
@@ -268,18 +280,25 @@ export class SpannerDriver implements DbDriver {
|
|
|
268
280
|
|
|
269
281
|
try {
|
|
270
282
|
this.logger.debug({ message: `Executing dml`, obj: { sql, params: namedParams } });
|
|
271
|
-
|
|
283
|
+
// DML rides the unary ExecuteBatchDml RPC (`batchUpdate`), never streaming `runUpdate`
|
|
284
|
+
// (ExecuteStreamingSql). The client's streaming transport TRANSPARENTLY RE-SENDS a DML
|
|
285
|
+
// whose response was lost: gax wraps every server-streaming call in retry-request, which
|
|
286
|
+
// silently replays on ANY pre-response error. Seqno replay protection only covers the
|
|
287
|
+
// same-transaction geometry; the geometries it cannot cover are where the 2026-08-13
|
|
288
|
+
// splice incident lived — an inline-begin replay BEGINS A FRESH TRANSACTION per attempt
|
|
289
|
+
// (abandoned applied-but-uncommitted siblings churn locks and collide with rows committed
|
|
290
|
+
// by other paths: spurious `6 ALREADY_EXISTS` under pool pressure), and the stream-
|
|
291
|
+
// resumption layer re-mints a NEW seqno into the SAME transaction after inline-begin
|
|
292
|
+
// learned its id (unprotected even on real Spanner). The unary RPC has none of that
|
|
293
|
+
// machinery — see dmlGaxOptions() for the per-backend retry policy on it.
|
|
294
|
+
const [rowCounts] = await this.withDeadline(
|
|
272
295
|
'spanner dml',
|
|
273
296
|
sql,
|
|
274
|
-
runner.
|
|
275
|
-
|
|
276
|
-
params: namedParams?.params,
|
|
277
|
-
types: namedParams?.types,
|
|
278
|
-
// gRPC deadline: cancels the RPC on a dead channel so the transaction's run function
|
|
279
|
-
// settles and the library releases the session (see runDml).
|
|
280
|
-
gaxOptions: { timeout: this.operationDeadlineMs() },
|
|
297
|
+
runner.batchUpdate([{ sql, params: namedParams?.params, types: namedParams?.types }], {
|
|
298
|
+
gaxOptions: this.dmlGaxOptions(),
|
|
281
299
|
})
|
|
282
300
|
);
|
|
301
|
+
const rowCount = rowCounts[0] ?? 0;
|
|
283
302
|
const durationMs = Number(process.hrtime.bigint() - startTime) / 1_000_000;
|
|
284
303
|
this.logger.debug({
|
|
285
304
|
message: `Dml executed`,
|
|
@@ -500,6 +519,29 @@ export class SpannerDriver implements DbDriver {
|
|
|
500
519
|
}
|
|
501
520
|
}
|
|
502
521
|
|
|
522
|
+
/**
|
|
523
|
+
* Per-call gax options for the DML RPC (ExecuteBatchDml). The gRPC deadline (see runDml)
|
|
524
|
+
* applies everywhere. The transparent-retry policy differs by backend:
|
|
525
|
+
*
|
|
526
|
+
* - Real Spanner: gax's default unary policy (UNAVAILABLE only) stands. A replay re-sends the
|
|
527
|
+
* identical request — same seqno, deduped in the same-transaction geometry — and in the
|
|
528
|
+
* inline-begin geometry only the transaction the client ultimately commits applies durably,
|
|
529
|
+
* so the transient-blip resilience is safe there.
|
|
530
|
+
* - Emulator (SPANNER_EMULATOR_HOST — the same switch the client library keys on): a replayed
|
|
531
|
+
* inline-begin DML begins a fresh transaction per attempt, and the abandoned
|
|
532
|
+
* applied-but-uncommitted siblings are exactly what fed the splice incident's spurious
|
|
533
|
+
* failures under pool pressure. `retry: null` makes the call single-attempt: a lost response
|
|
534
|
+
* surfaces as the loss to the caller, deterministically, instead of invisible multi-
|
|
535
|
+
* transaction churn. Loopback needs no blip resilience.
|
|
536
|
+
*/
|
|
537
|
+
private dmlGaxOptions(): { timeout: number; retry?: null } {
|
|
538
|
+
const gaxOptions: { timeout: number; retry?: null } = { timeout: this.operationDeadlineMs() };
|
|
539
|
+
if (process.env.SPANNER_EMULATOR_HOST) {
|
|
540
|
+
gaxOptions.retry = null;
|
|
541
|
+
}
|
|
542
|
+
return gaxOptions;
|
|
543
|
+
}
|
|
544
|
+
|
|
503
545
|
private operationDeadlineMs(): number {
|
|
504
546
|
return this.config.operationDeadlineMs ?? 60_000;
|
|
505
547
|
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { PassThrough } from 'stream';
|
|
2
|
+
import { Db, Record, StringColumn, Table, tableByName, withRecordColumns } from '@proteinjs/db';
|
|
3
|
+
import { TransactionContext } from '@proteinjs/db-transaction-context';
|
|
4
|
+
import { SpannerDriver } from '@proteinjs/db-driver-spanner';
|
|
5
|
+
import { registerTestUser, clearTestUser } from '@proteinjs/db/test';
|
|
6
|
+
import { SourceRepository } from '@proteinjs/reflection';
|
|
7
|
+
import { getDropTestTable } from './util/getDropTestTable';
|
|
8
|
+
import { SpannerEmulatorProvisioner } from './util/SpannerEmulatorProvisioner';
|
|
9
|
+
import '../generated/test/index';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* DML retry safety (the 2026-08-13 splice incident class): a DML request whose response is LOST
|
|
13
|
+
* in transport must NEVER be transparently re-sent by the client. On the pre-fix streaming DML
|
|
14
|
+
* path (`runUpdate` → ExecuteStreamingSql), gax wraps every attempt in retry-request, which
|
|
15
|
+
* silently replays on ANY pre-response error. Seqno replay protection only covers the
|
|
16
|
+
* same-transaction geometry; the replay geometries it cannot cover are where the incident lived:
|
|
17
|
+
* an inline-begin replay BEGINS A FRESH TRANSACTION per attempt (abandoned applied-but-
|
|
18
|
+
* uncommitted siblings churn locks and collide with rows committed by other paths — the
|
|
19
|
+
* spurious `6 ALREADY_EXISTS` under pool pressure), and the stream-resumption layer re-mints a
|
|
20
|
+
* NEW seqno into the SAME transaction after inline-begin learned its id (unprotected even on
|
|
21
|
+
* real Spanner).
|
|
22
|
+
*
|
|
23
|
+
* These tests drive the retry deterministically by injecting loss at the TRANSPORT SEAM — the
|
|
24
|
+
* resolved gRPC stub method, which the gapic layer looks up per attempt
|
|
25
|
+
* (`stub[methodName].apply`), i.e. the exact function every transparent retry re-invokes.
|
|
26
|
+
*
|
|
27
|
+
* Contract under test (the fix): DML rides the unary ExecuteBatchDml RPC, and under the emulator
|
|
28
|
+
* the call is single-attempt (`retry: null`) — a loss surfaces as the loss itself, after at most
|
|
29
|
+
* ONE wire execution.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
interface RetryAnchor extends Record {
|
|
33
|
+
name: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface RetryTarget extends Record {
|
|
37
|
+
name: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class RetryAnchorTestTable extends Table<RetryAnchor> {
|
|
41
|
+
name = 'db_test_dml_retry_anchor';
|
|
42
|
+
columns = withRecordColumns<RetryAnchor>({
|
|
43
|
+
name: new StringColumn('name'),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class RetryTargetTestTable extends Table<RetryTarget> {
|
|
48
|
+
name = 'db_test_dml_retry_target';
|
|
49
|
+
columns = withRecordColumns<RetryTarget>({
|
|
50
|
+
name: new StringColumn('name'),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const anchorTable: Table<RetryAnchor> = new RetryAnchorTestTable();
|
|
55
|
+
const targetTable: Table<RetryTarget> = new RetryTargetTestTable();
|
|
56
|
+
// Local tables — not in any reflection source graph, so thread getTable explicitly (the
|
|
57
|
+
// TransactionSafety pattern); other lookups resolve normally.
|
|
58
|
+
const getTable = (tableName: string) => {
|
|
59
|
+
if (tableName === anchorTable.name) {
|
|
60
|
+
return anchorTable;
|
|
61
|
+
}
|
|
62
|
+
if (tableName === targetTable.name) {
|
|
63
|
+
return targetTable;
|
|
64
|
+
}
|
|
65
|
+
return tableByName(tableName);
|
|
66
|
+
};
|
|
67
|
+
const spannerConfig = {
|
|
68
|
+
projectId: 'proteinjs-test',
|
|
69
|
+
instanceName: 'proteinjs-test',
|
|
70
|
+
databaseName: 'test',
|
|
71
|
+
};
|
|
72
|
+
const spannerDriver = new SpannerDriver(spannerConfig, getTable);
|
|
73
|
+
|
|
74
|
+
const INJECTED_LOSS = 'injected response loss';
|
|
75
|
+
const targetInsertSqlMarker = `INSERT INTO \`db_test_dml_retry_target\``;
|
|
76
|
+
const isTargetInsertSql = (sql: string) => sql.includes(targetInsertSqlMarker);
|
|
77
|
+
|
|
78
|
+
type StubPatch = { attempts: () => number; restore: () => void };
|
|
79
|
+
/**
|
|
80
|
+
* 'applied-then-lost': the first attempt reaches the emulator (the statement EXECUTES
|
|
81
|
+
* server-side) but its response is dropped — the incident's loss shape. 'lost-in-flight': the
|
|
82
|
+
* first attempt never reaches the emulator — a transparent replay makes the op silently
|
|
83
|
+
* SUCCEED behind a "failed" attempt (the smoking gun that a replay layer exists).
|
|
84
|
+
*/
|
|
85
|
+
type LossMode = 'applied-then-lost' | 'lost-in-flight';
|
|
86
|
+
|
|
87
|
+
/** The resolved gRPC stub the gapic layer re-invokes per attempt — the transport seam. */
|
|
88
|
+
const getSpannerStub = async (): Promise<any> => {
|
|
89
|
+
const spanner = (SpannerDriver as unknown as { SPANNER?: any }).SPANNER;
|
|
90
|
+
if (!spanner) {
|
|
91
|
+
throw new Error('SpannerDriver.SPANNER not initialized — run an op first');
|
|
92
|
+
}
|
|
93
|
+
const gapicClient = spanner.clients_.get('SpannerClient');
|
|
94
|
+
if (!gapicClient) {
|
|
95
|
+
throw new Error('SpannerClient gapic client not created — run a data op first');
|
|
96
|
+
}
|
|
97
|
+
return await gapicClient.spannerStub;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const makeLossError = (): Error => {
|
|
101
|
+
const error: any = new Error(INJECTED_LOSS);
|
|
102
|
+
error.code = 14; // UNAVAILABLE — the shape of a dropped connection / lost response
|
|
103
|
+
return error;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/** Inject loss on the FIRST matching ExecuteStreamingSql attempt; later attempts pass through. */
|
|
107
|
+
const patchStreamingLoss = (stub: any, isTargetSql: (sql: string) => boolean, mode: LossMode): StubPatch => {
|
|
108
|
+
const original = stub.executeStreamingSql;
|
|
109
|
+
let attempts = 0;
|
|
110
|
+
stub.executeStreamingSql = function (this: any, ...args: any[]) {
|
|
111
|
+
const request = args[0];
|
|
112
|
+
if (typeof request?.sql !== 'string' || !isTargetSql(request.sql)) {
|
|
113
|
+
return original.apply(this, args);
|
|
114
|
+
}
|
|
115
|
+
attempts += 1;
|
|
116
|
+
if (attempts > 1) {
|
|
117
|
+
return original.apply(this, args);
|
|
118
|
+
}
|
|
119
|
+
const wrapper: any = new PassThrough({ objectMode: true });
|
|
120
|
+
let injected = false;
|
|
121
|
+
const inject = () => {
|
|
122
|
+
if (injected) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
injected = true;
|
|
126
|
+
wrapper.emit('error', makeLossError());
|
|
127
|
+
};
|
|
128
|
+
if (mode === 'lost-in-flight') {
|
|
129
|
+
wrapper.cancel = () => undefined;
|
|
130
|
+
setImmediate(inject);
|
|
131
|
+
return wrapper;
|
|
132
|
+
}
|
|
133
|
+
const real = original.apply(this, args);
|
|
134
|
+
wrapper.cancel = () => real.cancel?.();
|
|
135
|
+
real.on('data', () => undefined); // drain — the response is consumed and dropped
|
|
136
|
+
real.on('metadata', () => undefined);
|
|
137
|
+
real.on('error', inject);
|
|
138
|
+
real.on('status', inject);
|
|
139
|
+
real.on('end', inject);
|
|
140
|
+
return wrapper;
|
|
141
|
+
};
|
|
142
|
+
return { attempts: () => attempts, restore: () => (stub.executeStreamingSql = original) };
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/** Inject loss on the FIRST matching ExecuteBatchDml attempt; later attempts pass through. */
|
|
146
|
+
const patchUnaryLoss = (stub: any, isTargetSql: (sql: string) => boolean, mode: LossMode): StubPatch => {
|
|
147
|
+
const original = stub.executeBatchDml;
|
|
148
|
+
let attempts = 0;
|
|
149
|
+
stub.executeBatchDml = function (this: any, ...args: any[]) {
|
|
150
|
+
const request = args[0];
|
|
151
|
+
const statements: any[] = request?.statements ?? [];
|
|
152
|
+
if (!statements.some((statement) => typeof statement?.sql === 'string' && isTargetSql(statement.sql))) {
|
|
153
|
+
return original.apply(this, args);
|
|
154
|
+
}
|
|
155
|
+
attempts += 1;
|
|
156
|
+
if (attempts > 1) {
|
|
157
|
+
return original.apply(this, args);
|
|
158
|
+
}
|
|
159
|
+
const callback = args[args.length - 1];
|
|
160
|
+
if (typeof callback !== 'function') {
|
|
161
|
+
throw new Error('expected trailing callback on unary stub call');
|
|
162
|
+
}
|
|
163
|
+
if (mode === 'lost-in-flight') {
|
|
164
|
+
setImmediate(() => callback(makeLossError()));
|
|
165
|
+
return { cancel: () => undefined };
|
|
166
|
+
}
|
|
167
|
+
const lossyCallback = () => callback(makeLossError());
|
|
168
|
+
return original.apply(this, [...args.slice(0, -1), lossyCallback]);
|
|
169
|
+
};
|
|
170
|
+
return { attempts: () => attempts, restore: () => (stub.executeBatchDml = original) };
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/** Pass-through counter for DML on the streaming transport — must stay ZERO post-fix. */
|
|
174
|
+
const patchStreamingDmlCounter = (stub: any, isTargetSql: (sql: string) => boolean): StubPatch => {
|
|
175
|
+
const original = stub.executeStreamingSql;
|
|
176
|
+
let attempts = 0;
|
|
177
|
+
stub.executeStreamingSql = function (this: any, ...args: any[]) {
|
|
178
|
+
if (typeof args[0]?.sql === 'string' && isTargetSql(args[0].sql)) {
|
|
179
|
+
attempts += 1;
|
|
180
|
+
}
|
|
181
|
+
return original.apply(this, args);
|
|
182
|
+
};
|
|
183
|
+
return { attempts: () => attempts, restore: () => (stub.executeStreamingSql = original) };
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
describe('DML retry safety (a lost response must never re-execute a statement)', () => {
|
|
187
|
+
const dropTable = getDropTestTable(spannerDriver);
|
|
188
|
+
const db = new Db(spannerDriver, getTable, new TransactionContext());
|
|
189
|
+
|
|
190
|
+
beforeAll(async () => {
|
|
191
|
+
registerTestUser();
|
|
192
|
+
// HERMETIC two-table world (the TransactionSafety pattern): the delete path's
|
|
193
|
+
// reverse-cascade scan walks getTables(); scope the registry to the local tables.
|
|
194
|
+
(SourceRepository.get() as unknown as { objectCache: { [key: string]: unknown[] } }).objectCache[
|
|
195
|
+
'@proteinjs/db/Table'
|
|
196
|
+
] = [anchorTable, targetTable];
|
|
197
|
+
await SpannerEmulatorProvisioner.ensureProvisioned(spannerConfig);
|
|
198
|
+
await spannerDriver.createDbIfNotExists();
|
|
199
|
+
await spannerDriver.getTableManager().loadTable(anchorTable);
|
|
200
|
+
await spannerDriver.getTableManager().loadTable(targetTable);
|
|
201
|
+
// Warm the data client so the gapic SpannerClient + resolved stub exist to patch.
|
|
202
|
+
await db.query(targetTable, { name: 'warmup' });
|
|
203
|
+
}, 60000);
|
|
204
|
+
|
|
205
|
+
afterAll(async () => {
|
|
206
|
+
await dropTable(targetTable);
|
|
207
|
+
await dropTable(anchorTable);
|
|
208
|
+
await SpannerEmulatorProvisioner.release();
|
|
209
|
+
delete (SourceRepository.get() as unknown as { objectCache: { [key: string]: unknown[] } }).objectCache[
|
|
210
|
+
'@proteinjs/db/Table'
|
|
211
|
+
];
|
|
212
|
+
clearTestUser();
|
|
213
|
+
}, 60000);
|
|
214
|
+
|
|
215
|
+
afterEach(async () => {
|
|
216
|
+
// Tests assert their own outcomes; this just keeps rows from leaking across tests.
|
|
217
|
+
for (const table of [targetTable, anchorTable]) {
|
|
218
|
+
const leftovers = await db.query(table as Table<any>, {});
|
|
219
|
+
for (const row of leftovers) {
|
|
220
|
+
await db.delete(table as Table<any>, { id: row.id } as any);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}, 30000);
|
|
224
|
+
|
|
225
|
+
test('in-transaction DML, response lost after the emulator applied it: the loss surfaces (no self-collision), one wire execution, nothing commits', async () => {
|
|
226
|
+
const stub = await getSpannerStub();
|
|
227
|
+
const streamingPatch = patchStreamingLoss(stub, isTargetInsertSql, 'applied-then-lost');
|
|
228
|
+
const unaryPatch = patchUnaryLoss(stub, isTargetInsertSql, 'applied-then-lost');
|
|
229
|
+
let outcome: unknown;
|
|
230
|
+
try {
|
|
231
|
+
outcome = await db
|
|
232
|
+
.runTransaction(async () => {
|
|
233
|
+
// Statement 1 establishes the transaction id; statement 2 is the target DML whose
|
|
234
|
+
// response is lost AFTER the emulator applied it. Pre-fix, retry-request transparently
|
|
235
|
+
// replayed it (2 wire attempts; the op only survived because same-txn same-seqno
|
|
236
|
+
// replays happen to be absorbed by backend replay protection — the inline-begin
|
|
237
|
+
// geometry has no such shield). The contract: ONE wire execution, the LOSS surfaces.
|
|
238
|
+
await db.insert(anchorTable, { name: 'RetryAnchorA' });
|
|
239
|
+
await db.insert(targetTable, { name: 'RetryTargetA' });
|
|
240
|
+
})
|
|
241
|
+
.then(() => 'resolved' as const)
|
|
242
|
+
.catch((error: Error) => error);
|
|
243
|
+
} finally {
|
|
244
|
+
streamingPatch.restore();
|
|
245
|
+
unaryPatch.restore();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// The surfaced failure is the injected loss — NOT a replay self-collision (6 ALREADY_EXISTS).
|
|
249
|
+
expect(outcome).toBeInstanceOf(Error);
|
|
250
|
+
expect(String((outcome as Error).message)).toContain(INJECTED_LOSS);
|
|
251
|
+
expect((outcome as any).code).not.toBe(6);
|
|
252
|
+
// Exactly one wire execution of the target DML across both transports.
|
|
253
|
+
expect(streamingPatch.attempts() + unaryPatch.attempts()).toBe(1);
|
|
254
|
+
// The failed transaction committed nothing.
|
|
255
|
+
expect(await db.query(targetTable, { name: 'RetryTargetA' })).toHaveLength(0);
|
|
256
|
+
expect(await db.query(anchorTable, { name: 'RetryAnchorA' })).toHaveLength(0);
|
|
257
|
+
}, 30000);
|
|
258
|
+
|
|
259
|
+
test('standalone DML (inline begin), request lost in flight: the loss surfaces, one wire attempt, no row materializes behind the failure', async () => {
|
|
260
|
+
const stub = await getSpannerStub();
|
|
261
|
+
const streamingPatch = patchStreamingLoss(stub, isTargetInsertSql, 'lost-in-flight');
|
|
262
|
+
const unaryPatch = patchUnaryLoss(stub, isTargetInsertSql, 'lost-in-flight');
|
|
263
|
+
let outcome: unknown;
|
|
264
|
+
try {
|
|
265
|
+
outcome = await db
|
|
266
|
+
.insert(targetTable, { name: 'RetryTargetB' })
|
|
267
|
+
.then(() => 'resolved' as const)
|
|
268
|
+
.catch((error: Error) => error);
|
|
269
|
+
} finally {
|
|
270
|
+
streamingPatch.restore();
|
|
271
|
+
unaryPatch.restore();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// The op fails with the injected loss — it is NOT silently healed by a transparent replay
|
|
275
|
+
// (a replay resolves the op and materializes a row behind a "failed" attempt).
|
|
276
|
+
expect(outcome).toBeInstanceOf(Error);
|
|
277
|
+
expect(String((outcome as Error).message)).toContain(INJECTED_LOSS);
|
|
278
|
+
expect(streamingPatch.attempts() + unaryPatch.attempts()).toBe(1);
|
|
279
|
+
expect(await db.query(targetTable, { name: 'RetryTargetB' })).toHaveLength(0);
|
|
280
|
+
}, 30000);
|
|
281
|
+
|
|
282
|
+
test('happy path: DML rides the unary RPC (streaming carries none), commits exactly once with faithful row counts', async () => {
|
|
283
|
+
const stub = await getSpannerStub();
|
|
284
|
+
const isTargetDmlSql = (sql: string) =>
|
|
285
|
+
sql.includes('`db_test_dml_retry_target`') && !sql.trim().startsWith('SELECT');
|
|
286
|
+
const streamingCounter = patchStreamingDmlCounter(stub, isTargetDmlSql);
|
|
287
|
+
try {
|
|
288
|
+
const inserted = await db.runTransaction(async () => {
|
|
289
|
+
await db.insert(anchorTable, { name: 'RetryAnchorC' });
|
|
290
|
+
return await db.insert(targetTable, { name: 'RetryTargetC' });
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const committed = await db.query(targetTable, { name: 'RetryTargetC' });
|
|
294
|
+
expect(committed).toHaveLength(1);
|
|
295
|
+
// Row counts flow faithfully through the unary path.
|
|
296
|
+
expect(await db.update(targetTable, { name: 'RetryTargetC-updated' }, { id: inserted.id })).toBe(1);
|
|
297
|
+
expect(await db.delete(targetTable, { id: inserted.id })).toBe(1);
|
|
298
|
+
// No DML (insert/update/delete) ever touched the streaming transport, whose retry
|
|
299
|
+
// layers transparently replay lost responses.
|
|
300
|
+
expect(streamingCounter.attempts()).toBe(0);
|
|
301
|
+
} finally {
|
|
302
|
+
streamingCounter.restore();
|
|
303
|
+
}
|
|
304
|
+
}, 30000);
|
|
305
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { recordIteratorTests } from '@proteinjs/db/test';
|
|
2
|
+
import { SpannerDriver } from '@proteinjs/db-driver-spanner';
|
|
3
|
+
import { getDropTestTable } from './util/getDropTestTable';
|
|
4
|
+
import { TransactionContext } from '@proteinjs/db-transaction-context';
|
|
5
|
+
import '../generated/test/index';
|
|
6
|
+
|
|
7
|
+
const spannerDriver = new SpannerDriver({
|
|
8
|
+
projectId: 'proteinjs-test',
|
|
9
|
+
instanceName: 'proteinjs-test',
|
|
10
|
+
databaseName: 'test',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe(
|
|
14
|
+
'RecordIterator cursor-window tests',
|
|
15
|
+
recordIteratorTests(spannerDriver, new TransactionContext(), getDropTestTable(spannerDriver))
|
|
16
|
+
);
|