strapi/packages/core/database/lib/query/query-builder.js

269 lines
5.5 KiB
JavaScript
Raw Normal View History

2021-06-17 16:17:15 +02:00
'use strict';
const _ = require('lodash/fp');
const helpers = require('./helpers');
const createQueryBuilder = (uid, db) => {
const meta = db.metadata.get(uid);
const { tableName } = meta;
// TODO: we could use a state to track the entire query instead of using knex directly
let state = {
type: 'select',
select: [],
count: null,
first: false,
data: null,
where: [],
joins: [],
populate: null,
limit: null,
offset: null,
orderBy: [],
groupBy: [],
};
let counter = 0;
const getAlias = () => `t${counter++}`;
// TODO: actually rename columns to attributes then pick them
// const pickAttributes = _.pick(Object.keys(meta.attributes));
return {
alias: getAlias(),
getAlias,
insert(data) {
state.type = 'insert';
state.data = data;
return this;
},
delete() {
state.type = 'delete';
return this;
},
update(data) {
state.type = 'update';
state.data = data;
return this;
},
count(count = '*') {
state.type = 'count';
state.count = count;
return this;
},
// TODO: convert where into aliases where & nested joins
2021-06-22 17:13:11 +02:00
where(where = {}) {
2021-06-17 16:17:15 +02:00
const processedWhere = helpers.processWhere(where, { qb: this, uid, db });
state.where.push(processedWhere);
return this;
},
// TODO: handle aliasing logic
select(args) {
state.type = 'select';
state.select = _.castArray(args).map(col => this.aliasColumn(col));
return this;
},
addSelect(args) {
state.select.push(..._.castArray(args).map(col => this.aliasColumn(col)));
return this;
},
limit(limit) {
state.limit = limit;
return this;
},
offset(offset) {
state.offset = offset;
return this;
},
// TODO: map to column name
orderBy(orderBy) {
state.orderBy = helpers.processOrderBy(orderBy, { qb: this, uid, db });
return this;
},
// TODO: add processing
groupBy(groupBy) {
state.groupBy = groupBy;
2021-06-24 18:28:36 +02:00
return this;
2021-06-17 16:17:15 +02:00
},
// TODO: implement
having() {},
// TODO: add necessary joins to make populate easier / faster
populate(populate) {
state.populate = helpers.processPopulate(populate, { qb: this, uid, db });
return this;
},
init(params = {}) {
const { where, select, limit, offset, orderBy, groupBy, populate } = params;
if (where) {
this.where(where);
}
if (select) {
this.select(select);
} else {
this.select('*');
}
if (limit) {
this.limit(limit);
}
if (offset) {
this.offset(offset);
}
if (orderBy) {
this.orderBy(orderBy);
}
if (groupBy) {
this.groupBy(groupBy);
}
if (populate) {
this.populate(populate);
}
return this;
},
first() {
state.first = true;
return this;
},
join(join) {
state.joins.push(join);
return this;
},
aliasColumn(columnName) {
if (columnName.indexOf('.') >= 0) return columnName;
return this.alias + '.' + columnName;
},
2021-06-28 21:37:44 +02:00
async execute({ mapResults = true } = {}) {
2021-06-24 18:28:36 +02:00
const aliasedTableName = state.type === 'insert' ? tableName : { [this.alias]: tableName };
try {
const qb = db.connection(aliasedTableName);
switch (state.type) {
case 'select': {
if (state.select.length === 0) {
state.select = [this.aliasColumn('*')];
}
if (state.joins.length > 0) {
// add ordered columns to distinct in case of joins
qb.distinct();
2021-06-30 20:00:03 +02:00
// TODO: add column if they aren't there already
2021-06-24 18:28:36 +02:00
state.select.unshift(...state.orderBy.map(({ column }) => column));
}
qb.select(state.select);
break;
2021-06-17 16:17:15 +02:00
}
2021-06-24 18:28:36 +02:00
case 'count': {
qb.count({ count: state.count });
break;
2021-06-17 16:17:15 +02:00
}
2021-06-24 18:28:36 +02:00
case 'insert': {
qb.insert(state.data);
2021-06-17 16:17:15 +02:00
2021-06-24 18:28:36 +02:00
if (db.dialect.useReturning() && _.has('id', meta.attributes)) {
qb.returning('id');
}
2021-06-17 16:17:15 +02:00
2021-06-24 18:28:36 +02:00
break;
2021-06-17 16:17:15 +02:00
}
2021-06-24 18:28:36 +02:00
case 'update': {
qb.update(state.data);
2021-06-17 16:17:15 +02:00
2021-06-24 18:28:36 +02:00
break;
2021-06-17 16:17:15 +02:00
}
2021-06-24 18:28:36 +02:00
case 'delete': {
qb.del();
2021-06-17 16:17:15 +02:00
2021-06-24 18:28:36 +02:00
break;
2021-06-17 16:17:15 +02:00
}
2021-06-24 18:28:36 +02:00
}
2021-06-17 16:17:15 +02:00
2021-06-24 18:28:36 +02:00
if (state.limit) {
qb.limit(state.limit);
2021-06-17 16:17:15 +02:00
}
2021-06-24 18:28:36 +02:00
if (state.offset) {
qb.offset(state.offset);
}
2021-06-17 16:17:15 +02:00
2021-06-24 18:28:36 +02:00
if (state.orderBy.length > 0) {
qb.orderBy(state.orderBy);
}
2021-06-17 16:17:15 +02:00
2021-06-24 18:28:36 +02:00
if (state.first) {
qb.first();
}
2021-06-17 16:17:15 +02:00
2021-06-24 18:28:36 +02:00
if (state.groupBy.length > 0) {
qb.groupBy(state.groupBy);
}
2021-06-17 16:17:15 +02:00
2021-06-24 18:28:36 +02:00
if (state.where) {
helpers.applyWhere(qb, state.where);
}
2021-06-17 16:17:15 +02:00
2021-06-24 18:28:36 +02:00
// TODO: apply joins
if (state.joins.length > 0) {
helpers.applyJoins(qb, state.joins);
}
2021-06-17 16:17:15 +02:00
2021-06-28 21:37:44 +02:00
const rows = await qb;
2021-06-17 16:17:15 +02:00
2021-06-30 20:00:03 +02:00
if (state.populate && !_.isNil(rows)) {
// TODO: hanlde populate
await helpers.applyPopulate(_.castArray(rows), state.populate, { qb: this, uid, db });
2021-06-28 21:37:44 +02:00
}
2021-06-17 16:17:15 +02:00
2021-06-30 20:00:03 +02:00
let results = rows;
if (mapResults && state.type === 'select') {
results = Array.isArray(rows)
? rows.map(row => helpers.fromRow(meta, row))
: helpers.fromRow(meta, rows);
2021-06-24 18:28:36 +02:00
}
2021-06-17 16:17:15 +02:00
2021-06-24 18:28:36 +02:00
return results;
} catch (error) {
db.dialect.transformErrors(error);
}
2021-06-17 16:17:15 +02:00
},
};
};
module.exports = createQueryBuilder;