imodel-pg 0.3.0 → 0.3.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.
- package/index.d.mts +1 -1
- package/index.mjs +229 -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.2
|
|
3
3
|
* (c) 2019-2025 undefined
|
|
4
4
|
* @license undefined
|
|
5
5
|
*/
|
|
@@ -934,6 +934,7 @@ WHERE ${Sql`AND`.glue(pKeys.map(f => Sql`${main.field(f)}=${tmp.field(f)}`), get
|
|
|
934
934
|
/** @import { Environment, IConnection } from 'imodel' */
|
|
935
935
|
/** @import { PgEnvTrans } from '../index.mjs' */
|
|
936
936
|
/** @import { DBIndex, DBTable, DBField } from 'imodel' */
|
|
937
|
+
const tableType = 'BASE TABLE';
|
|
937
938
|
/**
|
|
938
939
|
*
|
|
939
940
|
* @param {Environment<PgEnvTrans>} env
|
|
@@ -1065,7 +1066,7 @@ async function loadDefault(env, query, fields) {
|
|
|
1065
1066
|
return fields;
|
|
1066
1067
|
}
|
|
1067
1068
|
const sql = Sql`
|
|
1068
|
-
SELECT ${Sql`,`.glue(data.map((v, k) => Sql`${v} as ${Sql(`a${k}`)}`))}
|
|
1069
|
+
SELECT ${Sql`,`.glue(data.map((v, k) => Sql`${v.default} as ${Sql(`a${k}`)}`))}
|
|
1069
1070
|
`;
|
|
1070
1071
|
const values = await query(env, sql);
|
|
1071
1072
|
for (const [index, field] of data.entries()) {
|
|
@@ -1143,7 +1144,7 @@ async function loadBaseTables(env, schema, query, tables) {
|
|
|
1143
1144
|
WHERE
|
|
1144
1145
|
table_schema = ${schema}
|
|
1145
1146
|
AND
|
|
1146
|
-
table_type =
|
|
1147
|
+
table_type = ${tableType}
|
|
1147
1148
|
AND
|
|
1148
1149
|
table_name IN (${Sql`,`.glue(tables.map(t => Sql`${t}`))})`;
|
|
1149
1150
|
const rows = await query(env, sql);
|
|
@@ -1194,11 +1195,14 @@ async function loadTables(env, schema, query, tables) {
|
|
|
1194
1195
|
/** @import { DBIndex, DBTable, DBField } from 'imodel' */
|
|
1195
1196
|
/**
|
|
1196
1197
|
*
|
|
1197
|
-
* @param {DBIndex[]} indexes
|
|
1198
|
-
* @returns
|
|
1198
|
+
* @param {DBIndex[]} [indexes]
|
|
1199
|
+
* @returns {Record<string, DBIndex>}
|
|
1199
1200
|
*/
|
|
1200
1201
|
function buildNewIndexes(indexes) {
|
|
1201
|
-
|
|
1202
|
+
if (!indexes) {
|
|
1203
|
+
return {};
|
|
1204
|
+
}
|
|
1205
|
+
/** @type {Record<string, {fields: string[]; includes: string[][]; unique?: boolean;}>} */
|
|
1202
1206
|
const indexMap = {};
|
|
1203
1207
|
for (const {
|
|
1204
1208
|
fields,
|
|
@@ -1217,12 +1221,12 @@ function buildNewIndexes(indexes) {
|
|
|
1217
1221
|
if (!index) {
|
|
1218
1222
|
indexMap[key] = {
|
|
1219
1223
|
fields: keys,
|
|
1220
|
-
includes: [includes],
|
|
1224
|
+
includes: [includes || []],
|
|
1221
1225
|
unique
|
|
1222
1226
|
};
|
|
1223
1227
|
continue;
|
|
1224
1228
|
}
|
|
1225
|
-
index.includes.push(includes);
|
|
1229
|
+
index.includes.push(includes || []);
|
|
1226
1230
|
index.unique ||= unique;
|
|
1227
1231
|
}
|
|
1228
1232
|
/** @type {Record<string, DBIndex>} */
|
|
@@ -1257,7 +1261,7 @@ async function createIndex(env, query, table, {
|
|
|
1257
1261
|
CREATE ${unique ? Sql`UNIQUE` : Sql``}
|
|
1258
1262
|
INDEX ON ${Sql.Table(table)}
|
|
1259
1263
|
(${Sql`,`.glue(...fields.map(f => Sql.Field(f)))})
|
|
1260
|
-
${includes
|
|
1264
|
+
${includes?.length ? Sql`
|
|
1261
1265
|
INCLUDE(${Sql`,`.glue(...includes.map(f => Sql.Field(f)))})
|
|
1262
1266
|
` : Sql``}
|
|
1263
1267
|
`;
|
|
@@ -1284,6 +1288,216 @@ async function createNewTable(env, query, {
|
|
|
1284
1288
|
await createIndex(env, query, table, index);
|
|
1285
1289
|
}
|
|
1286
1290
|
}
|
|
1291
|
+
/**
|
|
1292
|
+
*
|
|
1293
|
+
* @param {{fields: string[]; includes: string[]?; unique?: boolean; name: string[]}} oldIndex
|
|
1294
|
+
* @param {DBIndex} [newIndex]
|
|
1295
|
+
* @returns
|
|
1296
|
+
*/
|
|
1297
|
+
function indexIsEq({
|
|
1298
|
+
includes,
|
|
1299
|
+
name,
|
|
1300
|
+
unique
|
|
1301
|
+
}, newIndex) {
|
|
1302
|
+
if (!newIndex) {
|
|
1303
|
+
return false;
|
|
1304
|
+
}
|
|
1305
|
+
if (name?.length !== 1) {
|
|
1306
|
+
return false;
|
|
1307
|
+
}
|
|
1308
|
+
if (!includes) {
|
|
1309
|
+
return false;
|
|
1310
|
+
}
|
|
1311
|
+
if (Boolean(newIndex.unique) !== Boolean(unique)) {
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
const oldIncludes = new Set(includes);
|
|
1315
|
+
const newIncludes = new Set(newIndex.includes);
|
|
1316
|
+
if (oldIncludes.size !== newIncludes.size) {
|
|
1317
|
+
return false;
|
|
1318
|
+
}
|
|
1319
|
+
for (const k of oldIncludes) {
|
|
1320
|
+
if (!newIncludes.has(k)) {
|
|
1321
|
+
return false;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
return true;
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
*
|
|
1328
|
+
* @param {DBIndex[]} [indexes]
|
|
1329
|
+
* @returns
|
|
1330
|
+
*/
|
|
1331
|
+
function buildOldIndexes(indexes) {
|
|
1332
|
+
/** @type {Record<string, {fields: string[]; includes: string[]?; unique?: boolean; name: string[]}>} */
|
|
1333
|
+
const indexMap = {};
|
|
1334
|
+
if (!indexes) {
|
|
1335
|
+
return indexMap;
|
|
1336
|
+
}
|
|
1337
|
+
for (const {
|
|
1338
|
+
fields,
|
|
1339
|
+
includes,
|
|
1340
|
+
unique,
|
|
1341
|
+
name
|
|
1342
|
+
} of indexes) {
|
|
1343
|
+
const keys = [];
|
|
1344
|
+
for (const f of fields) {
|
|
1345
|
+
if (keys.includes(f)) {
|
|
1346
|
+
continue;
|
|
1347
|
+
}
|
|
1348
|
+
keys.push(f);
|
|
1349
|
+
}
|
|
1350
|
+
const key = keys.join('\n');
|
|
1351
|
+
const index = indexMap[key];
|
|
1352
|
+
if (!index) {
|
|
1353
|
+
indexMap[key] = {
|
|
1354
|
+
fields: keys,
|
|
1355
|
+
includes: includes || [],
|
|
1356
|
+
unique,
|
|
1357
|
+
name: name ? [name] : []
|
|
1358
|
+
};
|
|
1359
|
+
continue;
|
|
1360
|
+
}
|
|
1361
|
+
index.includes = null;
|
|
1362
|
+
if (name) {
|
|
1363
|
+
index.name.push(name);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
return indexMap;
|
|
1367
|
+
}
|
|
1368
|
+
function toTypeHad(type) {
|
|
1369
|
+
switch (type) {
|
|
1370
|
+
case 'ipv4':
|
|
1371
|
+
case 'ipv6':
|
|
1372
|
+
return 'ip';
|
|
1373
|
+
case 'ipnetv4':
|
|
1374
|
+
case 'ipnetv6':
|
|
1375
|
+
return 'ipnet';
|
|
1376
|
+
case 'object':
|
|
1377
|
+
return 'json';
|
|
1378
|
+
}
|
|
1379
|
+
return type;
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
*
|
|
1383
|
+
* @param {Environment<PgEnvTrans>} env
|
|
1384
|
+
* @param {<T extends pg.QueryResultRow>(env: Environment<PgEnvTrans>, sql: Sql) => Promise<T[]>} query
|
|
1385
|
+
* @param {DBTable} newTable
|
|
1386
|
+
* @param {DBTable} oldTable
|
|
1387
|
+
*/
|
|
1388
|
+
async function changeTable(env, query, newTable, oldTable) {
|
|
1389
|
+
const {
|
|
1390
|
+
table
|
|
1391
|
+
} = newTable;
|
|
1392
|
+
const newIndexes = new Map(Object.entries(buildNewIndexes(newTable.indexes)));
|
|
1393
|
+
const oldIndexes = buildOldIndexes(oldTable.indexes);
|
|
1394
|
+
for (const [key, index] of Object.entries(oldIndexes)) {
|
|
1395
|
+
if (indexIsEq(index, newIndexes.get(key))) {
|
|
1396
|
+
newIndexes.delete(key);
|
|
1397
|
+
continue;
|
|
1398
|
+
}
|
|
1399
|
+
for (const n of index.name || []) {
|
|
1400
|
+
const sql = Sql`DROP INDEX ${Sql.Id(n, 'index')}`;
|
|
1401
|
+
await query(env, sql);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
// 改名
|
|
1405
|
+
if (table !== oldTable.table) {
|
|
1406
|
+
const sql = Sql`
|
|
1407
|
+
ALTER TABLE ${Sql.Table(oldTable.table)}
|
|
1408
|
+
RENAME TO ${Sql.Table(table)}
|
|
1409
|
+
`;
|
|
1410
|
+
await query(env, sql);
|
|
1411
|
+
}
|
|
1412
|
+
const primary = Object.entries(newTable.fields).filter(([, v]) => v.primary).sort(([, {
|
|
1413
|
+
primary: a = 0
|
|
1414
|
+
}], [, {
|
|
1415
|
+
primary: b = 0
|
|
1416
|
+
}]) => a - b).map(([v]) => v);
|
|
1417
|
+
const oldPrimary = Object.entries(oldTable.fields).filter(([, v]) => v.primary).sort(([, {
|
|
1418
|
+
primary: a = 0
|
|
1419
|
+
}], [, {
|
|
1420
|
+
primary: b = 0
|
|
1421
|
+
}]) => a - b).map(([v]) => v);
|
|
1422
|
+
const prEq = oldPrimary.length === primary.length && primary.join('\n') === oldPrimary.join('\n');
|
|
1423
|
+
if (!prEq && oldPrimary.length) {
|
|
1424
|
+
let name = oldTable.primary;
|
|
1425
|
+
if (!name) {
|
|
1426
|
+
const sql = Sql`
|
|
1427
|
+
SELECT constraint_name
|
|
1428
|
+
FROM information_schema.table_constraints
|
|
1429
|
+
WHERE table_name = ${table} AND constraint_type = 'PRIMARY KEY'
|
|
1430
|
+
`;
|
|
1431
|
+
name = (await query(env, sql).then(v => v[0]?.constraint_name)) || '';
|
|
1432
|
+
}
|
|
1433
|
+
// @ts-ignore
|
|
1434
|
+
// eslint-disable-next-line max-len
|
|
1435
|
+
const sql = Sql`ALTER TABLE ${Sql.Table(table)} DROP CONSTRAINT ${Sql.Id(name)}`;
|
|
1436
|
+
await query(env, sql);
|
|
1437
|
+
}
|
|
1438
|
+
const oldFields = new Map(Object.entries(oldTable.fields));
|
|
1439
|
+
// 列处理
|
|
1440
|
+
/** @type {Sql[]} */
|
|
1441
|
+
const COLUMNs = [];
|
|
1442
|
+
for (const [fieldName, field] of Object.entries(newTable.fields)) {
|
|
1443
|
+
const old = oldFields.get(fieldName);
|
|
1444
|
+
oldFields.delete(fieldName);
|
|
1445
|
+
if (!old) {
|
|
1446
|
+
COLUMNs.push(add(fieldName, field.type, field, env));
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
const type = toTypeHad(field.type);
|
|
1450
|
+
const {
|
|
1451
|
+
size,
|
|
1452
|
+
scale,
|
|
1453
|
+
array,
|
|
1454
|
+
default: defaultValue = null,
|
|
1455
|
+
nullable
|
|
1456
|
+
} = field;
|
|
1457
|
+
if (type !== old.type || Number(array) !== Number(old.array)) ;
|
|
1458
|
+
const COLUMN = Sql`ALTER COLUMN ${Sql.Field(fieldName)}`;
|
|
1459
|
+
const newType = getType(type, {
|
|
1460
|
+
scale,
|
|
1461
|
+
size
|
|
1462
|
+
}, array);
|
|
1463
|
+
const oldType = getType(old.type, old, old.array);
|
|
1464
|
+
if (newType !== oldType) {
|
|
1465
|
+
COLUMNs.push(Sql`${COLUMN} TYPE ${Sql(newType)}`);
|
|
1466
|
+
// TODO: USING "description"::int2
|
|
1467
|
+
}
|
|
1468
|
+
if (Boolean(nullable) !== Boolean(old.nullable)) {
|
|
1469
|
+
COLUMNs.push(nullable ? Sql`${COLUMN} DROP NOT NULL` : Sql`${COLUMN} SET NOT NULL`);
|
|
1470
|
+
}
|
|
1471
|
+
if (defaultValue !== (old.default ?? null)) {
|
|
1472
|
+
COLUMNs.push(defaultValue !== null ? Sql`${COLUMN} SET DEFAULT ${defaultValue}` : Sql`${COLUMN} DROP DEFAULT`);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
{
|
|
1476
|
+
for (const [fieldName, {
|
|
1477
|
+
nullable
|
|
1478
|
+
}] of oldFields) {
|
|
1479
|
+
if (nullable) {
|
|
1480
|
+
continue;
|
|
1481
|
+
}
|
|
1482
|
+
COLUMNs.push(Sql`ALTER COLUMN ${Sql.Field(fieldName)} DROP NOT NULL`);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
if (COLUMNs.length) {
|
|
1486
|
+
const sql = Sql`ALTER TABLE ${Sql.Table(table)} ${Sql`,`.glue(COLUMNs)}`;
|
|
1487
|
+
await query(env, sql);
|
|
1488
|
+
}
|
|
1489
|
+
if (!prEq && primary.length) {
|
|
1490
|
+
const sql = Sql`
|
|
1491
|
+
ALTER TABLE ${Sql.Table(table)}
|
|
1492
|
+
ADD PRIMARY KEY ${Sql`,`.glue(primary.map(f => Sql.Field(f)))}
|
|
1493
|
+
`;
|
|
1494
|
+
await query(env, sql);
|
|
1495
|
+
}
|
|
1496
|
+
// 重建索引
|
|
1497
|
+
for (const index of newIndexes.values()) {
|
|
1498
|
+
await createIndex(env, query, table, index);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1287
1501
|
/**
|
|
1288
1502
|
*
|
|
1289
1503
|
* @param {Environment<PgEnvTrans>} env
|
|
@@ -1296,12 +1510,14 @@ async function syncTables(env, schema, query, tables, del) {
|
|
|
1296
1510
|
const tableNames = tables.map(v => v.table);
|
|
1297
1511
|
const dbTables = await loadTables(env, schema, query, tableNames);
|
|
1298
1512
|
const tableMap = new Map(dbTables.map(v => [v.table, v]));
|
|
1299
|
-
|
|
1513
|
+
for (const table of tables) {
|
|
1300
1514
|
const old = tableMap.get(table.table);
|
|
1301
|
-
if (old)
|
|
1302
|
-
|
|
1515
|
+
if (old) {
|
|
1516
|
+
await changeTable(env, query, table, old);
|
|
1517
|
+
} else {
|
|
1518
|
+
await createNewTable(env, query, table);
|
|
1303
1519
|
}
|
|
1304
|
-
}
|
|
1520
|
+
}
|
|
1305
1521
|
}
|
|
1306
1522
|
|
|
1307
1523
|
/** @import { Environment, IConnection } from 'imodel' */
|