@twin.org/entity-storage-connector-mysql 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/IMySqlEntityStorageConnectorConfig.js +4 -0
- package/dist/es/models/IMySqlEntityStorageConnectorConfig.js.map +1 -0
- package/dist/es/models/IMySqlEntityStorageConnectorConstructorOptions.js +2 -0
- package/dist/es/models/IMySqlEntityStorageConnectorConstructorOptions.js.map +1 -0
- package/dist/{esm/index.mjs → es/mysqlEntityStorageConnector.js} +305 -122
- package/dist/es/mysqlEntityStorageConnector.js.map +1 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/models/IMySqlEntityStorageConnectorConstructorOptions.d.ts +5 -1
- package/dist/types/mysqlEntityStorageConnector.d.ts +24 -10
- package/docs/changelog.md +64 -0
- package/docs/reference/classes/MySqlEntityStorageConnector.md +66 -24
- package/docs/reference/interfaces/IMySqlEntityStorageConnectorConstructorOptions.md +8 -0
- package/locales/en.json +6 -5
- package/package.json +28 -10
- package/dist/cjs/index.cjs +0 -535
package/dist/es/index.js
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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
|
|
19
|
+
static _DEFAULT_LIMIT = 40;
|
|
16
20
|
/**
|
|
17
|
-
*
|
|
21
|
+
* Partition id field name.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
static _PARTITION_KEY = "partitionId";
|
|
25
|
+
/**
|
|
26
|
+
* Partition id field value.
|
|
27
|
+
* @internal
|
|
18
28
|
*/
|
|
19
|
-
|
|
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(
|
|
41
|
-
Guards.stringValue(
|
|
42
|
-
Guards.object(
|
|
43
|
-
Guards.stringValue(
|
|
44
|
-
Guards.stringValue(
|
|
45
|
-
Guards.stringValue(
|
|
46
|
-
Guards.stringValue(
|
|
47
|
-
Guards.stringValue(
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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:
|
|
150
|
+
source: MySqlEntityStorageConnector.CLASS_NAME,
|
|
95
151
|
ts: Date.now(),
|
|
96
152
|
message: "databaseCreateFailed",
|
|
97
153
|
error: BaseError.fromError(error),
|
|
98
154
|
data: {
|
|
99
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
149
|
-
|
|
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(
|
|
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(
|
|
167
|
-
|
|
168
|
-
const
|
|
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
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 = [
|
|
214
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
237
|
-
* @param
|
|
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,
|
|
242
|
-
const
|
|
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 =
|
|
311
|
+
const returnSize = limit ?? MySqlEntityStorageConnector._DEFAULT_LIMIT;
|
|
245
312
|
let orderByClause = "";
|
|
246
|
-
if (
|
|
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
|
-
|
|
257
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
264
|
-
cursor:
|
|
265
|
-
?
|
|
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(
|
|
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
|
-
|
|
280
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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 (!
|
|
461
|
-
throw new GeneralError(
|
|
638
|
+
if (!this._entitySchema.properties) {
|
|
639
|
+
throw new GeneralError(MySqlEntityStorageConnector.CLASS_NAME, "entitySchemaPropertiesUndefined");
|
|
462
640
|
}
|
|
463
641
|
const primaryKeys = [];
|
|
464
|
-
const
|
|
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
|