@proteinjs/db-driver-spanner 1.0.2

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +32 -0
  4. package/dist/generated/index.d.ts +8 -0
  5. package/dist/generated/index.d.ts.map +1 -0
  6. package/dist/generated/index.js +38 -0
  7. package/dist/generated/index.js.map +1 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +19 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/src/SpannerColumnTypeFactory.d.ts +5 -0
  13. package/dist/src/SpannerColumnTypeFactory.d.ts.map +1 -0
  14. package/dist/src/SpannerColumnTypeFactory.js +31 -0
  15. package/dist/src/SpannerColumnTypeFactory.js.map +1 -0
  16. package/dist/src/SpannerConfig.d.ts +8 -0
  17. package/dist/src/SpannerConfig.d.ts.map +1 -0
  18. package/dist/src/SpannerConfig.js +7 -0
  19. package/dist/src/SpannerConfig.js.map +1 -0
  20. package/dist/src/SpannerDriver.d.ts +30 -0
  21. package/dist/src/SpannerDriver.d.ts.map +1 -0
  22. package/dist/src/SpannerDriver.js +204 -0
  23. package/dist/src/SpannerDriver.js.map +1 -0
  24. package/dist/src/SpannerSchemaMetadata.d.ts +21 -0
  25. package/dist/src/SpannerSchemaMetadata.d.ts.map +1 -0
  26. package/dist/src/SpannerSchemaMetadata.js +229 -0
  27. package/dist/src/SpannerSchemaMetadata.js.map +1 -0
  28. package/dist/src/SpannerSchemaOperations.d.ts +10 -0
  29. package/dist/src/SpannerSchemaOperations.d.ts.map +1 -0
  30. package/dist/src/SpannerSchemaOperations.js +215 -0
  31. package/dist/src/SpannerSchemaOperations.js.map +1 -0
  32. package/dist/test/Crud.test.d.ts +2 -0
  33. package/dist/test/Crud.test.d.ts.map +1 -0
  34. package/dist/test/Crud.test.js +12 -0
  35. package/dist/test/Crud.test.js.map +1 -0
  36. package/dist/test/TableManager.test.d.ts +2 -0
  37. package/dist/test/TableManager.test.d.ts.map +1 -0
  38. package/dist/test/TableManager.test.js +17 -0
  39. package/dist/test/TableManager.test.js.map +1 -0
  40. package/dist/test/dropTable.d.ts +4 -0
  41. package/dist/test/dropTable.d.ts.map +1 -0
  42. package/dist/test/dropTable.js +115 -0
  43. package/dist/test/dropTable.js.map +1 -0
  44. package/generated/index.ts +34 -0
  45. package/index.ts +2 -0
  46. package/jest.config.js +19 -0
  47. package/package.json +42 -0
  48. package/src/SpannerColumnTypeFactory.ts +25 -0
  49. package/src/SpannerConfig.ts +9 -0
  50. package/src/SpannerDriver.ts +115 -0
  51. package/src/SpannerSchemaMetadata.ts +96 -0
  52. package/src/SpannerSchemaOperations.ts +122 -0
  53. package/test/Crud.test.ts +17 -0
  54. package/test/TableManager.test.ts +24 -0
  55. package/test/dropTable.ts +32 -0
  56. package/test/setup.js +1 -0
  57. package/tsconfig.json +23 -0
@@ -0,0 +1,115 @@
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
+ exports.getDropTable = void 0;
40
+ var db_1 = require("@proteinjs/db");
41
+ var getDropTable = function (spannerDriver) {
42
+ return function (table) { return __awaiter(void 0, void 0, void 0, function () {
43
+ var tableManager, foreignKeys, _a, _b, _c, _i, columnName, foreignKey, indexes, _d, _e, _f, _g, indexName, error_1;
44
+ return __generator(this, function (_h) {
45
+ switch (_h.label) {
46
+ case 0:
47
+ tableManager = spannerDriver.getTableManager();
48
+ return [4 /*yield*/, tableManager.schemaMetadata.tableExists(table)];
49
+ case 1:
50
+ if (!_h.sent()) return [3 /*break*/, 15];
51
+ return [4 /*yield*/, tableManager.schemaMetadata.getForeignKeys(table)];
52
+ case 2:
53
+ foreignKeys = _h.sent();
54
+ _a = foreignKeys;
55
+ _b = [];
56
+ for (_c in _a)
57
+ _b.push(_c);
58
+ _i = 0;
59
+ _h.label = 3;
60
+ case 3:
61
+ if (!(_i < _b.length)) return [3 /*break*/, 6];
62
+ _c = _b[_i];
63
+ if (!(_c in _a)) return [3 /*break*/, 5];
64
+ columnName = _c;
65
+ foreignKey = foreignKeys[columnName];
66
+ return [4 /*yield*/, spannerDriver.runUpdateSchema(new db_1.StatementFactory().dropForeignKey(table.name, { table: foreignKey.referencedTableName, column: foreignKey.referencedColumnName, referencedByColumn: columnName }).sql)];
67
+ case 4:
68
+ _h.sent();
69
+ _h.label = 5;
70
+ case 5:
71
+ _i++;
72
+ return [3 /*break*/, 3];
73
+ case 6: return [4 /*yield*/, tableManager.schemaMetadata.getIndexes(table)];
74
+ case 7:
75
+ indexes = _h.sent();
76
+ _d = indexes;
77
+ _e = [];
78
+ for (_f in _d)
79
+ _e.push(_f);
80
+ _g = 0;
81
+ _h.label = 8;
82
+ case 8:
83
+ if (!(_g < _e.length)) return [3 /*break*/, 13];
84
+ _f = _e[_g];
85
+ if (!(_f in _d)) return [3 /*break*/, 12];
86
+ indexName = _f;
87
+ if (indexName == 'PRIMARY_KEY')
88
+ return [3 /*break*/, 12];
89
+ _h.label = 9;
90
+ case 9:
91
+ _h.trys.push([9, 11, , 12]);
92
+ // console.info(`Dropping index: ${indexName}`);
93
+ return [4 /*yield*/, spannerDriver.runUpdateSchema(new db_1.StatementFactory().dropIndex({ name: indexName, columns: indexes[indexName] }, table.name).sql)];
94
+ case 10:
95
+ // console.info(`Dropping index: ${indexName}`);
96
+ _h.sent();
97
+ return [3 /*break*/, 12];
98
+ case 11:
99
+ error_1 = _h.sent();
100
+ console.error("Failed to drop index: ".concat(indexName, "\nreason: ").concat(error_1.details));
101
+ return [3 /*break*/, 12];
102
+ case 12:
103
+ _g++;
104
+ return [3 /*break*/, 8];
105
+ case 13: return [4 /*yield*/, spannerDriver.runUpdateSchema("DROP TABLE ".concat(table.name))];
106
+ case 14:
107
+ _h.sent();
108
+ _h.label = 15;
109
+ case 15: return [2 /*return*/];
110
+ }
111
+ });
112
+ }); };
113
+ };
114
+ exports.getDropTable = getDropTable;
115
+ //# sourceMappingURL=dropTable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dropTable.js","sourceRoot":"","sources":["../../test/dropTable.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oCAAwD;AAGjD,IAAM,YAAY,GAAG,UAAC,aAA4B;IACvD,OAAO,UAAO,KAAiB;;;;;oBAEvB,YAAY,GAAG,aAAa,CAAC,eAAe,EAAE,CAAC;oBACjD,qBAAM,YAAY,CAAC,cAAc,CAAC,WAAW,CAAC,KAAK,CAAC,EAAA;;yBAApD,SAAoD,EAApD,yBAAoD;oBAClC,qBAAM,YAAY,CAAC,cAAc,CAAC,cAAc,CAAC,KAAK,CAAC,EAAA;;oBAArE,WAAW,GAAG,SAAuD;yBACpD,WAAW;;;;;;;;;;;oBAC1B,UAAU,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;oBAC3C,qBAAM,aAAa,CAAC,eAAe,CAAC,IAAI,qBAAgB,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,mBAAmB,EAAE,MAAM,EAAE,UAAU,CAAC,oBAAoB,EAAE,kBAAkB,EAAE,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,EAAA;;oBAA9M,SAA8M,CAAC;;;;;wBAGjM,qBAAM,YAAY,CAAC,cAAc,CAAC,UAAU,CAAC,KAAK,CAAC,EAAA;;oBAA7D,OAAO,GAAG,SAAmD;yBAE7C,OAAO;;;;;;;;;;;oBAC3B,IAAI,SAAS,IAAI,aAAa;wBAC5B,yBAAS;;;;oBAGT,gDAAgD;oBAChD,qBAAM,aAAa,CAAC,eAAe,CAAC,IAAI,qBAAgB,EAAE,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAA;;oBADvI,gDAAgD;oBAChD,SAAuI,CAAC;;;;oBAExI,OAAO,CAAC,KAAK,CAAC,gCAAyB,SAAS,uBAAa,OAAK,CAAC,OAAO,CAAE,CAAC,CAAC;;;;;yBAGlF,qBAAM,aAAa,CAAC,eAAe,CAAC,qBAAc,KAAK,CAAC,IAAI,CAAE,CAAC,EAAA;;oBAA/D,SAA+D,CAAC;;;;;SAGnE,CAAA;AACH,CAAC,CAAA;AA5BY,QAAA,YAAY,gBA4BxB"}
@@ -0,0 +1,34 @@
1
+ /** Load Dependency Source Graphs */
2
+
3
+ import '@google-cloud/spanner';
4
+ import '@proteinjs/db';
5
+ import '@proteinjs/db-query';
6
+ import '@proteinjs/reflection';
7
+ import '@proteinjs/util';
8
+
9
+
10
+ /** Generate Source Graph */
11
+
12
+ const sourceGraph = "{\"options\":{\"directed\":true,\"multigraph\":false,\"compound\":false},\"nodes\":[{\"v\":\"@proteinjs/db-driver-spanner/SpannerConfig\",\"value\":{\"packageName\":\"@proteinjs/db-driver-spanner\",\"name\":\"SpannerConfig\",\"filePath\":\"/home/runner/work/db/db/packages/drivers/spanner/src/SpannerConfig.ts\",\"qualifiedName\":\"@proteinjs/db-driver-spanner/SpannerConfig\",\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/reflection\",\"name\":\"Loadable\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/reflection/Loadable\",\"typeParameters\":[],\"directParents\":null},{\"packageName\":\"@proteinjs/db-driver-spanner\",\"name\":\"{\\n projectId: string,\\n instanceName: string,\\n databaseName: string,\\n}\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db-driver-spanner/{\\n projectId: string,\\n instanceName: string,\\n databaseName: string,\\n}\",\"typeParameters\":[],\"directParents\":null}],\"sourceType\":1}},{\"v\":\"@proteinjs/reflection/Loadable\"},{\"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\":false,\"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\":false,\"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\":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\"}],\"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\":\"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: DbDriverStatementConfig) => Statement\",\"filePath\":null,\"qualifiedName\":\"/(config: DbDriverStatementConfig) => Statement\",\"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: DbDriverStatementConfig) => Statement\",\"filePath\":null,\"qualifiedName\":\"/(config: DbDriverStatementConfig) => Statement\",\"typeParameters\":null,\"directParents\":null}}]},{\"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\":\"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}}]}],\"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/SpannerConfig\",\"w\":\"@proteinjs/reflection/Loadable\",\"value\":\"extends type\"},{\"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\"}]}";
13
+
14
+
15
+ /** Generate Source Links */
16
+
17
+ import { SpannerDriver } from '../src/SpannerDriver';
18
+ import { SpannerSchemaMetadata } from '../src/SpannerSchemaMetadata';
19
+ import { SpannerSchemaOperations } from '../src/SpannerSchemaOperations';
20
+
21
+ const sourceLinks = {
22
+ '@proteinjs/db-driver-spanner/SpannerDriver': SpannerDriver,
23
+ '@proteinjs/db-driver-spanner/SpannerSchemaMetadata': SpannerSchemaMetadata,
24
+ '@proteinjs/db-driver-spanner/SpannerSchemaOperations': SpannerSchemaOperations,
25
+ };
26
+
27
+
28
+ /** Load Source Graph and Links */
29
+
30
+ import { SourceRepository } from '@proteinjs/reflection';
31
+ SourceRepository.merge(sourceGraph, sourceLinks);
32
+
33
+
34
+ export * from '../index';
package/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './src/SpannerDriver';
2
+ export * from './src/SpannerConfig';
package/jest.config.js ADDED
@@ -0,0 +1,19 @@
1
+ module.exports = {
2
+ "roots": [
3
+ "<rootDir>/test"
4
+ ],
5
+ "transform": {
6
+ "^.+\\.tsx?$": "ts-jest"
7
+ },
8
+ "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
9
+ "moduleFileExtensions": [
10
+ "ts",
11
+ "tsx",
12
+ "js",
13
+ "jsx",
14
+ "json",
15
+ "node"
16
+ ],
17
+ "testEnvironment": "node",
18
+ "setupFiles": ['./test/setup']
19
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@proteinjs/db-driver-spanner",
3
+ "version": "1.0.2",
4
+ "description": "Db driver for Google Spanner",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/brentbahry/db.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/brentbahry/db/issues"
14
+ },
15
+ "homepage": "https://github.com/brentbahry/db#readme",
16
+ "author": "Brent Bahry",
17
+ "license": "ISC",
18
+ "scripts": {
19
+ "clean": "rm -rf dist/ node_modules/ generated/ package-lock.json",
20
+ "build": "reflection-build && tsc",
21
+ "watch": "reflection-watch",
22
+ "test": "jest --runInBand"
23
+ },
24
+ "dependencies": {
25
+ "@google-cloud/spanner": "7.5.0",
26
+ "@proteinjs/db": "^1.0.3",
27
+ "@proteinjs/db-query": "^1.0.2",
28
+ "@proteinjs/reflection": "1.0.2",
29
+ "@proteinjs/util": "1.0.18"
30
+ },
31
+ "devDependencies": {
32
+ "@proteinjs/reflection-build": "1.0.5",
33
+ "@types/jest": "29.5.5",
34
+ "@types/node": "14.0.27",
35
+ "jest": "29.7.0",
36
+ "ts-jest": "29.1.1",
37
+ "typescript": "5.2.2"
38
+ },
39
+ "main": "./dist/generated/index.js",
40
+ "types": "./dist/generated/index.d.ts",
41
+ "gitHead": "05f576fa1cbd32912a08f7242b009d73103de30f"
42
+ }
@@ -0,0 +1,25 @@
1
+ import { BinaryColumn, BooleanColumn, Column, DateColumn, DateTimeColumn, DecimalColumn, FloatColumn, IntegerColumn, StringColumn } from '@proteinjs/db';
2
+
3
+ // max size of a row in spanner is 10mb
4
+ export class SpannerColumnTypeFactory {
5
+ getType(column: Column<any, any>): string {
6
+ if (column instanceof IntegerColumn)
7
+ return 'INT64';
8
+ else if (column instanceof StringColumn)
9
+ return `STRING(${column.maxLength})`;
10
+ else if (column instanceof FloatColumn)
11
+ return 'FLOAT64';
12
+ else if (column instanceof DecimalColumn)
13
+ return column.large ? 'BIGNUMERIC' : 'NUMERIC';
14
+ else if (column instanceof BooleanColumn)
15
+ return 'BOOL';
16
+ else if (column instanceof DateColumn)
17
+ return 'DATE';
18
+ else if (column instanceof DateTimeColumn)
19
+ return 'TIMESTAMP';
20
+ else if (column instanceof BinaryColumn)
21
+ return `BYTES(${!(column as BinaryColumn).maxLength ? 'MAX' : (column as BinaryColumn).maxLength})`;
22
+
23
+ throw new Error(`Invalid column type: ${column.constructor.name}, must extend a base column`);
24
+ }
25
+ }
@@ -0,0 +1,9 @@
1
+ import { Loadable, SourceRepository } from '@proteinjs/reflection';
2
+
3
+ export const getSpannerConfig = () => SourceRepository.get().object<SpannerConfig>('@proteinjs/db-driver-spanner/SpannerConfig');
4
+
5
+ export type SpannerConfig = Loadable & {
6
+ projectId: string,
7
+ instanceName: string,
8
+ databaseName: string,
9
+ }
@@ -0,0 +1,115 @@
1
+ import { Database, Instance, Spanner } from '@google-cloud/spanner';
2
+ import { DbDriver, DbDriverStatementConfig, TableManager } from '@proteinjs/db';
3
+ import { SpannerConfig, getSpannerConfig } from './SpannerConfig';
4
+ import { Logger } from '@proteinjs/util';
5
+ import { Statement } from '@proteinjs/db-query';
6
+ import { SpannerSchemaOperations } from './SpannerSchemaOperations';
7
+ import { SpannerColumnTypeFactory } from './SpannerColumnTypeFactory';
8
+ import { SpannerSchemaMetadata } from './SpannerSchemaMetadata';
9
+
10
+ export class SpannerDriver implements DbDriver {
11
+ private static SPANNER: Spanner;
12
+ private static SPANNER_INSTANCE: Instance;
13
+ private static SPANNER_DB: Database;
14
+ private logger = new Logger(this.constructor.name);
15
+ private config: SpannerConfig;
16
+
17
+ constructor(config?: SpannerConfig) {
18
+ this.config = config ? config : getSpannerConfig();
19
+ }
20
+
21
+ private getSpanner(): Spanner {
22
+ if (!SpannerDriver.SPANNER)
23
+ SpannerDriver.SPANNER = new Spanner({ projectId: this.config.projectId });
24
+
25
+
26
+ return SpannerDriver.SPANNER;
27
+ }
28
+
29
+ private getSpannerInstance(): Instance {
30
+ if (!SpannerDriver.SPANNER_INSTANCE)
31
+ SpannerDriver.SPANNER_INSTANCE = this.getSpanner().instance(this.config.instanceName);
32
+
33
+ return SpannerDriver.SPANNER_INSTANCE;
34
+ }
35
+
36
+ private getSpannerDb(): Database {
37
+ if (!SpannerDriver.SPANNER_DB)
38
+ SpannerDriver.SPANNER_DB = this.getSpannerInstance().database(this.config.databaseName);
39
+
40
+ return SpannerDriver.SPANNER_DB;
41
+ }
42
+
43
+ getDbName() {
44
+ return this.config.databaseName;
45
+ }
46
+
47
+ getTableManager(): TableManager {
48
+ const columnTypeFactory = new SpannerColumnTypeFactory();
49
+ const schemaOperations = new SpannerSchemaOperations(this);
50
+ const schemaMetadata = new SpannerSchemaMetadata(this, false);
51
+ return new TableManager(this, columnTypeFactory, schemaOperations, schemaMetadata);
52
+ }
53
+
54
+ async createDbIfNotExists(): Promise<void> {
55
+ if (await this.dbExists(this.getDbName()))
56
+ return;
57
+
58
+ await this.getSpannerInstance().createDatabase(this.getDbName());
59
+ }
60
+
61
+ private async dbExists(databaseName: string): Promise<boolean> {
62
+ const [exists] = await this.getSpannerInstance().database(databaseName).exists();
63
+ return exists;
64
+ }
65
+
66
+ async runQuery(generateStatement: (config: DbDriverStatementConfig) => Statement): Promise<any[]> {
67
+ const { sql, namedParams } = generateStatement({ useParams: true, useNamedParams: true, prefixTablesWithDb: false });
68
+ try {
69
+ this.logger.debug(`Executing query: ${sql}`);
70
+ const [rows] = await this.getSpannerDb().run({ sql, params: namedParams?.params });
71
+ return rows.map(row => row.toJSON());
72
+ // return JSON.parse(JSON.stringify((await this.getSpannerDb().run({ sql, params: namedParams?.params }))[0]));
73
+ } catch(error: any) {
74
+ this.logger.error(`Failed when executing query: ${sql}\nreason: ${error.details}`);
75
+ throw error;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Execute a write operation.
81
+ *
82
+ * @returns number of affected rows
83
+ */
84
+ async runDml(generateStatement: (config: DbDriverStatementConfig) => Statement): Promise<number> {
85
+ const { sql, namedParams } = generateStatement({ useParams: true, useNamedParams: true, prefixTablesWithDb: false });
86
+ try {
87
+ return await this.getSpannerDb().runTransactionAsync(async (transaction) => {
88
+ this.logger.debug(`Executing dml: ${sql}`);
89
+ const [rowCount] = await transaction.runUpdate({
90
+ sql,
91
+ params: namedParams?.params,
92
+ });
93
+ await transaction.commit();
94
+ return rowCount;
95
+ });
96
+ } catch(error: any) {
97
+ this.logger.error(`Failed when executing dml: ${sql}\nreason: ${error.details}`);
98
+ throw error;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Execute a schema write operation.
104
+ */
105
+ async runUpdateSchema(sql: string): Promise<void> {
106
+ try {
107
+ this.logger.debug(`Executing schema update: ${sql}`)
108
+ const [operation] = await this.getSpannerDb().updateSchema(sql);
109
+ await operation.promise();
110
+ } catch(error: any) {
111
+ this.logger.error(`Failed when executing schema update: ${sql}\nreason: ${error.details}`);
112
+ throw error;
113
+ }
114
+ }
115
+ }
@@ -0,0 +1,96 @@
1
+ import { ParameterizationConfig, QueryBuilder, SchemaMetadata, Table } from '@proteinjs/db';
2
+
3
+ export class SpannerSchemaMetadata extends SchemaMetadata {
4
+ async getColumnMetadata(table: Table<any>): Promise<{[columnName: string]: { type: string, isNullable: boolean }}> {
5
+ const qb = QueryBuilder.fromObject({
6
+ 'TABLE_NAME': table.name
7
+ }, 'COLUMNS');
8
+ const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
9
+ const results = await this.dbDriver.runQuery(generateStatement);
10
+ const columnMetadata: {[columnName: string]: { type: string, isNullable: boolean }} = {};
11
+ for (const row of results)
12
+ columnMetadata[row['COLUMN_NAME']] = { type: row['SPANNER_TYPE'], isNullable: row['IS_NULLABLE'] === 'YES' };
13
+
14
+ return columnMetadata;
15
+ }
16
+
17
+ async getPrimaryKey(table: Table<any>): Promise<string[]> {
18
+ const indexes = await this.getIndexes(table);
19
+ return indexes['PRIMARY_KEY'];
20
+ }
21
+
22
+ async getForeignKeys(table: Table<any>): Promise<{[columnName: string]: { referencedTableName: string, referencedColumnName: string }}> {
23
+ const tableConstraintsQb = QueryBuilder.fromObject({
24
+ 'TABLE_NAME': table.name,
25
+ 'CONSTRAINT_TYPE': 'FOREIGN KEY'
26
+ }, 'TABLE_CONSTRAINTS');
27
+ const generateTableConstraintsStatement = (config: ParameterizationConfig) => tableConstraintsQb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
28
+ const tableConstraints = await this.dbDriver.runQuery(generateTableConstraintsStatement);
29
+ const foreignKeys: {[columnName: string]: { referencedTableName: string, referencedColumnName: string }} = {};
30
+ for (const tableConstraint of tableConstraints) {
31
+ const referentialConstraintsQb = QueryBuilder.fromObject({
32
+ 'CONSTRAINT_NAME': tableConstraint['CONSTRAINT_NAME']
33
+ }, 'REFERENTIAL_CONSTRAINTS');
34
+ const generateReferentialConstraintsStatement = (config: ParameterizationConfig) => referentialConstraintsQb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
35
+ const referentialConstraints = await this.dbDriver.runQuery(generateReferentialConstraintsStatement);
36
+ const referencedTableConstraintName = referentialConstraints[0]['UNIQUE_CONSTRAINT_NAME'];
37
+
38
+ const referenceTableConstraintsQb = QueryBuilder.fromObject({
39
+ 'CONSTRAINT_NAME': referencedTableConstraintName
40
+ }, 'TABLE_CONSTRAINTS');
41
+ const generateReferenceTableConstraintsStatement = (config: ParameterizationConfig) => referenceTableConstraintsQb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
42
+ const referenceTableConstraints = await this.dbDriver.runQuery(generateReferenceTableConstraintsStatement);
43
+ const referencedTableName = referenceTableConstraints[0]['TABLE_NAME'];
44
+
45
+ const keyColumnUsageQb = QueryBuilder.fromObject({
46
+ 'TABLE_NAME': table.name,
47
+ 'CONSTRAINT_NAME': tableConstraint['CONSTRAINT_NAME']
48
+ }, 'KEY_COLUMN_USAGE');
49
+ const generateKeyColumnUsageStatement = (config: ParameterizationConfig) => keyColumnUsageQb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
50
+ const keyColumnUsages = await this.dbDriver.runQuery(generateKeyColumnUsageStatement);
51
+ const referencingColumnName = keyColumnUsages[0]['COLUMN_NAME'];
52
+
53
+ foreignKeys[referencingColumnName] = {
54
+ referencedTableName,
55
+ referencedColumnName: 'id',
56
+ };
57
+ }
58
+
59
+ return foreignKeys;
60
+ }
61
+
62
+ async getUniqueColumns(table: Table<any>): Promise<string[]> {
63
+ const indexes = await this.getIndexes(table);
64
+ const uniqueColumns: string[] = [];
65
+ for (const indexName in indexes) {
66
+ if (!indexName.endsWith('_unique'))
67
+ continue;
68
+
69
+ const indexedColumns = indexes[indexName];
70
+ uniqueColumns.push(...indexedColumns);
71
+ }
72
+
73
+ return uniqueColumns;
74
+ }
75
+
76
+ async getIndexes(table: Table<any>): Promise<{[keyName: string]: string[]}> {
77
+ const qb = new QueryBuilder('INDEX_COLUMNS')
78
+ .condition({ field: 'TABLE_NAME', operator: '=', value: table.name })
79
+ .sort([{ field: 'ORDINAL_POSITION', desc: false }])
80
+ ;
81
+ const generateStatement = (config: ParameterizationConfig) => qb.toSql({ dbName: 'INFORMATION_SCHEMA', ...config });
82
+ const results: any[] = await this.dbDriver.runQuery(generateStatement);
83
+ const indexes: { [keyName: string]: string[] } = {};
84
+ for (const row of results) {
85
+ if (!row['INDEX_NAME'])
86
+ continue;
87
+
88
+ if (!indexes[row['INDEX_NAME']])
89
+ indexes[row['INDEX_NAME']] = [];
90
+
91
+ indexes[row['INDEX_NAME']].push(row['COLUMN_NAME']);
92
+ }
93
+
94
+ return indexes;
95
+ }
96
+ }
@@ -0,0 +1,122 @@
1
+ import { Logger } from '@proteinjs/util';
2
+ import { Table, SchemaOperations, TableChanges, StatementFactory, AlterTableParams, StatementUtil, getColumnByName } from '@proteinjs/db';
3
+ import { SpannerDriver } from './SpannerDriver';
4
+ import { SpannerColumnTypeFactory } from './SpannerColumnTypeFactory';
5
+
6
+ export class SpannerSchemaOperations implements SchemaOperations {
7
+ private logger = new Logger(this.constructor.name);
8
+
9
+ constructor(
10
+ private spannerDriver: SpannerDriver
11
+ ){}
12
+
13
+ async createTable(table: Table<any>) {
14
+ const indexes: { name?: string, columns: string|string[], unique?: boolean }[] = [];
15
+ for (let index of table.indexes) {
16
+ indexes.push({ name: index.name, columns: index.columns as string[] });
17
+ }
18
+
19
+ const serializedColumns: { name: string, type: string, nullable?: boolean }[] = [];
20
+ const foreignKeys: { table: string, column: string, referencedByColumn: string }[] = [];
21
+ for (const columnPropertyName in table.columns) {
22
+ const column = table.columns[columnPropertyName];
23
+ const columnType = new SpannerColumnTypeFactory().getType(column);
24
+ serializedColumns.push({ name: column.name, type: columnType, nullable: column.options?.nullable });
25
+ this.logger.info(`[${table.name}] Creating column: ${column.name} (${column.constructor.name})`)
26
+ if (column.options?.unique?.unique) {
27
+ indexes.push({ name: column.options.unique.indexName, columns: column.name, unique: true });
28
+ this.logger.info(`[${table.name}.${column.name}] Adding unique constraint`);
29
+ }
30
+
31
+ if (column.options?.references) {
32
+ foreignKeys.push({ table: column.options.references.table, column: 'id', referencedByColumn: column.name });
33
+ this.logger.info(`[${table.name}.${column.name}] Adding foreign key -> ${column.options.references.table}.id`);
34
+ }
35
+ }
36
+ const createTableSql = new StatementFactory().createTable(table.name, serializedColumns, 'id', foreignKeys).sql;
37
+ await this.spannerDriver.runUpdateSchema(createTableSql);
38
+
39
+ for (let index of indexes) {
40
+ const createIndexSql = new StatementFactory().createIndex(index, table.name).sql;
41
+ const indexName = StatementUtil.getIndexName(table.name, index);
42
+ this.logger.info(`[${table.name}] Creating index: ${indexName} (${typeof index.columns === 'string' ? index.columns : index.columns.join(', ')})`);
43
+ await this.spannerDriver.runUpdateSchema(createIndexSql);
44
+ this.logger.info(`[${table.name}] Created index: ${indexName} (${typeof index.columns === 'string' ? index.columns : index.columns.join(', ')})`);
45
+ }
46
+ }
47
+
48
+ async alterTable(table: Table<any>, tableChanges: TableChanges) {
49
+ const alterParams: AlterTableParams = {
50
+ tableName: table.name,
51
+ columnsToAdd: [],
52
+ foreignKeysToDrop: [],
53
+ foreignKeysToAdd: [],
54
+ columnRenames: [],
55
+ };
56
+ const indexesToDrop = tableChanges.indexesToDrop;
57
+ const indexesToAdd = tableChanges.indexesToCreate;
58
+ for (const columnPropertyName of tableChanges.columnsToCreate) {
59
+ const column = table.columns[columnPropertyName];
60
+ const columnType = new SpannerColumnTypeFactory().getType(column);
61
+ alterParams.columnsToAdd?.push({ name: column.name, type: columnType, nullable: column.options?.nullable });
62
+ this.logger.info(`[${table.name}] Creating column: ${column.name} (${column.constructor.name})`);
63
+ if (column.options?.unique?.unique && tableChanges.columnsWithUniqueConstraintsToCreate.includes(column.name)) {
64
+ indexesToAdd.push({ name: column.options.unique.indexName, columns: column.name, unique: true });
65
+ this.logger.info(`[${table.name}.${column.name}] Adding unique constraint`);
66
+ }
67
+
68
+ if (column.options?.references && tableChanges.columnsWithForeignKeysToCreate.includes(column.name)) {
69
+ alterParams.foreignKeysToAdd?.push({ table: column.options.references.table, column: 'id', referencedByColumn: column.name });
70
+ this.logger.info(`[${table.name}.${column.name}] Adding foreign key -> ${column.options.references.table}.id`);
71
+ }
72
+ }
73
+
74
+ for (const columnName of tableChanges.columnsWithUniqueConstraintsToDrop) {
75
+ indexesToDrop.push({ columns: columnName, unique: true });
76
+ this.logger.info(`[${table.name}.${columnName}] Dropping unique constraint`);
77
+ }
78
+
79
+ for (const foreignKey of tableChanges.foreignKeysToDrop) {
80
+ alterParams.foreignKeysToDrop?.push(foreignKey);
81
+ this.logger.info(`[${table.name}.${foreignKey.referencedByColumn}] Dropping foreign key -> ${foreignKey.table}.${foreignKey.column}`);
82
+ }
83
+
84
+ for (const columnTypeChange of tableChanges.columnTypeChanges) {
85
+ const errorMessage = `[${table.name}.${columnTypeChange.name}] Unable to change column types in Spanner. Attempted to change type to: ${columnTypeChange.newType}`;
86
+ this.logger.error(errorMessage);
87
+ throw new Error(errorMessage);
88
+ }
89
+
90
+ for (const columnNullableChange of tableChanges.columnNullableChanges) {
91
+ const errorMessage = `[${table.name}.${columnNullableChange.name}] Unable to update nullable constraint on existing column in Spanner. Attempted to update nullable constraint to: ${columnNullableChange.nullable === true}`;
92
+ this.logger.error(errorMessage);
93
+ throw new Error(errorMessage);
94
+ }
95
+
96
+ for (const columnPropertyName of tableChanges.columnsToRename) {
97
+ const column = table.columns[columnPropertyName];
98
+ const errorMessage = `[${table.name}.${column.oldName}] Unable to rename columns in Spanner. Attempted to perform rename: ${column.oldName} -> ${column.name}`;
99
+ this.logger.error(errorMessage);
100
+ throw new Error(errorMessage);
101
+ }
102
+
103
+ const alterStatements = new StatementFactory().alterTable(alterParams);
104
+ for (let alterStatement of alterStatements)
105
+ await this.spannerDriver.runUpdateSchema(alterStatement.sql);
106
+
107
+ for (let index of tableChanges.indexesToDrop) {
108
+ const dropIndexSql = new StatementFactory().dropIndex(index, table.name).sql;
109
+ this.logger.info(`[${table.name}] Dropping index: ${index.name} (${typeof index.columns === 'string' ? index.columns : index.columns.join(', ')})`);
110
+ await this.spannerDriver.runUpdateSchema(dropIndexSql);
111
+ this.logger.info(`[${table.name}] Dropped index: ${index.name} (${typeof index.columns === 'string' ? index.columns : index.columns.join(', ')})`);
112
+ }
113
+
114
+ for (let index of tableChanges.indexesToCreate) {
115
+ const createIndexSql = new StatementFactory().createIndex(index, table.name).sql;
116
+ const indexName = StatementUtil.getIndexName(table.name, index);
117
+ this.logger.info(`[${table.name}] Creating index: ${indexName} (${typeof index.columns === 'string' ? index.columns : index.columns.join(', ')})`);
118
+ await this.spannerDriver.runUpdateSchema(createIndexSql);
119
+ this.logger.info(`[${table.name}] Created index: ${indexName} (${typeof index.columns === 'string' ? index.columns : index.columns.join(', ')})`);
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,17 @@
1
+ import { crudTests } from '@proteinjs/db'
2
+ import { SpannerDriver } from '../src/SpannerDriver'
3
+ import { getDropTable } from './dropTable'
4
+
5
+ const spannerDriver = new SpannerDriver({
6
+ projectId: 'proteinjs-test',
7
+ instanceName: 'proteinjs-test',
8
+ databaseName: 'test',
9
+ });
10
+
11
+ describe(
12
+ 'CRUD Tests',
13
+ crudTests(
14
+ spannerDriver,
15
+ getDropTable(spannerDriver)
16
+ )
17
+ );