@twin.org/entity-storage-connector-postgresql 0.0.2-next.8 → 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.
- package/dist/es/index.js +6 -0
- package/dist/es/index.js.map +1 -0
- package/dist/es/models/IPostgreSqlEntityStorageConnectorConfig.js +4 -0
- package/dist/es/models/IPostgreSqlEntityStorageConnectorConfig.js.map +1 -0
- package/dist/es/models/IPostgreSqlEntityStorageConnectorConstructorOptions.js +2 -0
- package/dist/es/models/IPostgreSqlEntityStorageConnectorConstructorOptions.js.map +1 -0
- package/dist/{esm/index.mjs → es/postgreSqlEntityStorageConnector.js} +300 -124
- package/dist/es/postgreSqlEntityStorageConnector.js.map +1 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/models/IPostgreSqlEntityStorageConnectorConstructorOptions.d.ts +5 -1
- package/dist/types/postgreSqlEntityStorageConnector.d.ts +10 -5
- package/docs/changelog.md +61 -0
- package/docs/reference/classes/PostgreSqlEntityStorageConnector.md +24 -10
- package/docs/reference/interfaces/IPostgreSqlEntityStorageConnectorConstructorOptions.md +8 -0
- package/locales/en.json +4 -3
- package/package.json +27 -9
- package/dist/cjs/index.cjs +0 -523
|
@@ -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
|
|
19
|
+
static _DEFAULT_LIMIT = 40;
|
|
16
20
|
/**
|
|
17
|
-
*
|
|
21
|
+
* Partition id field name.
|
|
22
|
+
* @internal
|
|
18
23
|
*/
|
|
19
|
-
|
|
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(
|
|
41
|
-
Guards.stringValue(
|
|
42
|
-
Guards.object(
|
|
43
|
-
Guards.stringValue(
|
|
44
|
-
Guards.stringValue(
|
|
45
|
-
Guards.stringValue(
|
|
46
|
-
Guards.stringValue(
|
|
47
|
-
Guards.stringValue(
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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:
|
|
137
|
+
source: PostgreSqlEntityStorageConnector.CLASS_NAME,
|
|
102
138
|
ts: Date.now(),
|
|
103
139
|
message: "databaseCreateFailed",
|
|
104
140
|
error: BaseError.fromError(error),
|
|
105
141
|
data: {
|
|
106
|
-
|
|
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(
|
|
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)}" = $
|
|
181
|
+
whereClauses.push(`"${String(secondaryIndex)}" = $2`);
|
|
135
182
|
values.push(id);
|
|
136
183
|
}
|
|
137
184
|
else {
|
|
138
|
-
|
|
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 (
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 = [
|
|
227
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
251
|
-
* @param
|
|
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,
|
|
256
|
-
const
|
|
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 =
|
|
331
|
+
const returnSize = limit ?? PostgreSqlEntityStorageConnector._DEFAULT_LIMIT;
|
|
259
332
|
let orderByClause = "";
|
|
260
|
-
if (
|
|
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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
296
|
-
cursor:
|
|
297
|
-
?
|
|
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(
|
|
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
|
-
|
|
493
|
+
if (Is.empty(this._connection)) {
|
|
494
|
+
this._connection = postgres(this.createConnectionConfig());
|
|
326
495
|
}
|
|
327
|
-
|
|
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 =
|
|
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}) = $${
|
|
578
|
+
return `("${comparator.property.split(".")[0]}"::jsonb ${jsonPath}) = $${valueIndex}`;
|
|
411
579
|
}
|
|
412
580
|
else if (comparator.comparison === ComparisonOperator.Equals) {
|
|
413
|
-
return `"${prop}" = $${
|
|
581
|
+
return `"${prop}" = $${valueIndex}`;
|
|
414
582
|
}
|
|
415
583
|
else if (comparator.comparison === ComparisonOperator.NotEquals) {
|
|
416
|
-
return `"${prop}" <> $${
|
|
584
|
+
return `"${prop}" <> $${valueIndex}`;
|
|
417
585
|
}
|
|
418
586
|
else if (comparator.comparison === ComparisonOperator.GreaterThan) {
|
|
419
|
-
return `"${prop}" > $${
|
|
587
|
+
return `"${prop}" > $${valueIndex}`;
|
|
420
588
|
}
|
|
421
589
|
else if (comparator.comparison === ComparisonOperator.LessThan) {
|
|
422
|
-
return `"${prop}" < $${
|
|
590
|
+
return `"${prop}" < $${valueIndex}`;
|
|
423
591
|
}
|
|
424
592
|
else if (comparator.comparison === ComparisonOperator.GreaterThanOrEqual) {
|
|
425
|
-
return `"${prop}" >= $${
|
|
593
|
+
return `"${prop}" >= $${valueIndex}`;
|
|
426
594
|
}
|
|
427
595
|
else if (comparator.comparison === ComparisonOperator.LessThanOrEqual) {
|
|
428
|
-
return `"${prop}" <= $${
|
|
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 @> $${
|
|
599
|
+
return `EXISTS (SELECT 1 FROM jsonb_array_elements("${prop}") elem WHERE elem @> $${valueIndex}::jsonb)`;
|
|
432
600
|
}
|
|
433
|
-
throw new GeneralError(
|
|
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(
|
|
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(
|
|
672
|
+
throw new GeneralError(PostgreSqlEntityStorageConnector.CLASS_NAME, "entitySchemaPropertiesUndefined");
|
|
503
673
|
}
|
|
504
674
|
const primaryKeys = [];
|
|
505
|
-
const
|
|
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
|