@solidxai/core 0.1.10 → 0.1.11-beta.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/controllers/datasource-introspection.controller.d.ts +234 -0
- package/dist/controllers/datasource-introspection.controller.d.ts.map +1 -0
- package/dist/controllers/datasource-introspection.controller.js +98 -0
- package/dist/controllers/datasource-introspection.controller.js.map +1 -0
- package/dist/controllers/datasource-management.controller.d.ts +18 -0
- package/dist/controllers/datasource-management.controller.d.ts.map +1 -0
- package/dist/controllers/datasource-management.controller.js +55 -0
- package/dist/controllers/datasource-management.controller.js.map +1 -0
- package/dist/dtos/create-datasource-management.dto.d.ts +25 -0
- package/dist/dtos/create-datasource-management.dto.d.ts.map +1 -0
- package/dist/dtos/create-datasource-management.dto.js +169 -0
- package/dist/dtos/create-datasource-management.dto.js.map +1 -0
- package/dist/dtos/datasource-introspection-mapping.dto.d.ts +45 -0
- package/dist/dtos/datasource-introspection-mapping.dto.d.ts.map +1 -0
- package/dist/dtos/datasource-introspection-mapping.dto.js +233 -0
- package/dist/dtos/datasource-introspection-mapping.dto.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces/datasource-introspection-provider.interface.d.ts +26 -0
- package/dist/interfaces/datasource-introspection-provider.interface.d.ts.map +1 -0
- package/dist/interfaces/datasource-introspection-provider.interface.js +3 -0
- package/dist/interfaces/datasource-introspection-provider.interface.js.map +1 -0
- package/dist/repository/scheduled-job.repository.d.ts +3 -0
- package/dist/repository/scheduled-job.repository.d.ts.map +1 -1
- package/dist/repository/scheduled-job.repository.js +60 -0
- package/dist/repository/scheduled-job.repository.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts +32 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +1011 -230
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +21 -0
- package/dist/services/action-metadata.service.d.ts +7 -0
- package/dist/services/action-metadata.service.d.ts.map +1 -1
- package/dist/services/action-metadata.service.js +76 -3
- package/dist/services/action-metadata.service.js.map +1 -1
- package/dist/services/authentication.service.js +1 -1
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/dashboard-providers/mq-dashboard-latency-trend-provider.service.d.ts.map +1 -1
- package/dist/services/dashboard-providers/mq-dashboard-latency-trend-provider.service.js +2 -2
- package/dist/services/dashboard-providers/mq-dashboard-latency-trend-provider.service.js.map +1 -1
- package/dist/services/dashboard-providers/mq-dashboard-messages-over-time-provider.service.d.ts.map +1 -1
- package/dist/services/dashboard-providers/mq-dashboard-messages-over-time-provider.service.js +2 -2
- package/dist/services/dashboard-providers/mq-dashboard-messages-over-time-provider.service.js.map +1 -1
- package/dist/services/dashboard-providers/mq-dashboard-provider-utils.d.ts +3 -0
- package/dist/services/dashboard-providers/mq-dashboard-provider-utils.d.ts.map +1 -1
- package/dist/services/dashboard-providers/mq-dashboard-provider-utils.js +71 -0
- package/dist/services/dashboard-providers/mq-dashboard-provider-utils.js.map +1 -1
- package/dist/services/dashboard-providers/mq-dashboard-queue-sla-heatmap-provider.service.d.ts.map +1 -1
- package/dist/services/dashboard-providers/mq-dashboard-queue-sla-heatmap-provider.service.js +3 -3
- package/dist/services/dashboard-providers/mq-dashboard-queue-sla-heatmap-provider.service.js.map +1 -1
- package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.d.ts +11 -0
- package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.d.ts.map +1 -0
- package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js +181 -0
- package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js.map +1 -0
- package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.d.ts +11 -0
- package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.d.ts.map +1 -0
- package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js +90 -0
- package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js.map +1 -0
- package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.d.ts +11 -0
- package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.d.ts.map +1 -0
- package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js +156 -0
- package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js.map +1 -0
- package/dist/services/datasource-introspection.service.d.ts +288 -0
- package/dist/services/datasource-introspection.service.d.ts.map +1 -0
- package/dist/services/datasource-introspection.service.js +1278 -0
- package/dist/services/datasource-introspection.service.js.map +1 -0
- package/dist/services/datasource-management.service.d.ts +60 -0
- package/dist/services/datasource-management.service.d.ts.map +1 -0
- package/dist/services/datasource-management.service.js +461 -0
- package/dist/services/datasource-management.service.js.map +1 -0
- package/dist/services/field-metadata.service.d.ts.map +1 -1
- package/dist/services/field-metadata.service.js +19 -0
- package/dist/services/field-metadata.service.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts +1 -3
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +47 -15
- package/dist/services/model-metadata.service.js.map +1 -1
- package/dist/services/role-metadata.service.d.ts +5 -0
- package/dist/services/role-metadata.service.d.ts.map +1 -1
- package/dist/services/role-metadata.service.js +85 -31
- package/dist/services/role-metadata.service.js.map +1 -1
- package/dist/services/selection-providers/mq-dashboard-message-broker-variable-options-provider.service.d.ts.map +1 -1
- package/dist/services/selection-providers/mq-dashboard-message-broker-variable-options-provider.service.js +2 -3
- package/dist/services/selection-providers/mq-dashboard-message-broker-variable-options-provider.service.js.map +1 -1
- package/dist/services/selection-providers/mq-dashboard-queue-name-variable-options-provider.service.d.ts.map +1 -1
- package/dist/services/selection-providers/mq-dashboard-queue-name-variable-options-provider.service.js +2 -3
- package/dist/services/selection-providers/mq-dashboard-queue-name-variable-options-provider.service.js.map +1 -1
- package/dist/services/setting.service.d.ts +1 -0
- package/dist/services/setting.service.d.ts.map +1 -1
- package/dist/services/setting.service.js +32 -8
- package/dist/services/setting.service.js.map +1 -1
- package/dist/services/solid-ts-morph.service.d.ts +1 -1
- package/dist/services/solid-ts-morph.service.d.ts.map +1 -1
- package/dist/services/solid-ts-morph.service.js.map +1 -1
- package/dist/services/view-metadata.service.d.ts +6 -0
- package/dist/services/view-metadata.service.d.ts.map +1 -1
- package/dist/services/view-metadata.service.js +66 -1
- package/dist/services/view-metadata.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +16 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist-tests/api/authenticate.spec.js +119 -0
- package/dist-tests/api/authenticate.spec.js.map +1 -0
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +97 -0
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +1 -0
- package/dist-tests/api/ping.spec.js +21 -0
- package/dist-tests/api/ping.spec.js.map +1 -0
- package/dist-tests/helpers/auth.js +41 -0
- package/dist-tests/helpers/auth.js.map +1 -0
- package/dist-tests/helpers/env.js +11 -0
- package/dist-tests/helpers/env.js.map +1 -0
- package/docs/agent-hub-grooming.md +301 -0
- package/docs/dashboards/AGENTIC_DASHBOARD_IMPLEMENTATION_PLAN.md +438 -0
- package/docs/dashboards/dashboard-curl-smoke-tests.txt +146 -0
- package/docs/dashboards/delete-legacy-dashboard-metadata.sql +172 -0
- package/docs/datasource-introspection-ddl-analysis.md +326 -0
- package/docs/datasource-introspection-implementation-plan.md +306 -0
- package/docs/grouping-enhancements.md +89 -0
- package/docs/java-spring/README.md +3 -0
- package/docs/java-spring/solid-core-module-deep-dive-report.md +1317 -0
- package/docs/module-package-import-handoff.md +691 -0
- package/docs/seed-changes.md +65 -0
- package/docs/test-data-workflow.md +200 -0
- package/docs/type-declaration-import-issue.md +24 -0
- package/package.json +1 -1
- package/src/controllers/datasource-introspection.controller.ts +58 -0
- package/src/controllers/datasource-management.controller.ts +24 -0
- package/src/dtos/create-datasource-management.dto.ts +139 -0
- package/src/dtos/datasource-introspection-mapping.dto.ts +182 -0
- package/src/index.ts +5 -0
- package/src/interfaces/datasource-introspection-provider.interface.ts +28 -0
- package/src/repository/scheduled-job.repository.ts +73 -0
- package/src/seeders/module-metadata-seeder.service.ts +1223 -262
- package/src/seeders/seed-data/solid-core-metadata.json +22 -1
- package/src/services/action-metadata.service.ts +87 -2
- package/src/services/authentication.service.ts +1 -1
- package/src/services/dashboard-providers/mq-dashboard-latency-trend-provider.service.ts +9 -4
- package/src/services/dashboard-providers/mq-dashboard-messages-over-time-provider.service.ts +9 -4
- package/src/services/dashboard-providers/mq-dashboard-provider-utils.ts +82 -0
- package/src/services/dashboard-providers/mq-dashboard-queue-sla-heatmap-provider.service.ts +10 -4
- package/src/services/datasource-introspection/mssql-datasource-introspection-provider.service.ts +185 -0
- package/src/services/datasource-introspection/mysql-datasource-introspection-provider.service.ts +95 -0
- package/src/services/datasource-introspection/postgres-datasource-introspection-provider.service.ts +160 -0
- package/src/services/datasource-introspection.service.ts +1536 -0
- package/src/services/datasource-management.service.ts +566 -0
- package/src/services/field-metadata.service.ts +21 -1
- package/src/services/model-metadata.service.ts +50 -13
- package/src/services/role-metadata.service.ts +100 -47
- package/src/services/selection-providers/mq-dashboard-message-broker-variable-options-provider.service.ts +2 -4
- package/src/services/selection-providers/mq-dashboard-queue-name-variable-options-provider.service.ts +2 -4
- package/src/services/setting.service.ts +43 -12
- package/src/services/solid-ts-morph.service.ts +2 -2
- package/src/services/view-metadata.service.ts +79 -1
- package/src/solid-core.module.ts +16 -0
- package/.claude/settings.local.json +0 -15
- package/src/services/1.js +0 -6
|
@@ -5381,6 +5381,19 @@
|
|
|
5381
5381
|
"moduleUserKey": "solid-core",
|
|
5382
5382
|
"modelUserKey": "fieldMetadata"
|
|
5383
5383
|
},
|
|
5384
|
+
{
|
|
5385
|
+
"displayName": "Datasource Management",
|
|
5386
|
+
"name": "datasource-management-action",
|
|
5387
|
+
"type": "custom",
|
|
5388
|
+
"domain": "",
|
|
5389
|
+
"context": "",
|
|
5390
|
+
"customComponent": "/admin/core/solid-core/datasources",
|
|
5391
|
+
"customIsModal": true,
|
|
5392
|
+
"serverEndpoint": "",
|
|
5393
|
+
"viewUserKey": "",
|
|
5394
|
+
"moduleUserKey": "solid-core",
|
|
5395
|
+
"modelUserKey": "moduleMetadata"
|
|
5396
|
+
},
|
|
5384
5397
|
{
|
|
5385
5398
|
"displayName": "Field Metadata Tree Action",
|
|
5386
5399
|
"name": "fieldMetadata-tree-action",
|
|
@@ -6105,6 +6118,14 @@
|
|
|
6105
6118
|
"moduleUserKey": "solid-core",
|
|
6106
6119
|
"parentMenuItemUserKey": "appBuilder-menu-item"
|
|
6107
6120
|
},
|
|
6121
|
+
{
|
|
6122
|
+
"displayName": "Datasources",
|
|
6123
|
+
"name": "datasource-management-menu-item",
|
|
6124
|
+
"sequenceNumber": 4,
|
|
6125
|
+
"actionUserKey": "datasource-management-action",
|
|
6126
|
+
"moduleUserKey": "solid-core",
|
|
6127
|
+
"parentMenuItemUserKey": "appBuilder-menu-item"
|
|
6128
|
+
},
|
|
6108
6129
|
{
|
|
6109
6130
|
"displayName": "Menu Item",
|
|
6110
6131
|
"name": "menuItemMetadata-menu-item",
|
|
@@ -14737,4 +14758,4 @@
|
|
|
14737
14758
|
}
|
|
14738
14759
|
],
|
|
14739
14760
|
"savedFilters": []
|
|
14740
|
-
}
|
|
14761
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Injectable } from '@nestjs/common';
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
2
|
import { ModuleRef } from "@nestjs/core";
|
|
3
3
|
import { InjectEntityManager } from '@nestjs/typeorm';
|
|
4
4
|
import { CRUDService } from 'src/services/crud.service';
|
|
@@ -10,6 +10,79 @@ import { ActionMetadata } from '../entities/action-metadata.entity';
|
|
|
10
10
|
|
|
11
11
|
@Injectable()
|
|
12
12
|
export class ActionMetadataService extends CRUDService<ActionMetadata> {
|
|
13
|
+
private readonly logger = new Logger(ActionMetadataService.name);
|
|
14
|
+
private readonly comparableFields = [
|
|
15
|
+
'name',
|
|
16
|
+
'displayName',
|
|
17
|
+
'type',
|
|
18
|
+
'domain',
|
|
19
|
+
'context',
|
|
20
|
+
'customComponent',
|
|
21
|
+
'customIsModal',
|
|
22
|
+
'serverEndpoint',
|
|
23
|
+
'module',
|
|
24
|
+
'model',
|
|
25
|
+
'view',
|
|
26
|
+
] as const;
|
|
27
|
+
private readonly relationCompareFields = new Set(['module', 'model', 'view']);
|
|
28
|
+
private readonly jsonCompareFields = new Set(['domain', 'context']);
|
|
29
|
+
|
|
30
|
+
private normalizeJsonFieldValue(value: any): any {
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
const trimmedValue = value.trim();
|
|
33
|
+
if (
|
|
34
|
+
(trimmedValue.startsWith('{') && trimmedValue.endsWith('}')) ||
|
|
35
|
+
(trimmedValue.startsWith('[') && trimmedValue.endsWith(']'))
|
|
36
|
+
) {
|
|
37
|
+
try {
|
|
38
|
+
return this.normalizeJsonFieldValue(JSON.parse(trimmedValue));
|
|
39
|
+
} catch {
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
return value.map((item) => this.normalizeJsonFieldValue(item));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (value && typeof value === 'object') {
|
|
51
|
+
return Object.keys(value)
|
|
52
|
+
.sort()
|
|
53
|
+
.reduce((result, key) => {
|
|
54
|
+
result[key] = this.normalizeJsonFieldValue(value[key]);
|
|
55
|
+
return result;
|
|
56
|
+
}, {} as Record<string, any>);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private getCanonicalJsonFieldString(value: any): string {
|
|
63
|
+
return JSON.stringify(this.normalizeJsonFieldValue(value) ?? null);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private hasChanges(existingSolidAction: ActionMetadata, updateSolidActionDto: any): boolean {
|
|
67
|
+
return this.comparableFields.some((key) => {
|
|
68
|
+
const value = updateSolidActionDto[key];
|
|
69
|
+
if (typeof value === 'undefined') {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (this.relationCompareFields.has(key)) {
|
|
74
|
+
const relationValue = value as any;
|
|
75
|
+
return (existingSolidAction as any)[key]?.id !== relationValue?.id;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (this.jsonCompareFields.has(key)) {
|
|
79
|
+
return this.getCanonicalJsonFieldString((existingSolidAction as any)[key]) !== this.getCanonicalJsonFieldString(value);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (existingSolidAction as any)[key] !== value;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
13
86
|
constructor(
|
|
14
87
|
@InjectEntityManager()
|
|
15
88
|
readonly entityManager: EntityManager,
|
|
@@ -37,11 +110,23 @@ export class ActionMetadataService extends CRUDService<ActionMetadata> {
|
|
|
37
110
|
const existingSolidAction = await this.repo.findOne({
|
|
38
111
|
where: {
|
|
39
112
|
name: updateSolidActionDto.name
|
|
40
|
-
}
|
|
113
|
+
},
|
|
114
|
+
relations: {
|
|
115
|
+
module: true,
|
|
116
|
+
model: true,
|
|
117
|
+
view: true,
|
|
118
|
+
},
|
|
41
119
|
})
|
|
42
120
|
|
|
43
121
|
// if found
|
|
44
122
|
if (existingSolidAction) {
|
|
123
|
+
const hasChanges = this.hasChanges(existingSolidAction, updateSolidActionDto);
|
|
124
|
+
|
|
125
|
+
if (!hasChanges) {
|
|
126
|
+
this.logger.debug(`Skipping action upsert for ${updateSolidActionDto.name}; no changes detected.`);
|
|
127
|
+
return existingSolidAction;
|
|
128
|
+
}
|
|
129
|
+
|
|
45
130
|
const updatedSolidActionDto = { ...existingSolidAction, ...updateSolidActionDto };
|
|
46
131
|
return this.repo.save(updatedSolidActionDto);
|
|
47
132
|
}
|
|
@@ -309,7 +309,7 @@ export class AuthenticationService {
|
|
|
309
309
|
if (signUpDto.roles && signUpDto.roles.length > 0) {
|
|
310
310
|
for (let i = 0; i < signUpDto.roles.length; i++) {
|
|
311
311
|
const roleName = signUpDto.roles[i];
|
|
312
|
-
await this.roleMetadataService.
|
|
312
|
+
await this.roleMetadataService.assertRoleExistsByName(roleName);
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
user.username = signUpDto.username;
|
|
@@ -2,7 +2,13 @@ import { Injectable } from "@nestjs/common";
|
|
|
2
2
|
import { DashboardWidgetDataProvider } from "src/decorators/dashboard-widget-data-provider.decorator";
|
|
3
3
|
import { MqMessageRepository } from "src/repository/mq-message.repository";
|
|
4
4
|
import { IDashboardWidgetDataProvider, IDashboardWidgetDataProviderContext, IDashboardWidgetDataResponseEnvelope } from "src/interfaces";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
applyMqDashboardFilters,
|
|
7
|
+
buildMqDashboardBucketExpression,
|
|
8
|
+
normalizeBucket,
|
|
9
|
+
normalizeMqDashboardBucketValue,
|
|
10
|
+
toNumber,
|
|
11
|
+
} from "src/services/dashboard-providers/mq-dashboard-provider-utils";
|
|
6
12
|
|
|
7
13
|
@DashboardWidgetDataProvider()
|
|
8
14
|
@Injectable()
|
|
@@ -29,7 +35,7 @@ export class MqDashboardLatencyTrendProvider implements IDashboardWidgetDataProv
|
|
|
29
35
|
applyMqDashboardFilters(qb, ctxt.variables ?? {});
|
|
30
36
|
qb.andWhere("mqMessage.elapsedMillis IS NOT NULL");
|
|
31
37
|
|
|
32
|
-
const bucketExpr =
|
|
38
|
+
const bucketExpr = buildMqDashboardBucketExpression(qb, bucket, "mqMessage.createdAt");
|
|
33
39
|
const rows = await qb
|
|
34
40
|
.select(bucketExpr, "bucket")
|
|
35
41
|
.addSelect("AVG(mqMessage.elapsedMillis)", "value")
|
|
@@ -37,7 +43,7 @@ export class MqDashboardLatencyTrendProvider implements IDashboardWidgetDataProv
|
|
|
37
43
|
.orderBy("bucket", "ASC")
|
|
38
44
|
.getRawMany<{ bucket: string; value: string | number }>();
|
|
39
45
|
|
|
40
|
-
const categories = rows.map((r) =>
|
|
46
|
+
const categories = rows.map((r) => normalizeMqDashboardBucketValue(r.bucket));
|
|
41
47
|
const values = rows.map((r) => Number(toNumber(r.value).toFixed(2)));
|
|
42
48
|
|
|
43
49
|
return {
|
|
@@ -55,4 +61,3 @@ export class MqDashboardLatencyTrendProvider implements IDashboardWidgetDataProv
|
|
|
55
61
|
};
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
|
-
|
package/src/services/dashboard-providers/mq-dashboard-messages-over-time-provider.service.ts
CHANGED
|
@@ -2,7 +2,13 @@ import { Injectable } from "@nestjs/common";
|
|
|
2
2
|
import { DashboardWidgetDataProvider } from "src/decorators/dashboard-widget-data-provider.decorator";
|
|
3
3
|
import { MqMessageRepository } from "src/repository/mq-message.repository";
|
|
4
4
|
import { IDashboardWidgetDataProvider, IDashboardWidgetDataProviderContext, IDashboardWidgetDataResponseEnvelope } from "src/interfaces";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
applyMqDashboardFilters,
|
|
7
|
+
buildMqDashboardBucketExpression,
|
|
8
|
+
normalizeBucket,
|
|
9
|
+
normalizeMqDashboardBucketValue,
|
|
10
|
+
toNumber,
|
|
11
|
+
} from "src/services/dashboard-providers/mq-dashboard-provider-utils";
|
|
6
12
|
|
|
7
13
|
@DashboardWidgetDataProvider()
|
|
8
14
|
@Injectable()
|
|
@@ -31,7 +37,7 @@ export class MqDashboardMessagesOverTimeProvider implements IDashboardWidgetData
|
|
|
31
37
|
const qb = await this.mqMessageRepository.createSecurityRuleAwareQueryBuilder("mqMessage");
|
|
32
38
|
applyMqDashboardFilters(qb, ctxt.variables ?? {});
|
|
33
39
|
|
|
34
|
-
const bucketExpr =
|
|
40
|
+
const bucketExpr = buildMqDashboardBucketExpression(qb, bucket, "mqMessage.createdAt");
|
|
35
41
|
const rows = await qb
|
|
36
42
|
.select(bucketExpr, "bucket")
|
|
37
43
|
.addSelect("mqMessage.stage", "stage")
|
|
@@ -43,7 +49,7 @@ export class MqDashboardMessagesOverTimeProvider implements IDashboardWidgetData
|
|
|
43
49
|
|
|
44
50
|
const bucketToStageMap = new Map<string, Record<string, number>>();
|
|
45
51
|
for (const row of rows) {
|
|
46
|
-
const key =
|
|
52
|
+
const key = normalizeMqDashboardBucketValue(row.bucket);
|
|
47
53
|
const entry = bucketToStageMap.get(key) ?? {};
|
|
48
54
|
entry[row.stage] = (entry[row.stage] ?? 0) + toNumber(row.count, 0);
|
|
49
55
|
bucketToStageMap.set(key, entry);
|
|
@@ -76,4 +82,3 @@ export class MqDashboardMessagesOverTimeProvider implements IDashboardWidgetData
|
|
|
76
82
|
};
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
|
-
|
|
@@ -54,6 +54,59 @@ export function toNumber(value: any, fallback = 0): number {
|
|
|
54
54
|
return Number.isFinite(n) ? n : fallback;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
export function buildMqDashboardBucketExpression(
|
|
58
|
+
qb: SelectQueryBuilder<MqMessage>,
|
|
59
|
+
bucket: 'hour' | 'day' | 'week' | 'month',
|
|
60
|
+
columnExpr: string,
|
|
61
|
+
): string {
|
|
62
|
+
const driver = `${qb.connection.options.type ?? ''}`.toLowerCase();
|
|
63
|
+
|
|
64
|
+
switch (driver) {
|
|
65
|
+
case 'postgres':
|
|
66
|
+
case 'cockroachdb':
|
|
67
|
+
return `DATE_TRUNC('${bucket}', ${columnExpr})`;
|
|
68
|
+
case 'mysql':
|
|
69
|
+
case 'mariadb':
|
|
70
|
+
switch (bucket) {
|
|
71
|
+
case 'hour':
|
|
72
|
+
return `DATE_FORMAT(${columnExpr}, '%Y-%m-%d %H:00:00')`;
|
|
73
|
+
case 'day':
|
|
74
|
+
return `DATE_FORMAT(${columnExpr}, '%Y-%m-%d 00:00:00')`;
|
|
75
|
+
case 'week':
|
|
76
|
+
return `STR_TO_DATE(DATE_FORMAT(${columnExpr}, '%x-%v-1'), '%x-%v-%w')`;
|
|
77
|
+
case 'month':
|
|
78
|
+
return `DATE_FORMAT(${columnExpr}, '%Y-%m-01 00:00:00')`;
|
|
79
|
+
default:
|
|
80
|
+
throw new Error(`Unsupported bucket ${bucket} for driver ${driver}`);
|
|
81
|
+
}
|
|
82
|
+
case 'mssql':
|
|
83
|
+
case 'sqlserver':
|
|
84
|
+
switch (bucket) {
|
|
85
|
+
case 'hour':
|
|
86
|
+
return `DATEADD(hour, DATEDIFF(hour, 0, ${columnExpr}), 0)`;
|
|
87
|
+
case 'day':
|
|
88
|
+
return `DATEADD(day, DATEDIFF(day, 0, ${columnExpr}), 0)`;
|
|
89
|
+
case 'week':
|
|
90
|
+
return `DATEADD(week, DATEDIFF(week, 0, ${columnExpr}), 0)`;
|
|
91
|
+
case 'month':
|
|
92
|
+
return `DATEFROMPARTS(YEAR(${columnExpr}), MONTH(${columnExpr}), 1)`;
|
|
93
|
+
default:
|
|
94
|
+
throw new Error(`Unsupported bucket ${bucket} for driver ${driver}`);
|
|
95
|
+
}
|
|
96
|
+
default:
|
|
97
|
+
throw new Error(`Unsupported driver ${driver} for mq dashboard bucket expressions.`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function normalizeMqDashboardBucketValue(value: any): string {
|
|
102
|
+
if (!value) return '';
|
|
103
|
+
const parsed = value instanceof Date ? value : new Date(value);
|
|
104
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
105
|
+
return `${value}`;
|
|
106
|
+
}
|
|
107
|
+
return parsed.toISOString();
|
|
108
|
+
}
|
|
109
|
+
|
|
57
110
|
|
|
58
111
|
function applyDateFilter(qb: SelectQueryBuilder<MqMessage>, dateVariable: any): void {
|
|
59
112
|
const range = resolveDateRange(dateVariable);
|
|
@@ -88,6 +141,35 @@ function applyStringOrArrayFilter(
|
|
|
88
141
|
qb.andWhere(`${qualifiedColumn} IN (:...${paramKey})`, { [paramKey]: values });
|
|
89
142
|
}
|
|
90
143
|
|
|
144
|
+
export function applyCaseInsensitiveContainsFilter<T>(
|
|
145
|
+
qb: SelectQueryBuilder<T>,
|
|
146
|
+
qualifiedColumn: string,
|
|
147
|
+
query: string | undefined,
|
|
148
|
+
paramKey: string,
|
|
149
|
+
) {
|
|
150
|
+
if (!query) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const driver = `${qb.connection.options.type ?? ''}`.toLowerCase();
|
|
155
|
+
const value = `%${query}%`;
|
|
156
|
+
|
|
157
|
+
switch (driver) {
|
|
158
|
+
case 'postgres':
|
|
159
|
+
case 'cockroachdb':
|
|
160
|
+
qb.andWhere(`${qualifiedColumn} ILIKE :${paramKey}`, { [paramKey]: value });
|
|
161
|
+
return;
|
|
162
|
+
case 'mysql':
|
|
163
|
+
case 'mariadb':
|
|
164
|
+
case 'mssql':
|
|
165
|
+
case 'sqlserver':
|
|
166
|
+
qb.andWhere(`LOWER(${qualifiedColumn}) LIKE LOWER(:${paramKey})`, { [paramKey]: value });
|
|
167
|
+
return;
|
|
168
|
+
default:
|
|
169
|
+
qb.andWhere(`${qualifiedColumn} LIKE :${paramKey}`, { [paramKey]: value });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
91
173
|
function resolveDateRange(dateVariable: any): { from?: Date; to?: Date } {
|
|
92
174
|
if (!dateVariable) return {};
|
|
93
175
|
|
|
@@ -2,7 +2,13 @@ import { Injectable } from "@nestjs/common";
|
|
|
2
2
|
import { DashboardWidgetDataProvider } from "src/decorators/dashboard-widget-data-provider.decorator";
|
|
3
3
|
import { IDashboardWidgetDataProvider, IDashboardWidgetDataProviderContext, IDashboardWidgetDataResponseEnvelope } from "src/interfaces";
|
|
4
4
|
import { MqMessageRepository } from "src/repository/mq-message.repository";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
applyMqDashboardFilters,
|
|
7
|
+
buildMqDashboardBucketExpression,
|
|
8
|
+
normalizeBucket,
|
|
9
|
+
normalizeMqDashboardBucketValue,
|
|
10
|
+
toNumber,
|
|
11
|
+
} from "src/services/dashboard-providers/mq-dashboard-provider-utils";
|
|
6
12
|
|
|
7
13
|
type HeatmapLegendThreshold = {
|
|
8
14
|
label: string;
|
|
@@ -71,7 +77,7 @@ export class MqDashboardQueueSlaHeatmapProvider implements IDashboardWidgetDataP
|
|
|
71
77
|
applyMqDashboardFilters(qb, ctxt.variables ?? {}, { ensureQueueJoin: true });
|
|
72
78
|
qb.andWhere("mqMessage.elapsedMillis IS NOT NULL");
|
|
73
79
|
|
|
74
|
-
const bucketExpr =
|
|
80
|
+
const bucketExpr = buildMqDashboardBucketExpression(qb, bucket, "mqMessage.createdAt");
|
|
75
81
|
const rows = await qb
|
|
76
82
|
.select(bucketExpr, "bucket")
|
|
77
83
|
.addSelect("mqMessageQueue.name", "queueName")
|
|
@@ -93,7 +99,7 @@ export class MqDashboardQueueSlaHeatmapProvider implements IDashboardWidgetDataP
|
|
|
93
99
|
const xCategories = Array.from(
|
|
94
100
|
new Set(
|
|
95
101
|
rows
|
|
96
|
-
.map((row) => row?.bucket
|
|
102
|
+
.map((row) => normalizeMqDashboardBucketValue(row?.bucket))
|
|
97
103
|
.filter((bucketValue) => !!bucketValue)
|
|
98
104
|
)
|
|
99
105
|
).sort();
|
|
@@ -108,7 +114,7 @@ export class MqDashboardQueueSlaHeatmapProvider implements IDashboardWidgetDataP
|
|
|
108
114
|
const pointDetails: HeatmapPointDetail[] = [];
|
|
109
115
|
|
|
110
116
|
for (const row of rows) {
|
|
111
|
-
const bucketValue = row?.bucket
|
|
117
|
+
const bucketValue = normalizeMqDashboardBucketValue(row?.bucket);
|
|
112
118
|
const queueName = `${row?.queueName ?? ""}`.trim();
|
|
113
119
|
const xIndex = xIndexMap.get(bucketValue);
|
|
114
120
|
const yIndex = yIndexMap.get(queueName);
|
package/src/services/datasource-introspection/mssql-datasource-introspection-provider.service.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { Injectable } from "@nestjs/common";
|
|
2
|
+
import { DataSource } from "typeorm";
|
|
3
|
+
import {
|
|
4
|
+
DatasourceIntrospectionColumn,
|
|
5
|
+
DatasourceIntrospectionProvider,
|
|
6
|
+
DatasourceIntrospectionTableRef,
|
|
7
|
+
} from "src/interfaces/datasource-introspection-provider.interface";
|
|
8
|
+
|
|
9
|
+
@Injectable()
|
|
10
|
+
export class MssqlDatasourceIntrospectionProviderService implements DatasourceIntrospectionProvider {
|
|
11
|
+
readonly type = "mssql";
|
|
12
|
+
|
|
13
|
+
supports(type: string) {
|
|
14
|
+
return type?.toLowerCase() === this.type;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async listTables(dataSource: DataSource): Promise<DatasourceIntrospectionTableRef[]> {
|
|
18
|
+
const rows = await dataSource.query(`
|
|
19
|
+
SELECT
|
|
20
|
+
TABLE_SCHEMA AS tableSchema,
|
|
21
|
+
TABLE_NAME AS tableName
|
|
22
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
23
|
+
WHERE TABLE_TYPE = 'BASE TABLE'
|
|
24
|
+
AND TABLE_SCHEMA NOT IN ('sys', 'INFORMATION_SCHEMA')
|
|
25
|
+
ORDER BY TABLE_SCHEMA, TABLE_NAME
|
|
26
|
+
`);
|
|
27
|
+
|
|
28
|
+
return rows.map((row: any) => ({
|
|
29
|
+
tableSchema: row.tableSchema ?? null,
|
|
30
|
+
tableName: row.tableName,
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async listColumns(dataSource: DataSource): Promise<DatasourceIntrospectionColumn[]> {
|
|
35
|
+
const rows = await dataSource.query(`
|
|
36
|
+
SELECT
|
|
37
|
+
schema_name(t.schema_id) AS tableSchema,
|
|
38
|
+
t.name AS tableName,
|
|
39
|
+
c.name AS columnName,
|
|
40
|
+
ty.name AS dataType,
|
|
41
|
+
c.is_identity AS isAutoGenerated,
|
|
42
|
+
CASE WHEN c.is_nullable = 1 THEN 'YES' ELSE 'NO' END AS isNullable,
|
|
43
|
+
dc.definition AS defaultValue,
|
|
44
|
+
CASE
|
|
45
|
+
WHEN ty.name IN ('nchar', 'nvarchar') AND c.max_length > 0 THEN c.max_length / 2
|
|
46
|
+
ELSE c.max_length
|
|
47
|
+
END AS characterMaximumLength,
|
|
48
|
+
c.precision AS numericPrecision,
|
|
49
|
+
c.scale AS numericScale,
|
|
50
|
+
c.column_id AS ordinalPosition
|
|
51
|
+
FROM sys.tables t
|
|
52
|
+
INNER JOIN sys.columns c ON c.object_id = t.object_id
|
|
53
|
+
INNER JOIN sys.types ty ON ty.user_type_id = c.user_type_id
|
|
54
|
+
LEFT JOIN sys.default_constraints dc ON dc.object_id = c.default_object_id
|
|
55
|
+
WHERE schema_name(t.schema_id) NOT IN ('sys', 'INFORMATION_SCHEMA')
|
|
56
|
+
ORDER BY schema_name(t.schema_id), t.name, c.column_id
|
|
57
|
+
`);
|
|
58
|
+
|
|
59
|
+
return rows.map((row: any) => ({
|
|
60
|
+
tableSchema: row.tableSchema ?? null,
|
|
61
|
+
tableName: row.tableName,
|
|
62
|
+
columnName: row.columnName,
|
|
63
|
+
dataType: row.dataType,
|
|
64
|
+
isAutoGenerated: Boolean(row.isAutoGenerated),
|
|
65
|
+
isNullable: String(row.isNullable).toUpperCase() === "YES",
|
|
66
|
+
defaultValue: row.defaultValue ?? null,
|
|
67
|
+
characterMaximumLength: this.toNumberOrNull(row.characterMaximumLength),
|
|
68
|
+
numericPrecision: this.toNumberOrNull(row.numericPrecision),
|
|
69
|
+
numericScale: this.toNumberOrNull(row.numericScale),
|
|
70
|
+
ordinalPosition: this.toNumberOrNull(row.ordinalPosition),
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async getTableDefinition(dataSource: DataSource, table: DatasourceIntrospectionTableRef): Promise<string | null> {
|
|
75
|
+
const rows = await dataSource.query(
|
|
76
|
+
`
|
|
77
|
+
DECLARE @schemaName NVARCHAR(128) = @0;
|
|
78
|
+
DECLARE @tableName NVARCHAR(128) = @1;
|
|
79
|
+
|
|
80
|
+
WITH target_table AS (
|
|
81
|
+
SELECT t.object_id, s.name AS schema_name, t.name AS table_name
|
|
82
|
+
FROM sys.tables t
|
|
83
|
+
INNER JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
84
|
+
WHERE t.name = @tableName
|
|
85
|
+
AND (@schemaName IS NULL OR s.name = @schemaName)
|
|
86
|
+
),
|
|
87
|
+
column_lines AS (
|
|
88
|
+
SELECT
|
|
89
|
+
c.column_id,
|
|
90
|
+
' [' + c.name + '] '
|
|
91
|
+
+ UPPER(ty.name)
|
|
92
|
+
+ CASE
|
|
93
|
+
WHEN ty.name IN ('varchar', 'char', 'varbinary', 'binary') THEN
|
|
94
|
+
'(' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST(c.max_length AS VARCHAR(16)) END + ')'
|
|
95
|
+
WHEN ty.name IN ('nvarchar', 'nchar') THEN
|
|
96
|
+
'(' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST(c.max_length / 2 AS VARCHAR(16)) END + ')'
|
|
97
|
+
WHEN ty.name IN ('decimal', 'numeric') THEN
|
|
98
|
+
'(' + CAST(c.precision AS VARCHAR(16)) + ',' + CAST(c.scale AS VARCHAR(16)) + ')'
|
|
99
|
+
WHEN ty.name IN ('datetime2', 'datetimeoffset', 'time') THEN
|
|
100
|
+
'(' + CAST(c.scale AS VARCHAR(16)) + ')'
|
|
101
|
+
ELSE
|
|
102
|
+
''
|
|
103
|
+
END
|
|
104
|
+
+ CASE
|
|
105
|
+
WHEN ic.object_id IS NOT NULL THEN
|
|
106
|
+
' IDENTITY(' + CAST(CONVERT(BIGINT, ic.seed_value) AS VARCHAR(32)) + ',' + CAST(CONVERT(BIGINT, ic.increment_value) AS VARCHAR(32)) + ')'
|
|
107
|
+
ELSE
|
|
108
|
+
''
|
|
109
|
+
END
|
|
110
|
+
+ CASE
|
|
111
|
+
WHEN dc.definition IS NOT NULL THEN ' DEFAULT ' + dc.definition
|
|
112
|
+
ELSE ''
|
|
113
|
+
END
|
|
114
|
+
+ CASE
|
|
115
|
+
WHEN c.is_nullable = 1 THEN ' NULL'
|
|
116
|
+
ELSE ' NOT NULL'
|
|
117
|
+
END AS line
|
|
118
|
+
FROM target_table tt
|
|
119
|
+
INNER JOIN sys.columns c ON c.object_id = tt.object_id
|
|
120
|
+
INNER JOIN sys.types ty ON ty.user_type_id = c.user_type_id
|
|
121
|
+
LEFT JOIN sys.identity_columns ic
|
|
122
|
+
ON ic.object_id = c.object_id
|
|
123
|
+
AND ic.column_id = c.column_id
|
|
124
|
+
LEFT JOIN sys.default_constraints dc ON dc.object_id = c.default_object_id
|
|
125
|
+
),
|
|
126
|
+
constraint_lines AS (
|
|
127
|
+
SELECT
|
|
128
|
+
kc.name AS constraint_name,
|
|
129
|
+
' CONSTRAINT [' + kc.name + '] PRIMARY KEY ('
|
|
130
|
+
+ STUFF((
|
|
131
|
+
SELECT ', [' + c.name + ']'
|
|
132
|
+
FROM sys.index_columns ic
|
|
133
|
+
INNER JOIN sys.columns c
|
|
134
|
+
ON c.object_id = ic.object_id
|
|
135
|
+
AND c.column_id = ic.column_id
|
|
136
|
+
WHERE ic.object_id = kc.parent_object_id
|
|
137
|
+
AND ic.index_id = kc.unique_index_id
|
|
138
|
+
ORDER BY ic.key_ordinal
|
|
139
|
+
FOR XML PATH(''), TYPE
|
|
140
|
+
).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
|
|
141
|
+
+ ')' AS line
|
|
142
|
+
FROM target_table tt
|
|
143
|
+
INNER JOIN sys.key_constraints kc ON kc.parent_object_id = tt.object_id
|
|
144
|
+
WHERE kc.type = 'PK'
|
|
145
|
+
)
|
|
146
|
+
SELECT
|
|
147
|
+
'CREATE TABLE [' + tt.schema_name + '].[' + tt.table_name + '] (' + CHAR(10)
|
|
148
|
+
+ ISNULL((
|
|
149
|
+
SELECT STUFF((
|
|
150
|
+
SELECT ',' + CHAR(10) + cl.line
|
|
151
|
+
FROM column_lines cl
|
|
152
|
+
ORDER BY cl.column_id
|
|
153
|
+
FOR XML PATH(''), TYPE
|
|
154
|
+
).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
|
|
155
|
+
), '')
|
|
156
|
+
+ ISNULL((
|
|
157
|
+
SELECT CASE
|
|
158
|
+
WHEN EXISTS (SELECT 1 FROM constraint_lines)
|
|
159
|
+
THEN ',' + CHAR(10) + STUFF((
|
|
160
|
+
SELECT ',' + CHAR(10) + cn.line
|
|
161
|
+
FROM constraint_lines cn
|
|
162
|
+
ORDER BY cn.constraint_name
|
|
163
|
+
FOR XML PATH(''), TYPE
|
|
164
|
+
).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
|
|
165
|
+
ELSE ''
|
|
166
|
+
END
|
|
167
|
+
), '')
|
|
168
|
+
+ CHAR(10) + ');' AS ddl
|
|
169
|
+
FROM target_table tt
|
|
170
|
+
`,
|
|
171
|
+
[table.tableSchema, table.tableName],
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return rows?.[0]?.ddl ?? null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private toNumberOrNull(value: unknown) {
|
|
178
|
+
if (value === null || value === undefined || value === "") {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const parsed = Number(value);
|
|
183
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
184
|
+
}
|
|
185
|
+
}
|
package/src/services/datasource-introspection/mysql-datasource-introspection-provider.service.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Injectable } from "@nestjs/common";
|
|
2
|
+
import { DataSource } from "typeorm";
|
|
3
|
+
import {
|
|
4
|
+
DatasourceIntrospectionColumn,
|
|
5
|
+
DatasourceIntrospectionProvider,
|
|
6
|
+
DatasourceIntrospectionTableRef,
|
|
7
|
+
} from "src/interfaces/datasource-introspection-provider.interface";
|
|
8
|
+
|
|
9
|
+
@Injectable()
|
|
10
|
+
export class MysqlDatasourceIntrospectionProviderService implements DatasourceIntrospectionProvider {
|
|
11
|
+
readonly type = "mysql";
|
|
12
|
+
|
|
13
|
+
supports(type: string) {
|
|
14
|
+
return type?.toLowerCase() === this.type;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async listTables(dataSource: DataSource): Promise<DatasourceIntrospectionTableRef[]> {
|
|
18
|
+
const rows = await dataSource.query(`
|
|
19
|
+
SELECT
|
|
20
|
+
TABLE_SCHEMA AS tableSchema,
|
|
21
|
+
TABLE_NAME AS tableName
|
|
22
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
23
|
+
WHERE TABLE_TYPE = 'BASE TABLE'
|
|
24
|
+
AND TABLE_SCHEMA = DATABASE()
|
|
25
|
+
ORDER BY TABLE_SCHEMA, TABLE_NAME
|
|
26
|
+
`);
|
|
27
|
+
|
|
28
|
+
return rows.map((row: any) => ({
|
|
29
|
+
tableSchema: row.tableSchema ?? null,
|
|
30
|
+
tableName: row.tableName,
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async listColumns(dataSource: DataSource): Promise<DatasourceIntrospectionColumn[]> {
|
|
35
|
+
const rows = await dataSource.query(`
|
|
36
|
+
SELECT
|
|
37
|
+
TABLE_SCHEMA AS tableSchema,
|
|
38
|
+
TABLE_NAME AS tableName,
|
|
39
|
+
COLUMN_NAME AS columnName,
|
|
40
|
+
DATA_TYPE AS dataType,
|
|
41
|
+
EXTRA AS extra,
|
|
42
|
+
IS_NULLABLE AS isNullable,
|
|
43
|
+
COLUMN_DEFAULT AS defaultValue,
|
|
44
|
+
CHARACTER_MAXIMUM_LENGTH AS characterMaximumLength,
|
|
45
|
+
NUMERIC_PRECISION AS numericPrecision,
|
|
46
|
+
NUMERIC_SCALE AS numericScale,
|
|
47
|
+
ORDINAL_POSITION AS ordinalPosition
|
|
48
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
49
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
|
50
|
+
ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION
|
|
51
|
+
`);
|
|
52
|
+
|
|
53
|
+
return rows.map((row: any) => ({
|
|
54
|
+
tableSchema: row.tableSchema ?? null,
|
|
55
|
+
tableName: row.tableName,
|
|
56
|
+
columnName: row.columnName,
|
|
57
|
+
dataType: row.dataType,
|
|
58
|
+
isAutoGenerated: String(row.extra ?? "").toLowerCase().includes("auto_increment"),
|
|
59
|
+
isNullable: String(row.isNullable).toUpperCase() === "YES",
|
|
60
|
+
defaultValue: row.defaultValue ?? null,
|
|
61
|
+
characterMaximumLength: this.toNumberOrNull(row.characterMaximumLength),
|
|
62
|
+
numericPrecision: this.toNumberOrNull(row.numericPrecision),
|
|
63
|
+
numericScale: this.toNumberOrNull(row.numericScale),
|
|
64
|
+
ordinalPosition: this.toNumberOrNull(row.ordinalPosition),
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async getTableDefinition(dataSource: DataSource, table: DatasourceIntrospectionTableRef): Promise<string | null> {
|
|
69
|
+
const schemaName = table.tableSchema ?? dataSource.options.database;
|
|
70
|
+
const rows = await dataSource.query(
|
|
71
|
+
`SHOW CREATE TABLE \`${schemaName}\`.\`${table.tableName}\``,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const firstRow = rows?.[0];
|
|
75
|
+
if (!firstRow || typeof firstRow !== "object") {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const ddlKey = Object.keys(firstRow).find((key) => key.toLowerCase().startsWith("create table"));
|
|
80
|
+
if (!ddlKey) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return String(firstRow[ddlKey] ?? "").trim() || null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private toNumberOrNull(value: unknown) {
|
|
88
|
+
if (value === null || value === undefined || value === "") {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const parsed = Number(value);
|
|
93
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
94
|
+
}
|
|
95
|
+
}
|