fix: 修复了部分内置字段查询结果为string导致的上层转换问题
This commit is contained in:
parent
c5456b3fcb
commit
195e97b3d9
|
|
@ -8,6 +8,12 @@ const translator_1 = require("./translator");
|
|||
const lodash_1 = require("lodash");
|
||||
const assert_1 = tslib_1.__importDefault(require("assert"));
|
||||
const relation_1 = require("oak-domain/lib/store/relation");
|
||||
const ToNumberAttrs = new Set([
|
||||
'$$seq$$',
|
||||
'$$createAt$$',
|
||||
'$$updateAt$$',
|
||||
'$$deleteAt$$',
|
||||
]);
|
||||
function convertGeoTextToObject(geoText) {
|
||||
if (geoText.startsWith('POINT')) {
|
||||
const coord = geoText.match(/(-?\d+\.?\d*)/g);
|
||||
|
|
@ -203,6 +209,14 @@ class PostgreSQLStore extends CascadeStore_1.CascadeStore {
|
|||
// PostgreSQL count 返回字符串
|
||||
r[attr] = parseInt(value, 10);
|
||||
}
|
||||
else if (attr.startsWith("#sum") || attr.startsWith("#avg") || attr.startsWith("#min") || attr.startsWith("#max")) {
|
||||
// PostgreSQL sum/avg/min/max 返回字符串
|
||||
r[attr] = parseFloat(value);
|
||||
}
|
||||
else if (ToNumberAttrs.has(attr)) {
|
||||
// PostgreSQL sum/avg/min/max 返回字符串
|
||||
r[attr] = parseInt(value, 10);
|
||||
}
|
||||
else {
|
||||
r[attr] = value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import {
|
||||
EntityDict,
|
||||
OperateOption,
|
||||
OperationResult,
|
||||
TxnOption,
|
||||
StorageSchema,
|
||||
SelectOption,
|
||||
AggregationResult,
|
||||
Geo
|
||||
import {
|
||||
EntityDict,
|
||||
OperateOption,
|
||||
OperationResult,
|
||||
TxnOption,
|
||||
StorageSchema,
|
||||
SelectOption,
|
||||
AggregationResult,
|
||||
Geo
|
||||
} from 'oak-domain/lib/types';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { CascadeStore } from 'oak-domain/lib/store/CascadeStore';
|
||||
|
|
@ -22,6 +22,13 @@ import { CreateEntityOption } from '../types/Translator';
|
|||
import { QueryResult } from 'pg';
|
||||
import { DbStore } from '../types/dbStore';
|
||||
|
||||
const ToNumberAttrs = new Set([
|
||||
'$$seq$$',
|
||||
'$$createAt$$',
|
||||
'$$updateAt$$',
|
||||
'$$deleteAt$$',
|
||||
]);
|
||||
|
||||
function convertGeoTextToObject(geoText: string): Geo {
|
||||
if (geoText.startsWith('POINT')) {
|
||||
const coord = geoText.match(/(-?\d+\.?\d*)/g) as string[];
|
||||
|
|
@ -59,119 +66,119 @@ function convertGeoTextToObject(geoText: string): Geo {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
throw new Error(`Unsupported geometry type: ${geoText.slice(0, 50)}`);
|
||||
}
|
||||
|
||||
export class PostgreSQLStore<
|
||||
ED extends EntityDict & BaseEntityDict,
|
||||
ED extends EntityDict & BaseEntityDict,
|
||||
Cxt extends AsyncContext<ED>
|
||||
> extends CascadeStore<ED> implements DbStore<ED, Cxt> {
|
||||
|
||||
|
||||
protected countAbjointRow<T extends keyof ED, OP extends SelectOption, Cxt extends SyncContext<ED>>(
|
||||
entity: T,
|
||||
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
|
||||
context: Cxt,
|
||||
entity: T,
|
||||
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
|
||||
context: Cxt,
|
||||
option: OP
|
||||
): number {
|
||||
throw new Error('PostgreSQL store 不支持同步取数据');
|
||||
}
|
||||
|
||||
|
||||
protected aggregateAbjointRowSync<T extends keyof ED, OP extends SelectOption, Cxt extends SyncContext<ED>>(
|
||||
entity: T,
|
||||
aggregation: ED[T]['Aggregation'],
|
||||
context: Cxt,
|
||||
entity: T,
|
||||
aggregation: ED[T]['Aggregation'],
|
||||
context: Cxt,
|
||||
option: OP
|
||||
): AggregationResult<ED[T]['Schema']> {
|
||||
throw new Error('PostgreSQL store 不支持同步取数据');
|
||||
}
|
||||
|
||||
|
||||
protected selectAbjointRow<T extends keyof ED, OP extends SelectOption>(
|
||||
entity: T,
|
||||
selection: ED[T]['Selection'],
|
||||
context: SyncContext<ED>,
|
||||
entity: T,
|
||||
selection: ED[T]['Selection'],
|
||||
context: SyncContext<ED>,
|
||||
option: OP
|
||||
): Partial<ED[T]['Schema']>[] {
|
||||
throw new Error('PostgreSQL store 不支持同步取数据');
|
||||
}
|
||||
|
||||
|
||||
protected updateAbjointRow<T extends keyof ED, OP extends OperateOption>(
|
||||
entity: T,
|
||||
operation: ED[T]['Operation'],
|
||||
context: SyncContext<ED>,
|
||||
entity: T,
|
||||
operation: ED[T]['Operation'],
|
||||
context: SyncContext<ED>,
|
||||
option: OP
|
||||
): number {
|
||||
throw new Error('PostgreSQL store 不支持同步更新数据');
|
||||
}
|
||||
|
||||
|
||||
async exec(script: string, txnId?: string) {
|
||||
await this.connector.exec(script, txnId);
|
||||
}
|
||||
|
||||
|
||||
connector: PostgreSQLConnector;
|
||||
translator: PostgreSQLTranslator<ED>;
|
||||
|
||||
|
||||
constructor(storageSchema: StorageSchema<ED>, configuration: PostgreSQLConfiguration) {
|
||||
super(storageSchema);
|
||||
this.connector = new PostgreSQLConnector(configuration);
|
||||
this.translator = new PostgreSQLTranslator(storageSchema);
|
||||
}
|
||||
|
||||
|
||||
checkRelationAsync<T extends keyof ED, Cxt extends AsyncContext<ED>>(
|
||||
entity: T,
|
||||
operation: Omit<ED[T]['Operation'] | ED[T]['Selection'], 'id'>,
|
||||
entity: T,
|
||||
operation: Omit<ED[T]['Operation'] | ED[T]['Selection'], 'id'>,
|
||||
context: Cxt
|
||||
): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
|
||||
protected async aggregateAbjointRowAsync<T extends keyof ED, OP extends SelectOption, Cxt extends AsyncContext<ED>>(
|
||||
entity: T,
|
||||
aggregation: ED[T]['Aggregation'],
|
||||
context: Cxt,
|
||||
entity: T,
|
||||
aggregation: ED[T]['Aggregation'],
|
||||
context: Cxt,
|
||||
option: OP
|
||||
): Promise<AggregationResult<ED[T]['Schema']>> {
|
||||
const sql = this.translator.translateAggregate(entity, aggregation, option);
|
||||
const result = await this.connector.exec(sql, context.getCurrentTxnId());
|
||||
return this.formResult(entity, result[0]);
|
||||
}
|
||||
|
||||
|
||||
aggregate<T extends keyof ED, OP extends SelectOption>(
|
||||
entity: T,
|
||||
aggregation: ED[T]['Aggregation'],
|
||||
context: Cxt,
|
||||
entity: T,
|
||||
aggregation: ED[T]['Aggregation'],
|
||||
context: Cxt,
|
||||
option: OP
|
||||
): Promise<AggregationResult<ED[T]['Schema']>> {
|
||||
return this.aggregateAsync(entity, aggregation, context, option);
|
||||
}
|
||||
|
||||
|
||||
protected supportManyToOneJoin(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected supportMultipleCreate(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private formResult<T extends keyof ED>(entity: T, result: any): any {
|
||||
const schema = this.getSchema();
|
||||
|
||||
|
||||
function resolveAttribute<E extends keyof ED>(
|
||||
entity2: E,
|
||||
r: Record<string, any>,
|
||||
attr: string,
|
||||
entity2: E,
|
||||
r: Record<string, any>,
|
||||
attr: string,
|
||||
value: any
|
||||
) {
|
||||
const { attributes, view } = schema[entity2];
|
||||
|
||||
|
||||
if (!view) {
|
||||
const i = attr.indexOf(".");
|
||||
|
||||
|
||||
if (i !== -1) {
|
||||
const attrHead = attr.slice(0, i);
|
||||
const attrTail = attr.slice(i + 1);
|
||||
const rel = judgeRelation(schema, entity2, attrHead);
|
||||
|
||||
|
||||
if (rel === 1) {
|
||||
set(r, attr, value);
|
||||
} else {
|
||||
|
|
@ -190,7 +197,7 @@ export class PostgreSQLStore<
|
|||
}
|
||||
} else if (attributes[attr]) {
|
||||
const { type } = attributes[attr];
|
||||
|
||||
|
||||
switch (type) {
|
||||
case 'date':
|
||||
case 'time': {
|
||||
|
|
@ -266,6 +273,14 @@ export class PostgreSQLStore<
|
|||
} else if (attr.startsWith("#count")) {
|
||||
// PostgreSQL count 返回字符串
|
||||
r[attr] = parseInt(value, 10);
|
||||
}
|
||||
else if (attr.startsWith("#sum") || attr.startsWith("#avg") || attr.startsWith("#min") || attr.startsWith("#max")) {
|
||||
// PostgreSQL sum/avg/min/max 返回字符串
|
||||
r[attr] = parseFloat(value);
|
||||
}
|
||||
else if (ToNumberAttrs.has(attr)) {
|
||||
// PostgreSQL sum/avg/min/max 返回字符串
|
||||
r[attr] = parseInt(value, 10);
|
||||
} else {
|
||||
r[attr] = value;
|
||||
}
|
||||
|
|
@ -281,7 +296,7 @@ export class PostgreSQLStore<
|
|||
function removeNullObjects<E extends keyof ED>(r: Record<string, any>, e: E) {
|
||||
for (let attr in r) {
|
||||
const rel = judgeRelation(schema, e, attr);
|
||||
|
||||
|
||||
if (rel === 2) {
|
||||
if (r[attr].id === null) {
|
||||
assert(schema[e].toModi || r.entity !== attr);
|
||||
|
|
@ -305,7 +320,7 @@ export class PostgreSQLStore<
|
|||
|
||||
function formSingleRow(r: any): any {
|
||||
let result2: Record<string, any> = {};
|
||||
|
||||
|
||||
for (let attr in r) {
|
||||
const value = r[attr];
|
||||
resolveAttribute(entity, result2, attr, value);
|
||||
|
|
@ -320,7 +335,7 @@ export class PostgreSQLStore<
|
|||
}
|
||||
return formSingleRow(result);
|
||||
}
|
||||
|
||||
|
||||
protected async selectAbjointRowAsync<T extends keyof ED>(
|
||||
entity: T,
|
||||
selection: ED[T]['Selection'],
|
||||
|
|
@ -331,7 +346,7 @@ export class PostgreSQLStore<
|
|||
const result = await this.connector.exec(sql, context.getCurrentTxnId());
|
||||
return this.formResult(entity, result[0]);
|
||||
}
|
||||
|
||||
|
||||
protected async updateAbjointRowAsync<T extends keyof ED>(
|
||||
entity: T,
|
||||
operation: ED[T]['Operation'],
|
||||
|
|
@ -363,76 +378,76 @@ export class PostgreSQLStore<
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async operate<T extends keyof ED>(
|
||||
entity: T,
|
||||
operation: ED[T]['Operation'],
|
||||
context: Cxt,
|
||||
entity: T,
|
||||
operation: ED[T]['Operation'],
|
||||
context: Cxt,
|
||||
option: OperateOption
|
||||
): Promise<OperationResult<ED>> {
|
||||
const { action } = operation;
|
||||
assert(!['select', 'download', 'stat'].includes(action), '不支持使用 select operation');
|
||||
return await super.operateAsync(entity, operation as any, context, option);
|
||||
}
|
||||
|
||||
|
||||
async select<T extends keyof ED>(
|
||||
entity: T,
|
||||
selection: ED[T]['Selection'],
|
||||
context: Cxt,
|
||||
entity: T,
|
||||
selection: ED[T]['Selection'],
|
||||
context: Cxt,
|
||||
option: SelectOption
|
||||
): Promise<Partial<ED[T]['Schema']>[]> {
|
||||
return await super.selectAsync(entity, selection, context, option);
|
||||
}
|
||||
|
||||
|
||||
protected async countAbjointRowAsync<T extends keyof ED>(
|
||||
entity: T,
|
||||
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
|
||||
context: AsyncContext<ED>,
|
||||
entity: T,
|
||||
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
|
||||
context: AsyncContext<ED>,
|
||||
option: SelectOption
|
||||
): Promise<number> {
|
||||
const sql = this.translator.translateCount(entity, selection, option);
|
||||
const result = await this.connector.exec(sql, context.getCurrentTxnId());
|
||||
|
||||
|
||||
// PostgreSQL 返回的 count 是 string 类型(bigint)
|
||||
const cnt = result[0][0]?.cnt;
|
||||
return typeof cnt === 'string' ? parseInt(cnt, 10) : (cnt || 0);
|
||||
}
|
||||
|
||||
|
||||
async count<T extends keyof ED>(
|
||||
entity: T,
|
||||
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
|
||||
context: Cxt,
|
||||
entity: T,
|
||||
selection: Pick<ED[T]['Selection'], 'filter' | 'count'>,
|
||||
context: Cxt,
|
||||
option: SelectOption
|
||||
) {
|
||||
return this.countAsync(entity, selection, context, option);
|
||||
}
|
||||
|
||||
|
||||
async begin(option?: TxnOption): Promise<string> {
|
||||
return await this.connector.startTransaction(option);
|
||||
}
|
||||
|
||||
|
||||
async commit(txnId: string): Promise<void> {
|
||||
await this.connector.commitTransaction(txnId);
|
||||
}
|
||||
|
||||
|
||||
async rollback(txnId: string): Promise<void> {
|
||||
await this.connector.rollbackTransaction(txnId);
|
||||
}
|
||||
|
||||
|
||||
async connect() {
|
||||
await this.connector.connect();
|
||||
}
|
||||
|
||||
|
||||
async disconnect() {
|
||||
await this.connector.disconnect();
|
||||
}
|
||||
|
||||
|
||||
async initialize(option: CreateEntityOption) {
|
||||
const schema = this.getSchema();
|
||||
|
||||
|
||||
// 可选:先创建 PostGIS 扩展
|
||||
// await this.connector.exec('CREATE EXTENSION IF NOT EXISTS postgis;');
|
||||
|
||||
|
||||
for (const entity in schema) {
|
||||
const sqls = this.translator.translateCreateEntity(entity, option);
|
||||
for (const sql of sqls) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue