oak-db 3.3.14 → 4.0.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.
@@ -3,10 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PostgreSQLTranslator = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const assert_1 = tslib_1.__importDefault(require("assert"));
6
+ const crypto_1 = require("crypto");
6
7
  const util_1 = require("util");
7
8
  const lodash_1 = require("lodash");
8
9
  const types_1 = require("oak-domain/lib/types");
9
10
  const sqlTranslator_1 = require("../sqlTranslator");
11
+ const indexName_1 = require("../utils/indexName");
10
12
  const GeoTypes = [
11
13
  {
12
14
  type: 'point',
@@ -96,10 +98,103 @@ function transformGeoData(data) {
96
98
  }
97
99
  }
98
100
  class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
101
+ maxIndexNameLength = 63;
102
+ physicalIndexNameCache;
103
+ makeIndexNameCacheKey(entityName, tableName, logicalName, suffix = '') {
104
+ return [entityName, tableName, logicalName, suffix].join('\u0000');
105
+ }
106
+ getIndexSuffixes(index) {
107
+ if (index.config?.type !== 'fulltext' || !Array.isArray(index.config.tsConfig)) {
108
+ return [''];
109
+ }
110
+ if (index.config.tsConfig.length <= 1) {
111
+ return [''];
112
+ }
113
+ return index.config.tsConfig.map((lang) => `_${lang}`);
114
+ }
115
+ ensurePhysicalIndexNameCache() {
116
+ if (this.physicalIndexNameCache) {
117
+ return this.physicalIndexNameCache;
118
+ }
119
+ const entries = [];
120
+ for (const entityName in this.schema) {
121
+ const tableName = this.schema[entityName].storageName || entityName;
122
+ const indexes = this.schema[entityName].indexes || [];
123
+ for (const index of indexes) {
124
+ for (const suffix of this.getIndexSuffixes(index)) {
125
+ const key = this.makeIndexNameCacheKey(entityName, tableName, index.name, suffix);
126
+ entries.push({
127
+ key,
128
+ baseName: (0, indexName_1.buildCompactPhysicalIndexName)({
129
+ entityName,
130
+ tableName,
131
+ logicalName: index.name,
132
+ suffix,
133
+ maxLength: this.maxIndexNameLength,
134
+ }),
135
+ });
136
+ }
137
+ }
138
+ }
139
+ const grouped = new Map();
140
+ entries.forEach(({ key, baseName }) => {
141
+ const bucket = grouped.get(baseName);
142
+ if (bucket) {
143
+ bucket.push(key);
144
+ }
145
+ else {
146
+ grouped.set(baseName, [key]);
147
+ }
148
+ });
149
+ const cache = new Map();
150
+ grouped.forEach((keys, baseName) => {
151
+ if (keys.length === 1) {
152
+ cache.set(keys[0], baseName);
153
+ return;
154
+ }
155
+ keys.sort();
156
+ keys.forEach((key) => {
157
+ const hash = (0, crypto_1.createHash)('sha1')
158
+ .update(key)
159
+ .digest('hex')
160
+ .slice(0, 8);
161
+ const prefixLength = Math.max(1, this.maxIndexNameLength - hash.length - 1);
162
+ cache.set(key, `${baseName.slice(0, prefixLength)}_${hash}`);
163
+ });
164
+ });
165
+ this.physicalIndexNameCache = cache;
166
+ return cache;
167
+ }
99
168
  // 生成 enum 类型名称
100
169
  getEnumTypeName(entity, attr) {
101
170
  return `${entity}_${attr}_enum`.toLowerCase();
102
171
  }
172
+ getPhysicalIndexName(entityName, tableName, logicalName, suffix = '') {
173
+ const key = this.makeIndexNameCacheKey(entityName, tableName, logicalName, suffix);
174
+ return this.ensurePhysicalIndexNameCache().get(key)
175
+ || (0, indexName_1.buildCompactPhysicalIndexName)({
176
+ entityName,
177
+ tableName,
178
+ logicalName,
179
+ suffix,
180
+ maxLength: this.maxIndexNameLength,
181
+ });
182
+ }
183
+ getLegacyPhysicalIndexNames(entityName, tableName, logicalName, suffix = '') {
184
+ const names = (0, indexName_1.buildLegacyPhysicalIndexNames)({
185
+ entityName,
186
+ tableName,
187
+ logicalName,
188
+ suffix,
189
+ maxLength: this.maxIndexNameLength,
190
+ serverTruncatesWhenOverflow: true,
191
+ });
192
+ const currentName = this.getPhysicalIndexName(entityName, tableName, logicalName, suffix);
193
+ if (!names.includes(currentName)) {
194
+ names.push(currentName);
195
+ }
196
+ return Array.from(new Set(names));
197
+ }
103
198
  /**
104
199
  * 将 MySQL 风格的 JSON 路径转换为 PostgreSQL 路径数组格式
105
200
  * 例如: ".foo.bar[0].baz" -> '{foo,bar,0,baz}'
@@ -954,7 +1049,7 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
954
1049
  indexSql += 'IF NOT EXISTS ';
955
1050
  }
956
1051
  // 索引名称(多语言时添加后缀)
957
- indexSql += `"${String(entity)}_${name}${suffix}" ON "${tableName}" `;
1052
+ indexSql += `"${this.getPhysicalIndexName(String(entity), tableName, name, suffix)}" ON "${tableName}" `;
958
1053
  // 索引方法
959
1054
  if (indexType === 'hash') {
960
1055
  indexSql += 'USING HASH ';
@@ -1469,14 +1564,6 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
1469
1564
  if (typeof indexFrom === 'number' && indexFrom > 0) {
1470
1565
  sql += ` OFFSET ${indexFrom}`;
1471
1566
  }
1472
- // FOR UPDATE 锁定
1473
- if (option?.forUpdate) {
1474
- sql += ' FOR UPDATE';
1475
- if (typeof option.forUpdate === 'string') {
1476
- // PostgreSQL 支持: NOWAIT, SKIP LOCKED, OF table_name
1477
- sql += ` ${option.forUpdate}`;
1478
- }
1479
- }
1480
1567
  return sql;
1481
1568
  }
1482
1569
  translateUpdate(entity, operation, option) {
@@ -2004,6 +2091,35 @@ class PostgreSQLTranslator extends sqlTranslator_1.SqlTranslator {
2004
2091
  }
2005
2092
  return sql;
2006
2093
  }
2094
+ translateColumnDefinition(entity, attr, attrDef) {
2095
+ let sql = `"${attr}" `;
2096
+ const { type, params, default: defaultValue, unique, notNull, sequenceStart, enumeration, } = attrDef;
2097
+ if (type === 'sequence' || (typeof sequenceStart === 'number')) {
2098
+ sql += `bigint GENERATED BY DEFAULT AS IDENTITY (START WITH ${sequenceStart || 10000}) UNIQUE`;
2099
+ return sql;
2100
+ }
2101
+ if (type === 'enum') {
2102
+ (0, assert_1.default)(enumeration, 'Enum type requires enumeration values');
2103
+ sql += `"${this.getEnumTypeName(entity, attr)}"`;
2104
+ }
2105
+ else {
2106
+ sql += this.populateDataTypeDef(type, params, enumeration);
2107
+ }
2108
+ if (notNull || type === 'geometry') {
2109
+ sql += ' NOT NULL';
2110
+ }
2111
+ if (unique) {
2112
+ sql += ' UNIQUE';
2113
+ }
2114
+ if (defaultValue !== undefined && !sequenceStart) {
2115
+ (0, assert_1.default)(type !== 'ref', 'ref type should not have default value');
2116
+ sql += ` DEFAULT ${this.translateAttrValue(type, defaultValue)}`;
2117
+ }
2118
+ if (attr === 'id') {
2119
+ sql += ' PRIMARY KEY';
2120
+ }
2121
+ return sql;
2122
+ }
2007
2123
  /**
2008
2124
  * 比较两个 SQL 语句是否等价(用于 schema diff)
2009
2125
  * 忽略空格、大小写等格式差异
package/lib/index.d.ts CHANGED
@@ -2,3 +2,4 @@ export * from './MySQL/store';
2
2
  export { MySqlSelectOption, MysqlOperateOption } from './MySQL/translator';
3
3
  export * from './PostgreSQL/store';
4
4
  export { PostgreSQLSelectOption, PostgreSQLOperateOption } from './PostgreSQL/translator';
5
+ export type { MigrationPlan, MigrationPlanningOptions, MigrationWarning, RenameCandidate, SchemaInspectionResult, TableStats, Plan, } from './types/migration';
@@ -0,0 +1,27 @@
1
+ import { Attribute } from 'oak-domain/lib/types';
2
+ import { AttributeChangeClassification, AttributeSnapshot, MigrationEntityDict, MigrationIndexWithOrigin, MigrationSchema, MigrationPlanningOptions, IndexSnapshot, MigrationPlan, SchemaMigrationAdapter } from './types/migration';
3
+ export declare function normalizeAttribute(attr: Attribute, options?: {
4
+ schema?: MigrationSchema;
5
+ column?: string;
6
+ dialect?: SchemaMigrationAdapter['dialect'];
7
+ compareForeignKeys?: boolean;
8
+ }): AttributeSnapshot;
9
+ export declare function normalizeIndex(index: MigrationIndexWithOrigin): IndexSnapshot;
10
+ export declare function areAttributesEquivalent(oldAttr: Attribute, newAttr: Attribute, options?: {
11
+ oldSchema?: MigrationSchema;
12
+ newSchema?: MigrationSchema;
13
+ column?: string;
14
+ oldColumn?: string;
15
+ newColumn?: string;
16
+ dialect?: SchemaMigrationAdapter['dialect'];
17
+ compareForeignKeys?: boolean;
18
+ }): boolean;
19
+ export declare function areIndexesEquivalent(oldIndex: MigrationIndexWithOrigin, newIndex: MigrationIndexWithOrigin): boolean;
20
+ export declare function classifyAttributeChange(oldAttr?: Attribute, newAttr?: Attribute, options?: {
21
+ oldSchema?: MigrationSchema;
22
+ newSchema?: MigrationSchema;
23
+ column?: string;
24
+ dialect?: SchemaMigrationAdapter['dialect'];
25
+ compareForeignKeys?: boolean;
26
+ }): AttributeChangeClassification;
27
+ export declare function buildMigrationPlan<ED extends MigrationEntityDict>(currentSchema: MigrationSchema<ED>, targetSchema: MigrationSchema<ED>, adapter: SchemaMigrationAdapter<ED>, options?: MigrationPlanningOptions): MigrationPlan<ED>;