@proteinjs/db-driver-spanner 1.15.0 → 1.16.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 +16 -0
- package/dist/src/SpannerSchemaOperations.js +3 -3
- package/dist/src/SpannerSchemaOperations.js.map +1 -1
- package/dist/test/SpannerEmulatorProvisioner.test.d.ts +2 -0
- package/dist/test/SpannerEmulatorProvisioner.test.d.ts.map +1 -0
- package/dist/test/SpannerEmulatorProvisioner.test.js +197 -0
- package/dist/test/SpannerEmulatorProvisioner.test.js.map +1 -0
- package/dist/test/util/SpannerEmulatorProvisioner.d.ts +21 -3
- package/dist/test/util/SpannerEmulatorProvisioner.d.ts.map +1 -1
- package/dist/test/util/SpannerEmulatorProvisioner.js +46 -17
- package/dist/test/util/SpannerEmulatorProvisioner.js.map +1 -1
- package/package.json +5 -5
- package/src/SpannerSchemaOperations.ts +2 -2
- package/test/SpannerEmulatorProvisioner.test.ts +105 -0
- package/test/util/SpannerEmulatorProvisioner.ts +31 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [1.16.0](https://github.com/proteinjs/db/compare/@proteinjs/db-driver-spanner@1.15.0...@proteinjs/db-driver-spanner@1.16.0) (2026-08-13)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **spanner-test:** provisioner re-ensure is zero-DDL — reconcile the retention pin instead of blind ALTER ([312427d](https://github.com/proteinjs/db/commit/312427de54856eb3d7508afa699f141f3edc1c1e))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* composite unique indexes — Table.indexes gains unique, threaded through schema ops ([dabefd0](https://github.com/proteinjs/db/commit/dabefd07c4fc797c3d06c8c0770b7674ad33d018))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
# [1.15.0](https://github.com/proteinjs/db/compare/@proteinjs/db-driver-spanner@1.14.4...@proteinjs/db-driver-spanner@1.15.0) (2026-08-12)
|
|
7
23
|
|
|
8
24
|
|
|
@@ -51,14 +51,14 @@ var SpannerSchemaOperations = /** @class */ (function () {
|
|
|
51
51
|
SpannerSchemaOperations.prototype.createTable = function (table) {
|
|
52
52
|
var _a, _b, _c, _d;
|
|
53
53
|
return __awaiter(this, void 0, void 0, function () {
|
|
54
|
-
var indexes, _i, _e, _f, name_1, columns, serializedColumns, foreignKeys, columnPropertyName, column, columnType, createTableSql, _g, indexes_1, index, createIndexSql, indexName;
|
|
54
|
+
var indexes, _i, _e, _f, name_1, columns, unique, serializedColumns, foreignKeys, columnPropertyName, column, columnType, createTableSql, _g, indexes_1, index, createIndexSql, indexName;
|
|
55
55
|
return __generator(this, function (_h) {
|
|
56
56
|
switch (_h.label) {
|
|
57
57
|
case 0:
|
|
58
58
|
indexes = [];
|
|
59
59
|
for (_i = 0, _e = table.indexes; _i < _e.length; _i++) {
|
|
60
|
-
_f = _e[_i], name_1 = _f.name, columns = _f.columns;
|
|
61
|
-
indexes.push({ name: name_1, columns: columns.map(function (x) { return table.columns[x].name; }) });
|
|
60
|
+
_f = _e[_i], name_1 = _f.name, columns = _f.columns, unique = _f.unique;
|
|
61
|
+
indexes.push({ name: name_1, columns: columns.map(function (x) { return table.columns[x].name; }), unique: unique });
|
|
62
62
|
}
|
|
63
63
|
serializedColumns = [];
|
|
64
64
|
foreignKeys = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpannerSchemaOperations.js","sourceRoot":"","sources":["../../src/SpannerSchemaOperations.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4CAA2C;AAC3C,oCAOuB;AAEvB,uEAAsE;AAEtE,IAAM,SAAS,GAAG,UAAC,GAAW;IAC5B,OAAA,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;AAA5E,CAA4E,CAAC;AAE/E;IAGE,iCAAoB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;QAFxC,WAAM,GAAG,IAAI,eAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,cAAc,CAAQ,EAAE,CAAC,CAAC;IAEtD,CAAC;IAE9C,6CAAW,GAAjB,UAAkB,KAAiB;;;;;;;wBAC3B,OAAO,GAA6D,EAAE,CAAC;wBAC7E,
|
|
1
|
+
{"version":3,"file":"SpannerSchemaOperations.js","sourceRoot":"","sources":["../../src/SpannerSchemaOperations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpannerEmulatorProvisioner.test.d.ts","sourceRoot":"","sources":["../../test/SpannerEmulatorProvisioner.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
var spanner_1 = require("@google-cloud/spanner");
|
|
40
|
+
var SpannerEmulatorProvisioner_1 = require("./util/SpannerEmulatorProvisioner");
|
|
41
|
+
var spannerConfig = {
|
|
42
|
+
projectId: 'proteinjs-test',
|
|
43
|
+
instanceName: 'proteinjs-test',
|
|
44
|
+
databaseName: 'test',
|
|
45
|
+
};
|
|
46
|
+
var SCRATCH_TABLE = 'db_test_provisioner_reensure';
|
|
47
|
+
var retentionRows = function (database) { return __awaiter(void 0, void 0, void 0, function () {
|
|
48
|
+
var rows;
|
|
49
|
+
return __generator(this, function (_a) {
|
|
50
|
+
switch (_a.label) {
|
|
51
|
+
case 0: return [4 /*yield*/, database.run({
|
|
52
|
+
sql: "SELECT s.OPTION_VALUE FROM INFORMATION_SCHEMA.DATABASE_OPTIONS s WHERE s.OPTION_NAME = 'version_retention_period'",
|
|
53
|
+
json: true,
|
|
54
|
+
})];
|
|
55
|
+
case 1:
|
|
56
|
+
rows = (_a.sent())[0];
|
|
57
|
+
return [2 /*return*/, rows];
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}); };
|
|
61
|
+
/**
|
|
62
|
+
* The provisioner's re-ensure invariant (see the class doc): `ensureProvisioned` on an
|
|
63
|
+
* already-provisioned, already-pinned emulator issues ZERO DDL. It runs in every fresh
|
|
64
|
+
* process's first beforeAll, and fresh processes appear MID-RUN (jest workerIdleMemoryLimit
|
|
65
|
+
* restarts) — right after a predecessor process was killed with a read-write transaction in
|
|
66
|
+
* flight. The emulator rejects ANY schema change while such a transaction is active, so a
|
|
67
|
+
* re-ensure that emits DDL fails whole suites with FAILED_PRECONDITION (the 11-suite
|
|
68
|
+
* flow-server CI failure, run 31643570286).
|
|
69
|
+
*
|
|
70
|
+
* The tests assert OUTCOMES against a real open read-write transaction — no mocks: a control
|
|
71
|
+
* leg first proves the open transaction really does reject DDL right now, so `ensureProvisioned`
|
|
72
|
+
* resolving in the same window proves it issued none.
|
|
73
|
+
*/
|
|
74
|
+
describe('SpannerEmulatorProvisioner', function () {
|
|
75
|
+
var spanner = new spanner_1.Spanner({ projectId: spannerConfig.projectId });
|
|
76
|
+
var database;
|
|
77
|
+
beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
78
|
+
return __generator(this, function (_a) {
|
|
79
|
+
switch (_a.label) {
|
|
80
|
+
case 0:
|
|
81
|
+
// The run's one legitimate DDL window: on a fresh emulator this creates instance +
|
|
82
|
+
// database and pins retention; on a provisioned one it must reconcile without DDL.
|
|
83
|
+
return [4 /*yield*/, SpannerEmulatorProvisioner_1.SpannerEmulatorProvisioner.ensureProvisioned(spannerConfig)];
|
|
84
|
+
case 1:
|
|
85
|
+
// The run's one legitimate DDL window: on a fresh emulator this creates instance +
|
|
86
|
+
// database and pins retention; on a provisioned one it must reconcile without DDL.
|
|
87
|
+
_a.sent();
|
|
88
|
+
database = spanner.instance(spannerConfig.instanceName).database(spannerConfig.databaseName);
|
|
89
|
+
database.on('error', function () { return undefined; });
|
|
90
|
+
return [2 /*return*/];
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}); }, 60000);
|
|
94
|
+
afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
95
|
+
return __generator(this, function (_a) {
|
|
96
|
+
switch (_a.label) {
|
|
97
|
+
case 0: return [4 /*yield*/, database.close().catch(function () { return undefined; })];
|
|
98
|
+
case 1:
|
|
99
|
+
_a.sent();
|
|
100
|
+
spanner.close();
|
|
101
|
+
SpannerEmulatorProvisioner_1.SpannerEmulatorProvisioner.release();
|
|
102
|
+
return [2 /*return*/];
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}); }, 60000);
|
|
106
|
+
test('pins version_retention_period on a fresh or unpinned database', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
107
|
+
var rows;
|
|
108
|
+
return __generator(this, function (_a) {
|
|
109
|
+
switch (_a.label) {
|
|
110
|
+
case 0: return [4 /*yield*/, retentionRows(database)];
|
|
111
|
+
case 1:
|
|
112
|
+
rows = _a.sent();
|
|
113
|
+
expect(rows).toEqual([{ OPTION_VALUE: '1m' }]);
|
|
114
|
+
return [2 /*return*/];
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}); }, 30000);
|
|
118
|
+
test('re-ensure issues zero DDL: succeeds while an open read-write transaction rejects all schema changes', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
119
|
+
var tables, createOp, transaction, rows;
|
|
120
|
+
return __generator(this, function (_a) {
|
|
121
|
+
switch (_a.label) {
|
|
122
|
+
case 0: return [4 /*yield*/, database.run({
|
|
123
|
+
sql: "SELECT t.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = '' AND t.TABLE_NAME = '".concat(SCRATCH_TABLE, "'"),
|
|
124
|
+
json: true,
|
|
125
|
+
})];
|
|
126
|
+
case 1:
|
|
127
|
+
tables = (_a.sent())[0];
|
|
128
|
+
if (!(tables.length === 0)) return [3 /*break*/, 4];
|
|
129
|
+
return [4 /*yield*/, database.updateSchema("CREATE TABLE ".concat(SCRATCH_TABLE, " (id STRING(36)) PRIMARY KEY (id)"))];
|
|
130
|
+
case 2:
|
|
131
|
+
createOp = (_a.sent())[0];
|
|
132
|
+
return [4 /*yield*/, createOp.promise()];
|
|
133
|
+
case 3:
|
|
134
|
+
_a.sent();
|
|
135
|
+
_a.label = 4;
|
|
136
|
+
case 4: return [4 /*yield*/, database.getTransaction()];
|
|
137
|
+
case 5:
|
|
138
|
+
transaction = (_a.sent())[0];
|
|
139
|
+
_a.label = 6;
|
|
140
|
+
case 6:
|
|
141
|
+
_a.trys.push([6, , 11, 13]);
|
|
142
|
+
return [4 /*yield*/, transaction.runUpdate({
|
|
143
|
+
sql: "INSERT INTO ".concat(SCRATCH_TABLE, " (id) VALUES (@id)"),
|
|
144
|
+
params: { id: "re-ensure-".concat(Date.now()) },
|
|
145
|
+
})];
|
|
146
|
+
case 7:
|
|
147
|
+
_a.sent();
|
|
148
|
+
// Control leg: the open transaction really does reject DDL in this window — the same
|
|
149
|
+
// rejection the pre-fix unconditional pin ALTER died on. If the emulator ever stops
|
|
150
|
+
// rejecting schema changes under an open transaction, this control fails and flags that
|
|
151
|
+
// the invariant leg below has lost its bite.
|
|
152
|
+
return [4 /*yield*/, expect((function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
153
|
+
var op;
|
|
154
|
+
return __generator(this, function (_a) {
|
|
155
|
+
switch (_a.label) {
|
|
156
|
+
case 0: return [4 /*yield*/, database.updateSchema("ALTER DATABASE `".concat(spannerConfig.databaseName, "` SET OPTIONS (version_retention_period = '1m')"))];
|
|
157
|
+
case 1:
|
|
158
|
+
op = (_a.sent())[0];
|
|
159
|
+
return [4 /*yield*/, op.promise()];
|
|
160
|
+
case 2:
|
|
161
|
+
_a.sent();
|
|
162
|
+
return [2 /*return*/];
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}); })()).rejects.toMatchObject({ code: 9 /* gRPC FAILED_PRECONDITION */ })];
|
|
166
|
+
case 8:
|
|
167
|
+
// Control leg: the open transaction really does reject DDL in this window — the same
|
|
168
|
+
// rejection the pre-fix unconditional pin ALTER died on. If the emulator ever stops
|
|
169
|
+
// rejecting schema changes under an open transaction, this control fails and flags that
|
|
170
|
+
// the invariant leg below has lost its bite.
|
|
171
|
+
_a.sent();
|
|
172
|
+
// The invariant: re-ensure resolves inside the same window — it issued zero DDL.
|
|
173
|
+
return [4 /*yield*/, expect(SpannerEmulatorProvisioner_1.SpannerEmulatorProvisioner.ensureProvisioned(spannerConfig)).resolves.toBeUndefined()];
|
|
174
|
+
case 9:
|
|
175
|
+
// The invariant: re-ensure resolves inside the same window — it issued zero DDL.
|
|
176
|
+
_a.sent();
|
|
177
|
+
return [4 /*yield*/, retentionRows(database)];
|
|
178
|
+
case 10:
|
|
179
|
+
rows = _a.sent();
|
|
180
|
+
expect(rows).toEqual([{ OPTION_VALUE: '1m' }]);
|
|
181
|
+
return [3 /*break*/, 13];
|
|
182
|
+
case 11:
|
|
183
|
+
// Never strand the transaction on the shared test emulator — that would poison later
|
|
184
|
+
// suites' DDL exactly like the class this test guards against.
|
|
185
|
+
return [4 /*yield*/, transaction.rollback().catch(function () { return undefined; })];
|
|
186
|
+
case 12:
|
|
187
|
+
// Never strand the transaction on the shared test emulator — that would poison later
|
|
188
|
+
// suites' DDL exactly like the class this test guards against.
|
|
189
|
+
_a.sent();
|
|
190
|
+
transaction.end();
|
|
191
|
+
return [7 /*endfinally*/];
|
|
192
|
+
case 13: return [2 /*return*/];
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}); }, 60000);
|
|
196
|
+
});
|
|
197
|
+
//# sourceMappingURL=SpannerEmulatorProvisioner.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpannerEmulatorProvisioner.test.js","sourceRoot":"","sources":["../../test/SpannerEmulatorProvisioner.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,9 +19,23 @@
|
|
|
19
19
|
* emulator's RSS plateaus instead of growing to OOM. Emulator-only by construction — the whole
|
|
20
20
|
* path is gated on SPANNER_EMULATOR_HOST; cloud databases are never touched.
|
|
21
21
|
*
|
|
22
|
+
* INVARIANT — re-ensuring on an already-provisioned emulator issues ZERO DDL. `ensureProvisioned`
|
|
23
|
+
* runs in every fresh process's first beforeAll (the once-per-run schema epoch lives on
|
|
24
|
+
* `process`), and fresh processes appear MID-RUN: jest's `workerIdleMemoryLimit` restarts the
|
|
25
|
+
* worker between suites, killing the old one with whatever background read-write transaction its
|
|
26
|
+
* suites left in flight. The emulator rejects ANY schema change while a read-write transaction is
|
|
27
|
+
* active (FAILED_PRECONDITION "a concurrent schema change operation or read-write transaction is
|
|
28
|
+
* already in progress"), and a transaction stranded by a killed process stays active well past the
|
|
29
|
+
* next suite's beforeAll — an unconditional pin ALTER here failed 11 of 101 flow-server suites in
|
|
30
|
+
* exactly that pattern (CI run 31643570286). Reads are immune, so the pin reconciles: read the
|
|
31
|
+
* current option, ALTER only on drift. The run's only legitimate DDL window stays the first
|
|
32
|
+
* provisioning of a fresh emulator, where no predecessor process can have stranded anything.
|
|
33
|
+
*
|
|
22
34
|
* @internal This class is intended to be used only in tests. Do not use it in production code.
|
|
23
35
|
*/
|
|
24
36
|
export declare class SpannerEmulatorProvisioner {
|
|
37
|
+
/** The retention the harness pins emulator databases to (see class doc). */
|
|
38
|
+
private static readonly VERSION_RETENTION_PERIOD;
|
|
25
39
|
/** Kept until release(): closing right after the create-operations races their trailing LRO
|
|
26
40
|
* callbacks into an unhandled "client has already been closed" that fails the suite. */
|
|
27
41
|
private static client;
|
|
@@ -32,10 +46,14 @@ export declare class SpannerEmulatorProvisioner {
|
|
|
32
46
|
databaseName: string;
|
|
33
47
|
}): Promise<void>;
|
|
34
48
|
/**
|
|
35
|
-
*
|
|
49
|
+
* Reconcile the emulator database's version_retention_period to 1m (see class doc). Runs on
|
|
36
50
|
* both the fresh-create and already-exists paths — a database that survived from a previous
|
|
37
|
-
* run must end up pinned too.
|
|
38
|
-
*
|
|
51
|
+
* run must end up pinned too. Read-before-write, NOT a blind re-pin: the ALTER is DDL, and
|
|
52
|
+
* per the class invariant a re-ensure on an already-pinned database must issue zero DDL (a
|
|
53
|
+
* stranded read-write transaction from a killed predecessor process fails ANY schema change
|
|
54
|
+
* with FAILED_PRECONDITION; the read is immune). The current value comes from
|
|
55
|
+
* INFORMATION_SCHEMA.DATABASE_OPTIONS — the admin API's `getMetadata()` does not surface the
|
|
56
|
+
* option on the emulator (returns '' pinned or not; verified against emulator image 2026-08).
|
|
39
57
|
*/
|
|
40
58
|
private static pinVersionRetention;
|
|
41
59
|
/** Close the admin client — call from afterAll so jest's event loop can drain. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpannerEmulatorProvisioner.d.ts","sourceRoot":"","sources":["../../../test/util/SpannerEmulatorProvisioner.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"SpannerEmulatorProvisioner.d.ts","sourceRoot":"","sources":["../../../test/util/SpannerEmulatorProvisioner.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,0BAA0B;IACrC,4EAA4E;IAC5E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAQ;IAExD;6FACyF;IACzF,OAAO,CAAC,MAAM,CAAC,MAAM,CAAsB;IAE3C,iGAAiG;WACpF,iBAAiB,CAAC,MAAM,EAAE;QACrC,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmCjB;;;;;;;;;OASG;mBACkB,mBAAmB;IA0BxC,kFAAkF;IAClF,MAAM,CAAC,OAAO,IAAI,IAAI;IAQtB,OAAO,CAAC,MAAM,CAAC,oBAAoB;CAKpC"}
|
|
@@ -59,6 +59,18 @@ var spanner_1 = require("@google-cloud/spanner");
|
|
|
59
59
|
* emulator's RSS plateaus instead of growing to OOM. Emulator-only by construction — the whole
|
|
60
60
|
* path is gated on SPANNER_EMULATOR_HOST; cloud databases are never touched.
|
|
61
61
|
*
|
|
62
|
+
* INVARIANT — re-ensuring on an already-provisioned emulator issues ZERO DDL. `ensureProvisioned`
|
|
63
|
+
* runs in every fresh process's first beforeAll (the once-per-run schema epoch lives on
|
|
64
|
+
* `process`), and fresh processes appear MID-RUN: jest's `workerIdleMemoryLimit` restarts the
|
|
65
|
+
* worker between suites, killing the old one with whatever background read-write transaction its
|
|
66
|
+
* suites left in flight. The emulator rejects ANY schema change while a read-write transaction is
|
|
67
|
+
* active (FAILED_PRECONDITION "a concurrent schema change operation or read-write transaction is
|
|
68
|
+
* already in progress"), and a transaction stranded by a killed process stays active well past the
|
|
69
|
+
* next suite's beforeAll — an unconditional pin ALTER here failed 11 of 101 flow-server suites in
|
|
70
|
+
* exactly that pattern (CI run 31643570286). Reads are immune, so the pin reconciles: read the
|
|
71
|
+
* current option, ALTER only on drift. The run's only legitimate DDL window stays the first
|
|
72
|
+
* provisioning of a fresh emulator, where no predecessor process can have stranded anything.
|
|
73
|
+
*
|
|
62
74
|
* @internal This class is intended to be used only in tests. Do not use it in production code.
|
|
63
75
|
*/
|
|
64
76
|
var SpannerEmulatorProvisioner = /** @class */ (function () {
|
|
@@ -129,34 +141,49 @@ var SpannerEmulatorProvisioner = /** @class */ (function () {
|
|
|
129
141
|
});
|
|
130
142
|
};
|
|
131
143
|
/**
|
|
132
|
-
*
|
|
144
|
+
* Reconcile the emulator database's version_retention_period to 1m (see class doc). Runs on
|
|
133
145
|
* both the fresh-create and already-exists paths — a database that survived from a previous
|
|
134
|
-
* run must end up pinned too.
|
|
135
|
-
*
|
|
146
|
+
* run must end up pinned too. Read-before-write, NOT a blind re-pin: the ALTER is DDL, and
|
|
147
|
+
* per the class invariant a re-ensure on an already-pinned database must issue zero DDL (a
|
|
148
|
+
* stranded read-write transaction from a killed predecessor process fails ANY schema change
|
|
149
|
+
* with FAILED_PRECONDITION; the read is immune). The current value comes from
|
|
150
|
+
* INFORMATION_SCHEMA.DATABASE_OPTIONS — the admin API's `getMetadata()` does not surface the
|
|
151
|
+
* option on the emulator (returns '' pinned or not; verified against emulator image 2026-08).
|
|
136
152
|
*/
|
|
137
153
|
SpannerEmulatorProvisioner.pinVersionRetention = function (instance, databaseName) {
|
|
154
|
+
var _a;
|
|
138
155
|
return __awaiter(this, void 0, void 0, function () {
|
|
139
|
-
var database, operation;
|
|
140
|
-
return __generator(this, function (
|
|
141
|
-
switch (
|
|
156
|
+
var database, rows, currentRetention, operation;
|
|
157
|
+
return __generator(this, function (_b) {
|
|
158
|
+
switch (_b.label) {
|
|
142
159
|
case 0:
|
|
143
160
|
database = instance.database(databaseName);
|
|
144
161
|
database.on('error', function () { return undefined; });
|
|
145
|
-
|
|
162
|
+
_b.label = 1;
|
|
146
163
|
case 1:
|
|
147
|
-
|
|
148
|
-
return [4 /*yield*/, database.
|
|
164
|
+
_b.trys.push([1, , 5, 7]);
|
|
165
|
+
return [4 /*yield*/, database.run({
|
|
166
|
+
sql: "SELECT s.OPTION_VALUE FROM INFORMATION_SCHEMA.DATABASE_OPTIONS s WHERE s.OPTION_NAME = 'version_retention_period'",
|
|
167
|
+
json: true,
|
|
168
|
+
})];
|
|
149
169
|
case 2:
|
|
150
|
-
|
|
151
|
-
|
|
170
|
+
rows = (_b.sent())[0];
|
|
171
|
+
currentRetention = (_a = rows[0]) === null || _a === void 0 ? void 0 : _a.OPTION_VALUE;
|
|
172
|
+
if (currentRetention === SpannerEmulatorProvisioner.VERSION_RETENTION_PERIOD) {
|
|
173
|
+
return [2 /*return*/];
|
|
174
|
+
}
|
|
175
|
+
return [4 /*yield*/, database.updateSchema("ALTER DATABASE `".concat(databaseName, "` SET OPTIONS (version_retention_period = '").concat(SpannerEmulatorProvisioner.VERSION_RETENTION_PERIOD, "')"))];
|
|
152
176
|
case 3:
|
|
153
|
-
|
|
154
|
-
return [
|
|
155
|
-
case 4:
|
|
156
|
-
|
|
157
|
-
|
|
177
|
+
operation = (_b.sent())[0];
|
|
178
|
+
return [4 /*yield*/, operation.promise()];
|
|
179
|
+
case 4:
|
|
180
|
+
_b.sent();
|
|
181
|
+
return [3 /*break*/, 7];
|
|
182
|
+
case 5: return [4 /*yield*/, database.close().catch(function () { return undefined; })];
|
|
183
|
+
case 6:
|
|
184
|
+
_b.sent();
|
|
158
185
|
return [7 /*endfinally*/];
|
|
159
|
-
case
|
|
186
|
+
case 7: return [2 /*return*/];
|
|
160
187
|
}
|
|
161
188
|
});
|
|
162
189
|
});
|
|
@@ -176,6 +203,8 @@ var SpannerEmulatorProvisioner = /** @class */ (function () {
|
|
|
176
203
|
throw error;
|
|
177
204
|
}
|
|
178
205
|
};
|
|
206
|
+
/** The retention the harness pins emulator databases to (see class doc). */
|
|
207
|
+
SpannerEmulatorProvisioner.VERSION_RETENTION_PERIOD = '1m';
|
|
179
208
|
return SpannerEmulatorProvisioner;
|
|
180
209
|
}());
|
|
181
210
|
exports.SpannerEmulatorProvisioner = SpannerEmulatorProvisioner;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpannerEmulatorProvisioner.js","sourceRoot":"","sources":["../../../test/util/SpannerEmulatorProvisioner.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iDAAgD;AAEhD
|
|
1
|
+
{"version":3,"file":"SpannerEmulatorProvisioner.js","sourceRoot":"","sources":["../../../test/util/SpannerEmulatorProvisioner.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proteinjs/db-driver-spanner",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.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.
|
|
45
|
+
"@proteinjs/db": "^1.29.0",
|
|
46
46
|
"@proteinjs/db-query": "^1.6.0",
|
|
47
|
-
"@proteinjs/db-spanner-common": "^1.3.
|
|
48
|
-
"@proteinjs/db-transaction-context": "^0.5.
|
|
47
|
+
"@proteinjs/db-spanner-common": "^1.3.2",
|
|
48
|
+
"@proteinjs/db-transaction-context": "^0.5.2",
|
|
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": "84d29cc38018380203c374ee1a2c35b943704ccd"
|
|
67
67
|
}
|
|
@@ -20,8 +20,8 @@ export class SpannerSchemaOperations implements SchemaOperations {
|
|
|
20
20
|
|
|
21
21
|
async createTable(table: Table<any>) {
|
|
22
22
|
const indexes: { name?: string; columns: string[]; unique?: boolean }[] = [];
|
|
23
|
-
for (const { name, columns } of table.indexes) {
|
|
24
|
-
indexes.push({ name, columns: columns.map((x) => table.columns[x as string]!.name) });
|
|
23
|
+
for (const { name, columns, unique } of table.indexes) {
|
|
24
|
+
indexes.push({ name, columns: columns.map((x) => table.columns[x as string]!.name), unique });
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const serializedColumns: { name: string; type: string; nullable?: boolean }[] = [];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Spanner, Database, Transaction } from '@google-cloud/spanner';
|
|
2
|
+
import { SpannerEmulatorProvisioner } from './util/SpannerEmulatorProvisioner';
|
|
3
|
+
|
|
4
|
+
const spannerConfig = {
|
|
5
|
+
projectId: 'proteinjs-test',
|
|
6
|
+
instanceName: 'proteinjs-test',
|
|
7
|
+
databaseName: 'test',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const SCRATCH_TABLE = 'db_test_provisioner_reensure';
|
|
11
|
+
|
|
12
|
+
const retentionRows = async (database: Database) => {
|
|
13
|
+
const [rows] = await database.run({
|
|
14
|
+
sql: `SELECT s.OPTION_VALUE FROM INFORMATION_SCHEMA.DATABASE_OPTIONS s WHERE s.OPTION_NAME = 'version_retention_period'`,
|
|
15
|
+
json: true,
|
|
16
|
+
});
|
|
17
|
+
return rows as { OPTION_VALUE: string }[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The provisioner's re-ensure invariant (see the class doc): `ensureProvisioned` on an
|
|
22
|
+
* already-provisioned, already-pinned emulator issues ZERO DDL. It runs in every fresh
|
|
23
|
+
* process's first beforeAll, and fresh processes appear MID-RUN (jest workerIdleMemoryLimit
|
|
24
|
+
* restarts) — right after a predecessor process was killed with a read-write transaction in
|
|
25
|
+
* flight. The emulator rejects ANY schema change while such a transaction is active, so a
|
|
26
|
+
* re-ensure that emits DDL fails whole suites with FAILED_PRECONDITION (the 11-suite
|
|
27
|
+
* flow-server CI failure, run 31643570286).
|
|
28
|
+
*
|
|
29
|
+
* The tests assert OUTCOMES against a real open read-write transaction — no mocks: a control
|
|
30
|
+
* leg first proves the open transaction really does reject DDL right now, so `ensureProvisioned`
|
|
31
|
+
* resolving in the same window proves it issued none.
|
|
32
|
+
*/
|
|
33
|
+
describe('SpannerEmulatorProvisioner', () => {
|
|
34
|
+
const spanner = new Spanner({ projectId: spannerConfig.projectId });
|
|
35
|
+
let database: Database;
|
|
36
|
+
|
|
37
|
+
beforeAll(async () => {
|
|
38
|
+
// The run's one legitimate DDL window: on a fresh emulator this creates instance +
|
|
39
|
+
// database and pins retention; on a provisioned one it must reconcile without DDL.
|
|
40
|
+
await SpannerEmulatorProvisioner.ensureProvisioned(spannerConfig);
|
|
41
|
+
database = spanner.instance(spannerConfig.instanceName).database(spannerConfig.databaseName);
|
|
42
|
+
database.on('error', () => undefined);
|
|
43
|
+
}, 60_000);
|
|
44
|
+
|
|
45
|
+
afterAll(async () => {
|
|
46
|
+
await database.close().catch(() => undefined);
|
|
47
|
+
spanner.close();
|
|
48
|
+
SpannerEmulatorProvisioner.release();
|
|
49
|
+
}, 60_000);
|
|
50
|
+
|
|
51
|
+
test('pins version_retention_period on a fresh or unpinned database', async () => {
|
|
52
|
+
// Outcome of the beforeAll ensure: the option is present and pinned. Guards the skip
|
|
53
|
+
// condition from ever decaying into never-pinning.
|
|
54
|
+
const rows = await retentionRows(database);
|
|
55
|
+
expect(rows).toEqual([{ OPTION_VALUE: '1m' }]);
|
|
56
|
+
}, 30_000);
|
|
57
|
+
|
|
58
|
+
test('re-ensure issues zero DDL: succeeds while an open read-write transaction rejects all schema changes', async () => {
|
|
59
|
+
// Scratch table for the transaction's uncommitted write — created BEFORE the transaction
|
|
60
|
+
// opens (DDL is legal here), only when a previous run didn't leave it behind.
|
|
61
|
+
const [tables] = await database.run({
|
|
62
|
+
sql: `SELECT t.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = '' AND t.TABLE_NAME = '${SCRATCH_TABLE}'`,
|
|
63
|
+
json: true,
|
|
64
|
+
});
|
|
65
|
+
if (tables.length === 0) {
|
|
66
|
+
const [createOp] = await database.updateSchema(`CREATE TABLE ${SCRATCH_TABLE} (id STRING(36)) PRIMARY KEY (id)`);
|
|
67
|
+
await createOp.promise();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Model the stranded transaction a killed jest worker leaves behind: a read-write
|
|
71
|
+
// transaction with real uncommitted work, held open across the re-ensure.
|
|
72
|
+
const [transaction] = (await database.getTransaction()) as unknown as [Transaction];
|
|
73
|
+
try {
|
|
74
|
+
await transaction.runUpdate({
|
|
75
|
+
sql: `INSERT INTO ${SCRATCH_TABLE} (id) VALUES (@id)`,
|
|
76
|
+
params: { id: `re-ensure-${Date.now()}` },
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Control leg: the open transaction really does reject DDL in this window — the same
|
|
80
|
+
// rejection the pre-fix unconditional pin ALTER died on. If the emulator ever stops
|
|
81
|
+
// rejecting schema changes under an open transaction, this control fails and flags that
|
|
82
|
+
// the invariant leg below has lost its bite.
|
|
83
|
+
await expect(
|
|
84
|
+
(async () => {
|
|
85
|
+
const [op] = await database.updateSchema(
|
|
86
|
+
`ALTER DATABASE \`${spannerConfig.databaseName}\` SET OPTIONS (version_retention_period = '1m')`
|
|
87
|
+
);
|
|
88
|
+
await op.promise();
|
|
89
|
+
})()
|
|
90
|
+
).rejects.toMatchObject({ code: 9 /* gRPC FAILED_PRECONDITION */ });
|
|
91
|
+
|
|
92
|
+
// The invariant: re-ensure resolves inside the same window — it issued zero DDL.
|
|
93
|
+
await expect(SpannerEmulatorProvisioner.ensureProvisioned(spannerConfig)).resolves.toBeUndefined();
|
|
94
|
+
|
|
95
|
+
// And it reconciled rather than skipped: retention still reads pinned.
|
|
96
|
+
const rows = await retentionRows(database);
|
|
97
|
+
expect(rows).toEqual([{ OPTION_VALUE: '1m' }]);
|
|
98
|
+
} finally {
|
|
99
|
+
// Never strand the transaction on the shared test emulator — that would poison later
|
|
100
|
+
// suites' DDL exactly like the class this test guards against.
|
|
101
|
+
await transaction.rollback().catch(() => undefined);
|
|
102
|
+
transaction.end();
|
|
103
|
+
}
|
|
104
|
+
}, 60_000);
|
|
105
|
+
});
|
|
@@ -21,9 +21,24 @@ import { Spanner } from '@google-cloud/spanner';
|
|
|
21
21
|
* emulator's RSS plateaus instead of growing to OOM. Emulator-only by construction — the whole
|
|
22
22
|
* path is gated on SPANNER_EMULATOR_HOST; cloud databases are never touched.
|
|
23
23
|
*
|
|
24
|
+
* INVARIANT — re-ensuring on an already-provisioned emulator issues ZERO DDL. `ensureProvisioned`
|
|
25
|
+
* runs in every fresh process's first beforeAll (the once-per-run schema epoch lives on
|
|
26
|
+
* `process`), and fresh processes appear MID-RUN: jest's `workerIdleMemoryLimit` restarts the
|
|
27
|
+
* worker between suites, killing the old one with whatever background read-write transaction its
|
|
28
|
+
* suites left in flight. The emulator rejects ANY schema change while a read-write transaction is
|
|
29
|
+
* active (FAILED_PRECONDITION "a concurrent schema change operation or read-write transaction is
|
|
30
|
+
* already in progress"), and a transaction stranded by a killed process stays active well past the
|
|
31
|
+
* next suite's beforeAll — an unconditional pin ALTER here failed 11 of 101 flow-server suites in
|
|
32
|
+
* exactly that pattern (CI run 31643570286). Reads are immune, so the pin reconciles: read the
|
|
33
|
+
* current option, ALTER only on drift. The run's only legitimate DDL window stays the first
|
|
34
|
+
* provisioning of a fresh emulator, where no predecessor process can have stranded anything.
|
|
35
|
+
*
|
|
24
36
|
* @internal This class is intended to be used only in tests. Do not use it in production code.
|
|
25
37
|
*/
|
|
26
38
|
export class SpannerEmulatorProvisioner {
|
|
39
|
+
/** The retention the harness pins emulator databases to (see class doc). */
|
|
40
|
+
private static readonly VERSION_RETENTION_PERIOD = '1m';
|
|
41
|
+
|
|
27
42
|
/** Kept until release(): closing right after the create-operations races their trailing LRO
|
|
28
43
|
* callbacks into an unhandled "client has already been closed" that fails the suite. */
|
|
29
44
|
private static client: Spanner | undefined;
|
|
@@ -69,10 +84,14 @@ export class SpannerEmulatorProvisioner {
|
|
|
69
84
|
}
|
|
70
85
|
|
|
71
86
|
/**
|
|
72
|
-
*
|
|
87
|
+
* Reconcile the emulator database's version_retention_period to 1m (see class doc). Runs on
|
|
73
88
|
* both the fresh-create and already-exists paths — a database that survived from a previous
|
|
74
|
-
* run must end up pinned too.
|
|
75
|
-
*
|
|
89
|
+
* run must end up pinned too. Read-before-write, NOT a blind re-pin: the ALTER is DDL, and
|
|
90
|
+
* per the class invariant a re-ensure on an already-pinned database must issue zero DDL (a
|
|
91
|
+
* stranded read-write transaction from a killed predecessor process fails ANY schema change
|
|
92
|
+
* with FAILED_PRECONDITION; the read is immune). The current value comes from
|
|
93
|
+
* INFORMATION_SCHEMA.DATABASE_OPTIONS — the admin API's `getMetadata()` does not surface the
|
|
94
|
+
* option on the emulator (returns '' pinned or not; verified against emulator image 2026-08).
|
|
76
95
|
*/
|
|
77
96
|
private static async pinVersionRetention(
|
|
78
97
|
instance: ReturnType<Spanner['instance']>,
|
|
@@ -83,8 +102,16 @@ export class SpannerEmulatorProvisioner {
|
|
|
83
102
|
const database = instance.database(databaseName);
|
|
84
103
|
database.on('error', () => undefined);
|
|
85
104
|
try {
|
|
105
|
+
const [rows] = await database.run({
|
|
106
|
+
sql: `SELECT s.OPTION_VALUE FROM INFORMATION_SCHEMA.DATABASE_OPTIONS s WHERE s.OPTION_NAME = 'version_retention_period'`,
|
|
107
|
+
json: true,
|
|
108
|
+
});
|
|
109
|
+
const currentRetention = (rows[0] as { OPTION_VALUE?: string } | undefined)?.OPTION_VALUE;
|
|
110
|
+
if (currentRetention === SpannerEmulatorProvisioner.VERSION_RETENTION_PERIOD) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
86
113
|
const [operation] = await database.updateSchema(
|
|
87
|
-
`ALTER DATABASE \`${databaseName}\` SET OPTIONS (version_retention_period = '
|
|
114
|
+
`ALTER DATABASE \`${databaseName}\` SET OPTIONS (version_retention_period = '${SpannerEmulatorProvisioner.VERSION_RETENTION_PERIOD}')`
|
|
88
115
|
);
|
|
89
116
|
await operation.promise();
|
|
90
117
|
} finally {
|