增加虚拟列;

修复查询bug;
UpdateBuilder和InsertBuilder增加自定义值
This commit is contained in:
2024-11-06 10:52:50 +08:00
parent 592f9f2c40
commit 803ca64276
4 changed files with 97 additions and 33 deletions

View File

@ -11,6 +11,8 @@ export interface IEntityFieldConfig {
prop: string
/** 是否是主键 */
primary: boolean
/** 是否是虚拟字段 */
virtual: boolean
/** 编码方式(存入数据库时) */
encode: (data: any) => string
/** 解码方式(查询数据库时) */
@ -134,11 +136,12 @@ export function Table(table: string): Decorators.ClassDecorator<BasicEntity> {
* @param encode 字段编码凡是
* @param decode 字段解码方式
* @param primary 是否是主键
* @param virtual 是否是虚拟字段
*/
export function Field(name: string | undefined, encode: IEntityFieldConfig["encode"], decode: IEntityFieldConfig["decode"], primary: boolean): Decorators.PropDecorator<BasicEntity> {
export function Field(name: string | undefined, encode: IEntityFieldConfig["encode"], decode: IEntityFieldConfig["decode"], primary: boolean, virtual: boolean): Decorators.PropDecorator<BasicEntity> {
return function (target, propertyKey) {
const conf = entityConfigOf(target.constructor)
conf.fields.push({ name: name ?? propertyKey, prop: propertyKey, encode, decode, primary });
conf.fields.push({ name: name ?? propertyKey, prop: propertyKey, encode, decode, primary, virtual });
}
}
@ -148,22 +151,32 @@ export function Field(name: string | undefined, encode: IEntityFieldConfig["enco
* @param name 字段名称
* @param option 字段选项
*/
export function IntColumn(name: string, option?: { primary: boolean }): Decorators.PropDecorator<BasicEntity>
export function IntColumn(option?: { primary: boolean }): Decorators.PropDecorator<BasicEntity>
export function IntColumn(name: string, option?: { primary: boolean, virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function IntColumn(option?: { primary: boolean, virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function IntColumn(name?: any, option?: any) {
if (typeof name != "string") {
option = name;
name = undefined;
}
return Field(name, v => escapeValue(v), v => v, option?.primary ?? false);
return Field(name, v => escapeValue(v), v => v, option?.primary ?? false, option?.virtual ?? false);
}
/**
* 定义浮点数(包括定点小数)
* @param name 字段名称
* @param option 字段选项
*/
export function FloatColumn(name?: string) { return Field(name, v => escapeValue(v), v => v, false); }
export function FloatColumn(option?: { virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function FloatColumn(name: string, option?: { virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function FloatColumn(name?: any, option?: any) {
if (typeof name !== "string") {
option = name;
name = undefined;
}
return Field(name, v => escapeValue(v), v => v, false, option?.virtual ?? false);
}
/**
@ -171,21 +184,29 @@ export function FloatColumn(name?: string) { return Field(name, v => escapeValue
* @param name 字段名称
* @param option 字段选项
*/
export function StringColumn(name: string, option?: { primary?: boolean }): Decorators.PropDecorator<BasicEntity>
export function StringColumn(option?: { primary?: boolean }): Decorators.PropDecorator<BasicEntity>
export function StringColumn(option?: { primary?: boolean, virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function StringColumn(name: string, option?: { primary?: boolean, virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function StringColumn(name?: any, option?: any) {
if (typeof name != "string") {
option = name
name = undefined
}
return Field(name, v => escapeValue(v), v => v, option?.primary ?? false);
return Field(name, v => escapeValue(v), v => v, option?.primary ?? false, option?.virtual ?? false);
}
/**
* 定义布尔字段
* @param name 字段名称
* @param option 字段选项
*/
export function BooleanColumn(name?: string) {
export function BooleanColumn(option?: { virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function BooleanColumn(name: string, option?: { virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function BooleanColumn(name?: any, option?: any) {
if (typeof name != "string") {
option = name
name = undefined
}
const checker = (v: any) => {
if (typeof v == "boolean") return v;
if (typeof v != "string") return false;
@ -193,14 +214,22 @@ export function BooleanColumn(name?: string) {
return ["true", "yes", "on"].includes(v)
}
return Field(name, (v) => escapeValue(checker(v)), checker, false);
return Field(name, (v) => escapeValue(checker(v)), checker, false, option?.virtual ?? false);
}
/**
* 定义日期字段
* @param name 字段名称
* @param option 字段选项
*/
export function DateColumn(name?: string) {
export function DateColumn(option?: { virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function DateColumn(name: string, option?: { virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function DateColumn(name?: any, option?: any) {
if (typeof name != "string") {
option = name
name = undefined
}
return Field(name, v => {
if (v instanceof DateColumn) v = moment(v);
if (typeof v == "string") v = moment(v);
@ -209,14 +238,21 @@ export function DateColumn(name?: string) {
}, v => {
if (typeof v == "string" || (v instanceof global.Date)) return moment(v).format("YYYY-MM-DD");
return null;
}, false);
}, false, option?.virtual ?? false);
}
/**
* 定义日期时间字段
* @param name 字段名称
*/
export function DateTimeColumn(name?: string) {
export function DateTimeColumn(option?: { virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function DateTimeColumn(name: string, option?: { virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function DateTimeColumn(name?: any, option?: any) {
if (typeof name != "string") {
option = name
name = undefined
}
return Field(name, v => {
if (v instanceof DateColumn) v = moment(v);
if (typeof v == "string") v = moment(v);
@ -225,12 +261,12 @@ export function DateTimeColumn(name?: string) {
}, v => {
if (typeof v == "string" || (v instanceof global.Date)) return moment(v).format("YYYY-MM-DD HH:mm:ss");
return null;
}, false);
}, false, option?.virtual ?? false);
}
//数组定义
function _Array<T>(name: string | undefined, typeName: string, itemEncoder: (v: any) => (string | null), itemDecoder: ((v: any) => (T | null)) | undefined) {
function _Array<T>(name: string | undefined, typeName: string, itemEncoder: (v: any) => (string | null), itemDecoder: ((v: any) => (T | null)) | undefined, virtual: boolean) {
return Field(name, v => {
if (!(v instanceof Array)) throw new DatabaseError("DB_ENTITY_FIELD_NOT_ARRAY", `字段${name}需要给定一个数组`);
return escapeValue(`{${v.map(item => itemEncoder(item)).join(",")}}`) + `::${typeName}`;
@ -238,7 +274,7 @@ function _Array<T>(name: string | undefined, typeName: string, itemEncoder: (v:
if (!(v instanceof Array)) return null;
if (itemDecoder) return v.map(item => itemDecoder(item));
else return v;
}, false);
}, false, virtual);
}
/**
@ -246,8 +282,8 @@ function _Array<T>(name: string | undefined, typeName: string, itemEncoder: (v:
* @param name 字段名称
* @param option 字段选项
*/
export function IntArrayColumn(name: string, option?: { bytes?: 2 | 4 | 8 }): Decorators.PropDecorator<BasicEntity>
export function IntArrayColumn(option?: { bytes?: 2 | 4 | 8 }): Decorators.PropDecorator<BasicEntity>
export function IntArrayColumn(name: string, option?: { bytes?: 2 | 4 | 8, virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function IntArrayColumn(option?: { bytes?: 2 | 4 | 8, virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function IntArrayColumn(name?: any, option?: any) {
if (name && typeof name !== "string") {
option = name
@ -262,26 +298,41 @@ export function IntArrayColumn(name?: any, option?: any) {
item = parseInt(item);
if (isNaN(item) || !isFinite(item)) return null;
return item;
});
}, option?.virtual ?? false);
}
/**
* 定义字符串数组
* @param name 字段名称
* @param option 字段选项
*/
export function StringArrayColumn(name?: string) {
export function StringArrayColumn(option?: { virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function StringArrayColumn(name: string, option?: { virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function StringArrayColumn(name?: any, option?: any) {
if (typeof name != "string") {
option = name
name = undefined
}
return _Array(name, "varchar[]", item => {
if (typeof item == "string") return item;
else return null;
}, item => item);
}, item => item, option?.virtual ?? false);
}
/**
* 定义jsonb字段
* @param name 字段名称
*/
export function JsonColumn(name?: string) {
return Field(name, v => escapeValue(JSON.stringify(v)) + "::jsonb", v => v, false);
export function JsonColumn(option?: { virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function JsonColumn(name: string, option?: { virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function JsonColumn(name?: any, option?: any) {
if (typeof name != "string") {
option = name
name = undefined
}
return Field(name, v => escapeValue(JSON.stringify(v)) + "::jsonb", v => v, false, option?.virtual ?? false);
}
/**
@ -292,7 +343,7 @@ export function TSVectorColumn(name?: string) {
return Field(name, v => {
if (!(v instanceof TSVector)) throw new DatabaseError("DB_ENTITY_FIELD_NOT_TSVECTOR", `字段${name}需要给定一个TSVector`)
return `E'${v.value}'::tsvector`;
}, v => TSVector.fromValue(v), false);
}, v => TSVector.fromValue(v), false, false);
}
//获取实体配置

View File

@ -5,8 +5,16 @@ export {
TSVectorColumn,
JoinOne, JoinMany,
BasicEntity,
IEntityConfig,
} from "./entity";
export {
IInsertBuilder,
IUpdateBuilder,
IDeleteBuilder,
ISelectBuilder,
} from "./query";
export * from "./type";
export * from "./database";
export * from "./error";

View File

@ -23,6 +23,7 @@ interface IQueryFunc<R extends QueryResultRow = any> {
(sql: string, args?: any[]): Promise<QueryResult<R>>
}
type DataOrCustom<T> = T | (() => string);
type StringArgs<Str extends string, P extends string, S extends string> = Str extends `${string}${P}${infer R}${S}${infer T}` ? (R | StringArgs<T, P, S>) : never;
@ -189,7 +190,7 @@ export interface IInsertBuilder<E extends BasicEntity> extends IReturningFn<E> {
* 设置要插入的数据
* @param data 要插入的数据
*/
data<Name extends BasicFieldName<E>>(data: Pick<E, Name> | Pick<E, Name>[]): this;
data<Name extends BasicFieldName<E>>(data: { [P in Name]: DataOrCustom<E[P]> } | Array<{ [P in Name]: DataOrCustom<E[P]> }>): this;
/** 执行插入 */
query(): Promise<E[]>
@ -200,7 +201,7 @@ export interface IUpdateBuilder<E extends BasicEntity> extends IWhereFn<E>, IRet
* 设置更新数据
* @param data 要更新的数据
*/
set(data: Partial<Pick<E, BasicFieldName<E>>>): this
set(data: { [P in BasicFieldName<E>]?: DataOrCustom<E[P]> }): this
/** 执行更新 */
query(): Promise<E[]>
@ -435,7 +436,7 @@ function buildCondition<E extends BasicEntity>(builder: SQLBuilder<E>, option: C
wheres.push(`${fieldName}<${field.encode(opv)}`);
break;
case "lte":
wheres.push(`${fieldName}<=${field.encode(topVal)}`);
wheres.push(`${fieldName}<=${field.encode(opv)}`);
break;
case "like":
wheres.push(`${fieldName} like ${escapeValue(opv)}`);
@ -754,7 +755,7 @@ export class InsertBuilder<E extends BasicEntity> extends WithReturning(SQLBuild
this.#query = query;
}
public data<Name extends BasicFieldName<E>>(data: Pick<E, Name> | Pick<E, Name>[]) {
public data<Name extends BasicFieldName<E>>(data: { [P in Name]: DataOrCustom<E[P]> } | Array<{ [P in Name]: DataOrCustom<E[P]> }>) {
data = (data instanceof Array) ? data : [data];
this.#values = [];
this.#columns = [];
@ -763,9 +764,11 @@ export class InsertBuilder<E extends BasicEntity> extends WithReturning(SQLBuild
for (const item of data) {
const buffer: (string | null)[] = [];
for (const col of this.config.fields) {
if (col.virtual) continue;
if (!(col.prop in item)) continue;
const val = item[col.prop as keyof typeof item];
if (val === null || val === undefined) buffer.push("null");
else if (typeof val === "function") buffer.push(val());
else buffer.push(col.encode(val));
if (!columnsSet) this.#columns.push(escapeID(col.name));
}
@ -801,12 +804,14 @@ export class UpdateBuilder<E extends BasicEntity> extends WithReturning(WithWher
this.#query = query;
}
public set(data: Partial<Pick<E, BasicFieldName<E>>>) {
public set(data: { [P in BasicFieldName<E>]?: DataOrCustom<E[P]> }) {
this.#updates = []
for (const col of this.config.fields) {
if (col.virtual) continue;
const val = data[col.prop as keyof typeof data];
if (val === undefined) continue;
if (val === null) this.#updates.push(`${escapeID(col.name)}=null`);
else if (val === null) this.#updates.push(`${escapeID(col.name)}=null`);
else if (typeof val === "function") this.#updates.push(`${escapeID(col.name)}=${val()}`);
else this.#updates.push(`${escapeID(col.name)}=${col.encode(val)}`);
}
return this;