@twin.org/entity-storage-connector-postgresql 0.0.2-next.9 → 0.0.3-next.1

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.
@@ -1,27 +1,47 @@
1
- import { Guards, ComponentFactory, BaseError, GeneralError, Is, ObjectHelper } from '@twin.org/core';
2
- import { EntitySchemaFactory, EntitySchemaHelper, EntitySchemaPropertyType, SortDirection, ComparisonOperator, LogicalOperator } from '@twin.org/entity';
3
- import postgres from 'postgres';
4
-
5
1
  // Copyright 2024 IOTA Stiftung.
6
2
  // SPDX-License-Identifier: Apache-2.0.
3
+ import { ContextIdHelper, ContextIdStore } from "@twin.org/context";
4
+ import { BaseError, Coerce, ComponentFactory, GeneralError, Guards, Is, ObjectHelper } from "@twin.org/core";
5
+ import { ComparisonOperator, EntitySchemaFactory, EntitySchemaHelper, EntitySchemaPropertyType, LogicalOperator, SortDirection } from "@twin.org/entity";
6
+ import postgres from "postgres";
7
7
  /**
8
8
  * Class for performing entity storage operations using ql.
9
9
  */
10
- class PostgreSqlEntityStorageConnector {
10
+ export class PostgreSqlEntityStorageConnector {
11
+ /**
12
+ * Runtime name for the class.
13
+ */
14
+ static CLASS_NAME = "PostgreSqlEntityStorageConnector";
11
15
  /**
12
16
  * Limit the number of entities when finding.
13
17
  * @internal
14
18
  */
15
- static _PAGE_SIZE = 40;
19
+ static _DEFAULT_LIMIT = 40;
16
20
  /**
17
- * Runtime name for the class.
21
+ * Partition id field name.
22
+ * @internal
18
23
  */
19
- CLASS_NAME = "PostgreSqlEntityStorageConnector";
24
+ static _PARTITION_KEY = "partitionId";
25
+ /**
26
+ * Partition id field value.
27
+ * @internal
28
+ */
29
+ static _PARTITION_KEY_VALUE = "root";
20
30
  /**
21
31
  * The schema for the entity.
22
32
  * @internal
23
33
  */
24
34
  _entitySchema;
35
+ /**
36
+ * The keys to use from the context ids to create partitions.
37
+ * @internal
38
+ */
39
+ _partitionContextIds;
40
+ /**
41
+ * The primary key property.
42
+ * @internal
43
+ */
44
+ _primaryKeyProperty;
25
45
  /**
26
46
  * The configuration for the connector.
27
47
  * @internal
@@ -37,15 +57,17 @@ class PostgreSqlEntityStorageConnector {
37
57
  * @param options The options for the connector.
38
58
  */
39
59
  constructor(options) {
40
- Guards.object(this.CLASS_NAME, "options", options);
41
- Guards.stringValue(this.CLASS_NAME, "options.entitySchema", options.entitySchema);
42
- Guards.object(this.CLASS_NAME, "options.config", options.config);
43
- Guards.stringValue(this.CLASS_NAME, "options.config.host", options.config.host);
44
- Guards.stringValue(this.CLASS_NAME, "options.config.user", options.config.user);
45
- Guards.stringValue(this.CLASS_NAME, "options.config.password", options.config.password);
46
- Guards.stringValue(this.CLASS_NAME, "options.config.database", options.config.database);
47
- Guards.stringValue(this.CLASS_NAME, "options.config.tableName", options.config.tableName);
60
+ Guards.object(PostgreSqlEntityStorageConnector.CLASS_NAME, "options", options);
61
+ Guards.stringValue(PostgreSqlEntityStorageConnector.CLASS_NAME, "options.entitySchema", options.entitySchema);
62
+ Guards.object(PostgreSqlEntityStorageConnector.CLASS_NAME, "options.config", options.config);
63
+ Guards.stringValue(PostgreSqlEntityStorageConnector.CLASS_NAME, "options.config.host", options.config.host);
64
+ Guards.stringValue(PostgreSqlEntityStorageConnector.CLASS_NAME, "options.config.user", options.config.user);
65
+ Guards.stringValue(PostgreSqlEntityStorageConnector.CLASS_NAME, "options.config.password", options.config.password);
66
+ Guards.stringValue(PostgreSqlEntityStorageConnector.CLASS_NAME, "options.config.database", options.config.database);
67
+ Guards.stringValue(PostgreSqlEntityStorageConnector.CLASS_NAME, "options.config.tableName", options.config.tableName);
48
68
  this._entitySchema = EntitySchemaFactory.get(options.entitySchema);
69
+ this._partitionContextIds = options.partitionContextIds;
70
+ this._primaryKeyProperty = EntitySchemaHelper.getPrimaryKey(this._entitySchema);
49
71
  this._config = options.config;
50
72
  }
51
73
  /**
@@ -57,59 +79,80 @@ class PostgreSqlEntityStorageConnector {
57
79
  const nodeLogging = ComponentFactory.getIfExists(nodeLoggingComponentType);
58
80
  try {
59
81
  const dbConnection = await this.createConnection();
60
- await nodeLogging?.log({
61
- level: "info",
62
- source: this.CLASS_NAME,
63
- ts: Date.now(),
64
- message: "databaseCreating",
65
- data: {
66
- database: this._config.database
67
- }
68
- });
69
- const res = await dbConnection.unsafe(`SELECT datname FROM pg_catalog.pg_database WHERE datname = '${this._config.database}'`);
70
- if (res.length === 0) {
82
+ const databaseExists = await this.databaseExists();
83
+ if (!databaseExists) {
84
+ await nodeLogging?.log({
85
+ level: "info",
86
+ source: PostgreSqlEntityStorageConnector.CLASS_NAME,
87
+ ts: Date.now(),
88
+ message: "databaseCreating",
89
+ data: {
90
+ databaseName: this._config.database
91
+ }
92
+ });
71
93
  await dbConnection.unsafe(`CREATE DATABASE "${this._config.database}";`);
94
+ await this.waitForDatabaseExists();
72
95
  }
73
- await nodeLogging?.log({
74
- level: "info",
75
- source: this.CLASS_NAME,
76
- ts: Date.now(),
77
- message: "databaseExists",
78
- data: {
79
- database: this._config.database
80
- }
81
- });
82
- const tableExistsQuery = `SELECT to_regclass('${this._config.tableName}')`;
83
- const tableExistsResult = await dbConnection.unsafe(tableExistsQuery);
84
- if (!tableExistsResult[0].to_regclass) {
85
- const createTableQuery = `CREATE TABLE IF NOT EXISTS ${this._config.tableName} (${this.mapPostgreSqlProperties(this._entitySchema)})`;
96
+ else {
97
+ await nodeLogging?.log({
98
+ level: "info",
99
+ source: PostgreSqlEntityStorageConnector.CLASS_NAME,
100
+ ts: Date.now(),
101
+ message: "databaseExists",
102
+ data: {
103
+ databaseName: this._config.database
104
+ }
105
+ });
106
+ }
107
+ const tableExists = await this.tableExists();
108
+ if (!tableExists) {
109
+ await nodeLogging?.log({
110
+ level: "info",
111
+ source: PostgreSqlEntityStorageConnector.CLASS_NAME,
112
+ ts: Date.now(),
113
+ message: "tableCreating",
114
+ data: {
115
+ tableName: this._config.tableName
116
+ }
117
+ });
118
+ const createTableQuery = `CREATE TABLE ${this._config.tableName} (${this.mapPostgreSqlProperties(this._entitySchema)})`;
86
119
  await dbConnection.unsafe(createTableQuery);
120
+ await this.waitForTableExists();
121
+ }
122
+ else {
123
+ await nodeLogging?.log({
124
+ level: "info",
125
+ source: PostgreSqlEntityStorageConnector.CLASS_NAME,
126
+ ts: Date.now(),
127
+ message: "tableExists",
128
+ data: {
129
+ tableName: this._config.tableName
130
+ }
131
+ });
87
132
  }
88
- await nodeLogging?.log({
89
- level: "info",
90
- source: this.CLASS_NAME,
91
- ts: Date.now(),
92
- message: "tableExists",
93
- data: {
94
- table: this._config.tableName
95
- }
96
- });
97
133
  }
98
134
  catch (error) {
99
135
  await nodeLogging?.log({
100
136
  level: "error",
101
- source: this.CLASS_NAME,
137
+ source: PostgreSqlEntityStorageConnector.CLASS_NAME,
102
138
  ts: Date.now(),
103
139
  message: "databaseCreateFailed",
104
140
  error: BaseError.fromError(error),
105
141
  data: {
106
- database: this._config.database
142
+ databaseName: this._config.database
107
143
  }
108
144
  });
109
145
  return false;
110
146
  }
111
147
  return true;
112
148
  }
149
+ /**
150
+ * Returns the class name of the component.
151
+ * @returns The class name of the component.
152
+ */
153
+ className() {
154
+ return PostgreSqlEntityStorageConnector.CLASS_NAME;
155
+ }
113
156
  /**
114
157
  * Get the schema for the entities.
115
158
  * @returns The schema for the entities.
@@ -125,21 +168,24 @@ class PostgreSqlEntityStorageConnector {
125
168
  * @returns The object if it can be found or undefined.
126
169
  */
127
170
  async get(id, secondaryIndex, conditions) {
128
- Guards.stringValue(this.CLASS_NAME, "id", id);
171
+ Guards.stringValue(PostgreSqlEntityStorageConnector.CLASS_NAME, "id", id);
172
+ const contextIds = await ContextIdStore.getContextIds();
173
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
129
174
  try {
130
175
  const dbConnection = await this.createConnection();
131
176
  const whereClauses = [];
132
177
  const values = [];
178
+ whereClauses.push(`"${PostgreSqlEntityStorageConnector._PARTITION_KEY}" = $1`);
179
+ values.push(partitionKey ?? PostgreSqlEntityStorageConnector._PARTITION_KEY_VALUE);
133
180
  if (secondaryIndex) {
134
- whereClauses.push(`"${String(secondaryIndex)}" = $1`);
181
+ whereClauses.push(`"${String(secondaryIndex)}" = $2`);
135
182
  values.push(id);
136
183
  }
137
184
  else {
138
- const primaryKey = EntitySchemaHelper.getPrimaryKey(this.getSchema());
139
- whereClauses.push(`"${primaryKey.property}" = $1`);
185
+ whereClauses.push(`"${this._primaryKeyProperty.property}" = $2`);
140
186
  values.push(id);
141
187
  }
142
- if (conditions) {
188
+ if (Is.arrayValue(conditions)) {
143
189
  for (const condition of conditions) {
144
190
  whereClauses.push(`"${String(condition.property)}" = $${values.length + 1}`);
145
191
  values.push(condition.value);
@@ -147,7 +193,7 @@ class PostgreSqlEntityStorageConnector {
147
193
  }
148
194
  const query = `SELECT * FROM "${this._config.tableName}" WHERE ${whereClauses.join(" AND ")} LIMIT 1`;
149
195
  const rows = await dbConnection.unsafe(query, values);
150
- if (Array.isArray(rows) && rows.length === 1) {
196
+ if (Is.array(rows) && rows.length === 1) {
151
197
  if (this._entitySchema.properties) {
152
198
  for (const prop of this._entitySchema.properties) {
153
199
  const row = rows[0];
@@ -165,11 +211,13 @@ class PostgreSqlEntityStorageConnector {
165
211
  }
166
212
  }
167
213
  }
168
- return rows[0];
214
+ const entity = ObjectHelper.removeEmptyProperties(rows[0], { removeNull: true });
215
+ ObjectHelper.propertyDelete(entity, PostgreSqlEntityStorageConnector._PARTITION_KEY);
216
+ return entity;
169
217
  }
170
218
  }
171
219
  catch (err) {
172
- throw new GeneralError(this.CLASS_NAME, "getFailed", {
220
+ throw new GeneralError(PostgreSqlEntityStorageConnector.CLASS_NAME, "getFailed", {
173
221
  id
174
222
  }, err);
175
223
  }
@@ -182,10 +230,11 @@ class PostgreSqlEntityStorageConnector {
182
230
  * @returns The id of the entity.
183
231
  */
184
232
  async set(entity, conditions) {
185
- Guards.object(this.CLASS_NAME, "entity", entity);
233
+ Guards.object(PostgreSqlEntityStorageConnector.CLASS_NAME, "entity", entity);
234
+ const contextIds = await ContextIdStore.getContextIds();
235
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
186
236
  EntitySchemaHelper.validateEntity(entity, this.getSchema());
187
- const primaryKey = EntitySchemaHelper.getPrimaryKey(this.getSchema());
188
- const id = entity[primaryKey.property];
237
+ const id = entity[this._primaryKeyProperty.property];
189
238
  try {
190
239
  if (Is.arrayValue(conditions)) {
191
240
  const itemData = await this.get(id);
@@ -193,20 +242,37 @@ class PostgreSqlEntityStorageConnector {
193
242
  return;
194
243
  }
195
244
  }
196
- const columns = Object.keys(entity)
197
- .map(key => `"${key}"`)
198
- .join(", ");
199
- // eslint-disable-next-line no-confusing-arrow
200
- const values = Object.values(entity).map(value => value === undefined ? null : value);
201
- const placeholders = values.map((_, index) => `$${index + 1}`).join(", ");
245
+ const finalEntity = ObjectHelper.clone(entity);
246
+ const props = [...(this._entitySchema.properties ?? [])];
247
+ props.unshift({
248
+ property: PostgreSqlEntityStorageConnector._PARTITION_KEY,
249
+ type: EntitySchemaPropertyType.String
250
+ });
251
+ ObjectHelper.propertySet(finalEntity, PostgreSqlEntityStorageConnector._PARTITION_KEY, partitionKey ?? PostgreSqlEntityStorageConnector._PARTITION_KEY_VALUE);
252
+ ObjectHelper.propertySet(finalEntity, PostgreSqlEntityStorageConnector._PARTITION_KEY, partitionKey ?? PostgreSqlEntityStorageConnector._PARTITION_KEY_VALUE);
253
+ const keys = [];
254
+ const values = [];
255
+ for (const prop of props) {
256
+ if (!(Is.empty(finalEntity[prop.property]) && (prop.optional ?? false))) {
257
+ keys.push(prop.property);
258
+ if (finalEntity[prop.property] === undefined) {
259
+ values.push(null);
260
+ }
261
+ else {
262
+ values.push(finalEntity[prop.property]);
263
+ }
264
+ }
265
+ }
266
+ let sql = `INSERT INTO "${this._config.tableName}"`;
267
+ sql += ` (${keys.map(key => `"${key}"`).join(", ")})`;
268
+ sql += ` VALUES (${values.map((_, i) => `$${i + 1}`).join(", ")})`;
269
+ sql += ` ON CONFLICT ("${PostgreSqlEntityStorageConnector._PARTITION_KEY}", "${this._primaryKeyProperty.property}")`;
270
+ sql += ` DO UPDATE SET ${keys.map(key => `"${key}" = EXCLUDED."${key}"`).join(", ")};`;
202
271
  const dbConnection = await this.createConnection();
203
- await dbConnection.unsafe(`INSERT INTO "${this._config.tableName}" (${columns}) VALUES (${placeholders}) ON CONFLICT ("${primaryKey.property}") DO UPDATE SET ${columns
204
- .split(", ")
205
- .map(col => `${col} = EXCLUDED.${col}`)
206
- .join(", ")};`, values);
272
+ await dbConnection.unsafe(sql, values);
207
273
  }
208
274
  catch (err) {
209
- throw new GeneralError(this.CLASS_NAME, "setFailed", {
275
+ throw new GeneralError(PostgreSqlEntityStorageConnector.CLASS_NAME, "setFailed", {
210
276
  id
211
277
  }, err);
212
278
  }
@@ -218,26 +284,31 @@ class PostgreSqlEntityStorageConnector {
218
284
  * @returns Nothing.
219
285
  */
220
286
  async remove(id, conditions) {
221
- Guards.stringValue(this.CLASS_NAME, "id", id);
287
+ Guards.stringValue(PostgreSqlEntityStorageConnector.CLASS_NAME, "id", id);
288
+ const contextIds = await ContextIdStore.getContextIds();
289
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
222
290
  try {
223
291
  const dbConnection = await this.createConnection();
224
292
  const itemData = await this.get(id);
225
293
  if (Is.notEmpty(itemData)) {
226
- const values = [id];
227
- let whereClauses = [];
294
+ const values = [];
295
+ const whereClauses = [];
296
+ whereClauses.push(`"${this._primaryKeyProperty.property}" = $${values.length + 1}`);
297
+ values.push(id);
298
+ whereClauses.push(`"${PostgreSqlEntityStorageConnector._PARTITION_KEY}" = $${values.length + 1}`);
299
+ values.push(partitionKey ?? PostgreSqlEntityStorageConnector._PARTITION_KEY_VALUE);
228
300
  if (Is.arrayValue(conditions)) {
229
- whereClauses = conditions.map(condition => {
301
+ whereClauses.push(...conditions.map(condition => {
230
302
  values.push(condition.value);
231
303
  return `"${String(condition.property)}" = $${values.length}`;
232
- });
304
+ }));
233
305
  }
234
- const primaryKey = EntitySchemaHelper.getPrimaryKey(this.getSchema());
235
- const query = `DELETE FROM "${this._config.tableName}" WHERE "${primaryKey.property}" = $1${whereClauses.length > 0 ? ` AND ${whereClauses.join(" AND ")}` : ""}`;
306
+ const query = `DELETE FROM "${this._config.tableName}" WHERE ${whereClauses.join(" AND ")}`;
236
307
  await dbConnection.unsafe(query, values);
237
308
  }
238
309
  }
239
310
  catch (err) {
240
- throw new GeneralError(this.CLASS_NAME, "removeFailed", {
311
+ throw new GeneralError(PostgreSqlEntityStorageConnector.CLASS_NAME, "removeFailed", {
241
312
  id
242
313
  }, err);
243
314
  }
@@ -247,17 +318,19 @@ class PostgreSqlEntityStorageConnector {
247
318
  * @param conditions The conditions to match for the entities.
248
319
  * @param sortProperties The optional sort order.
249
320
  * @param properties The optional properties to return, defaults to all.
250
- * @param cursor The cursor to request the next page of entities.
251
- * @param pageSize The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
321
+ * @param cursor The cursor to request the next chunk of entities.
322
+ * @param limit The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
252
323
  * @returns All the entities for the storage matching the conditions,
253
324
  * and a cursor which can be used to request more entities.
254
325
  */
255
- async query(conditions, sortProperties, properties, cursor, pageSize) {
256
- const sql = "";
326
+ async query(conditions, sortProperties, properties, cursor, limit) {
327
+ const contextIds = await ContextIdStore.getContextIds();
328
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
329
+ let sql = "";
257
330
  try {
258
- const returnSize = pageSize ?? PostgreSqlEntityStorageConnector._PAGE_SIZE;
331
+ const returnSize = limit ?? PostgreSqlEntityStorageConnector._DEFAULT_LIMIT;
259
332
  let orderByClause = "";
260
- if (Array.isArray(sortProperties)) {
333
+ if (Is.arrayValue(sortProperties)) {
261
334
  const orderClauses = [];
262
335
  for (const sortProperty of sortProperties) {
263
336
  const direction = sortProperty.sortDirection === SortDirection.Ascending ? "ASC" : "DESC";
@@ -267,12 +340,25 @@ class PostgreSqlEntityStorageConnector {
267
340
  }
268
341
  const whereClauses = [];
269
342
  const values = [];
270
- if (conditions) {
271
- this.buildQueryParameters("", conditions, whereClauses, values);
343
+ const finalConditions = {
344
+ conditions: [],
345
+ logicalOperator: LogicalOperator.And
346
+ };
347
+ finalConditions.conditions.push({
348
+ property: PostgreSqlEntityStorageConnector._PARTITION_KEY,
349
+ comparison: ComparisonOperator.Equals,
350
+ value: partitionKey ?? PostgreSqlEntityStorageConnector._PARTITION_KEY_VALUE
351
+ });
352
+ if (!Is.empty(conditions)) {
353
+ finalConditions.conditions.push(conditions);
272
354
  }
273
- const query = `SELECT ${properties ? properties.map(p => `"${String(p)}"`).join(", ") : "*"} FROM "${this._config.tableName}" ${whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : ""} ${orderByClause} LIMIT ${returnSize} OFFSET ${cursor ? Number(cursor) : 0}::integer`;
355
+ this.buildQueryParameters("", finalConditions, whereClauses, values, 1);
356
+ const startIndex = Coerce.number(cursor) ?? 0;
357
+ sql = `SELECT ${properties ? properties.map(p => `"${String(p)}"`).join(", ") : "*"} FROM "${this._config.tableName}"`;
358
+ sql += ` WHERE ${whereClauses.join(" AND ")}`;
359
+ sql += ` ${orderByClause} LIMIT ${returnSize} OFFSET ${startIndex}`;
274
360
  const dbConnection = await this.createConnection();
275
- const rows = await dbConnection.unsafe(query, values);
361
+ const rows = await dbConnection.unsafe(sql, values);
276
362
  if (this._entitySchema.properties) {
277
363
  for (const row of rows) {
278
364
  for (const prop of this._entitySchema.properties) {
@@ -280,7 +366,7 @@ class PostgreSqlEntityStorageConnector {
280
366
  propColumn = propColumn.toLowerCase();
281
367
  if ((prop.type === EntitySchemaPropertyType.Object ||
282
368
  prop.type === EntitySchemaPropertyType.Array) &&
283
- typeof row[propColumn] === "string") {
369
+ Is.string(row[propColumn])) {
284
370
  const rowValue = JSON.parse(row[propColumn]);
285
371
  delete row[propColumn];
286
372
  row[prop.property] = rowValue;
@@ -291,15 +377,20 @@ class PostgreSqlEntityStorageConnector {
291
377
  }
292
378
  }
293
379
  }
380
+ const entities = rows;
381
+ for (let i = 0; i < entities.length; i++) {
382
+ ObjectHelper.propertyDelete(entities[i], PostgreSqlEntityStorageConnector._PARTITION_KEY);
383
+ entities[i] = ObjectHelper.removeEmptyProperties(entities[i], { removeNull: true });
384
+ }
294
385
  return {
295
- entities: rows,
296
- cursor: Array.isArray(rows) && rows.length === returnSize
297
- ? String((cursor ? Number(cursor) : 0) + returnSize)
386
+ entities,
387
+ cursor: Is.array(rows) && rows.length === returnSize
388
+ ? Coerce.string(startIndex + returnSize)
298
389
  : undefined
299
390
  };
300
391
  }
301
392
  catch (err) {
302
- throw new GeneralError(this.CLASS_NAME, "queryFailed", { sql }, err);
393
+ throw new GeneralError(PostgreSqlEntityStorageConnector.CLASS_NAME, "queryFailed", { sql }, err);
303
394
  }
304
395
  }
305
396
  /**
@@ -308,25 +399,101 @@ class PostgreSqlEntityStorageConnector {
308
399
  */
309
400
  async tableDrop() {
310
401
  try {
402
+ const tableExists = await this.tableExists();
403
+ if (!tableExists) {
404
+ return;
405
+ }
311
406
  const dbConnection = await this.createConnection();
312
407
  await dbConnection.unsafe(`DROP TABLE ${this._config.tableName};`);
408
+ await this.waitForTableNotExists();
313
409
  }
314
410
  catch {
315
411
  // Ignore errors
316
412
  }
317
413
  }
414
+ /**
415
+ * Check if the database exists.
416
+ * @returns True if the database exists, false otherwise.
417
+ * @internal
418
+ */
419
+ async databaseExists() {
420
+ try {
421
+ const dbConnection = await this.createConnection();
422
+ const res = await dbConnection.unsafe(`SELECT datname FROM pg_catalog.pg_database WHERE datname = '${this._config.database}'`);
423
+ return res.length > 0;
424
+ }
425
+ catch {
426
+ return false;
427
+ }
428
+ }
429
+ /**
430
+ * Wait for a database to exist.
431
+ * @returns Nothing.
432
+ * @internal
433
+ */
434
+ async waitForDatabaseExists() {
435
+ for (let attempt = 0; attempt < 20; attempt++) {
436
+ const databaseExists = await this.databaseExists();
437
+ if (databaseExists) {
438
+ break;
439
+ }
440
+ await new Promise(resolve => setTimeout(resolve, 250));
441
+ }
442
+ }
443
+ /**
444
+ * Check if the table exists.
445
+ * @returns True if the table exists, false otherwise.
446
+ * @internal
447
+ */
448
+ async tableExists() {
449
+ try {
450
+ const dbConnection = await this.createConnection();
451
+ const tableExistsQuery = `SELECT to_regclass('${this._config.tableName}')`;
452
+ const tableExistsResult = await dbConnection.unsafe(tableExistsQuery);
453
+ return tableExistsResult[0].to_regclass !== null;
454
+ }
455
+ catch {
456
+ return false;
457
+ }
458
+ }
459
+ /**
460
+ * Wait for a table to exist.
461
+ * @returns Nothing.
462
+ * @internal
463
+ */
464
+ async waitForTableExists() {
465
+ for (let attempt = 0; attempt < 20; attempt++) {
466
+ const tableExists = await this.tableExists();
467
+ if (tableExists) {
468
+ break;
469
+ }
470
+ await new Promise(resolve => setTimeout(resolve, 250));
471
+ }
472
+ }
473
+ /**
474
+ * Wait for a table to not exist.
475
+ * @returns Nothing.
476
+ * @internal
477
+ */
478
+ async waitForTableNotExists() {
479
+ for (let attempt = 0; attempt < 20; attempt++) {
480
+ const tableExists = await this.tableExists();
481
+ if (!tableExists) {
482
+ break;
483
+ }
484
+ await new Promise(resolve => setTimeout(resolve, 250));
485
+ }
486
+ }
318
487
  /**
319
488
  * Create a new DB connection.
320
489
  * @returns The PostgreSql connection.
321
490
  * @internal
322
491
  */
323
492
  async createConnection() {
324
- if (this._connection) {
325
- return this._connection;
493
+ if (Is.empty(this._connection)) {
494
+ this._connection = postgres(this.createConnectionConfig());
326
495
  }
327
- const newConnection = await postgres(this.createConnectionConfig());
328
- this._connection = newConnection;
329
- return newConnection;
496
+ return this._connection;
330
497
  }
331
498
  /**
332
499
  * Create a new DB connection configuration.
@@ -347,9 +514,10 @@ class PostgreSqlEntityStorageConnector {
347
514
  * @param condition The conditions to create the query from.
348
515
  * @param whereClauses The where clauses to use in the query.
349
516
  * @param values The values to use in the query.
517
+ * @param valueIndex The current value index.
350
518
  * @internal
351
519
  */
352
- buildQueryParameters(objectPath, condition, whereClauses, values) {
520
+ buildQueryParameters(objectPath, condition, whereClauses, values, valueIndex) {
353
521
  if (Is.undefined(condition)) {
354
522
  return;
355
523
  }
@@ -360,8 +528,9 @@ class PostgreSqlEntityStorageConnector {
360
528
  const joinConditions = condition.conditions.map(c => {
361
529
  const subWhereClauses = [];
362
530
  const subValues = [];
363
- this.buildQueryParameters(objectPath, c, subWhereClauses, subValues);
531
+ this.buildQueryParameters(objectPath, c, subWhereClauses, subValues, valueIndex);
364
532
  values.push(...subValues);
533
+ valueIndex += subValues.length;
365
534
  return subWhereClauses.join(" AND ");
366
535
  });
367
536
  const logicalOperator = this.mapConditionalOperator(condition.logicalOperator);
@@ -372,7 +541,7 @@ class PostgreSqlEntityStorageConnector {
372
541
  return;
373
542
  }
374
543
  const schemaProp = this._entitySchema.properties?.find(p => p.property === condition.property);
375
- const comparison = this.mapComparisonOperator(objectPath, condition, schemaProp?.type, values);
544
+ const comparison = this.mapComparisonOperator(objectPath, condition, schemaProp?.type, values, valueIndex);
376
545
  whereClauses.push(comparison);
377
546
  }
378
547
  /**
@@ -381,22 +550,21 @@ class PostgreSqlEntityStorageConnector {
381
550
  * @param comparator The operator to map.
382
551
  * @param type The type of the property.
383
552
  * @param values The values to use in the query.
553
+ * @param valueIndex The current value index.
384
554
  * @returns The comparison expression.
385
555
  * @throws GeneralError if the comparison operator is not supported.
386
556
  * @internal
387
557
  */
388
- mapComparisonOperator(objectPath, comparator, type, values) {
558
+ mapComparisonOperator(objectPath, comparator, type, values, valueIndex) {
389
559
  let prop = objectPath;
390
560
  if (prop.length > 0) {
391
561
  prop += ".";
392
562
  }
393
563
  prop += comparator.property;
394
564
  if (comparator.comparison === ComparisonOperator.In) {
395
- const inValues = Array.isArray(comparator.value) ? comparator.value : [comparator.value];
565
+ const inValues = Is.array(comparator.value) ? comparator.value : [comparator.value];
396
566
  values.push(...inValues.map(val => this.propertyToDbValue(val, type)));
397
- const placeholders = inValues
398
- .map((_, index) => `$${values.length - inValues.length + index + 1}`)
399
- .join(", ");
567
+ const placeholders = inValues.map((_, index) => `$${valueIndex + index}`).join(", ");
400
568
  return `"${prop}" IN (${placeholders})`;
401
569
  }
402
570
  const dbValue = this.propertyToDbValue(comparator.value, type);
@@ -407,30 +575,30 @@ class PostgreSqlEntityStorageConnector {
407
575
  .slice(1)
408
576
  .map((p, i, arr) => (i === arr.length - 1 ? `->> '${p}'` : `-> '${p}'`))
409
577
  .join("");
410
- return `("${comparator.property.split(".")[0]}"::jsonb ${jsonPath}) = $${values.length}`;
578
+ return `("${comparator.property.split(".")[0]}"::jsonb ${jsonPath}) = $${valueIndex}`;
411
579
  }
412
580
  else if (comparator.comparison === ComparisonOperator.Equals) {
413
- return `"${prop}" = $${values.length}`;
581
+ return `"${prop}" = $${valueIndex}`;
414
582
  }
415
583
  else if (comparator.comparison === ComparisonOperator.NotEquals) {
416
- return `"${prop}" <> $${values.length}`;
584
+ return `"${prop}" <> $${valueIndex}`;
417
585
  }
418
586
  else if (comparator.comparison === ComparisonOperator.GreaterThan) {
419
- return `"${prop}" > $${values.length}`;
587
+ return `"${prop}" > $${valueIndex}`;
420
588
  }
421
589
  else if (comparator.comparison === ComparisonOperator.LessThan) {
422
- return `"${prop}" < $${values.length}`;
590
+ return `"${prop}" < $${valueIndex}`;
423
591
  }
424
592
  else if (comparator.comparison === ComparisonOperator.GreaterThanOrEqual) {
425
- return `"${prop}" >= $${values.length}`;
593
+ return `"${prop}" >= $${valueIndex}`;
426
594
  }
427
595
  else if (comparator.comparison === ComparisonOperator.LessThanOrEqual) {
428
- return `"${prop}" <= $${values.length}`;
596
+ return `"${prop}" <= $${valueIndex}`;
429
597
  }
430
598
  else if (comparator.comparison === ComparisonOperator.Includes) {
431
- return `EXISTS (SELECT 1 FROM jsonb_array_elements("${prop}") elem WHERE elem @> $${values.length}::jsonb)`;
599
+ return `EXISTS (SELECT 1 FROM jsonb_array_elements("${prop}") elem WHERE elem @> $${valueIndex}::jsonb)`;
432
600
  }
433
- throw new GeneralError(this.CLASS_NAME, "comparisonNotSupported", {
601
+ throw new GeneralError(PostgreSqlEntityStorageConnector.CLASS_NAME, "comparisonNotSupported", {
434
602
  comparison: comparator.comparison
435
603
  });
436
604
  }
@@ -473,7 +641,9 @@ class PostgreSqlEntityStorageConnector {
473
641
  else if (operator === LogicalOperator.Or) {
474
642
  return "OR";
475
643
  }
476
- throw new GeneralError(this.CLASS_NAME, "conditionalNotSupported", { operator });
644
+ throw new GeneralError(PostgreSqlEntityStorageConnector.CLASS_NAME, "conditionalNotSupported", {
645
+ operator
646
+ });
477
647
  }
478
648
  /**
479
649
  * Verify the conditions for the entity.
@@ -499,10 +669,17 @@ class PostgreSqlEntityStorageConnector {
499
669
  [EntitySchemaPropertyType.Boolean]: "BOOLEAN"
500
670
  };
501
671
  if (!entitySchema.properties) {
502
- throw new GeneralError(this.CLASS_NAME, "entitySchemaPropertiesUndefined");
672
+ throw new GeneralError(PostgreSqlEntityStorageConnector.CLASS_NAME, "entitySchemaPropertiesUndefined");
503
673
  }
504
674
  const primaryKeys = [];
505
- const columnDefinitions = entitySchema.properties
675
+ const props = [...entitySchema.properties];
676
+ props.unshift({
677
+ property: PostgreSqlEntityStorageConnector._PARTITION_KEY,
678
+ type: EntitySchemaPropertyType.String,
679
+ optional: false,
680
+ isPrimary: true
681
+ });
682
+ const columnDefinitions = props
506
683
  .map(prop => {
507
684
  const sqlType = sqlTypeMap[prop.type] || "TEXT";
508
685
  const columnName = String(prop.property);
@@ -513,9 +690,8 @@ class PostgreSqlEntityStorageConnector {
513
690
  return `"${columnName}" ${sqlType}${nullable}`;
514
691
  })
515
692
  .join(", ");
516
- const primaryKeyDefinition = primaryKeys.length > 0 ? `, PRIMARY KEY (${primaryKeys.join(", ")})` : "";
693
+ const primaryKeyDefinition = primaryKeys.length > 0 ? `, PRIMARY KEY ("${primaryKeys.join('", "')}")` : "";
517
694
  return columnDefinitions + primaryKeyDefinition;
518
695
  }
519
696
  }
520
-
521
- export { PostgreSqlEntityStorageConnector };
697
+ //# sourceMappingURL=postgreSqlEntityStorageConnector.js.map