增加虚拟列;

修复查询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

@ -1,6 +1,6 @@
{ {
"name": "@yizhi/postgres", "name": "@yizhi/postgres",
"version": "1.0.1", "version": "1.0.4",
"main": "dist/index.js", "main": "dist/index.js",
"types": "typing/index.d.ts", "types": "typing/index.d.ts",
"scripts": {}, "scripts": {},

View File

@ -11,6 +11,8 @@ export interface IEntityFieldConfig {
prop: string prop: string
/** 是否是主键 */ /** 是否是主键 */
primary: boolean primary: boolean
/** 是否是虚拟字段 */
virtual: boolean
/** 编码方式(存入数据库时) */ /** 编码方式(存入数据库时) */
encode: (data: any) => string encode: (data: any) => string
/** 解码方式(查询数据库时) */ /** 解码方式(查询数据库时) */
@ -134,11 +136,12 @@ export function Table(table: string): Decorators.ClassDecorator<BasicEntity> {
* @param encode 字段编码凡是 * @param encode 字段编码凡是
* @param decode 字段解码方式 * @param decode 字段解码方式
* @param primary 是否是主键 * @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) { return function (target, propertyKey) {
const conf = entityConfigOf(target.constructor) 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 name 字段名称
* @param option 字段选项 * @param option 字段选项
*/ */
export function IntColumn(name: string, 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 }): Decorators.PropDecorator<BasicEntity> export function IntColumn(option?: { primary: boolean, virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function IntColumn(name?: any, option?: any) { export function IntColumn(name?: any, option?: any) {
if (typeof name != "string") { if (typeof name != "string") {
option = name; option = name;
name = undefined; 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 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 name 字段名称
* @param option 字段选项 * @param option 字段选项
*/ */
export function StringColumn(name: string, option?: { primary?: boolean }): Decorators.PropDecorator<BasicEntity> export function StringColumn(option?: { primary?: boolean, virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function StringColumn(option?: { primary?: boolean }): Decorators.PropDecorator<BasicEntity> export function StringColumn(name: string, option?: { primary?: boolean, virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function StringColumn(name?: any, option?: any) { export function StringColumn(name?: any, option?: any) {
if (typeof name != "string") { if (typeof name != "string") {
option = name option = name
name = undefined 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 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) => { const checker = (v: any) => {
if (typeof v == "boolean") return v; if (typeof v == "boolean") return v;
if (typeof v != "string") return false; if (typeof v != "string") return false;
@ -193,14 +214,22 @@ export function BooleanColumn(name?: string) {
return ["true", "yes", "on"].includes(v) 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 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 => { return Field(name, v => {
if (v instanceof DateColumn) v = moment(v); if (v instanceof DateColumn) v = moment(v);
if (typeof v == "string") v = moment(v); if (typeof v == "string") v = moment(v);
@ -209,14 +238,21 @@ export function DateColumn(name?: string) {
}, v => { }, v => {
if (typeof v == "string" || (v instanceof global.Date)) return moment(v).format("YYYY-MM-DD"); if (typeof v == "string" || (v instanceof global.Date)) return moment(v).format("YYYY-MM-DD");
return null; return null;
}, false); }, false, option?.virtual ?? false);
} }
/** /**
* 定义日期时间字段 * 定义日期时间字段
* @param name 字段名称 * @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 => { return Field(name, v => {
if (v instanceof DateColumn) v = moment(v); if (v instanceof DateColumn) v = moment(v);
if (typeof v == "string") v = moment(v); if (typeof v == "string") v = moment(v);
@ -225,12 +261,12 @@ export function DateTimeColumn(name?: string) {
}, v => { }, v => {
if (typeof v == "string" || (v instanceof global.Date)) return moment(v).format("YYYY-MM-DD HH:mm:ss"); if (typeof v == "string" || (v instanceof global.Date)) return moment(v).format("YYYY-MM-DD HH:mm:ss");
return null; 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 => { return Field(name, v => {
if (!(v instanceof Array)) throw new DatabaseError("DB_ENTITY_FIELD_NOT_ARRAY", `字段${name}需要给定一个数组`); if (!(v instanceof Array)) throw new DatabaseError("DB_ENTITY_FIELD_NOT_ARRAY", `字段${name}需要给定一个数组`);
return escapeValue(`{${v.map(item => itemEncoder(item)).join(",")}}`) + `::${typeName}`; 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 (!(v instanceof Array)) return null;
if (itemDecoder) return v.map(item => itemDecoder(item)); if (itemDecoder) return v.map(item => itemDecoder(item));
else return v; else return v;
}, false); }, false, virtual);
} }
/** /**
@ -246,8 +282,8 @@ function _Array<T>(name: string | undefined, typeName: string, itemEncoder: (v:
* @param name 字段名称 * @param name 字段名称
* @param option 字段选项 * @param option 字段选项
*/ */
export function IntArrayColumn(name: string, 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 }): Decorators.PropDecorator<BasicEntity> export function IntArrayColumn(option?: { bytes?: 2 | 4 | 8, virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
export function IntArrayColumn(name?: any, option?: any) { export function IntArrayColumn(name?: any, option?: any) {
if (name && typeof name !== "string") { if (name && typeof name !== "string") {
option = name option = name
@ -262,26 +298,41 @@ export function IntArrayColumn(name?: any, option?: any) {
item = parseInt(item); item = parseInt(item);
if (isNaN(item) || !isFinite(item)) return null; if (isNaN(item) || !isFinite(item)) return null;
return item; return item;
}); }, option?.virtual ?? false);
} }
/** /**
* 定义字符串数组 * 定义字符串数组
* @param name 字段名称 * @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 => { return _Array(name, "varchar[]", item => {
if (typeof item == "string") return item; if (typeof item == "string") return item;
else return null; else return null;
}, item => item); }, item => item, option?.virtual ?? false);
} }
/** /**
* 定义jsonb字段 * 定义jsonb字段
* @param name 字段名称 * @param name 字段名称
*/ */
export function JsonColumn(name?: string) { export function JsonColumn(option?: { virtual?: boolean }): Decorators.PropDecorator<BasicEntity>
return Field(name, v => escapeValue(JSON.stringify(v)) + "::jsonb", v => v, false); 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 => { return Field(name, v => {
if (!(v instanceof TSVector)) throw new DatabaseError("DB_ENTITY_FIELD_NOT_TSVECTOR", `字段${name}需要给定一个TSVector`) if (!(v instanceof TSVector)) throw new DatabaseError("DB_ENTITY_FIELD_NOT_TSVECTOR", `字段${name}需要给定一个TSVector`)
return `E'${v.value}'::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, TSVectorColumn,
JoinOne, JoinMany, JoinOne, JoinMany,
BasicEntity, BasicEntity,
IEntityConfig,
} from "./entity"; } from "./entity";
export {
IInsertBuilder,
IUpdateBuilder,
IDeleteBuilder,
ISelectBuilder,
} from "./query";
export * from "./type"; export * from "./type";
export * from "./database"; export * from "./database";
export * from "./error"; export * from "./error";

View File

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