Compare commits

..

2 Commits
3.3.13 ... dev

Author SHA1 Message Date
Pan Qiancheng c080b15078 fix: 完善DbStore类型,在init时自动创建扩展 2026-01-23 10:53:10 +08:00
Xu Chang ec085ddfd1 3.3.14-dev 2026-01-21 09:55:09 +08:00
5 changed files with 98 additions and 67 deletions

View File

@ -336,50 +336,62 @@ class PostgreSQLStore extends CascadeStore_1.CascadeStore {
await this.connector.disconnect(); await this.connector.disconnect();
} }
async initialize(option) { 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({ const txn = await this.connector.startTransaction({
isolationLevel: 'serializable', isolationLevel: 'serializable',
}); });
try { 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) { if (hasChineseTsConfig) {
console.log('Initializing Chinese text search configuration...'); console.log('Initializing Chinese text search configuration...');
const checkChineseConfigSql = ` const checkChineseConfigSql = `
SELECT COUNT(*) as cnt SELECT COUNT(*) as cnt
FROM pg_catalog.pg_ts_config FROM pg_catalog.pg_ts_config
WHERE cfgname = 'chinese'; 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); const count = parseInt(result[0][0]?.cnt || '0', 10);
if (count === 0) { if (count === 0) {
const createChineseConfigSql = ` const createChineseConfigSql = `
CREATE EXTENSION IF NOT EXISTS zhparser; CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = ${chineseParser || 'zhparser'});
CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser); ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l WITH simple;
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) { for (const entity in schema) {
const sqls = this.translator.translateCreateEntity(entity, option); const sqls = this.translator.translateCreateEntity(entity, option);
for (const sql of sqls) { for (const sql of sqls) {

View File

@ -16,6 +16,7 @@ export type Plan = {
updatedIndexes: Record<string, Index<any>[]>; updatedIndexes: Record<string, Index<any>[]>;
}; };
export interface DbStore<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>> extends AsyncRowStore<ED, Cxt> { export interface DbStore<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>> extends AsyncRowStore<ED, Cxt> {
checkRelationAsync<T extends keyof ED, Cxt extends AsyncContext<ED>>(entity: T, operation: Omit<ED[T]['Operation'] | ED[T]['Selection'], 'id'>, context: Cxt): Promise<void>;
connect: () => Promise<void>; connect: () => Promise<void>;
disconnect: () => Promise<void>; disconnect: () => Promise<void>;
initialize(options: CreateEntityOption): Promise<void>; initialize(options: CreateEntityOption): Promise<void>;

View File

@ -1,6 +1,6 @@
{ {
"name": "oak-db", "name": "oak-db",
"version": "3.3.13", "version": "3.3.14",
"description": "oak-db", "description": "oak-db",
"main": "lib/index", "main": "lib/index",
"author": { "author": {
@ -18,7 +18,7 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"mysql2": "^2.3.3", "mysql2": "^2.3.3",
"oak-domain": "^5.1.34", "oak-domain": "file:../oak-domain",
"pg": "^8.16.3", "pg": "^8.16.3",
"uuid": "^8.3.2" "uuid": "^8.3.2"
}, },

View File

@ -452,68 +452,85 @@ export class PostgreSQLStore<
} }
async initialize(option: CreateEntityOption) { 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({ const txn = await this.connector.startTransaction({
isolationLevel: 'serializable', isolationLevel: 'serializable',
}); });
try { 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) { if (hasChineseTsConfig) {
console.log('Initializing Chinese text search configuration...'); console.log('Initializing Chinese text search configuration...');
const checkChineseConfigSql = ` const checkChineseConfigSql = `
SELECT COUNT(*) as cnt SELECT COUNT(*) as cnt
FROM pg_catalog.pg_ts_config FROM pg_catalog.pg_ts_config
WHERE cfgname = 'chinese'; 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); const count = parseInt(result[0][0]?.cnt || '0', 10);
if (count === 0) { if (count === 0) {
const createChineseConfigSql = ` const createChineseConfigSql = `
CREATE EXTENSION IF NOT EXISTS zhparser; CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = ${chineseParser || 'zhparser'});
CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser); ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l WITH simple;
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) { for (const entity in schema) {
const sqls = this.translator.translateCreateEntity(entity, option); const sqls = this.translator.translateCreateEntity(entity, option);
for (const sql of sqls) { for (const sql of sqls) {
await this.connector.exec(sql, txn); await this.connector.exec(sql, txn);
} }
} }
await this.connector.commitTransaction(txn); await this.connector.commitTransaction(txn);
} catch (error) { } catch (error) {
await this.connector.rollbackTransaction(txn); await this.connector.rollbackTransaction(txn);
throw error; throw error;
} }
} }
// 从数据库中读取当前schema // 从数据库中读取当前schema
readSchema() { readSchema() {
return this.translator.readSchema((sql) => this.connector.exec(sql)); return this.translator.readSchema((sql) => this.connector.exec(sql));

View File

@ -24,6 +24,7 @@ export type Plan = {
}; };
export interface DbStore<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>> extends AsyncRowStore<ED, Cxt> { export interface DbStore<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>> extends AsyncRowStore<ED, Cxt> {
checkRelationAsync<T extends keyof ED, Cxt extends AsyncContext<ED>>(entity: T, operation: Omit<ED[T]['Operation'] | ED[T]['Selection'], 'id'>, context: Cxt): Promise<void>;
connect: () => Promise<void>; connect: () => Promise<void>;
disconnect: () => Promise<void>; disconnect: () => Promise<void>;
initialize(options: CreateEntityOption): Promise<void>; initialize(options: CreateEntityOption): Promise<void>;