@twin.org/entity-storage-connector-mysql 0.0.2-next.9 → 0.0.3-next.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.
@@ -0,0 +1,6 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export * from "./models/IMySqlEntityStorageConnectorConfig.js";
4
+ export * from "./models/IMySqlEntityStorageConnectorConstructorOptions.js";
5
+ export * from "./mysqlEntityStorageConnector.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,gDAAgD,CAAC;AAC/D,cAAc,4DAA4D,CAAC;AAC3E,cAAc,kCAAkC,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./models/IMySqlEntityStorageConnectorConfig.js\";\nexport * from \"./models/IMySqlEntityStorageConnectorConstructorOptions.js\";\nexport * from \"./mysqlEntityStorageConnector.js\";\n"]}
@@ -0,0 +1,4 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export {};
4
+ //# sourceMappingURL=IMySqlEntityStorageConnectorConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IMySqlEntityStorageConnectorConfig.js","sourceRoot":"","sources":["../../../src/models/IMySqlEntityStorageConnectorConfig.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Configuration for the MySql Entity Storage Connector.\n */\nexport interface IMySqlEntityStorageConnectorConfig {\n\t/**\n\t * The host for the MySql instance.\n\t */\n\thost: string;\n\n\t/**\n\t * The port for the MySql instance.\n\t */\n\tport?: number;\n\n\t/**\n\t * The user for the MySql instance.\n\t */\n\tuser: string;\n\n\t/**\n\t * The password for the MySql instance.\n\t */\n\tpassword: string;\n\n\t/**\n\t * The name of the database to be used.\n\t */\n\tdatabase: string;\n\n\t/**\n\t * The name of the table to be used.\n\t */\n\ttableName: string;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=IMySqlEntityStorageConnectorConstructorOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IMySqlEntityStorageConnectorConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/IMySqlEntityStorageConnectorConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IMySqlEntityStorageConnectorConfig } from \"./IMySqlEntityStorageConnectorConfig.js\";\n\n/**\n * The options for the MySql entity storage connector constructor.\n */\nexport interface IMySqlEntityStorageConnectorConstructorOptions {\n\t/**\n\t * The schema for the entity.\n\t */\n\tentitySchema: string;\n\n\t/**\n\t * The keys to use from the context ids to create partitions.\n\t */\n\tpartitionContextIds?: string[];\n\n\t/**\n\t * The type of logging component to use.\n\t * @default logging\n\t */\n\tloggingComponentType?: string;\n\n\t/**\n\t * The configuration for the connector.\n\t */\n\tconfig: IMySqlEntityStorageConnectorConfig;\n}\n"]}
@@ -1,27 +1,42 @@
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 { createConnection } from 'mysql2/promise';
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 { createConnection } from "mysql2/promise";
7
7
  /**
8
8
  * Class for performing entity storage operations using MySql.
9
9
  */
10
- class MySqlEntityStorageConnector {
10
+ export class MySqlEntityStorageConnector {
11
+ /**
12
+ * Runtime name for the class.
13
+ */
14
+ static CLASS_NAME = "MySqlEntityStorageConnector";
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
23
+ */
24
+ static _PARTITION_KEY = "partitionId";
25
+ /**
26
+ * Partition id field value.
27
+ * @internal
18
28
  */
19
- CLASS_NAME = "MySqlEntityStorageConnector";
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;
25
40
  /**
26
41
  * The configuration for the connector.
27
42
  * @internal
@@ -32,22 +47,43 @@ class MySqlEntityStorageConnector {
32
47
  * @internal
33
48
  */
34
49
  _connection;
50
+ /**
51
+ * The primary key property.
52
+ * @internal
53
+ */
54
+ _primaryKeyProperty;
35
55
  /**
36
56
  * Create a new instance of MySqlEntityStorageConnector.
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(MySqlEntityStorageConnector.CLASS_NAME, "options", options);
61
+ Guards.stringValue(MySqlEntityStorageConnector.CLASS_NAME, "options.entitySchema", options.entitySchema);
62
+ Guards.object(MySqlEntityStorageConnector.CLASS_NAME, "options.config", options.config);
63
+ Guards.stringValue(MySqlEntityStorageConnector.CLASS_NAME, "options.config.host", options.config.host);
64
+ Guards.stringValue(MySqlEntityStorageConnector.CLASS_NAME, "options.config.user", options.config.user);
65
+ Guards.stringValue(MySqlEntityStorageConnector.CLASS_NAME, "options.config.password", options.config.password);
66
+ Guards.stringValue(MySqlEntityStorageConnector.CLASS_NAME, "options.config.database", options.config.database);
67
+ Guards.stringValue(MySqlEntityStorageConnector.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
  }
73
+ /**
74
+ * Returns the class name of the component.
75
+ * @returns The class name of the component.
76
+ */
77
+ className() {
78
+ return MySqlEntityStorageConnector.CLASS_NAME;
79
+ }
80
+ /**
81
+ * Get the schema for the entities.
82
+ * @returns The schema for the entities.
83
+ */
84
+ getSchema() {
85
+ return this._entitySchema;
86
+ }
51
87
  /**
52
88
  * Initialize the MySql environment.
53
89
  * @param nodeLoggingComponentType Optional type of the logging component.
@@ -57,59 +93,72 @@ class MySqlEntityStorageConnector {
57
93
  const nodeLogging = ComponentFactory.getIfExists(nodeLoggingComponentType);
58
94
  try {
59
95
  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
- // Create the database if it does not exist
70
- await dbConnection.query(`CREATE DATABASE IF NOT EXISTS \`${this._config.database}\``);
71
- await nodeLogging?.log({
72
- level: "info",
73
- source: this.CLASS_NAME,
74
- ts: Date.now(),
75
- message: "databaseExists",
76
- data: {
77
- database: this._config.database
78
- }
79
- });
80
- await dbConnection.query(`CREATE TABLE IF NOT EXISTS \`${this._config.database}\`.\`${this._config.tableName}\` (${this.mapMySqlProperties(this._entitySchema)})`);
81
- await nodeLogging?.log({
82
- level: "info",
83
- source: this.CLASS_NAME,
84
- ts: Date.now(),
85
- message: "tableExists",
86
- data: {
87
- table: this._config.tableName
88
- }
89
- });
96
+ const databaseExists = await this.databaseExists();
97
+ if (!databaseExists) {
98
+ await nodeLogging?.log({
99
+ level: "info",
100
+ source: MySqlEntityStorageConnector.CLASS_NAME,
101
+ ts: Date.now(),
102
+ message: "databaseCreating",
103
+ data: {
104
+ databaseName: this._config.database
105
+ }
106
+ });
107
+ await dbConnection.query(`CREATE DATABASE IF NOT EXISTS \`${this._config.database}\``);
108
+ await this.waitForDatabaseExists();
109
+ }
110
+ else {
111
+ await nodeLogging?.log({
112
+ level: "info",
113
+ source: MySqlEntityStorageConnector.CLASS_NAME,
114
+ ts: Date.now(),
115
+ message: "databaseExists",
116
+ data: {
117
+ databaseName: this._config.database
118
+ }
119
+ });
120
+ }
121
+ const tableExists = await this.tableExists();
122
+ if (!tableExists) {
123
+ await nodeLogging?.log({
124
+ level: "info",
125
+ source: MySqlEntityStorageConnector.CLASS_NAME,
126
+ ts: Date.now(),
127
+ message: "tableCreating",
128
+ data: {
129
+ tableName: this._config.tableName
130
+ }
131
+ });
132
+ await dbConnection.query(`CREATE TABLE IF NOT EXISTS \`${this._config.database}\`.\`${this._config.tableName}\` (${this.mapMySqlProperties()})`);
133
+ await this.waitForTableExists();
134
+ }
135
+ else {
136
+ await nodeLogging?.log({
137
+ level: "info",
138
+ source: MySqlEntityStorageConnector.CLASS_NAME,
139
+ ts: Date.now(),
140
+ message: "tableExists",
141
+ data: {
142
+ tableName: this._config.tableName
143
+ }
144
+ });
145
+ }
90
146
  }
91
147
  catch (error) {
92
148
  await nodeLogging?.log({
93
149
  level: "error",
94
- source: this.CLASS_NAME,
150
+ source: MySqlEntityStorageConnector.CLASS_NAME,
95
151
  ts: Date.now(),
96
152
  message: "databaseCreateFailed",
97
153
  error: BaseError.fromError(error),
98
154
  data: {
99
- database: this._config.database
155
+ databaseName: this._config.database
100
156
  }
101
157
  });
102
158
  return false;
103
159
  }
104
160
  return true;
105
161
  }
106
- /**
107
- * Get the schema for the entities.
108
- * @returns The schema for the entities.
109
- */
110
- getSchema() {
111
- return this._entitySchema;
112
- }
113
162
  /**
114
163
  * Get an entity from MySql.
115
164
  * @param id The id of the entity to get, or the index value if secondaryIndex is set.
@@ -118,26 +167,23 @@ class MySqlEntityStorageConnector {
118
167
  * @returns The object if it can be found or undefined.
119
168
  */
120
169
  async get(id, secondaryIndex, conditions) {
121
- Guards.stringValue(this.CLASS_NAME, "id", id);
170
+ Guards.stringValue(MySqlEntityStorageConnector.CLASS_NAME, "id", id);
171
+ const contextIds = await ContextIdStore.getContextIds();
172
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
122
173
  try {
123
174
  const dbConnection = await this.createConnection();
124
175
  const whereClauses = [];
125
176
  const values = [];
177
+ whereClauses.push(`\`${MySqlEntityStorageConnector._PARTITION_KEY}\` = ?`);
178
+ values.push(partitionKey ?? MySqlEntityStorageConnector._PARTITION_KEY_VALUE);
126
179
  if (secondaryIndex) {
127
180
  whereClauses.push(`\`${String(secondaryIndex)}\` = ?`);
128
- values.push(id);
129
181
  }
130
182
  else {
131
- const primaryKeyProp = this._entitySchema.properties?.find(prop => prop.isPrimary);
132
- if (primaryKeyProp) {
133
- whereClauses.push(`\`${String(primaryKeyProp.property)}\` = ?`);
134
- }
135
- else {
136
- whereClauses.push("`id` = ?");
137
- }
138
- values.push(id);
183
+ whereClauses.push(`\`${String(this._primaryKeyProperty.property)}\` = ?`);
139
184
  }
140
- if (conditions) {
185
+ values.push(id);
186
+ if (Is.arrayValue(conditions)) {
141
187
  for (const condition of conditions) {
142
188
  whereClauses.push(`\`${String(condition.property)}\` = ?`);
143
189
  values.push(condition.value);
@@ -145,12 +191,14 @@ class MySqlEntityStorageConnector {
145
191
  }
146
192
  const query = `SELECT * FROM \`${this._config.database}\`.\`${this._config.tableName}\` WHERE ${whereClauses.join(" AND ")} LIMIT 1`;
147
193
  const [rows] = await dbConnection.query(query, values);
148
- if (Array.isArray(rows) && rows.length === 1) {
149
- return rows[0];
194
+ if (Is.array(rows) && rows.length === 1) {
195
+ const item = ObjectHelper.removeEmptyProperties(rows[0], { removeNull: true });
196
+ ObjectHelper.propertyDelete(item, MySqlEntityStorageConnector._PARTITION_KEY);
197
+ return item;
150
198
  }
151
199
  }
152
200
  catch (err) {
153
- throw new GeneralError(this.CLASS_NAME, "getFailed", {
201
+ throw new GeneralError(MySqlEntityStorageConnector.CLASS_NAME, "getFailed", {
154
202
  id
155
203
  }, err);
156
204
  }
@@ -163,9 +211,11 @@ class MySqlEntityStorageConnector {
163
211
  * @returns The id of the entity.
164
212
  */
165
213
  async set(entity, conditions) {
166
- Guards.object(this.CLASS_NAME, "entity", entity);
167
- EntitySchemaHelper.validateEntity(entity, this.getSchema());
168
- const id = entity["id"];
214
+ Guards.object(MySqlEntityStorageConnector.CLASS_NAME, "entity", entity);
215
+ const contextIds = await ContextIdStore.getContextIds();
216
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
217
+ EntitySchemaHelper.validateEntity(entity, this._entitySchema);
218
+ const id = entity[this._primaryKeyProperty.property];
169
219
  try {
170
220
  if (Is.arrayValue(conditions)) {
171
221
  const itemData = await this.get(id);
@@ -173,27 +223,36 @@ class MySqlEntityStorageConnector {
173
223
  return;
174
224
  }
175
225
  }
176
- const columns = Object.keys(entity)
177
- .map(key => `\`${key}\``)
178
- .join(", ");
179
- const values = Object.values(entity);
180
- for (const [index, value] of values.entries()) {
181
- const property = Object.keys(entity)[index];
182
- const schemaProp = this._entitySchema.properties?.find(p => p.property === property);
183
- if (schemaProp?.type === EntitySchemaPropertyType.Object ||
184
- schemaProp?.type === EntitySchemaPropertyType.Array) {
185
- values[index] = JSON.stringify(value);
226
+ const finalEntity = ObjectHelper.clone(entity);
227
+ const props = [...(this._entitySchema.properties ?? [])];
228
+ props.unshift({
229
+ property: MySqlEntityStorageConnector._PARTITION_KEY,
230
+ type: EntitySchemaPropertyType.String
231
+ });
232
+ ObjectHelper.propertySet(finalEntity, MySqlEntityStorageConnector._PARTITION_KEY, partitionKey ?? MySqlEntityStorageConnector._PARTITION_KEY_VALUE);
233
+ const keys = [];
234
+ const values = [];
235
+ for (const prop of props) {
236
+ if (!(Is.empty(finalEntity[prop.property]) && (prop.optional ?? false))) {
237
+ keys.push(prop.property);
238
+ if (prop.type === EntitySchemaPropertyType.Object ||
239
+ prop.type === EntitySchemaPropertyType.Array) {
240
+ values.push(JSON.stringify(finalEntity[prop.property]));
241
+ }
242
+ else {
243
+ values.push(finalEntity[prop.property]);
244
+ }
186
245
  }
187
246
  }
188
- const placeholders = values.map(() => "?").join(", ");
247
+ let sql = `INSERT INTO \`${this._config.database}\`.\`${this._config.tableName}\``;
248
+ sql += ` (${keys.map(key => `\`${key}\``).join(", ")})`;
249
+ sql += ` VALUES (${values.map(() => "?").join(", ")})`;
250
+ sql += ` ON DUPLICATE KEY UPDATE ${keys.map(key => `\`${key}\` = VALUES(\`${key}\`)`).join(", ")};`;
189
251
  const dbConnection = await this.createConnection();
190
- await dbConnection.query(`INSERT INTO \`${this._config.database}\`.\`${this._config.tableName}\` (${columns}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${columns
191
- .split(", ")
192
- .map(col => `${col} = VALUES(${col})`)
193
- .join(", ")};`, values);
252
+ await dbConnection.query(sql, values);
194
253
  }
195
254
  catch (err) {
196
- throw new GeneralError(this.CLASS_NAME, "setFailed", {
255
+ throw new GeneralError(MySqlEntityStorageConnector.CLASS_NAME, "setFailed", {
197
256
  id
198
257
  }, err);
199
258
  }
@@ -205,25 +264,31 @@ class MySqlEntityStorageConnector {
205
264
  * @returns Nothing.
206
265
  */
207
266
  async remove(id, conditions) {
208
- Guards.stringValue(this.CLASS_NAME, "id", id);
267
+ Guards.stringValue(MySqlEntityStorageConnector.CLASS_NAME, "id", id);
268
+ const contextIds = await ContextIdStore.getContextIds();
269
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
209
270
  try {
210
271
  const dbConnection = await this.createConnection();
211
- const itemData = await this.get(id);
272
+ const itemData = await this.get(id, undefined, conditions);
212
273
  if (Is.notEmpty(itemData)) {
213
- const values = [id];
214
- let whereClauses = [];
274
+ const values = [];
275
+ const whereClauses = [];
276
+ whereClauses.push(`\`${this._primaryKeyProperty.property}\` = ?`);
277
+ values.push(id);
278
+ whereClauses.push(`\`${MySqlEntityStorageConnector._PARTITION_KEY}\` = ?`);
279
+ values.push(partitionKey ?? MySqlEntityStorageConnector._PARTITION_KEY_VALUE);
215
280
  if (Is.arrayValue(conditions)) {
216
- whereClauses = conditions.map(condition => {
281
+ whereClauses.push(...conditions.map(condition => {
217
282
  values.push(condition.value);
218
283
  return `\`${String(condition.property)}\` = ?`;
219
- });
284
+ }));
220
285
  }
221
- const query = `DELETE FROM \`${this._config.database}\`.\`${this._config.tableName}\` WHERE \`id\` = ?${whereClauses.length > 0 ? ` AND ${whereClauses.join(" AND ")}` : ""}`;
286
+ const query = `DELETE FROM \`${this._config.database}\`.\`${this._config.tableName}\` WHERE ${whereClauses.join(" AND ")}`;
222
287
  await dbConnection.query(query, values);
223
288
  }
224
289
  }
225
290
  catch (err) {
226
- throw new GeneralError(this.CLASS_NAME, "removeFailed", {
291
+ throw new GeneralError(MySqlEntityStorageConnector.CLASS_NAME, "removeFailed", {
227
292
  id
228
293
  }, err);
229
294
  }
@@ -233,17 +298,19 @@ class MySqlEntityStorageConnector {
233
298
  * @param conditions The conditions to match for the entities.
234
299
  * @param sortProperties The optional sort order.
235
300
  * @param properties The optional properties to return, defaults to all.
236
- * @param cursor The cursor to request the next page of entities.
237
- * @param pageSize The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
301
+ * @param cursor The cursor to request the next chunk of entities.
302
+ * @param limit The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
238
303
  * @returns All the entities for the storage matching the conditions,
239
304
  * and a cursor which can be used to request more entities.
240
305
  */
241
- async query(conditions, sortProperties, properties, cursor, pageSize) {
242
- const sql = "";
306
+ async query(conditions, sortProperties, properties, cursor, limit) {
307
+ const contextIds = await ContextIdStore.getContextIds();
308
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
309
+ let sql = "";
243
310
  try {
244
- const returnSize = pageSize ?? MySqlEntityStorageConnector._PAGE_SIZE;
311
+ const returnSize = limit ?? MySqlEntityStorageConnector._DEFAULT_LIMIT;
245
312
  let orderByClause = "";
246
- if (Array.isArray(sortProperties)) {
313
+ if (Is.array(sortProperties)) {
247
314
  const orderClauses = [];
248
315
  for (const sortProperty of sortProperties) {
249
316
  const direction = sortProperty.sortDirection === SortDirection.Ascending ? "ASC" : "DESC";
@@ -253,21 +320,39 @@ class MySqlEntityStorageConnector {
253
320
  }
254
321
  const whereClauses = [];
255
322
  const values = [];
256
- if (conditions) {
257
- this.buildQueryParameters("", conditions, whereClauses, values);
323
+ const finalConditions = {
324
+ conditions: [],
325
+ logicalOperator: LogicalOperator.And
326
+ };
327
+ finalConditions.conditions.push({
328
+ property: MySqlEntityStorageConnector._PARTITION_KEY,
329
+ comparison: ComparisonOperator.Equals,
330
+ value: partitionKey ?? MySqlEntityStorageConnector._PARTITION_KEY_VALUE
331
+ });
332
+ if (!Is.empty(conditions)) {
333
+ finalConditions.conditions.push(conditions);
258
334
  }
259
- const query = `SELECT ${properties ? properties.map(p => `\`${String(p)}\``).join(", ") : "*"} FROM \`${this._config.database}\`.\`${this._config.tableName}\` WHERE ${whereClauses.length > 0 ? whereClauses.join(" AND ") : "1"} ${orderByClause} LIMIT ${returnSize} OFFSET ${cursor ? Number(cursor) : 0}`;
335
+ this.buildQueryParameters("", finalConditions, whereClauses, values);
336
+ const startIndex = Coerce.number(cursor) ?? 0;
337
+ sql = `SELECT ${properties ? properties.map(p => `\`${String(p)}\``).join(", ") : "*"} FROM \`${this._config.database}\`.\`${this._config.tableName}\``;
338
+ sql += ` WHERE ${whereClauses.join(" AND ")} ${orderByClause}`;
339
+ sql += ` LIMIT ${returnSize} OFFSET ${startIndex}`;
260
340
  const dbConnection = await this.createConnection();
261
- const [rows] = (await dbConnection?.query(query, values)) ?? [];
341
+ const [rows] = (await dbConnection.query(sql, values)) ?? [];
342
+ const entities = rows;
343
+ for (let i = 0; i < entities.length; i++) {
344
+ entities[i] = ObjectHelper.removeEmptyProperties(entities[i], { removeNull: true });
345
+ ObjectHelper.propertyDelete(entities[i], MySqlEntityStorageConnector._PARTITION_KEY);
346
+ }
262
347
  return {
263
- entities: rows,
264
- cursor: Array.isArray(rows) && rows.length === returnSize
265
- ? String((cursor ? Number(cursor) : 0) + returnSize)
348
+ entities,
349
+ cursor: Is.array(rows) && rows.length === returnSize
350
+ ? Coerce.string(startIndex + returnSize)
266
351
  : undefined
267
352
  };
268
353
  }
269
354
  catch (err) {
270
- throw new GeneralError(this.CLASS_NAME, "queryFailed", { sql }, err);
355
+ throw new GeneralError(MySqlEntityStorageConnector.CLASS_NAME, "queryFailed", { sql }, err);
271
356
  }
272
357
  }
273
358
  /**
@@ -276,13 +361,105 @@ class MySqlEntityStorageConnector {
276
361
  */
277
362
  async tableDrop() {
278
363
  try {
279
- const dbConnection = await this.createConnection();
280
- await dbConnection?.query(`DROP TABLE \`${this._config.database}\`.\`${this._config.tableName}\`;`);
364
+ if (await this.tableExists()) {
365
+ const dbConnection = await this.createConnection();
366
+ await dbConnection.query(`DROP TABLE \`${this._config.database}\`.\`${this._config.tableName}\`;`);
367
+ await this.waitForTableNotExists();
368
+ }
369
+ }
370
+ catch {
371
+ // Ignore errors
372
+ }
373
+ }
374
+ /**
375
+ * Empty the table.
376
+ * @returns Nothing.
377
+ */
378
+ async tableEmpty() {
379
+ try {
380
+ if (await this.tableExists()) {
381
+ const dbConnection = await this.createConnection();
382
+ await dbConnection.query(`TRUNCATE TABLE \`${this._config.database}\`.\`${this._config.tableName}\`;`);
383
+ }
281
384
  }
282
385
  catch {
283
386
  // Ignore errors
284
387
  }
285
388
  }
389
+ /**
390
+ * Check if the database exists.
391
+ * @returns True if the database exists, false otherwise.
392
+ */
393
+ async databaseExists() {
394
+ try {
395
+ const dbConnection = await this.createConnection();
396
+ const [rows] = await dbConnection.query("SHOW DATABASES LIKE ?;", [this._config.database]);
397
+ return Is.arrayValue(rows);
398
+ }
399
+ catch {
400
+ return false;
401
+ }
402
+ }
403
+ /**
404
+ * Wait for a database to exist.
405
+ * @returns Nothing.
406
+ * @internal
407
+ */
408
+ async waitForDatabaseExists() {
409
+ for (let attempt = 0; attempt < 20; attempt++) {
410
+ const databaseExists = await this.databaseExists();
411
+ if (databaseExists) {
412
+ break;
413
+ }
414
+ await new Promise(resolve => setTimeout(resolve, 250));
415
+ }
416
+ }
417
+ /**
418
+ * Check if the table exists.
419
+ * @returns True if the table exists, false otherwise.
420
+ * @internal
421
+ */
422
+ async tableExists() {
423
+ try {
424
+ const dbConnection = await this.createConnection();
425
+ const [rows] = await dbConnection.query("SHOW TABLES FROM ?? LIKE ?", [
426
+ this._config.database,
427
+ this._config.tableName
428
+ ]);
429
+ return Is.arrayValue(rows);
430
+ }
431
+ catch {
432
+ return false;
433
+ }
434
+ }
435
+ /**
436
+ * Wait for a table to exist.
437
+ * @returns Nothing.
438
+ * @internal
439
+ */
440
+ async waitForTableExists() {
441
+ for (let attempt = 0; attempt < 20; attempt++) {
442
+ const tableExists = await this.tableExists();
443
+ if (tableExists) {
444
+ break;
445
+ }
446
+ await new Promise(resolve => setTimeout(resolve, 250));
447
+ }
448
+ }
449
+ /**
450
+ * Wait for a table to not exist.
451
+ * @returns Nothing.
452
+ * @internal
453
+ */
454
+ async waitForTableNotExists() {
455
+ for (let attempt = 0; attempt < 20; attempt++) {
456
+ const tableExists = await this.tableExists();
457
+ if (!tableExists) {
458
+ break;
459
+ }
460
+ await new Promise(resolve => setTimeout(resolve, 250));
461
+ }
462
+ }
286
463
  /**
287
464
  * Create a new DB connection.
288
465
  * @returns The MySql connection.
@@ -361,7 +538,7 @@ class MySqlEntityStorageConnector {
361
538
  prop += comparator.property;
362
539
  // prop = prop.replace(/\./g, "->");
363
540
  if (comparator.comparison === ComparisonOperator.In) {
364
- const inValues = Array.isArray(comparator.value) ? comparator.value : [comparator.value];
541
+ const inValues = Is.array(comparator.value) ? comparator.value : [comparator.value];
365
542
  values.push(...inValues.map(val => this.propertyToDbValue(val, type)));
366
543
  const placeholders = inValues.map(() => "?").join(", ");
367
544
  return `\`${prop}\` IN (${placeholders})`;
@@ -392,7 +569,7 @@ class MySqlEntityStorageConnector {
392
569
  else if (comparator.comparison === ComparisonOperator.Includes) {
393
570
  return `JSON_CONTAINS(\`${prop}\`, ?)`;
394
571
  }
395
- throw new GeneralError(this.CLASS_NAME, "comparisonNotSupported", {
572
+ throw new GeneralError(MySqlEntityStorageConnector.CLASS_NAME, "comparisonNotSupported", {
396
573
  comparison: comparator.comparison
397
574
  });
398
575
  }
@@ -432,7 +609,9 @@ class MySqlEntityStorageConnector {
432
609
  else if (operator === LogicalOperator.Or) {
433
610
  return "OR";
434
611
  }
435
- throw new GeneralError(this.CLASS_NAME, "conditionalNotSupported", { operator });
612
+ throw new GeneralError(MySqlEntityStorageConnector.CLASS_NAME, "conditionalNotSupported", {
613
+ operator
614
+ });
436
615
  }
437
616
  /**
438
617
  * Verify the conditions for the entity.
@@ -444,11 +623,10 @@ class MySqlEntityStorageConnector {
444
623
  }
445
624
  /**
446
625
  * Map entity schema properties to SQL properties.
447
- * @param entitySchema The schema of the entity.
448
626
  * @returns The SQL properties as a string.
449
627
  * @throws GeneralError if the entity properties do not exist.
450
628
  */
451
- mapMySqlProperties(entitySchema) {
629
+ mapMySqlProperties() {
452
630
  const sqlTypeMap = {
453
631
  [EntitySchemaPropertyType.String]: "LONGTEXT",
454
632
  [EntitySchemaPropertyType.Number]: "FLOAT",
@@ -457,11 +635,17 @@ class MySqlEntityStorageConnector {
457
635
  [EntitySchemaPropertyType.Array]: "JSON",
458
636
  [EntitySchemaPropertyType.Boolean]: "TINYINT(1)"
459
637
  };
460
- if (!entitySchema.properties) {
461
- throw new GeneralError(this.CLASS_NAME, "entitySchemaPropertiesUndefined");
638
+ if (!this._entitySchema.properties) {
639
+ throw new GeneralError(MySqlEntityStorageConnector.CLASS_NAME, "entitySchemaPropertiesUndefined");
462
640
  }
463
641
  const primaryKeys = [];
464
- const columnDefinitions = entitySchema.properties
642
+ const props = [...this._entitySchema.properties];
643
+ props.unshift({
644
+ property: MySqlEntityStorageConnector._PARTITION_KEY,
645
+ type: EntitySchemaPropertyType.String,
646
+ isPrimary: true
647
+ });
648
+ const columnDefinitions = props
465
649
  .map(prop => {
466
650
  let sqlType = sqlTypeMap[prop.type] || "TEXT";
467
651
  if (prop.format) {
@@ -529,5 +713,4 @@ class MySqlEntityStorageConnector {
529
713
  return columnDefinitions + primaryKeyDefinition;
530
714
  }
531
715
  }
532
-
533
- export { MySqlEntityStorageConnector };
716
+ //# sourceMappingURL=mysqlEntityStorageConnector.js.map