@mlagie/sql-connector 2.0.5 → 2.0.7
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/.github/workflows/publish.yml +35 -25
- package/README.md +107 -14
- package/docs/fr/README.md +114 -19
- package/package.json +8 -3
- package/releases/2.0.6.md +80 -0
- package/releases/2.0.7.md +38 -0
- package/socket.yml +4 -0
- package/src/models/Model.js +35 -33
- package/src/models/ModelInstance.js +19 -28
- package/src/utils/{buildQuery.js → buildQuery/buildQuery.js} +6 -6
- package/src/utils/buildQuery/count.js +11 -0
- package/src/utils/formatObject.js +1 -0
- package/src/utils/generateCondition.js +0 -8
- package/tests/Model.test.js +666 -0
- package/tests/ModelInstance.test.js +335 -0
- package/tests/buildQuery.test.js +134 -19
- package/tests/connect.test.js +14 -4
- package/tests/formatObject.test.js +9 -0
- package/tests/generateCondition.test.js +200 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
const { error } = require("@mlagie/logger");
|
|
1
|
+
const { error, logs } = require("@mlagie/logger");
|
|
2
2
|
const { getConnexion } = require("../db/connexion");
|
|
3
3
|
const formatObject = require("../utils/formatObject");
|
|
4
4
|
const generateCondition = require("../utils/generateCondition");
|
|
5
|
-
const util = require("util");
|
|
6
5
|
const { getSafe, setSafe } = require("../utils/security/safe");
|
|
7
6
|
|
|
8
7
|
/**
|
|
@@ -18,19 +17,19 @@ class ModelInstance {
|
|
|
18
17
|
*/
|
|
19
18
|
constructor(name, data, schema = null) {
|
|
20
19
|
Object.defineProperties(this, {
|
|
21
|
-
|
|
20
|
+
_name: {
|
|
22
21
|
value: name,
|
|
23
22
|
writable: true,
|
|
24
23
|
configurable: true,
|
|
25
24
|
enumerable: false
|
|
26
25
|
},
|
|
27
|
-
|
|
26
|
+
_data: {
|
|
28
27
|
value: data,
|
|
29
28
|
writable: true,
|
|
30
29
|
configurable: true,
|
|
31
30
|
enumerable: true
|
|
32
31
|
},
|
|
33
|
-
|
|
32
|
+
_schema: {
|
|
34
33
|
value: schema,
|
|
35
34
|
writable: true,
|
|
36
35
|
configurable: true,
|
|
@@ -63,22 +62,18 @@ class ModelInstance {
|
|
|
63
62
|
* @private
|
|
64
63
|
*/
|
|
65
64
|
_getTargetRow() {
|
|
66
|
-
const rows = Array.isArray(this.
|
|
65
|
+
const rows = Array.isArray(this._data) && Array.isArray(this._data[0]) ? this._data[0] : this._data;
|
|
67
66
|
return Array.isArray(rows) ? rows[0] : rows;
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
getRecordData() {
|
|
71
|
-
return Array.isArray(this.
|
|
70
|
+
return Array.isArray(this._data) ? this._data[0] ?? this._data : this._data;
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
toJSON() {
|
|
75
74
|
return this.getRecordData();
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
[util.inspect.custom]() {
|
|
79
|
-
return this.getRecordData();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
77
|
/**
|
|
83
78
|
* Updates a single entry in the database table.
|
|
84
79
|
*
|
|
@@ -96,15 +91,11 @@ class ModelInstance {
|
|
|
96
91
|
let rawRec = Array.isArray(recordsArray) ? recordsArray[0] : recordsArray;
|
|
97
92
|
|
|
98
93
|
if (typeof rawRec === 'string') {
|
|
99
|
-
|
|
100
|
-
rawRec = JSON.parse(rawRec);
|
|
101
|
-
} catch {
|
|
102
|
-
rawRec = recordsArray;
|
|
103
|
-
}
|
|
94
|
+
rawRec = JSON.parse(rawRec);
|
|
104
95
|
}
|
|
105
96
|
const rec = rawRec;
|
|
106
97
|
|
|
107
|
-
const schemaDict = this.
|
|
98
|
+
const schemaDict = this._schema && this._schema.schemaDict ? this._schema.schemaDict : null;
|
|
108
99
|
if (schemaDict) {
|
|
109
100
|
const pkKeys = Object.entries(schemaDict).filter(([, v]) => v && v.primary_key === true).map(([k]) => k);
|
|
110
101
|
if (pkKeys.length > 0) {
|
|
@@ -112,20 +103,19 @@ class ModelInstance {
|
|
|
112
103
|
for (const k of pkKeys) {
|
|
113
104
|
if (rec && Object.prototype.hasOwnProperty.call(rec, k)) setSafe(pkObj, k, getSafe(rec, k));
|
|
114
105
|
}
|
|
115
|
-
if (Object.keys(pkObj).length > 0) whereClause = generateCondition(formatObject(pkObj), false, this.
|
|
106
|
+
if (Object.keys(pkObj).length > 0) whereClause = generateCondition(formatObject(pkObj), false, this._schema);
|
|
116
107
|
}
|
|
117
108
|
}
|
|
118
|
-
if (!whereClause) whereClause = generateCondition(formatObject(rec), false, this.
|
|
109
|
+
if (!whereClause) whereClause = generateCondition(formatObject(rec), false, this._schema);
|
|
119
110
|
} catch {
|
|
120
111
|
const originalFallbackRec = this.getRecordData();
|
|
121
112
|
let fallbackRec = originalFallbackRec;
|
|
122
113
|
if (Array.isArray(fallbackRec)) fallbackRec = fallbackRec[0];
|
|
123
114
|
if (typeof fallbackRec === 'string') { try { fallbackRec = JSON.parse(fallbackRec); } catch { fallbackRec = originalFallbackRec; } }
|
|
124
|
-
whereClause = generateCondition(formatObject(fallbackRec), false, this.
|
|
115
|
+
whereClause = generateCondition(formatObject(fallbackRec), false, this._schema);
|
|
125
116
|
}
|
|
126
117
|
|
|
127
|
-
const sql_request = `UPDATE ${this.
|
|
128
|
-
|
|
118
|
+
const sql_request = `UPDATE ${this._name} SET ${setClause} WHERE ${whereClause}`;
|
|
129
119
|
const [result] = await getConnexion().promise().execute(sql_request).catch((err) => {
|
|
130
120
|
error(`Error executing query updateOne: ${err}`);
|
|
131
121
|
throw err;
|
|
@@ -135,10 +125,10 @@ class ModelInstance {
|
|
|
135
125
|
|
|
136
126
|
if (affected > 0) {
|
|
137
127
|
const record = this.getRecordData();
|
|
138
|
-
if (Array.isArray(this.
|
|
139
|
-
if (this.
|
|
128
|
+
if (Array.isArray(this._data)) {
|
|
129
|
+
if (this._data[0] && typeof this._data[0] === 'object') Object.assign(this._data[0], model);
|
|
140
130
|
} else if (record && typeof record === 'object') {
|
|
141
|
-
Object.assign(this.
|
|
131
|
+
Object.assign(this._data, model);
|
|
142
132
|
}
|
|
143
133
|
}
|
|
144
134
|
|
|
@@ -152,7 +142,7 @@ class ModelInstance {
|
|
|
152
142
|
* @throws {Error} Throws an error if the deletion fails.
|
|
153
143
|
*/
|
|
154
144
|
async delete(filter) {
|
|
155
|
-
const sql_request = `DELETE FROM ${this.
|
|
145
|
+
const sql_request = `DELETE FROM ${this._name} WHERE ${generateCondition(formatObject(filter))}`;
|
|
156
146
|
|
|
157
147
|
const rows = await getConnexion().promise().execute(sql_request).catch((err) => {
|
|
158
148
|
error(`Error executing query delete: ${err}`);
|
|
@@ -168,8 +158,9 @@ class ModelInstance {
|
|
|
168
158
|
* @throws {Error} Throws an error if the deletion fails.
|
|
169
159
|
*/
|
|
170
160
|
async deleteOne() {
|
|
171
|
-
const sql_request = `DELETE FROM ${this.
|
|
161
|
+
const sql_request = `DELETE FROM ${this._name} WHERE ${generateCondition(formatObject(this.getRecordData()))}`;
|
|
172
162
|
|
|
163
|
+
logs(sql_request)
|
|
173
164
|
const rows = await getConnexion().promise().execute(sql_request).catch((err) => {
|
|
174
165
|
error(`Error executing query deleteOne: ${err}`);
|
|
175
166
|
throw err;
|
|
@@ -192,7 +183,7 @@ class ModelInstance {
|
|
|
192
183
|
|
|
193
184
|
if (rows[0].length == 0) return 0;
|
|
194
185
|
|
|
195
|
-
return new ModelInstance(this.
|
|
186
|
+
return new ModelInstance(this._name, rows[0], this._schema)._data;
|
|
196
187
|
}
|
|
197
188
|
}
|
|
198
189
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
const formatObject = require("
|
|
2
|
-
const generateCondition = require("
|
|
3
|
-
const { escapeIdentifier, escapeOrderDirection, escapeValue } = require("
|
|
1
|
+
const formatObject = require("../formatObject");
|
|
2
|
+
const generateCondition = require("../generateCondition");
|
|
3
|
+
const { escapeIdentifier, escapeOrderDirection, escapeValue } = require("../sql");
|
|
4
|
+
const count = require("./count");
|
|
4
5
|
|
|
5
6
|
function buildGroupByItem(group) {
|
|
6
7
|
if (typeof group !== 'string') {
|
|
7
|
-
|
|
8
|
+
throw new Error("Group by items must be strings");
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
const trimmedGroup = group.trim();
|
|
@@ -40,8 +41,7 @@ function buildField(field) {
|
|
|
40
41
|
else if (field.distinct)
|
|
41
42
|
sql = `DISTINCT ${escapeIdentifier(field.distinct)}`;
|
|
42
43
|
else if (field.count)
|
|
43
|
-
sql =
|
|
44
|
-
|
|
44
|
+
sql = count(field.count);
|
|
45
45
|
if (field.as)
|
|
46
46
|
sql += ` AS ${escapeIdentifier(field.as)}`;
|
|
47
47
|
else if (field.sum)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const { escapeIdentifier, escapeValue } = require("../sql");
|
|
2
|
+
|
|
3
|
+
module.exports = (field) => {
|
|
4
|
+
if (typeof field === 'string') return `COUNT(${escapeIdentifier(field)})`;
|
|
5
|
+
if (field instanceof Array && field !== null) return field.map(col => `COUNT(${escapeIdentifier(col)})`).join(' + ');
|
|
6
|
+
if (field instanceof Object) {
|
|
7
|
+
const [key, value] = Object.entries(field)[0];
|
|
8
|
+
return `COUNT(CASE WHEN ${escapeIdentifier(key)} = ${escapeValue(value)} THEN 1 END)`;
|
|
9
|
+
}
|
|
10
|
+
throw new Error("Invalid field type for COUNT. Must be a string, array, or object.");
|
|
11
|
+
}
|
|
@@ -4,6 +4,7 @@ module.exports = function (obj) {
|
|
|
4
4
|
for (const key in obj) {
|
|
5
5
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
6
6
|
const value = getSafe(obj, key);
|
|
7
|
+
|
|
7
8
|
if (value instanceof Date) {
|
|
8
9
|
// convert Date to MySQL DATETIME (no timezone)
|
|
9
10
|
setSafe(obj, key, value.toISOString().slice(0, 19).replace('T', ' '));
|
|
@@ -37,9 +37,6 @@ module.exports = function (filter, isUpdate = false, schema = null) {
|
|
|
37
37
|
// normalize strings that may contain surrounding quotes or escaped quotes
|
|
38
38
|
if (typeof value === 'string') {
|
|
39
39
|
value = value.trim();
|
|
40
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
41
|
-
value = value.slice(1, -1);
|
|
42
|
-
}
|
|
43
40
|
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
44
41
|
}
|
|
45
42
|
if (Array.isArray(value)) {
|
|
@@ -67,9 +64,6 @@ module.exports = function (filter, isUpdate = false, schema = null) {
|
|
|
67
64
|
|
|
68
65
|
if (typeof value === 'string') {
|
|
69
66
|
value = value.trim();
|
|
70
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
71
|
-
value = value.slice(1, -1);
|
|
72
|
-
}
|
|
73
67
|
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
74
68
|
}
|
|
75
69
|
|
|
@@ -92,14 +86,12 @@ module.exports = function (filter, isUpdate = false, schema = null) {
|
|
|
92
86
|
if (fieldDef) {
|
|
93
87
|
if (fieldDef.type && fieldDef.type.name !== undefined) fieldType = fieldDef.type.name;
|
|
94
88
|
else if (fieldDef.type !== undefined) fieldType = fieldDef.type;
|
|
95
|
-
else if (fieldDef && fieldDef.name !== undefined) fieldType = fieldDef.name;
|
|
96
89
|
}
|
|
97
90
|
const normalizedFieldType = String(fieldType ?? "").toLowerCase();
|
|
98
91
|
const isDateLike = ["date", "datetime", "timestamp", "now"].includes(normalizedFieldType);
|
|
99
92
|
|
|
100
93
|
if (typeof value === "string") {
|
|
101
94
|
let val = value;
|
|
102
|
-
// strip surrounding quotes if any (double safety)
|
|
103
95
|
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) val = val.slice(1,-1);
|
|
104
96
|
// if ISO timestamp with Z, convert to MySQL DATETIME format
|
|
105
97
|
if (isDateLike && /T/.test(val)) {
|