imodel-pg 0.3.1 → 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.
Files changed (3) hide show
  1. package/index.d.mts +1 -1
  2. package/index.mjs +220 -7
  3. package/package.json +1 -1
package/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * imodel v0.3.1
2
+ * imodel v0.3.2
3
3
  * (c) 2019-2025 undefined
4
4
  * @license undefined
5
5
  */
package/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * imodel v0.3.1
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 = 'base table'
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);
@@ -1287,6 +1288,216 @@ async function createNewTable(env, query, {
1287
1288
  await createIndex(env, query, table, index);
1288
1289
  }
1289
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
+ }
1290
1501
  /**
1291
1502
  *
1292
1503
  * @param {Environment<PgEnvTrans>} env
@@ -1299,12 +1510,14 @@ async function syncTables(env, schema, query, tables, del) {
1299
1510
  const tableNames = tables.map(v => v.table);
1300
1511
  const dbTables = await loadTables(env, schema, query, tableNames);
1301
1512
  const tableMap = new Map(dbTables.map(v => [v.table, v]));
1302
- tables.map(table => {
1513
+ for (const table of tables) {
1303
1514
  const old = tableMap.get(table.table);
1304
- if (old) ; else {
1305
- return createNewTable(env, query, table);
1515
+ if (old) {
1516
+ await changeTable(env, query, table, old);
1517
+ } else {
1518
+ await createNewTable(env, query, table);
1306
1519
  }
1307
- });
1520
+ }
1308
1521
  }
1309
1522
 
1310
1523
  /** @import { Environment, IConnection } from 'imodel' */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imodel-pg",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "dependencies": {
5
5
  "pg": "^8.13.3",
6
6
  "tagged-sql": "^0.9.0"