@dbml/connector 5.6.0-alpha.2 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateRawRefs = generateRawRefs;
4
+ const utils_1 = require("./utils");
5
+ async function generateRawRefs(client) {
6
+ const refs = [];
7
+ const refsListSql = `
8
+ SELECT
9
+ c.CONSTRAINT_NAME AS "fk_name",
10
+ c.TABLE_NAME AS "child_table",
11
+ LISTAGG(cc.COLUMN_NAME, '${utils_1.LIST_SEPARATOR}') WITHIN GROUP (ORDER BY cc.POSITION) AS "child_columns",
12
+ p.TABLE_NAME AS "parent_table",
13
+ LISTAGG(pc.COLUMN_NAME, '${utils_1.LIST_SEPARATOR}') WITHIN GROUP (ORDER BY pc.POSITION) AS "parent_columns",
14
+ c.DELETE_RULE AS "delete_rule"
15
+ FROM USER_CONSTRAINTS c
16
+ JOIN USER_CONSTRAINTS p
17
+ ON c.R_CONSTRAINT_NAME = p.CONSTRAINT_NAME
18
+ JOIN USER_CONS_COLUMNS cc
19
+ ON c.CONSTRAINT_NAME = cc.CONSTRAINT_NAME
20
+ JOIN USER_CONS_COLUMNS pc
21
+ ON p.CONSTRAINT_NAME = pc.CONSTRAINT_NAME
22
+ AND cc.POSITION = pc.POSITION
23
+ LEFT JOIN USER_OBJECTS obj_child
24
+ ON obj_child.OBJECT_NAME = c.TABLE_NAME
25
+ AND obj_child.OBJECT_TYPE = 'TABLE'
26
+ LEFT JOIN USER_OBJECTS obj_parent
27
+ ON obj_parent.OBJECT_NAME = p.TABLE_NAME
28
+ AND obj_parent.OBJECT_TYPE = 'TABLE'
29
+ WHERE c.CONSTRAINT_TYPE = 'R'
30
+ GROUP BY
31
+ c.CONSTRAINT_NAME,
32
+ c.TABLE_NAME,
33
+ p.TABLE_NAME,
34
+ c.DELETE_RULE,
35
+ obj_child.CREATED,
36
+ obj_child.OBJECT_ID,
37
+ obj_parent.CREATED,
38
+ obj_parent.OBJECT_ID
39
+ ORDER BY
40
+ obj_child.CREATED,
41
+ obj_child.OBJECT_ID,
42
+ c.CONSTRAINT_NAME
43
+ `;
44
+ const refsQueryResult = await client.execute(refsListSql, [], utils_1.EXECUTE_OPTIONS);
45
+ refsQueryResult.rows?.forEach((refRow) => {
46
+ const { fk_name, child_table, child_columns, parent_table, parent_columns, delete_rule } = refRow;
47
+ const ep1 = {
48
+ tableName: child_table,
49
+ schemaName: '',
50
+ fieldNames: child_columns.split(utils_1.LIST_SEPARATOR),
51
+ relation: '*',
52
+ };
53
+ const ep2 = {
54
+ tableName: parent_table,
55
+ schemaName: '',
56
+ fieldNames: parent_columns.split(utils_1.LIST_SEPARATOR),
57
+ relation: '1',
58
+ };
59
+ refs.push({
60
+ name: fk_name,
61
+ endpoints: [ep1, ep2],
62
+ onDelete: delete_rule === 'NO ACTION' ? null : delete_rule,
63
+ onUpdate: null,
64
+ });
65
+ });
66
+ return refs;
67
+ }
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateTablesAndFields = generateTablesAndFields;
4
+ exports.getFieldType = getFieldType;
5
+ const utils_1 = require("./utils");
6
+ async function generateTablesAndFields(client) {
7
+ const fields = {};
8
+ // Note: DATA_DEFAULT is LONG type which doesn't support SQL string operations
9
+ // The oracledb driver fetches LONG as string, so we detect default type in JavaScript
10
+ const tablesAndFieldsSql = `
11
+ SELECT
12
+ cols.TABLE_NAME AS "table_name",
13
+ cols.COLUMN_NAME AS "column_name",
14
+ cols.COLUMN_ID AS "column_id",
15
+ cols.DATA_TYPE AS "data_type",
16
+ cols.DATA_LENGTH AS "data_length",
17
+ cols.DATA_PRECISION AS "data_precision",
18
+ cols.DATA_SCALE AS "data_scale",
19
+ CASE WHEN cols.IDENTITY_COLUMN = 'YES' THEN 1 ELSE 0 END AS "identity_increment",
20
+ CASE WHEN cols.NULLABLE = 'Y' THEN 1 ELSE 0 END AS "is_nullable",
21
+ cols.DATA_DEFAULT AS "data_default",
22
+ tcoms.COMMENTS AS "table_comment",
23
+ ccoms.COMMENTS AS "column_comment",
24
+ obj.CREATED AS "table_created"
25
+ FROM USER_TAB_COLUMNS cols
26
+ LEFT JOIN USER_COL_COMMENTS ccoms
27
+ ON ccoms.COLUMN_NAME = cols.COLUMN_NAME
28
+ AND ccoms.TABLE_NAME = cols.TABLE_NAME
29
+ LEFT JOIN USER_TAB_COMMENTS tcoms
30
+ ON tcoms.TABLE_NAME = cols.TABLE_NAME
31
+ LEFT JOIN USER_OBJECTS obj
32
+ ON obj.OBJECT_NAME = cols.TABLE_NAME
33
+ AND obj.OBJECT_TYPE = 'TABLE'
34
+ ORDER BY obj.CREATED, obj.OBJECT_ID, cols.COLUMN_ID
35
+ `;
36
+ const tablesAndFieldsResult = await client.execute(tablesAndFieldsSql, [], utils_1.EXECUTE_OPTIONS);
37
+ const tableMap = tablesAndFieldsResult.rows?.reduce((acc, row) => {
38
+ const { table_name, table_comment } = row;
39
+ if (!acc[table_name]) {
40
+ acc[table_name] = {
41
+ name: table_name,
42
+ schemaName: '',
43
+ note: table_comment ? { value: table_comment } : { value: '' },
44
+ };
45
+ }
46
+ if (!fields[table_name])
47
+ fields[table_name] = [];
48
+ const field = generateField(row);
49
+ fields[table_name].push(field);
50
+ return acc;
51
+ }, {}) || {};
52
+ return {
53
+ tables: Object.values(tableMap),
54
+ fields,
55
+ };
56
+ }
57
+ // Field utils
58
+ function getDefaultType(columnDefault) {
59
+ if (columnDefault === null || columnDefault === undefined) {
60
+ return 'expression';
61
+ }
62
+ const trimmed = columnDefault.trim();
63
+ // String defaults are wrapped in single quotes: 'value'
64
+ // Example: `'string'`, `'string with quotes'''`
65
+ if (/^'.*'$/.test(trimmed)) {
66
+ return 'string';
67
+ }
68
+ // Number defaults are integers or non-scientific floats
69
+ // Example: `123`, `0.12`, `.12`, `12.`
70
+ if (/^[0-9]+$/.test(trimmed) || /^[0-9]*\.[0-9]*$/.test(trimmed)) {
71
+ return 'number';
72
+ }
73
+ // Example: Strings that are not enclosed in `'` or do not contain only numbers and `.`
74
+ // * CURRENT_TIMESTAMP
75
+ // * SYSDATE
76
+ // * "number-column" + 2
77
+ return 'expression';
78
+ }
79
+ function generateField(row) {
80
+ const { column_name, data_type, data_length, data_precision, data_scale, identity_increment, is_nullable, data_default, column_comment, } = row;
81
+ const defaultType = getDefaultType(data_default);
82
+ const dbdefault = data_default && !identity_increment ? getDbdefault(data_default, defaultType) : null;
83
+ const fieldType = {
84
+ type_name: getFieldType(data_type, data_length, data_precision, data_scale),
85
+ schemaName: null,
86
+ };
87
+ return {
88
+ name: column_name,
89
+ type: fieldType,
90
+ dbdefault,
91
+ not_null: !is_nullable,
92
+ increment: !!identity_increment,
93
+ note: column_comment ? { value: column_comment } : { value: '' },
94
+ };
95
+ }
96
+ // Field default utils
97
+ function getDbdefault(columnDefault, defaultType) {
98
+ if (columnDefault === null) {
99
+ return null;
100
+ }
101
+ if (defaultType === 'string') {
102
+ const trimmed = columnDefault.trim();
103
+ return {
104
+ value: trimmed.slice(1, -1) // slice off single quotes at the start and end of the value
105
+ .replaceAll('\'\'', '\''), // escape sequences are preserved: in oracle, double single quotes = escape a single quote
106
+ type: defaultType,
107
+ };
108
+ }
109
+ return { value: columnDefault, type: defaultType };
110
+ }
111
+ // Field type utils
112
+ // These datatypes are never qualified: data size, data scale and data precision are all ignore.
113
+ // Ref: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html
114
+ const UNQUALIFIED_DATA_TYPES = new Set([
115
+ // datetime
116
+ 'date',
117
+ // number
118
+ 'binary_float',
119
+ 'binary_double',
120
+ // long and raw
121
+ 'long',
122
+ 'long raw',
123
+ // large object
124
+ 'blob',
125
+ 'clob',
126
+ 'nclob',
127
+ 'bfile',
128
+ // rowid
129
+ 'rowid',
130
+ // ansi supported
131
+ 'integer',
132
+ 'int',
133
+ 'smallint',
134
+ 'double precision',
135
+ 'real',
136
+ // any
137
+ 'AnyData',
138
+ 'AnyType',
139
+ 'AnyDataSet',
140
+ // xml
141
+ 'XMLType',
142
+ 'URIType',
143
+ // spatial
144
+ 'SDO_Geometry',
145
+ 'SDO_Topo_Geometry',
146
+ 'SDO_GeoRaster',
147
+ ]);
148
+ // These datatypes can be and only be qualified with data size
149
+ // For simplicity the data size in bytes is assumed
150
+ // Ref: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html
151
+ const SIZED_DATA_TYPES = new Set([
152
+ // character
153
+ 'char',
154
+ 'varchar2',
155
+ 'nchar',
156
+ 'nvarchar2',
157
+ // long and raw
158
+ 'raw',
159
+ // rowid
160
+ 'urowid',
161
+ // ansi supported
162
+ 'character varying',
163
+ 'character',
164
+ 'char varying',
165
+ 'nchar varying',
166
+ 'varchar',
167
+ 'national character varying',
168
+ 'national character',
169
+ 'national char varying',
170
+ 'national char',
171
+ ]);
172
+ // These datatypes can be and only be qualified with precision
173
+ // For datetime, these are the types that only have 1 precision
174
+ // Ref: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html
175
+ const PRECISION_DATA_TYPES = new Set([
176
+ // number
177
+ 'float',
178
+ ]);
179
+ // This datatype can be qualified with a fractional second precision
180
+ const TIMESTAMP_WITH_LOCAL_TIME_ZONE_DATA_TYPE = 'timestamp with local time zone';
181
+ // This datatype can be qualified with a second precision
182
+ const TIMESTAMP_WITH_TIME_ZONE_DATA_TYPE = 'timestamp with time zone';
183
+ // This datatype can be qualified with a second precision
184
+ const TIMESTAMP_DATA_TYPE = 'timestamp';
185
+ // This datatype can be qualified with a year precision
186
+ const INTERVAL_YEAR_DATA_TYPE = 'interval year to month';
187
+ // This datatype can be qualified with a day precision and a fraction second precision
188
+ const INTERVAL_DAY_DATA_TYPE = 'interval day to second';
189
+ // These datatypes can be and only be qualified with precision and scale
190
+ // For datetime, these are the types that can have 2 precisions
191
+ // Ref: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html
192
+ const PRECISION_SCALED_DATA_TYPES = new Set([
193
+ // number
194
+ 'number',
195
+ // ansi supported
196
+ 'numeric',
197
+ 'decimal',
198
+ 'dec',
199
+ ]);
200
+ // Format the field type to a standard string for aesthetic when exporting to DBML (`ANYDATA`, `anydata` are all normalized to `AnyData`)
201
+ // Some types contain multiple words with arbitrary spaces, for example, `TIMESTAMP (6) WITH LOCAL TIME ZONE`, we should normalize this to allow processing to be uniform, for example when checking for equality
202
+ function normalizeFieldType(dataType) {
203
+ const normalizedDataType = dataType.split(' ').filter(Boolean).join(' ').toLowerCase();
204
+ switch (normalizedDataType) {
205
+ case 'anydata':
206
+ return 'AnyData';
207
+ case 'anytype':
208
+ return 'AnyType';
209
+ case 'anydataset':
210
+ return 'AnyDataSet';
211
+ case 'xmltype':
212
+ return 'XMLType';
213
+ case 'uritype':
214
+ return 'URIType';
215
+ case 'sdo_geometry':
216
+ return 'SDO_Geometry';
217
+ case 'sdo_topo_geometry':
218
+ return 'SDO_Topo_Geometry';
219
+ case 'sdo_georaster':
220
+ return 'SDO_GeoRaster';
221
+ default:
222
+ return normalizedDataType;
223
+ }
224
+ }
225
+ function getFieldType(dataType, characterMaximumLength, numericPrecision, numericScale) {
226
+ const normalizedDataType = normalizeFieldType(dataType);
227
+ if (normalizedDataType === TIMESTAMP_WITH_LOCAL_TIME_ZONE_DATA_TYPE) {
228
+ if (numericScale !== null)
229
+ return `timestamp (${numericScale}) with local time zone`;
230
+ return normalizedDataType;
231
+ }
232
+ if (normalizedDataType === TIMESTAMP_WITH_TIME_ZONE_DATA_TYPE) {
233
+ if (numericScale !== null)
234
+ return `timestamp (${numericScale}) with time zone`;
235
+ return normalizedDataType;
236
+ }
237
+ if (normalizedDataType === TIMESTAMP_DATA_TYPE) {
238
+ if (numericScale !== null)
239
+ return `timestamp (${numericScale})`;
240
+ return normalizedDataType;
241
+ }
242
+ if (normalizedDataType === INTERVAL_YEAR_DATA_TYPE) {
243
+ if (numericPrecision !== null)
244
+ return `interval year (${numericPrecision}) to month`;
245
+ return normalizedDataType;
246
+ }
247
+ if (normalizedDataType === INTERVAL_DAY_DATA_TYPE) {
248
+ if (numericPrecision !== null && numericScale !== null)
249
+ return `interval day (${numericPrecision}) to second (${numericScale})`;
250
+ if (numericPrecision !== null)
251
+ return `interval day (${numericPrecision}) to second`;
252
+ if (numericScale !== null)
253
+ return `interval day to second (${numericScale})`;
254
+ return normalizedDataType;
255
+ }
256
+ if (SIZED_DATA_TYPES.has(normalizedDataType)) {
257
+ if (characterMaximumLength)
258
+ return `${normalizedDataType}(${characterMaximumLength})`;
259
+ return normalizedDataType;
260
+ }
261
+ if (PRECISION_DATA_TYPES.has(normalizedDataType)) {
262
+ if (numericPrecision !== null)
263
+ return `${normalizedDataType}(${numericPrecision})`;
264
+ return normalizedDataType;
265
+ }
266
+ if (PRECISION_SCALED_DATA_TYPES.has(normalizedDataType)) {
267
+ if (numericPrecision !== null && numericScale !== null)
268
+ return `${normalizedDataType}(${numericPrecision}, ${numericScale})`;
269
+ if (numericPrecision !== null)
270
+ return `${normalizedDataType}(${numericPrecision})`;
271
+ // There's no situation where numericScale is not null but numericPrecision is null
272
+ return normalizedDataType;
273
+ }
274
+ // This is not needed since we return `normalizedDataType` either if the condition holds true or not
275
+ // if (UNQUALIFIED_DATA_TYPES.has(normalizedDataType)) {
276
+ // return normalizedDataType;
277
+ // }
278
+ return normalizedDataType;
279
+ }
280
+ ;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.EXECUTE_OPTIONS = exports.LIST_SEPARATOR = void 0;
7
+ exports.processEasyConnectString = processEasyConnectString;
8
+ const oracledb_1 = __importDefault(require("oracledb"));
9
+ exports.LIST_SEPARATOR = '@@@@@@@@'; // Used to join multiple fields into a string field to simulate array column types
10
+ exports.EXECUTE_OPTIONS = { outFormat: oracledb_1.default.OUT_FORMAT_OBJECT };
11
+ // Expect an Easy Connect string format: username/password@[//]host[:port][/database]
12
+ // Explanation of the format: https://www.orafaq.com/wiki/EZCONNECT
13
+ function processEasyConnectString(connection) {
14
+ // Capturing group `username`: a string without the characters `/` `@` `:`
15
+ // Capturing group `password`: a string without the characters `/` `@` `:`
16
+ // Capturing group `dbidentifier`: a string containing the `host[:port][/database]` part
17
+ const matches = connection.match(/^(?<username>[^/@:]+)\/(?<password>[^/@:]+)@(\/\/)?(?<dbidentifier>.+)$/);
18
+ const { username, password, dbidentifier } = matches?.groups || {};
19
+ if (!username || !password || !dbidentifier) {
20
+ throw new Error('Invalid Easy Connect string. Expect a string of format \'username/password@[//]host[:port][/database]\'');
21
+ }
22
+ return {
23
+ username,
24
+ password,
25
+ dbidentifier,
26
+ };
27
+ }