diff --git a/lib/PostgreSQL/store.js b/lib/PostgreSQL/store.js index 12a2a54..714d1c0 100644 --- a/lib/PostgreSQL/store.js +++ b/lib/PostgreSQL/store.js @@ -336,50 +336,62 @@ class PostgreSQLStore extends CascadeStore_1.CascadeStore { await this.connector.disconnect(); } async initialize(option) { - // PG的DDL支持事务,所以这里直接用一个事务包裹所有的初始化操作 + const schema = this.getSchema(); + // ===== 第一阶段:事务外创建扩展 ===== + let hasGeoType = false; + let hasChineseTsConfig = false; + let chineseParser = null; + // 扫描 schema + for (const entity in schema) { + const { attributes, indexes } = schema[entity]; + for (const attr in attributes) { + if (attributes[attr].type === 'geometry') { + hasGeoType = true; + } + } + for (const index of indexes || []) { + if (index.config?.tsConfig === 'chinese' || index.config?.tsConfig?.includes('chinese')) { + hasChineseTsConfig = true; + } + if (index.config?.chineseParser) { + (0, assert_1.default)(!chineseParser || chineseParser === index.config.chineseParser, '当前定义了多个中文分词器,请保持一致'); + chineseParser = index.config.chineseParser; + } + } + } + // 在事务外创建扩展 + if (hasGeoType) { + console.log('Initializing PostGIS extension for geometry support...'); + await this.connector.exec('CREATE EXTENSION IF NOT EXISTS postgis;'); + } + if (hasChineseTsConfig) { + console.log('Initializing Chinese parser extension...'); + await this.connector.exec(`CREATE EXTENSION IF NOT EXISTS ${chineseParser || 'zhparser'};`); + } + // ===== 第二阶段:事务内创建配置和表 ===== const txn = await this.connector.startTransaction({ isolationLevel: 'serializable', }); try { - const schema = this.getSchema(); - let hasGeoType = false; - let hasChineseTsConfig = false; - for (const entity in schema) { - const { attributes, indexes } = schema[entity]; - for (const attr in attributes) { - const { type } = attributes[attr]; - if (type === 'geometry') { - hasGeoType = true; - } - } - for (const index of indexes || []) { - if (index.config?.tsConfig === 'chinese' || index.config?.tsConfig?.includes('chinese')) { - hasChineseTsConfig = true; - } - } - } - if (hasGeoType) { - console.log('Initializing PostGIS extension for geometry support...'); - await this.connector.exec('CREATE EXTENSION IF NOT EXISTS postgis;'); - } + // 创建中文文本搜索配置 if (hasChineseTsConfig) { console.log('Initializing Chinese text search configuration...'); const checkChineseConfigSql = ` - SELECT COUNT(*) as cnt - FROM pg_catalog.pg_ts_config - WHERE cfgname = 'chinese'; + SELECT COUNT(*) as cnt + FROM pg_catalog.pg_ts_config + WHERE cfgname = 'chinese'; `; - const result = await this.connector.exec(checkChineseConfigSql); + const result = await this.connector.exec(checkChineseConfigSql, txn); const count = parseInt(result[0][0]?.cnt || '0', 10); if (count === 0) { const createChineseConfigSql = ` - CREATE EXTENSION IF NOT EXISTS zhparser; - CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser); - ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l WITH simple; + CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = ${chineseParser || 'zhparser'}); + ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l WITH simple; `; - await this.connector.exec(createChineseConfigSql); + await this.connector.exec(createChineseConfigSql, txn); } } + // 创建实体表 for (const entity in schema) { const sqls = this.translator.translateCreateEntity(entity, option); for (const sql of sqls) { diff --git a/lib/types/dbStore.d.ts b/lib/types/dbStore.d.ts index 22ccc34..e48fa94 100644 --- a/lib/types/dbStore.d.ts +++ b/lib/types/dbStore.d.ts @@ -16,6 +16,7 @@ export type Plan = { updatedIndexes: Record[]>; }; export interface DbStore> extends AsyncRowStore { + checkRelationAsync>(entity: T, operation: Omit, context: Cxt): Promise; connect: () => Promise; disconnect: () => Promise; initialize(options: CreateEntityOption): Promise; diff --git a/src/PostgreSQL/store.ts b/src/PostgreSQL/store.ts index c66e797..0ae5a0c 100644 --- a/src/PostgreSQL/store.ts +++ b/src/PostgreSQL/store.ts @@ -452,68 +452,85 @@ export class PostgreSQLStore< } async initialize(option: CreateEntityOption) { - // PG的DDL支持事务,所以这里直接用一个事务包裹所有的初始化操作 + const schema = this.getSchema(); + + // ===== 第一阶段:事务外创建扩展 ===== + let hasGeoType = false; + let hasChineseTsConfig = false; + let chineseParser = null; + + // 扫描 schema + for (const entity in schema) { + const { attributes, indexes } = schema[entity]; + for (const attr in attributes) { + if (attributes[attr].type === 'geometry') { + hasGeoType = true; + } + } + for (const index of indexes || []) { + if (index.config?.tsConfig === 'chinese' || index.config?.tsConfig?.includes('chinese')) { + hasChineseTsConfig = true; + } + if (index.config?.chineseParser) { + assert(!chineseParser || chineseParser === index.config.chineseParser, + '当前定义了多个中文分词器,请保持一致'); + chineseParser = index.config.chineseParser; + } + } + } + + // 在事务外创建扩展 + if (hasGeoType) { + console.log('Initializing PostGIS extension for geometry support...'); + await this.connector.exec('CREATE EXTENSION IF NOT EXISTS postgis;'); + } + + if (hasChineseTsConfig) { + console.log('Initializing Chinese parser extension...'); + await this.connector.exec(`CREATE EXTENSION IF NOT EXISTS ${chineseParser || 'zhparser'};`); + } + + // ===== 第二阶段:事务内创建配置和表 ===== const txn = await this.connector.startTransaction({ isolationLevel: 'serializable', }); + try { - - const schema = this.getSchema(); - - let hasGeoType = false; - let hasChineseTsConfig = false; - - for (const entity in schema) { - const { attributes, indexes } = schema[entity]; - for (const attr in attributes) { - const { type } = attributes[attr]; - if (type === 'geometry') { - hasGeoType = true; - } - } - for (const index of indexes || []) { - if (index.config?.tsConfig === 'chinese' || index.config?.tsConfig?.includes('chinese')) { - hasChineseTsConfig = true; - } - } - } - - if (hasGeoType) { - console.log('Initializing PostGIS extension for geometry support...'); - await this.connector.exec('CREATE EXTENSION IF NOT EXISTS postgis;'); - } - + // 创建中文文本搜索配置 if (hasChineseTsConfig) { console.log('Initializing Chinese text search configuration...'); const checkChineseConfigSql = ` - SELECT COUNT(*) as cnt - FROM pg_catalog.pg_ts_config - WHERE cfgname = 'chinese'; + SELECT COUNT(*) as cnt + FROM pg_catalog.pg_ts_config + WHERE cfgname = 'chinese'; `; - const result = await this.connector.exec(checkChineseConfigSql); + const result = await this.connector.exec(checkChineseConfigSql, txn); const count = parseInt(result[0][0]?.cnt || '0', 10); + if (count === 0) { const createChineseConfigSql = ` - CREATE EXTENSION IF NOT EXISTS zhparser; - CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser); - ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l WITH simple; + CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = ${chineseParser || 'zhparser'}); + ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l WITH simple; `; - await this.connector.exec(createChineseConfigSql); + await this.connector.exec(createChineseConfigSql, txn); } } + // 创建实体表 for (const entity in schema) { const sqls = this.translator.translateCreateEntity(entity, option); for (const sql of sqls) { await this.connector.exec(sql, txn); } } + await this.connector.commitTransaction(txn); } catch (error) { await this.connector.rollbackTransaction(txn); throw error; } } + // 从数据库中读取当前schema readSchema() { return this.translator.readSchema((sql) => this.connector.exec(sql)); diff --git a/src/types/dbStore.ts b/src/types/dbStore.ts index 6264256..1c0a31a 100644 --- a/src/types/dbStore.ts +++ b/src/types/dbStore.ts @@ -24,6 +24,7 @@ export type Plan = { }; export interface DbStore> extends AsyncRowStore { + checkRelationAsync>(entity: T, operation: Omit, context: Cxt): Promise; connect: () => Promise; disconnect: () => Promise; initialize(options: CreateEntityOption): Promise;