imodel-pg 0.3.1 → 0.3.3
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/index.d.mts +1 -1
- package/index.mjs +238 -13
- package/package.json +1 -1
package/index.d.mts
CHANGED
package/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* imodel v0.3.
|
|
2
|
+
* imodel v0.3.3
|
|
3
3
|
* (c) 2019-2025 undefined
|
|
4
4
|
* @license undefined
|
|
5
5
|
*/
|
|
@@ -128,18 +128,28 @@ function getType(s, {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
/** @import { Environment, ColumnOptions } from 'imodel' */
|
|
131
|
+
/**
|
|
132
|
+
*
|
|
133
|
+
* @param {string} t
|
|
134
|
+
*/
|
|
135
|
+
function toSqlString2(t) {
|
|
136
|
+
return `'${t.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}'`;
|
|
137
|
+
}
|
|
131
138
|
/**
|
|
132
139
|
*
|
|
133
140
|
* @param {any} v
|
|
134
141
|
* @param {string} type
|
|
135
142
|
* @returns
|
|
136
143
|
*/
|
|
137
|
-
function
|
|
144
|
+
function toSql(v, type) {
|
|
145
|
+
if (type === 'json' || type === 'object') {
|
|
146
|
+
return `${toSqlString2(JSON.stringify(type))}::JSON`;
|
|
147
|
+
}
|
|
138
148
|
if (typeof v === 'bigint' || typeof v === 'number') {
|
|
139
149
|
return String(v);
|
|
140
150
|
}
|
|
141
151
|
if (typeof v === 'string') {
|
|
142
|
-
return
|
|
152
|
+
return toSqlString2(v);
|
|
143
153
|
}
|
|
144
154
|
if (v === true) {
|
|
145
155
|
return 'TRUE';
|
|
@@ -148,8 +158,9 @@ function toSqlString(v, type) {
|
|
|
148
158
|
return 'FALSE';
|
|
149
159
|
}
|
|
150
160
|
if (v instanceof Date) {
|
|
151
|
-
return
|
|
161
|
+
return v.toISOString();
|
|
152
162
|
}
|
|
163
|
+
return null;
|
|
153
164
|
}
|
|
154
165
|
/**
|
|
155
166
|
*
|
|
@@ -178,10 +189,10 @@ function getDefault(env, value, type, array) {
|
|
|
178
189
|
}
|
|
179
190
|
if (array) {
|
|
180
191
|
const values = Array.isArray(v) ? v : [v];
|
|
181
|
-
const t = values.map(v =>
|
|
182
|
-
return Sql(`
|
|
192
|
+
const t = values.map(v => toSql(v, type)).filter(Boolean);
|
|
193
|
+
return Sql(`{${t.join(',')}}`);
|
|
183
194
|
}
|
|
184
|
-
const t =
|
|
195
|
+
const t = toSql(v, type);
|
|
185
196
|
if (t) {
|
|
186
197
|
return Sql(t);
|
|
187
198
|
}
|
|
@@ -934,6 +945,7 @@ WHERE ${Sql`AND`.glue(pKeys.map(f => Sql`${main.field(f)}=${tmp.field(f)}`), get
|
|
|
934
945
|
/** @import { Environment, IConnection } from 'imodel' */
|
|
935
946
|
/** @import { PgEnvTrans } from '../index.mjs' */
|
|
936
947
|
/** @import { DBIndex, DBTable, DBField } from 'imodel' */
|
|
948
|
+
const tableType = 'BASE TABLE';
|
|
937
949
|
/**
|
|
938
950
|
*
|
|
939
951
|
* @param {Environment<PgEnvTrans>} env
|
|
@@ -1065,7 +1077,7 @@ async function loadDefault(env, query, fields) {
|
|
|
1065
1077
|
return fields;
|
|
1066
1078
|
}
|
|
1067
1079
|
const sql = Sql`
|
|
1068
|
-
SELECT ${Sql`,`.glue(data.map((v, k) => Sql`${v} as ${
|
|
1080
|
+
SELECT ${Sql`,`.glue(data.map((v, k) => Sql(`${v.default} as ${k}`)))}
|
|
1069
1081
|
`;
|
|
1070
1082
|
const values = await query(env, sql);
|
|
1071
1083
|
for (const [index, field] of data.entries()) {
|
|
@@ -1143,7 +1155,7 @@ async function loadBaseTables(env, schema, query, tables) {
|
|
|
1143
1155
|
WHERE
|
|
1144
1156
|
table_schema = ${schema}
|
|
1145
1157
|
AND
|
|
1146
|
-
table_type =
|
|
1158
|
+
table_type = ${tableType}
|
|
1147
1159
|
AND
|
|
1148
1160
|
table_name IN (${Sql`,`.glue(tables.map(t => Sql`${t}`))})`;
|
|
1149
1161
|
const rows = await query(env, sql);
|
|
@@ -1287,6 +1299,217 @@ async function createNewTable(env, query, {
|
|
|
1287
1299
|
await createIndex(env, query, table, index);
|
|
1288
1300
|
}
|
|
1289
1301
|
}
|
|
1302
|
+
/**
|
|
1303
|
+
*
|
|
1304
|
+
* @param {{fields: string[]; includes: string[]?; unique?: boolean; name: string[]}} oldIndex
|
|
1305
|
+
* @param {DBIndex} [newIndex]
|
|
1306
|
+
* @returns
|
|
1307
|
+
*/
|
|
1308
|
+
function indexIsEq({
|
|
1309
|
+
includes,
|
|
1310
|
+
name,
|
|
1311
|
+
unique
|
|
1312
|
+
}, newIndex) {
|
|
1313
|
+
if (!newIndex) {
|
|
1314
|
+
return false;
|
|
1315
|
+
}
|
|
1316
|
+
if (name?.length !== 1) {
|
|
1317
|
+
return false;
|
|
1318
|
+
}
|
|
1319
|
+
if (!includes) {
|
|
1320
|
+
return false;
|
|
1321
|
+
}
|
|
1322
|
+
if (Boolean(newIndex.unique) !== Boolean(unique)) {
|
|
1323
|
+
return false;
|
|
1324
|
+
}
|
|
1325
|
+
const oldIncludes = new Set(includes);
|
|
1326
|
+
const newIncludes = new Set(newIndex.includes);
|
|
1327
|
+
if (oldIncludes.size !== newIncludes.size) {
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
for (const k of oldIncludes) {
|
|
1331
|
+
if (!newIncludes.has(k)) {
|
|
1332
|
+
return false;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
return true;
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
*
|
|
1339
|
+
* @param {DBIndex[]} [indexes]
|
|
1340
|
+
* @returns
|
|
1341
|
+
*/
|
|
1342
|
+
function buildOldIndexes(indexes) {
|
|
1343
|
+
/** @type {Record<string, {fields: string[]; includes: string[]?; unique?: boolean; name: string[]}>} */
|
|
1344
|
+
const indexMap = {};
|
|
1345
|
+
if (!indexes) {
|
|
1346
|
+
return indexMap;
|
|
1347
|
+
}
|
|
1348
|
+
for (const {
|
|
1349
|
+
fields,
|
|
1350
|
+
includes,
|
|
1351
|
+
unique,
|
|
1352
|
+
name
|
|
1353
|
+
} of indexes) {
|
|
1354
|
+
const keys = [];
|
|
1355
|
+
for (const f of fields) {
|
|
1356
|
+
if (keys.includes(f)) {
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
keys.push(f);
|
|
1360
|
+
}
|
|
1361
|
+
const key = keys.join('\n');
|
|
1362
|
+
const index = indexMap[key];
|
|
1363
|
+
if (!index) {
|
|
1364
|
+
indexMap[key] = {
|
|
1365
|
+
fields: keys,
|
|
1366
|
+
includes: includes || [],
|
|
1367
|
+
unique,
|
|
1368
|
+
name: name ? [name] : []
|
|
1369
|
+
};
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
index.includes = null;
|
|
1373
|
+
if (name) {
|
|
1374
|
+
index.name.push(name);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return indexMap;
|
|
1378
|
+
}
|
|
1379
|
+
function toTypeHad(type) {
|
|
1380
|
+
switch (type) {
|
|
1381
|
+
case 'ipv4':
|
|
1382
|
+
case 'ipv6':
|
|
1383
|
+
return 'ip';
|
|
1384
|
+
case 'ipnetv4':
|
|
1385
|
+
case 'ipnetv6':
|
|
1386
|
+
return 'ipnet';
|
|
1387
|
+
case 'object':
|
|
1388
|
+
return 'json';
|
|
1389
|
+
}
|
|
1390
|
+
return type;
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
*
|
|
1394
|
+
* @param {Environment<PgEnvTrans>} env
|
|
1395
|
+
* @param {<T extends pg.QueryResultRow>(env: Environment<PgEnvTrans>, sql: Sql) => Promise<T[]>} query
|
|
1396
|
+
* @param {DBTable} newTable
|
|
1397
|
+
* @param {DBTable} oldTable
|
|
1398
|
+
*/
|
|
1399
|
+
async function changeTable(env, query, newTable, oldTable) {
|
|
1400
|
+
const {
|
|
1401
|
+
table
|
|
1402
|
+
} = newTable;
|
|
1403
|
+
const newIndexes = new Map(Object.entries(buildNewIndexes(newTable.indexes)));
|
|
1404
|
+
const oldIndexes = buildOldIndexes(oldTable.indexes);
|
|
1405
|
+
for (const [key, index] of Object.entries(oldIndexes)) {
|
|
1406
|
+
if (indexIsEq(index, newIndexes.get(key))) {
|
|
1407
|
+
newIndexes.delete(key);
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
for (const n of index.name || []) {
|
|
1411
|
+
const sql = Sql`DROP INDEX ${Sql.Id(n, 'index')}`;
|
|
1412
|
+
await query(env, sql);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
// 改名
|
|
1416
|
+
if (table !== oldTable.table) {
|
|
1417
|
+
const sql = Sql`
|
|
1418
|
+
ALTER TABLE ${Sql.Table(oldTable.table)}
|
|
1419
|
+
RENAME TO ${Sql.Table(table)}
|
|
1420
|
+
`;
|
|
1421
|
+
await query(env, sql);
|
|
1422
|
+
}
|
|
1423
|
+
const primary = Object.entries(newTable.fields).filter(([, v]) => v.primary).sort(([, {
|
|
1424
|
+
primary: a = 0
|
|
1425
|
+
}], [, {
|
|
1426
|
+
primary: b = 0
|
|
1427
|
+
}]) => a - b).map(([v]) => v);
|
|
1428
|
+
const oldPrimary = Object.entries(oldTable.fields).filter(([, v]) => v.primary).sort(([, {
|
|
1429
|
+
primary: a = 0
|
|
1430
|
+
}], [, {
|
|
1431
|
+
primary: b = 0
|
|
1432
|
+
}]) => a - b).map(([v]) => v);
|
|
1433
|
+
const prEq = oldPrimary.length === primary.length && primary.join('\n') === oldPrimary.join('\n');
|
|
1434
|
+
if (!prEq && oldPrimary.length) {
|
|
1435
|
+
let name = oldTable.primary;
|
|
1436
|
+
if (!name) {
|
|
1437
|
+
const sql = Sql`
|
|
1438
|
+
SELECT constraint_name
|
|
1439
|
+
FROM information_schema.table_constraints
|
|
1440
|
+
WHERE table_name = ${table} AND constraint_type = 'PRIMARY KEY'
|
|
1441
|
+
`;
|
|
1442
|
+
name = (await query(env, sql).then(v => v[0]?.constraint_name)) || '';
|
|
1443
|
+
}
|
|
1444
|
+
// @ts-ignore
|
|
1445
|
+
// eslint-disable-next-line max-len
|
|
1446
|
+
const sql = Sql`ALTER TABLE ${Sql.Table(table)} DROP CONSTRAINT ${Sql.Id(name)}`;
|
|
1447
|
+
await query(env, sql);
|
|
1448
|
+
}
|
|
1449
|
+
const oldFields = new Map(Object.entries(oldTable.fields));
|
|
1450
|
+
// 列处理
|
|
1451
|
+
/** @type {Sql[]} */
|
|
1452
|
+
const COLUMNs = [];
|
|
1453
|
+
for (const [fieldName, field] of Object.entries(newTable.fields)) {
|
|
1454
|
+
const old = oldFields.get(fieldName);
|
|
1455
|
+
oldFields.delete(fieldName);
|
|
1456
|
+
if (!old) {
|
|
1457
|
+
COLUMNs.push(add(fieldName, field.type, field, env));
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
const type = toTypeHad(field.type);
|
|
1461
|
+
const {
|
|
1462
|
+
size,
|
|
1463
|
+
scale,
|
|
1464
|
+
array,
|
|
1465
|
+
default: defaultValue = null,
|
|
1466
|
+
nullable
|
|
1467
|
+
} = field;
|
|
1468
|
+
if (type !== old.type || Number(array) !== Number(old.array)) ;
|
|
1469
|
+
const COLUMN = Sql`ALTER COLUMN ${Sql.Field(fieldName)}`;
|
|
1470
|
+
const newType = getType(type, {
|
|
1471
|
+
scale,
|
|
1472
|
+
size
|
|
1473
|
+
}, array);
|
|
1474
|
+
const oldType = getType(old.type, old, old.array);
|
|
1475
|
+
if (newType !== oldType) {
|
|
1476
|
+
COLUMNs.push(Sql`${COLUMN} TYPE ${Sql(newType)}`);
|
|
1477
|
+
// TODO: USING "description"::int2
|
|
1478
|
+
}
|
|
1479
|
+
if (Boolean(nullable) !== Boolean(old.nullable)) {
|
|
1480
|
+
COLUMNs.push(nullable ? Sql`${COLUMN} DROP NOT NULL` : Sql`${COLUMN} SET NOT NULL`);
|
|
1481
|
+
}
|
|
1482
|
+
if (defaultValue !== (old.default ?? null)) {
|
|
1483
|
+
const def = getDefault(env, defaultValue, type, array);
|
|
1484
|
+
COLUMNs.push(def ? Sql`${COLUMN} SET DEFAULT ${def}` : Sql`${COLUMN} DROP DEFAULT`);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
{
|
|
1488
|
+
for (const [fieldName, {
|
|
1489
|
+
nullable
|
|
1490
|
+
}] of oldFields) {
|
|
1491
|
+
if (nullable) {
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
COLUMNs.push(Sql`ALTER COLUMN ${Sql.Field(fieldName)} DROP NOT NULL`);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
if (COLUMNs.length) {
|
|
1498
|
+
const sql = Sql`ALTER TABLE ${Sql.Table(table)} ${Sql`,`.glue(COLUMNs)}`;
|
|
1499
|
+
await query(env, sql);
|
|
1500
|
+
}
|
|
1501
|
+
if (!prEq && primary.length) {
|
|
1502
|
+
const sql = Sql`
|
|
1503
|
+
ALTER TABLE ${Sql.Table(table)}
|
|
1504
|
+
ADD PRIMARY KEY ${Sql`,`.glue(primary.map(f => Sql.Field(f)))}
|
|
1505
|
+
`;
|
|
1506
|
+
await query(env, sql);
|
|
1507
|
+
}
|
|
1508
|
+
// 重建索引
|
|
1509
|
+
for (const index of newIndexes.values()) {
|
|
1510
|
+
await createIndex(env, query, table, index);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1290
1513
|
/**
|
|
1291
1514
|
*
|
|
1292
1515
|
* @param {Environment<PgEnvTrans>} env
|
|
@@ -1299,12 +1522,14 @@ async function syncTables(env, schema, query, tables, del) {
|
|
|
1299
1522
|
const tableNames = tables.map(v => v.table);
|
|
1300
1523
|
const dbTables = await loadTables(env, schema, query, tableNames);
|
|
1301
1524
|
const tableMap = new Map(dbTables.map(v => [v.table, v]));
|
|
1302
|
-
|
|
1525
|
+
for (const table of tables) {
|
|
1303
1526
|
const old = tableMap.get(table.table);
|
|
1304
|
-
if (old)
|
|
1305
|
-
|
|
1527
|
+
if (old) {
|
|
1528
|
+
await changeTable(env, query, table, old);
|
|
1529
|
+
} else {
|
|
1530
|
+
await createNewTable(env, query, table);
|
|
1306
1531
|
}
|
|
1307
|
-
}
|
|
1532
|
+
}
|
|
1308
1533
|
}
|
|
1309
1534
|
|
|
1310
1535
|
/** @import { Environment, IConnection } from 'imodel' */
|