adminforth 2.73.1 → 3.0.0-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.
Files changed (50) hide show
  1. package/commands/createApp/templates/package.json.hbs +1 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +49 -6
  5. package/dist/index.js.map +1 -1
  6. package/dist/modules/codeInjector.d.ts +0 -1
  7. package/dist/modules/codeInjector.d.ts.map +1 -1
  8. package/dist/modules/codeInjector.js +0 -17
  9. package/dist/modules/codeInjector.js.map +1 -1
  10. package/dist/modules/restApi.d.ts +1 -0
  11. package/dist/modules/restApi.d.ts.map +1 -1
  12. package/dist/modules/restApi.js +26 -1
  13. package/dist/modules/restApi.js.map +1 -1
  14. package/dist/modules/utils.d.ts +1 -0
  15. package/dist/modules/utils.d.ts.map +1 -1
  16. package/dist/modules/utils.js +13 -0
  17. package/dist/modules/utils.js.map +1 -1
  18. package/dist/spa/src/afcl/Select.vue +3 -7
  19. package/dist/spa/src/components/ListActionsThreeDots.vue +1 -1
  20. package/dist/spa/src/components/ResourceListTable.vue +10 -14
  21. package/dist/spa/src/types/Common.ts +0 -5
  22. package/dist/spa/tailwind.config.js +1 -3
  23. package/dist/types/Common.d.ts +0 -4
  24. package/dist/types/Common.d.ts.map +1 -1
  25. package/dist/types/Common.js.map +1 -1
  26. package/package.json +25 -7
  27. package/dist/dataConnectors/clickhouse.d.ts +0 -106
  28. package/dist/dataConnectors/clickhouse.d.ts.map +0 -1
  29. package/dist/dataConnectors/clickhouse.js +0 -611
  30. package/dist/dataConnectors/clickhouse.js.map +0 -1
  31. package/dist/dataConnectors/mongo.d.ts +0 -118
  32. package/dist/dataConnectors/mongo.d.ts.map +0 -1
  33. package/dist/dataConnectors/mongo.js +0 -458
  34. package/dist/dataConnectors/mongo.js.map +0 -1
  35. package/dist/dataConnectors/mysql.d.ts +0 -87
  36. package/dist/dataConnectors/mysql.d.ts.map +0 -1
  37. package/dist/dataConnectors/mysql.js +0 -569
  38. package/dist/dataConnectors/mysql.js.map +0 -1
  39. package/dist/dataConnectors/postgres.d.ts +0 -90
  40. package/dist/dataConnectors/postgres.d.ts.map +0 -1
  41. package/dist/dataConnectors/postgres.js +0 -538
  42. package/dist/dataConnectors/postgres.js.map +0 -1
  43. package/dist/dataConnectors/qdrant.d.ts +0 -57
  44. package/dist/dataConnectors/qdrant.d.ts.map +0 -1
  45. package/dist/dataConnectors/qdrant.js +0 -469
  46. package/dist/dataConnectors/qdrant.js.map +0 -1
  47. package/dist/dataConnectors/sqlite.d.ts +0 -92
  48. package/dist/dataConnectors/sqlite.d.ts.map +0 -1
  49. package/dist/dataConnectors/sqlite.js +0 -553
  50. package/dist/dataConnectors/sqlite.js.map +0 -1
@@ -1,87 +0,0 @@
1
- import { AdminForthResource, IAdminForthSingleFilter, IAdminForthAndOrFilter, IAdminForthDataSourceConnector, AdminForthConfig, IAggregationRule, IGroupByRule } from '../types/Back.js';
2
- import AdminForthBaseConnector from './baseConnector.js';
3
- declare class MysqlConnector extends AdminForthBaseConnector implements IAdminForthDataSourceConnector {
4
- setupClient(url: any): Promise<void>;
5
- OperatorsMap: {
6
- eq: string;
7
- ne: string;
8
- gt: string;
9
- lt: string;
10
- gte: string;
11
- lte: string;
12
- like: string;
13
- ilike: string;
14
- in: string;
15
- nin: string;
16
- and: string;
17
- or: string;
18
- isEmpty: string;
19
- isNotEmpty: string;
20
- };
21
- SortDirectionsMap: {
22
- asc: string;
23
- desc: string;
24
- };
25
- getAllTables(): Promise<Array<string>>;
26
- getAllColumnsInTable(tableName: string): Promise<Array<{
27
- name: string;
28
- sampleValue?: any;
29
- }>>;
30
- hasMySQLCascadeFk(resource: AdminForthResource, config: AdminForthConfig): Promise<boolean>;
31
- discoverFields(resource: AdminForthResource, config: AdminForthConfig): Promise<{}>;
32
- getFieldValue(field: any, value: any): any;
33
- setFieldValue(field: any, value: any): any;
34
- getFilterString(filter: IAdminForthSingleFilter | IAdminForthAndOrFilter): string;
35
- getFilterParams(filter: IAdminForthSingleFilter | IAdminForthAndOrFilter): any[];
36
- whereClauseAndValues(filters: IAdminForthAndOrFilter): {
37
- sql: string;
38
- values: any[];
39
- };
40
- getAggregateWithOriginalTypes({ resource, filters, aggregations, groupBy }: {
41
- resource: AdminForthResource;
42
- filters: IAdminForthAndOrFilter;
43
- aggregations: {
44
- [alias: string]: IAggregationRule;
45
- };
46
- groupBy?: IGroupByRule | IGroupByRule[];
47
- }): Promise<Array<{
48
- group?: string;
49
- [key: string]: any;
50
- }>>;
51
- getDataWithOriginalTypes({ resource, limit, offset, sort, filters, columns }: {
52
- resource: any;
53
- limit: any;
54
- offset: any;
55
- sort: any;
56
- filters: any;
57
- columns: any;
58
- }): Promise<any[]>;
59
- getCount({ resource, filters }: {
60
- resource: AdminForthResource;
61
- filters: IAdminForthAndOrFilter;
62
- }): Promise<number>;
63
- getMinMaxForColumnsWithOriginalTypes({ resource, columns }: {
64
- resource: any;
65
- columns: any;
66
- }): Promise<{}>;
67
- createRecordOriginalValues({ resource, record }: {
68
- resource: any;
69
- record: any;
70
- }): Promise<string>;
71
- updateRecordOriginalValues({ resource, recordId, newValues }: {
72
- resource: any;
73
- recordId: any;
74
- newValues: any;
75
- }): Promise<void>;
76
- deleteRecord({ resource, recordId }: {
77
- resource: any;
78
- recordId: any;
79
- }): Promise<boolean>;
80
- deleteMany({ resource, recordIds }: {
81
- resource: AdminForthResource;
82
- recordIds: string[];
83
- }): Promise<number>;
84
- close(): Promise<void>;
85
- }
86
- export default MysqlConnector;
87
- //# sourceMappingURL=mysql.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mysql.d.ts","sourceRoot":"","sources":["../../dataConnectors/mysql.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,8BAA8B,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,YAAY,EAAoC,MAAM,kBAAkB,CAAC;AAE3N,OAAO,uBAAuB,MAAM,oBAAoB,CAAC;AAMzD,cAAM,cAAe,SAAQ,uBAAwB,YAAW,8BAA8B;IAEtF,WAAW,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,IAAI,CAAC;IAarC,YAAY;;;;;;;;;;;;;;;MAeV;IAEF,iBAAiB;;;MAGf;IAEI,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAWtC,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IAqB5F,iBAAiB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IA+B3F,cAAc,CAAC,QAAQ,EAAE,kBAAkB,EAAE,MAAM,EAAE,gBAAgB;IAiF3E,aAAa,CAAC,KAAK,KAAA,EAAE,KAAK,KAAA;IAgC1B,aAAa,CAAC,KAAK,KAAA,EAAE,KAAK,KAAA;IAmB1B,eAAe,CAAC,MAAM,EAAE,uBAAuB,GAAG,sBAAsB,GAAG,MAAM;IAwDjF,eAAe,CAAC,MAAM,EAAE,uBAAuB,GAAG,sBAAsB,GAAG,GAAG,EAAE;IAmChF,oBAAoB,CAAC,OAAO,EAAE,sBAAsB,GAAI;QACtD,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,GAAG,EAAE,CAAC;KACf;IAOK,6BAA6B,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE;QAChF,QAAQ,EAAE,kBAAkB,CAAC;QAC7B,OAAO,EAAE,sBAAsB,CAAC;QAChC,YAAY,EAAE;YAAE,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAAA;SAAE,CAAC;QACpD,OAAO,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;KACzC,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC,CAAC;IAsHpD,wBAAwB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE;;;;;;;KAAA,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAyB7F,QAAQ,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;QAAE,QAAQ,EAAE,kBAAkB,CAAC;QAAC,OAAO,EAAE,sBAAsB,CAAC;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAmBpH,oCAAoC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE;;;KAAA;IAe1D,0BAA0B,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE;;;KAAA,GAAG,OAAO,CAAC,MAAM,CAAC;IAWjE,0BAA0B,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAG,SAAS,EAAE;;;;KAAA;IAQ7D,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE;;;KAAA,GAAG,OAAO,CAAC,OAAO,CAAC;IAOtD,UAAU,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE;QAAE,QAAQ,EAAE,kBAAkB,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAW3G,KAAK;CAGZ;AAED,eAAe,cAAc,CAAC"}
@@ -1,569 +0,0 @@
1
- import dayjs from 'dayjs';
2
- import utc from 'dayjs/plugin/utc.js';
3
- import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, } from '../types/Common.js';
4
- import AdminForthBaseConnector from './baseConnector.js';
5
- import mysql from 'mysql2/promise';
6
- import { dbLogger, afLogger } from '../modules/logger.js';
7
- dayjs.extend(utc);
8
- class MysqlConnector extends AdminForthBaseConnector {
9
- constructor() {
10
- super(...arguments);
11
- this.OperatorsMap = {
12
- [AdminForthFilterOperators.EQ]: '=',
13
- [AdminForthFilterOperators.NE]: '<>',
14
- [AdminForthFilterOperators.GT]: '>',
15
- [AdminForthFilterOperators.LT]: '<',
16
- [AdminForthFilterOperators.GTE]: '>=',
17
- [AdminForthFilterOperators.LTE]: '<=',
18
- [AdminForthFilterOperators.LIKE]: 'LIKE',
19
- [AdminForthFilterOperators.ILIKE]: 'ILIKE',
20
- [AdminForthFilterOperators.IN]: 'IN',
21
- [AdminForthFilterOperators.NIN]: 'NOT IN',
22
- [AdminForthFilterOperators.AND]: 'AND',
23
- [AdminForthFilterOperators.OR]: 'OR',
24
- [AdminForthFilterOperators.IS_EMPTY]: 'IS NULL',
25
- [AdminForthFilterOperators.IS_NOT_EMPTY]: 'IS NOT NULL',
26
- };
27
- this.SortDirectionsMap = {
28
- [AdminForthSortDirections.asc]: 'ASC',
29
- [AdminForthSortDirections.desc]: 'DESC',
30
- };
31
- }
32
- async setupClient(url) {
33
- try {
34
- this.client = mysql.createPool({
35
- uri: url,
36
- waitForConnections: true,
37
- connectionLimit: 10, // Adjust based on your needs
38
- queueLimit: 0
39
- });
40
- }
41
- catch (e) {
42
- afLogger.error(`Failed to connect to MySQL: ${e}`);
43
- }
44
- }
45
- async getAllTables() {
46
- const [rows] = await this.client.query(`
47
- SELECT table_name
48
- FROM information_schema.tables
49
- WHERE table_schema = DATABASE() AND table_type = 'BASE TABLE';
50
- `);
51
- return rows.map((row) => row.TABLE_NAME);
52
- }
53
- async getAllColumnsInTable(tableName) {
54
- const [columns] = await this.client.query(`SELECT column_name FROM information_schema.columns WHERE table_name = ? AND table_schema = DATABASE()`, [tableName]);
55
- const columnNames = columns.map((c) => c.COLUMN_NAME);
56
- const orderByField = ['updated_at', 'created_at', 'id'].find(f => columnNames.includes(f));
57
- let [rows] = orderByField
58
- ? await this.client.query(`SELECT * FROM \`${tableName}\` ORDER BY \`${orderByField}\` DESC LIMIT 1`)
59
- : await this.client.query(`SELECT * FROM \`${tableName}\` LIMIT 1`);
60
- const sampleRow = rows[0] || {};
61
- return columns.map((col) => ({
62
- name: col.COLUMN_NAME,
63
- sampleValue: sampleRow[col.COLUMN_NAME],
64
- }));
65
- }
66
- async hasMySQLCascadeFk(resource, config) {
67
- var _a;
68
- const cascadeColumn = resource.columns.find(c => { var _a; return ((_a = c.foreignResource) === null || _a === void 0 ? void 0 : _a.onDelete) === 'cascade'; });
69
- if (!cascadeColumn)
70
- return false;
71
- const parentResource = config.resources.find(r => r.resourceId === cascadeColumn.foreignResource.resourceId);
72
- if (!parentResource)
73
- return false;
74
- const [rows] = await this.client.execute(`
75
- SELECT 1
76
- FROM information_schema.REFERENTIAL_CONSTRAINTS
77
- WHERE CONSTRAINT_SCHEMA = DATABASE()
78
- AND TABLE_NAME = ?
79
- AND REFERENCED_TABLE_NAME = ?
80
- AND DELETE_RULE = 'CASCADE'
81
- LIMIT 1
82
- `, [resource.table, parentResource.table]);
83
- const hasCascadeOnTable = rows.length > 0;
84
- const isUploadPluginInstalled = (_a = resource.plugins) === null || _a === void 0 ? void 0 : _a.some(p => p.className === "UploadPlugin");
85
- if (hasCascadeOnTable && isUploadPluginInstalled) {
86
- afLogger.warn(`Table "${resource.table}" has ON DELETE CASCADE and UploadPlugin installed, which may conflict with adminForth cascade deletion`);
87
- }
88
- return hasCascadeOnTable;
89
- }
90
- async discoverFields(resource, config) {
91
- const [results] = await this.client.execute("SHOW COLUMNS FROM " + resource.table);
92
- await this.hasMySQLCascadeFk(resource, config);
93
- const fieldTypes = {};
94
- results.forEach((row) => {
95
- const field = {};
96
- const baseType = row.Type.toLowerCase();
97
- if (baseType == 'tinyint(1)') {
98
- field.type = AdminForthDataTypes.BOOLEAN;
99
- field._underlineType = 'bool';
100
- }
101
- else if (baseType.startsWith('tinyint')) {
102
- field.type = AdminForthDataTypes.INTEGER;
103
- field._underlineType = 'tinyint';
104
- field.minValue = baseType.includes('unsigned') ? 0 : -128;
105
- field.maxValue = baseType.includes('unsigned') ? 255 : 127;
106
- }
107
- else if (baseType.startsWith('smallint')) {
108
- field.type = AdminForthDataTypes.INTEGER;
109
- field._underlineType = 'tinyint';
110
- field.minValue = baseType.includes('unsigned') ? 0 : -32768;
111
- field.maxValue = baseType.includes('unsigned') ? 65535 : 32767;
112
- }
113
- else if (baseType.startsWith('int') || baseType.endsWith('int')) {
114
- field.type = AdminForthDataTypes.INTEGER;
115
- field._underlineType = 'int';
116
- field.minValue = baseType.includes('unsigned') ? 0 : null;
117
- }
118
- else if (baseType.startsWith('dec') || baseType.startsWith('numeric')) {
119
- field.type = AdminForthDataTypes.DECIMAL;
120
- field._underlineType = 'decimal';
121
- const [precision, scale] = baseType.match(/\d+/g);
122
- field.precision = parseInt(precision);
123
- field.scale = parseInt(scale);
124
- field.minValue = baseType.includes('unsigned') ? 0 : null;
125
- }
126
- else if (baseType.startsWith('float') || baseType.startsWith('double') || baseType.startsWith('real')) {
127
- field.type = AdminForthDataTypes.FLOAT;
128
- field._underlineType = 'float';
129
- field.minValue = baseType.includes('unsigned') ? 0 : null;
130
- }
131
- else if (baseType.startsWith('varchar')) {
132
- field.type = AdminForthDataTypes.STRING;
133
- field._underlineType = 'varchar';
134
- const length = baseType.match(/\d+/);
135
- field.maxLength = length ? parseInt(length[0]) : null;
136
- }
137
- else if (baseType.startsWith('char')) {
138
- field.type = AdminForthDataTypes.STRING;
139
- field._underlineType = 'char';
140
- const length = baseType.match(/\d+/);
141
- field.minLength = length ? parseInt(length[0]) : null;
142
- field.maxLength = length ? parseInt(length[0]) : null;
143
- }
144
- else if (baseType.endsWith('text')) {
145
- field.type = AdminForthDataTypes.TEXT;
146
- field._underlineType = 'text';
147
- }
148
- else if (baseType.startsWith('enum')) {
149
- field.type = AdminForthDataTypes.STRING;
150
- field._underlineType = 'enum';
151
- }
152
- else if (baseType.startsWith('json')) {
153
- field.type = AdminForthDataTypes.JSON;
154
- field._underlineType = 'json';
155
- }
156
- else if (baseType.startsWith('time')) {
157
- field.type = AdminForthDataTypes.TIME;
158
- field._underlineType = 'time';
159
- }
160
- else if (baseType.startsWith('datetime') || baseType.startsWith('timestamp')) {
161
- field.type = AdminForthDataTypes.DATETIME;
162
- field._underlineType = 'timestamp';
163
- }
164
- else if (baseType.startsWith('date')) {
165
- field.type = AdminForthDataTypes.DATE;
166
- field._underlineType = 'date';
167
- }
168
- else if (baseType.startsWith('year')) {
169
- field.type = AdminForthDataTypes.INTEGER;
170
- field._underlineType = 'year';
171
- field.minValue = 1901;
172
- field.maxValue = 2155;
173
- }
174
- else {
175
- field.type = 'unknown';
176
- }
177
- field._baseTypeDebug = baseType;
178
- field.primaryKey = row.Key === 'PRI';
179
- field.default = row.Default;
180
- field.required = row.Null === 'NO' && !row.Default;
181
- fieldTypes[row.Field] = field;
182
- });
183
- return fieldTypes;
184
- }
185
- getFieldValue(field, value) {
186
- if (field.type == AdminForthDataTypes.DATETIME) {
187
- if (!value) {
188
- return null;
189
- }
190
- return dayjs(value).toISOString();
191
- }
192
- else if (field.type == AdminForthDataTypes.DATE) {
193
- return value || null;
194
- }
195
- else if (field.type == AdminForthDataTypes.TIME) {
196
- return value || null;
197
- }
198
- else if (field.type == AdminForthDataTypes.BOOLEAN) {
199
- return value === null ? null : !!value;
200
- }
201
- else if (field.type == AdminForthDataTypes.JSON) {
202
- if (typeof value === 'string') {
203
- try {
204
- return JSON.parse(value);
205
- }
206
- catch (e) {
207
- return { 'error': `Failed to parse JSON: ${e.message}` };
208
- }
209
- }
210
- else if (typeof value === 'object') {
211
- return value;
212
- }
213
- else {
214
- afLogger.error(`JSON field value is not string or object, but has type: ${typeof value}`);
215
- afLogger.error(`Field:, ${field}`);
216
- return {};
217
- }
218
- }
219
- return value;
220
- }
221
- setFieldValue(field, value) {
222
- if (field.type == AdminForthDataTypes.DATETIME) {
223
- if (!value) {
224
- return null;
225
- }
226
- return dayjs.utc(value).format('YYYY-MM-DD HH:mm:ss');
227
- }
228
- else if (field.type == AdminForthDataTypes.BOOLEAN) {
229
- return value === null ? null : (value ? 1 : 0);
230
- }
231
- else if (field.type == AdminForthDataTypes.JSON) {
232
- if (field._underlineType === 'json') {
233
- return value;
234
- }
235
- else {
236
- return JSON.stringify(value);
237
- }
238
- }
239
- return value;
240
- }
241
- getFilterString(filter) {
242
- if (filter.field) {
243
- // Field-to-field comparison support
244
- if (filter.rightField) {
245
- const left = filter.field;
246
- const right = filter.rightField;
247
- const operator = this.OperatorsMap[filter.operator];
248
- return `${left} ${operator} ${right}`;
249
- }
250
- // filter is a Single filter
251
- let placeholder = '?';
252
- let field = filter.field;
253
- let operator = this.OperatorsMap[filter.operator];
254
- // Handle IS_EMPTY and IS_NOT_EMPTY operators
255
- if (filter.operator == AdminForthFilterOperators.IS_EMPTY || filter.operator == AdminForthFilterOperators.IS_NOT_EMPTY) {
256
- return `${field} ${operator}`;
257
- }
258
- else if (filter.operator == AdminForthFilterOperators.IN || filter.operator == AdminForthFilterOperators.NIN) {
259
- placeholder = `(${filter.value.map(() => '?').join(', ')})`;
260
- }
261
- else if (filter.operator == AdminForthFilterOperators.ILIKE) {
262
- placeholder = `LOWER(?)`;
263
- field = `LOWER(${field})`;
264
- operator = 'LIKE';
265
- }
266
- else if (filter.operator == AdminForthFilterOperators.NE) {
267
- if (filter.value === null) {
268
- operator = 'IS NOT';
269
- placeholder = 'NULL';
270
- }
271
- else {
272
- // for not equal, we need to add a null check
273
- // because nullish field will not match != value
274
- placeholder = `${placeholder} OR ${field} IS NULL)`;
275
- field = `(${field}`;
276
- }
277
- }
278
- else if (filter.operator == AdminForthFilterOperators.EQ && filter.value === null) {
279
- operator = 'IS';
280
- placeholder = 'NULL';
281
- }
282
- return `${field} ${operator} ${placeholder}`;
283
- }
284
- // filter is a single insecure raw sql
285
- if (filter.insecureRawSQL) {
286
- return filter.insecureRawSQL;
287
- }
288
- // filter is a AndOr filter
289
- return filter.subFilters.map((f) => {
290
- if (f.field || f.insecureRawSQL) {
291
- // subFilter is a Single filter
292
- return this.getFilterString(f);
293
- }
294
- // subFilter is a AndOr filter - add parentheses
295
- return `(${this.getFilterString(f)})`;
296
- }).join(` ${this.OperatorsMap[filter.operator]} `);
297
- }
298
- getFilterParams(filter) {
299
- if (filter.field) {
300
- if (filter.rightField) {
301
- // No params for field-to-field comparisons
302
- return [];
303
- }
304
- // filter is a Single filter
305
- // Handle IS_EMPTY and IS_NOT_EMPTY operators - no params needed
306
- if (filter.operator == AdminForthFilterOperators.IS_EMPTY || filter.operator == AdminForthFilterOperators.IS_NOT_EMPTY) {
307
- return [];
308
- }
309
- else if (filter.operator == AdminForthFilterOperators.LIKE || filter.operator == AdminForthFilterOperators.ILIKE) {
310
- return [`%${filter.value}%`];
311
- }
312
- else if (filter.operator == AdminForthFilterOperators.IN || filter.operator == AdminForthFilterOperators.NIN) {
313
- return filter.value;
314
- }
315
- else if (filter.operator == AdminForthFilterOperators.EQ && filter.value === null) {
316
- return [];
317
- }
318
- else if (filter.operator == AdminForthFilterOperators.NE && filter.value === null) {
319
- return [];
320
- }
321
- else {
322
- return [filter.value];
323
- }
324
- }
325
- // filter is a Single insecure raw sql
326
- if (filter.insecureRawSQL) {
327
- return [];
328
- }
329
- // filter is a AndOrFilter
330
- return filter.subFilters.reduce((params, f) => {
331
- return params.concat(this.getFilterParams(f));
332
- }, []);
333
- }
334
- whereClauseAndValues(filters) {
335
- return filters.subFilters.length ? {
336
- sql: `WHERE ${this.getFilterString(filters)}`,
337
- values: this.getFilterParams(filters)
338
- } : { sql: '', values: [] };
339
- }
340
- async getAggregateWithOriginalTypes({ resource, filters, aggregations, groupBy }) {
341
- var _a, _b, _c, _d;
342
- const tableName = resource.table;
343
- const selectParts = [];
344
- const medianFields = [];
345
- const groupExprs = [];
346
- const groupAliases = [];
347
- const groupByRules = this.normalizeGroupByRules(groupBy);
348
- for (const [index, groupByRule] of groupByRules.entries()) {
349
- let groupExpr;
350
- if (groupByRule.type === 'field') {
351
- groupExpr = `\`${groupByRule.field}\``;
352
- }
353
- else {
354
- const g = groupByRule;
355
- const tz = (_a = g.timezone) !== null && _a !== void 0 ? _a : 'UTC';
356
- const innerExpr = `COALESCE(CONVERT_TZ(\`${g.field}\`, 'UTC', '${tz}'), \`${g.field}\`)`;
357
- switch (g.truncation) {
358
- case 'day':
359
- groupExpr = `DATE_FORMAT(${innerExpr}, '%Y-%m-%d')`;
360
- break;
361
- case 'month':
362
- groupExpr = `DATE_FORMAT(${innerExpr}, '%Y-%m-01')`;
363
- break;
364
- case 'year':
365
- groupExpr = `DATE_FORMAT(${innerExpr}, '%Y-01-01')`;
366
- break;
367
- case 'week':
368
- groupExpr = `DATE_FORMAT(DATE_SUB(${innerExpr}, INTERVAL WEEKDAY(${innerExpr}) DAY), '%Y-%m-%d')`;
369
- break;
370
- }
371
- }
372
- const groupAlias = this.getGroupByResultAlias(groupByRule, index, groupByRules.length);
373
- groupExprs.push(groupExpr);
374
- groupAliases.push(groupAlias);
375
- selectParts.push(`${groupExpr} AS \`${groupAlias}\``);
376
- }
377
- for (const [alias, rule] of Object.entries(aggregations)) {
378
- const f = `\`${rule.field}\``;
379
- switch (rule.operation) {
380
- case 'sum':
381
- selectParts.push(`SUM(${f}) AS \`${alias}\``);
382
- break;
383
- case 'count':
384
- selectParts.push(`COUNT(*) AS \`${alias}\``);
385
- break;
386
- case 'count_distinct':
387
- selectParts.push(`COUNT(DISTINCT ${f}) AS \`${alias}\``);
388
- break;
389
- case 'avg':
390
- selectParts.push(`AVG(${f}) AS \`${alias}\``);
391
- break;
392
- case 'min':
393
- selectParts.push(`MIN(${f}) AS \`${alias}\``);
394
- break;
395
- case 'max':
396
- selectParts.push(`MAX(${f}) AS \`${alias}\``);
397
- break;
398
- case 'median':
399
- medianFields.push({ alias, field: rule.field });
400
- break;
401
- }
402
- }
403
- const { sql: where, values: filterValues } = this.whereClauseAndValues(filters);
404
- // Run non-median aggregations
405
- let rows = [];
406
- const hasNonMedian = selectParts.length > groupExprs.length;
407
- if (hasNonMedian) {
408
- let query = `SELECT ${selectParts.join(', ')} FROM \`${tableName}\` ${where}`;
409
- if (groupExprs.length)
410
- query += ` GROUP BY ${groupExprs.join(', ')} ORDER BY ${groupExprs.join(', ')} ASC`;
411
- dbLogger.trace(`🪲📜 MySQL AGG Q: ${query} values: ${JSON.stringify(filterValues)}`);
412
- const [result] = await this.client.execute(query, filterValues);
413
- rows = result;
414
- }
415
- // Run each median via window functions (MySQL 8+) — no session variables, no memory pressure
416
- for (const { alias, field } of medianFields) {
417
- const f = `\`${field}\``;
418
- const nullGuard = where ? `${where} AND ${f} IS NOT NULL` : `WHERE ${f} IS NOT NULL`;
419
- let medianQuery;
420
- if (groupExprs.length) {
421
- const groupSelect = groupExprs.map((expr, index) => `${expr} AS \`${groupAliases[index]}\``).join(', ');
422
- const groupColumns = groupAliases.map(alias => `\`${alias}\``).join(', ');
423
- medianQuery = `
424
- SELECT ${groupColumns}, AVG(${f}) AS \`${alias}\`
425
- FROM (
426
- SELECT ${groupSelect}, ${f},
427
- ROW_NUMBER() OVER (PARTITION BY ${groupExprs.join(', ')} ORDER BY ${f}) AS rn,
428
- COUNT(*) OVER (PARTITION BY ${groupExprs.join(', ')}) AS cnt
429
- FROM \`${tableName}\` ${nullGuard}
430
- ) t
431
- WHERE rn IN (FLOOR((cnt + 1) / 2.0), CEIL((cnt + 1) / 2.0))
432
- GROUP BY ${groupColumns}
433
- ORDER BY ${groupColumns} ASC
434
- `;
435
- }
436
- else {
437
- medianQuery = `
438
- SELECT AVG(${f}) AS \`${alias}\`
439
- FROM (
440
- SELECT ${f},
441
- ROW_NUMBER() OVER (ORDER BY ${f}) AS rn,
442
- COUNT(*) OVER () AS cnt
443
- FROM \`${tableName}\` ${nullGuard}
444
- ) t
445
- WHERE rn IN (FLOOR((cnt + 1) / 2.0), CEIL((cnt + 1) / 2.0))
446
- `;
447
- }
448
- dbLogger.trace(`🪲📜 MySQL MEDIAN Q: ${medianQuery} values: ${JSON.stringify(filterValues)}`);
449
- const [medianResult] = await this.client.execute(medianQuery, filterValues);
450
- const medianRows = medianResult;
451
- if (groupExprs.length) {
452
- const groupKey = (row) => groupAliases.map(alias => String(row[alias])).join('\u0000');
453
- if (rows.length === 0) {
454
- rows = medianRows.map((r) => (Object.assign(Object.assign({}, Object.fromEntries(groupAliases.map(groupAlias => [groupAlias, r[groupAlias]]))), { [alias]: r[alias] })));
455
- }
456
- else {
457
- const byGroup = new Map(medianRows.map((r) => [groupKey(r), r[alias]]));
458
- for (const row of rows) {
459
- row[alias] = (_b = byGroup.get(groupKey(row))) !== null && _b !== void 0 ? _b : null;
460
- }
461
- }
462
- }
463
- else {
464
- const medianVal = (_d = (_c = medianRows[0]) === null || _c === void 0 ? void 0 : _c[alias]) !== null && _d !== void 0 ? _d : null;
465
- if (rows.length === 0) {
466
- rows = [{ [alias]: medianVal }];
467
- }
468
- else {
469
- rows[0][alias] = medianVal;
470
- }
471
- }
472
- }
473
- return rows;
474
- }
475
- async getDataWithOriginalTypes({ resource, limit, offset, sort, filters, columns }) {
476
- const selectedColumns = (columns !== null && columns !== void 0 ? columns : resource.dataSourceColumns).map((col) => `${col.name}`).join(', ');
477
- const tableName = resource.table;
478
- const { sql: where, values: filterValues } = this.whereClauseAndValues(filters);
479
- const orderBy = sort.length ? `ORDER BY ${sort.map((s) => `${s.field} ${this.SortDirectionsMap[s.direction]}`).join(', ')}` : '';
480
- let selectQuery = `SELECT ${selectedColumns} FROM ${tableName}`;
481
- if (where)
482
- selectQuery += ` ${where}`;
483
- if (orderBy)
484
- selectQuery += ` ${orderBy}`;
485
- if (limit)
486
- selectQuery += ` LIMIT ${limit}`;
487
- if (offset)
488
- selectQuery += ` OFFSET ${offset}`;
489
- dbLogger.trace(`🪲📜 MySQL Q: ${selectQuery} values: ${JSON.stringify(filterValues)}`);
490
- const [results] = await this.client.execute(selectQuery, filterValues);
491
- return results.map((row) => {
492
- const newRow = {};
493
- for (const [key, value] of Object.entries(row)) {
494
- newRow[key] = value;
495
- }
496
- return newRow;
497
- });
498
- }
499
- async getCount({ resource, filters }) {
500
- const tableName = resource.table;
501
- let normalizedFilters = filters;
502
- // validate and normalize in case this method is called from dataAPI
503
- if (filters) {
504
- const filterValidation = this.validateAndNormalizeFilters(filters, resource);
505
- if (!filterValidation.ok) {
506
- throw new Error(filterValidation.error);
507
- }
508
- normalizedFilters = filterValidation.normalizedFilters;
509
- }
510
- const { sql: where, values: filterValues } = this.whereClauseAndValues(normalizedFilters);
511
- const q = `SELECT COUNT(*) FROM ${tableName} ${where}`;
512
- dbLogger.trace(`🪲📜 MySQL Q: ${q} values: ${JSON.stringify(filterValues)}`);
513
- const [results] = await this.client.execute(q, filterValues);
514
- return +results[0]["COUNT(*)"];
515
- }
516
- async getMinMaxForColumnsWithOriginalTypes({ resource, columns }) {
517
- const tableName = resource.table;
518
- const result = {};
519
- await Promise.all(columns.map(async (col) => {
520
- const q = `SELECT MIN(${col.name}) as min, MAX(${col.name}) as max FROM ${tableName}`;
521
- dbLogger.trace(`🪲📜 MySQL Q: ${q}`);
522
- const [results] = await this.client.execute(q);
523
- const { min, max } = results[0];
524
- result[col.name] = {
525
- min, max,
526
- };
527
- }));
528
- return result;
529
- }
530
- async createRecordOriginalValues({ resource, record }) {
531
- const tableName = resource.table;
532
- const columns = Object.keys(record);
533
- const placeholders = columns.map(() => '?').join(', ');
534
- const values = columns.map((colName) => typeof record[colName] === 'undefined' ? null : record[colName]);
535
- const q = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`;
536
- dbLogger.trace(`🪲📜 MySQL Q: ${q} values: ${JSON.stringify(values)}`);
537
- const ret = await this.client.execute(q, values);
538
- return ret.insertId;
539
- }
540
- async updateRecordOriginalValues({ resource, recordId, newValues }) {
541
- const values = [...Object.values(newValues), recordId];
542
- const columnsWithPlaceholders = Object.keys(newValues).map((col) => `${col} = ?`).join(', ');
543
- const q = `UPDATE ${resource.table} SET ${columnsWithPlaceholders} WHERE ${this.getPrimaryKey(resource)} = ?`;
544
- dbLogger.trace(`🪲📜 MySQL Q: ${q} values: ${JSON.stringify(values)}`);
545
- await this.client.execute(q, values);
546
- }
547
- async deleteRecord({ resource, recordId }) {
548
- const q = `DELETE FROM ${resource.table} WHERE ${this.getPrimaryKey(resource)} = ?`;
549
- dbLogger.trace(`🪲📜 MySQL Q: ${q} values: ${JSON.stringify([recordId])}`);
550
- const res = await this.client.execute(q, [recordId]);
551
- return res.rowCount > 0;
552
- }
553
- async deleteMany({ resource, recordIds }) {
554
- var _a;
555
- if (!recordIds || recordIds.length === 0) {
556
- return 0;
557
- }
558
- const placeholders = recordIds.map(() => '?').join(',');
559
- const query = `DELETE FROM ${resource.table} WHERE ${this.getPrimaryKey(resource)} IN (${placeholders})`;
560
- dbLogger.trace(`🪲📜 MySQL Q: ${query} values: ${JSON.stringify([recordIds])}`);
561
- const [result] = await this.client.execute(query, recordIds);
562
- return (_a = result.affectedRows) !== null && _a !== void 0 ? _a : 0;
563
- }
564
- async close() {
565
- await this.client.end();
566
- }
567
- }
568
- export default MysqlConnector;
569
- //# sourceMappingURL=mysql.js.map