ModelQuery: fix field disambiguation
This commit is contained in:
parent
9e38b003f9
commit
e403dfa863
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wms-core",
|
"name": "wms-core",
|
||||||
"version": "0.21.13-rc.1",
|
"version": "0.21.13-rc.2",
|
||||||
"description": "Node web application framework and toolbelt.",
|
"description": "Node web application framework and toolbelt.",
|
||||||
"repository": "https://gitlab.com/ArisuOngaku/wms-core",
|
"repository": "https://gitlab.com/ArisuOngaku/wms-core",
|
||||||
"author": "Alice Gaudon <alice@gaudon.pro>",
|
"author": "Alice Gaudon <alice@gaudon.pro>",
|
||||||
|
@ -5,7 +5,7 @@ import ModelComponent from "./ModelComponent";
|
|||||||
import {Type} from "../Utils";
|
import {Type} from "../Utils";
|
||||||
import ModelFactory from "./ModelFactory";
|
import ModelFactory from "./ModelFactory";
|
||||||
import ModelRelation from "./ModelRelation";
|
import ModelRelation from "./ModelRelation";
|
||||||
import ModelQuery, {ModelQueryResult} from "./ModelQuery";
|
import ModelQuery, {ModelQueryResult, SelectFields, SelectFieldValue} from "./ModelQuery";
|
||||||
import {Request} from "express";
|
import {Request} from "express";
|
||||||
|
|
||||||
export default abstract class Model {
|
export default abstract class Model {
|
||||||
@ -193,7 +193,7 @@ export default abstract class Model {
|
|||||||
|
|
||||||
public async exists(): Promise<boolean> {
|
public async exists(): Promise<boolean> {
|
||||||
if (this._cached_exists === undefined) {
|
if (this._cached_exists === undefined) {
|
||||||
const query = this._factory.select('1');
|
const query = this._factory.select('');
|
||||||
for (const indexField of this._factory.getPrimaryKeyFields()) {
|
for (const indexField of this._factory.getPrimaryKeyFields()) {
|
||||||
query.where(indexField, this[indexField]);
|
query.where(indexField, this[indexField]);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import ModelComponent from "./ModelComponent";
|
import ModelComponent from "./ModelComponent";
|
||||||
import Model from "./Model";
|
import Model from "./Model";
|
||||||
import ModelQuery, {ModelQueryResult} from "./ModelQuery";
|
import ModelQuery, {ModelQueryResult, SelectFields} from "./ModelQuery";
|
||||||
import {Request} from "express";
|
import {Request} from "express";
|
||||||
import {Type} from "../Utils";
|
import {Type} from "../Utils";
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ export default class ModelFactory<T extends Model> {
|
|||||||
return this.modelType.table;
|
return this.modelType.table;
|
||||||
}
|
}
|
||||||
|
|
||||||
public select(...fields: string[]): ModelQuery<T> {
|
public select(...fields: SelectFields): ModelQuery<T> {
|
||||||
return ModelQuery.select(this, ...fields);
|
return ModelQuery.select(this, ...fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@ import ModelFactory from "./ModelFactory";
|
|||||||
|
|
||||||
|
|
||||||
export default class ModelQuery<M extends Model> implements WhereFieldConsumer<M> {
|
export default class ModelQuery<M extends Model> implements WhereFieldConsumer<M> {
|
||||||
public static select<M extends Model>(factory: ModelFactory<M>, ...fields: string[]): ModelQuery<M> {
|
public static select<M extends Model>(factory: ModelFactory<M>, ...fields: SelectFields): ModelQuery<M> {
|
||||||
|
fields = fields.map(v => v === '' ? new SelectFieldValue('none', 1, true) : v);
|
||||||
return new ModelQuery(QueryType.SELECT, factory, fields.length > 0 ? fields : ['*']);
|
return new ModelQuery(QueryType.SELECT, factory, fields.length > 0 ? fields : ['*']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,8 +31,9 @@ export default class ModelQuery<M extends Model> implements WhereFieldConsumer<M
|
|||||||
private readonly type: QueryType;
|
private readonly type: QueryType;
|
||||||
private readonly factory: ModelFactory<M>;
|
private readonly factory: ModelFactory<M>;
|
||||||
private readonly table: string;
|
private readonly table: string;
|
||||||
private fields: (string | SelectFieldValue | UpdateFieldValue)[];
|
private readonly fields: SelectFields;
|
||||||
private _leftJoin?: string;
|
private _leftJoin?: string;
|
||||||
|
private _leftJoinAlias?: string;
|
||||||
private _leftJoinOn: WhereFieldValue[] = [];
|
private _leftJoinOn: WhereFieldValue[] = [];
|
||||||
private _where: (WhereFieldValue | WhereFieldValueGroup)[] = [];
|
private _where: (WhereFieldValue | WhereFieldValueGroup)[] = [];
|
||||||
private _limit?: number;
|
private _limit?: number;
|
||||||
@ -42,15 +44,16 @@ export default class ModelQuery<M extends Model> implements WhereFieldConsumer<M
|
|||||||
private _pivot?: string[];
|
private _pivot?: string[];
|
||||||
private _recursiveRelation?: RelationDatabaseProperties;
|
private _recursiveRelation?: RelationDatabaseProperties;
|
||||||
|
|
||||||
private constructor(type: QueryType, factory: ModelFactory<M>, fields?: (string | SelectFieldValue | UpdateFieldValue)[]) {
|
private constructor(type: QueryType, factory: ModelFactory<M>, fields?: SelectFields) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
this.table = factory.table;
|
this.table = factory.table;
|
||||||
this.fields = fields || [];
|
this.fields = fields || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public leftJoin(table: string): this {
|
public leftJoin(table: string, alias?: string): this {
|
||||||
this._leftJoin = table;
|
this._leftJoin = table;
|
||||||
|
this._leftJoinAlias = alias;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,19 +122,26 @@ export default class ModelQuery<M extends Model> implements WhereFieldConsumer<M
|
|||||||
public toString(final: boolean = false): string {
|
public toString(final: boolean = false): string {
|
||||||
let query = '';
|
let query = '';
|
||||||
|
|
||||||
// Prevent wildcard and fields from conflicting
|
|
||||||
if (this._leftJoin) {
|
|
||||||
this.fields = this.fields
|
|
||||||
.map(f => !f.toString().startsWith('(') && f.toString().split('.').length === 1 ? `\`${this.table}\`.${f}` : f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._pivot) this.fields.push(...this._pivot);
|
if (this._pivot) this.fields.push(...this._pivot);
|
||||||
|
|
||||||
let fields = this.fields.join(',');
|
// Prevent wildcard and fields from conflicting
|
||||||
|
let fieldArray = this.fields.map(f => {
|
||||||
|
let field = f.toString();
|
||||||
|
|
||||||
|
if (field.startsWith('(')) return f; // Skip sub-queries
|
||||||
|
|
||||||
|
let parts = field.split('.');
|
||||||
|
if (parts.length === 1) parts = [this.table, field]; // Add table disambiguation
|
||||||
|
|
||||||
|
return parts.map(v => v.startsWith('`') || v === '*' ? v : `\`${v}\``).join('.');
|
||||||
|
});
|
||||||
|
|
||||||
|
let fields = fieldArray.join(',');
|
||||||
|
|
||||||
let join = '';
|
let join = '';
|
||||||
if (this._leftJoin) {
|
if (this._leftJoin) {
|
||||||
join = `LEFT JOIN \`${this._leftJoin}\` ON ${this._leftJoinOn[0]}`;
|
const alias = this._leftJoinAlias ? ` AS \`${this._leftJoinAlias}\`` : '';
|
||||||
|
join = `LEFT JOIN \`${this._leftJoin}\`${alias} ON ${this._leftJoinOn[0]}`;
|
||||||
for (let i = 1; i < this._leftJoinOn.length; i++) {
|
for (let i = 1; i < this._leftJoinOn.length; i++) {
|
||||||
join += this._leftJoinOn[i].toString(false);
|
join += this._leftJoinOn[i].toString(false);
|
||||||
}
|
}
|
||||||
@ -343,7 +353,7 @@ class FieldValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SelectFieldValue extends FieldValue {
|
export class SelectFieldValue extends FieldValue {
|
||||||
public toString(first: boolean = true): string {
|
public toString(first: boolean = true): string {
|
||||||
return `(${this.value instanceof ModelQuery ? this.value : (this.raw ? this.value : '?')}) AS \`${this.field}\``;
|
return `(${this.value instanceof ModelQuery ? this.value : (this.raw ? this.value : '?')}) AS \`${this.field}\``;
|
||||||
}
|
}
|
||||||
@ -404,3 +414,5 @@ interface WhereFieldConsumer<M extends Model> {
|
|||||||
|
|
||||||
groupWhere(setter: (query: WhereFieldConsumer<M>) => void, operator?: WhereOperator): this;
|
groupWhere(setter: (query: WhereFieldConsumer<M>) => void, operator?: WhereOperator): this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SelectFields = (string | SelectFieldValue | UpdateFieldValue)[];
|
||||||
|
@ -158,7 +158,7 @@ export class ManyThroughModelRelation<S extends Model, O extends Model> extends
|
|||||||
super(model, foreignFactory, dbProperties);
|
super(model, foreignFactory, dbProperties);
|
||||||
this.dbProperties = dbProperties;
|
this.dbProperties = dbProperties;
|
||||||
this.constraint(query => query
|
this.constraint(query => query
|
||||||
.leftJoin(`${this.dbProperties.pivotTable} as pivot`)
|
.leftJoin(this.dbProperties.pivotTable, 'pivot')
|
||||||
.on(`pivot.${this.dbProperties.foreignPivotKey}`, `${this.foreignFactory.table}.${this.dbProperties.foreignKey}`)
|
.on(`pivot.${this.dbProperties.foreignPivotKey}`, `${this.foreignFactory.table}.${this.dbProperties.foreignKey}`)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,7 @@ export default class Validator<T> {
|
|||||||
if (querySupplier) {
|
if (querySupplier) {
|
||||||
query = querySupplier().where(foreignKey, val);
|
query = querySupplier().where(foreignKey, val);
|
||||||
} else {
|
} else {
|
||||||
query = (model instanceof Model ? <any>model.constructor : model).select('1').where(foreignKey, val);
|
query = (model instanceof Model ? <any>model.constructor : model).select('').where(foreignKey, val);
|
||||||
}
|
}
|
||||||
if (model instanceof Model && typeof model.id === 'number') query = query.where('id', model.id, WhereTest.NE);
|
if (model instanceof Model && typeof model.id === 'number') query = query.where('id', model.id, WhereTest.NE);
|
||||||
return (await query.execute(c)).results.length === 0;
|
return (await query.execute(c)).results.length === 0;
|
||||||
@ -206,7 +206,7 @@ export default class Validator<T> {
|
|||||||
|
|
||||||
public exists(modelClass: Function, foreignKey?: string): Validator<T> {
|
public exists(modelClass: Function, foreignKey?: string): Validator<T> {
|
||||||
this.addStep({
|
this.addStep({
|
||||||
verifyStep: async (val, thingName, c) => (await (<any>modelClass).select('1').where(foreignKey !== undefined ? foreignKey : thingName, val).execute(c)).results.length >= 1,
|
verifyStep: async (val, thingName, c) => (await (<any>modelClass).select('').where(foreignKey !== undefined ? foreignKey : thingName, val).execute(c)).results.length >= 1,
|
||||||
throw: () => new UnknownRelationValidationError((<any>modelClass).table, foreignKey),
|
throw: () => new UnknownRelationValidationError((<any>modelClass).table, foreignKey),
|
||||||
isFormat: false,
|
isFormat: false,
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@ import Model from "../src/db/Model";
|
|||||||
import {MIGRATIONS} from "./_migrations";
|
import {MIGRATIONS} from "./_migrations";
|
||||||
import ModelFactory from "../src/db/ModelFactory";
|
import ModelFactory from "../src/db/ModelFactory";
|
||||||
import {ValidationBag} from "../src/db/Validator";
|
import {ValidationBag} from "../src/db/Validator";
|
||||||
|
import Logger from "../src/Logger";
|
||||||
|
|
||||||
class FakeDummyModel extends Model {
|
class FakeDummyModel extends Model {
|
||||||
public id?: number = undefined;
|
public id?: number = undefined;
|
||||||
@ -18,6 +19,7 @@ class FakeDummyModel extends Model {
|
|||||||
let factory: ModelFactory<FakeDummyModel>;
|
let factory: ModelFactory<FakeDummyModel>;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
Logger.verbose();
|
||||||
MysqlConnectionManager.registerMigrations(MIGRATIONS);
|
MysqlConnectionManager.registerMigrations(MIGRATIONS);
|
||||||
ModelFactory.register(FakeDummyModel);
|
ModelFactory.register(FakeDummyModel);
|
||||||
await MysqlConnectionManager.prepare();
|
await MysqlConnectionManager.prepare();
|
||||||
|
@ -1,8 +1,32 @@
|
|||||||
import ModelQuery, {WhereOperator} from "../src/db/ModelQuery";
|
import ModelQuery, {SelectFieldValue, WhereOperator} from "../src/db/ModelQuery";
|
||||||
import ModelFactory from "../src/db/ModelFactory";
|
import ModelFactory from "../src/db/ModelFactory";
|
||||||
import Model from "../src/db/Model";
|
import Model from "../src/db/Model";
|
||||||
|
|
||||||
describe('Test ModelQuery', () => {
|
describe('Test ModelQuery', () => {
|
||||||
|
test('update', () => {
|
||||||
|
const query = ModelQuery.update({table: 'model'} as unknown as ModelFactory<Model>, {
|
||||||
|
'f1': 'v1',
|
||||||
|
'f2': 'v2',
|
||||||
|
'f3': 'v3',
|
||||||
|
}).where('f4', 'v4');
|
||||||
|
expect(query.toString(true)).toBe('UPDATE `model` SET `model`.`f1`=?,`model`.`f2`=?,`model`.`f3`=? WHERE `f4`=? ');
|
||||||
|
expect(query.variables).toStrictEqual(['v1','v2','v3','v4']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('function select', () => {
|
||||||
|
const query = ModelQuery.select({table: 'model'} as unknown as ModelFactory<Model>, 'f1', new SelectFieldValue('_count', 'COUNT(*)', true));
|
||||||
|
expect(query.toString(true)).toBe('SELECT `model`.`f1`,(COUNT(*)) AS `_count` FROM `model` ');
|
||||||
|
expect(query.variables).toStrictEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pivot', () => {
|
||||||
|
const query = ModelQuery.select({table: 'model'} as unknown as ModelFactory<Model>, 'f1');
|
||||||
|
query.pivot('pivot.f2', 'f3');
|
||||||
|
|
||||||
|
expect(query.toString(true)).toBe('SELECT `model`.`f1`,`pivot`.`f2`,`model`.`f3` FROM `model` ');
|
||||||
|
expect(query.variables).toStrictEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
test('groupWhere generates proper query', () => {
|
test('groupWhere generates proper query', () => {
|
||||||
const query = ModelQuery.select({table: 'model'} as unknown as ModelFactory<Model>, '*');
|
const query = ModelQuery.select({table: 'model'} as unknown as ModelFactory<Model>, '*');
|
||||||
query.where('f1', 'v1');
|
query.where('f1', 'v1');
|
||||||
@ -10,8 +34,8 @@ describe('Test ModelQuery', () => {
|
|||||||
.groupWhere(q => q.where('f4', 'v4'), WhereOperator.OR))
|
.groupWhere(q => q.where('f4', 'v4'), WhereOperator.OR))
|
||||||
.where('f5', 'v5');
|
.where('f5', 'v5');
|
||||||
|
|
||||||
expect(query.toString(true)).toBe('SELECT * FROM `model` WHERE `f1`=? AND (`f2`=? AND `f3`=? OR (`f4`=?)) AND `f5`=? ');
|
expect(query.toString(true)).toBe('SELECT `model`.* FROM `model` WHERE `f1`=? AND (`f2`=? AND `f3`=? OR (`f4`=?)) AND `f5`=? ');
|
||||||
expect(query.variables.length).toBe(5);
|
expect(query.variables).toStrictEqual(['v1','v2','v3','v4','v5']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('recursive queries', () => {
|
test('recursive queries', () => {
|
||||||
@ -22,6 +46,6 @@ describe('Test ModelQuery', () => {
|
|||||||
query.sortBy('f2', 'ASC').limit(8);
|
query.sortBy('f2', 'ASC').limit(8);
|
||||||
|
|
||||||
expect(query.toString(true)).toBe("WITH RECURSIVE cte AS (SELECT `model`.* FROM `model` WHERE `f1`=? UNION SELECT o.* FROM `model` AS o, cte AS c WHERE o.`foreign`=c.`local`) SELECT * FROM cte LEFT JOIN `test` ON `model`.`j1`=`test`.`j2` ORDER BY `f2` ASC LIMIT 8");
|
expect(query.toString(true)).toBe("WITH RECURSIVE cte AS (SELECT `model`.* FROM `model` WHERE `f1`=? UNION SELECT o.* FROM `model` AS o, cte AS c WHERE o.`foreign`=c.`local`) SELECT * FROM cte LEFT JOIN `test` ON `model`.`j1`=`test`.`j2` ORDER BY `f2` ASC LIMIT 8");
|
||||||
expect(query.variables.length).toBe(1);
|
expect(query.variables).toStrictEqual(['v1']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user