Files
pole-book/server/node_modules/@strapi/database/dist/query/query-builder.mjs

506 lines
16 KiB
JavaScript

import _ from 'lodash/fp';
import DatabaseError from '../errors/database.mjs';
import { transactionCtx } from '../transaction-context.mjs';
import { isKnexQuery } from '../utils/knex.mjs';
import { applySearch } from './helpers/search.mjs';
import { processOrderBy, wrapWithDeepSort } from './helpers/order-by.mjs';
import { createJoin, applyJoins } from './helpers/join.mjs';
import applyPopulate from './helpers/populate/apply.mjs';
import processPopulate from './helpers/populate/process.mjs';
import { processWhere, applyWhere } from './helpers/where.mjs';
import { toColumnName, toRow, fromRow } from './helpers/transform.mjs';
import ReadableStrapiQuery from './helpers/streams/readable.mjs';
const createQueryBuilder = (uid, db, initialState = {})=>{
const meta = db.metadata.get(uid);
const { tableName } = meta;
const state = _.defaults({
type: 'select',
select: [],
count: null,
max: null,
first: false,
data: null,
where: [],
joins: [],
populate: null,
limit: null,
offset: null,
transaction: null,
forUpdate: false,
onConflict: null,
merge: null,
ignore: false,
orderBy: [],
groupBy: [],
increments: [],
decrements: [],
aliasCounter: 0,
filters: null,
search: null,
processed: false
}, initialState);
const getAlias = ()=>{
const alias = `t${state.aliasCounter}`;
state.aliasCounter += 1;
return alias;
};
return {
alias: getAlias(),
getAlias,
state,
clone () {
return createQueryBuilder(uid, db, state);
},
select (args) {
state.type = 'select';
state.select = _.uniq(_.castArray(args));
return this;
},
addSelect (args) {
state.select = _.uniq([
...state.select,
..._.castArray(args)
]);
return this;
},
insert (data) {
state.type = 'insert';
state.data = data;
return this;
},
onConflict (args) {
state.onConflict = args;
return this;
},
merge (args) {
state.merge = args;
return this;
},
ignore () {
state.ignore = true;
return this;
},
delete () {
state.type = 'delete';
return this;
},
ref (name) {
return db.connection.ref(toColumnName(meta, name));
},
update (data) {
state.type = 'update';
state.data = data;
return this;
},
increment (column, amount = 1) {
state.type = 'update';
state.increments.push({
column,
amount
});
return this;
},
decrement (column, amount = 1) {
state.type = 'update';
state.decrements.push({
column,
amount
});
return this;
},
count (count = 'id') {
state.type = 'count';
state.count = count;
return this;
},
max (column) {
state.type = 'max';
state.max = column;
return this;
},
where (where = {}) {
if (!_.isPlainObject(where)) {
throw new Error('Where must be an object');
}
state.where.push(where);
return this;
},
limit (limit) {
state.limit = limit;
return this;
},
offset (offset) {
state.offset = offset;
return this;
},
orderBy (orderBy) {
state.orderBy = orderBy;
return this;
},
groupBy (groupBy) {
state.groupBy = groupBy;
return this;
},
populate (populate) {
state.populate = populate;
return this;
},
search (query) {
state.search = query;
return this;
},
transacting (transaction) {
state.transaction = transaction;
return this;
},
forUpdate () {
state.forUpdate = true;
return this;
},
init (params = {}) {
const { _q, filters, where, select, limit, offset, orderBy, groupBy, populate } = params;
if (!_.isNil(where)) {
this.where(where);
}
if (!_.isNil(_q)) {
this.search(_q);
}
if (!_.isNil(select)) {
this.select(select);
} else {
this.select('*');
}
if (!_.isNil(limit)) {
this.limit(limit);
}
if (!_.isNil(offset)) {
this.offset(offset);
}
if (!_.isNil(orderBy)) {
this.orderBy(orderBy);
}
if (!_.isNil(groupBy)) {
this.groupBy(groupBy);
}
if (!_.isNil(populate)) {
this.populate(populate);
}
if (!_.isNil(filters)) {
this.filters(filters);
}
return this;
},
filters (filters) {
state.filters = filters;
},
first () {
state.first = true;
return this;
},
join (join) {
if (!join.targetField) {
state.joins.push(join);
return this;
}
const model = db.metadata.get(uid);
const attribute = model.attributes[join.targetField];
createJoin({
db,
qb: this,
uid
}, {
alias: this.alias,
refAlias: join.alias,
attributeName: join.targetField,
attribute
});
return this;
},
mustUseAlias () {
return [
'select',
'count'
].includes(state.type);
},
aliasColumn (key, alias) {
if (typeof key !== 'string') {
return key;
}
if (key.indexOf('.') >= 0) {
return key;
}
if (!_.isNil(alias)) {
return `${alias}.${key}`;
}
return this.mustUseAlias() ? `${this.alias}.${key}` : key;
},
raw: db.connection.raw.bind(db.connection),
shouldUseSubQuery () {
return [
'delete',
'update'
].includes(state.type) && state.joins.length > 0;
},
runSubQuery () {
const originalType = state.type;
this.select('id');
const subQB = this.getKnexQuery();
const nestedSubQuery = db.getConnection().select('id').from(subQB.as('subQuery'));
const connection = db.getConnection(tableName);
return connection[originalType]().whereIn('id', nestedSubQuery);
},
processState () {
if (this.state.processed) {
return;
}
state.orderBy = processOrderBy(state.orderBy, {
qb: this,
uid,
db
});
if (!_.isNil(state.filters)) {
if (_.isFunction(state.filters)) {
const filters = state.filters({
qb: this,
uid,
meta,
db
});
if (!_.isNil(filters)) {
state.where.push(filters);
}
} else {
state.where.push(state.filters);
}
}
state.where = processWhere(state.where, {
qb: this,
uid,
db
});
state.populate = processPopulate(state.populate, {
qb: this,
uid,
db
});
state.data = toRow(meta, state.data);
this.processSelect();
this.state.processed = true;
},
shouldUseDistinct () {
return state.joins.length > 0 && _.isEmpty(state.groupBy);
},
shouldUseDeepSort () {
return state.orderBy.filter(({ column })=>column.indexOf('.') >= 0).filter(({ column })=>{
const col = column.split('.');
for(let i = 0; i < col.length - 1; i += 1){
const el = col[i];
// order by "rel"."xxx"
const isRelationAttribute = meta.attributes[el]?.type === 'relation';
// order by "t2"."xxx"
const isAliasedRelation = Object.values(state.joins).map((join)=>join.alias).includes(el);
if (isRelationAttribute || isAliasedRelation) {
return true;
}
}
return false;
}).length > 0;
},
processSelect () {
state.select = state.select.map((field)=>{
if (isKnexQuery(field)) {
return field;
}
return toColumnName(meta, field);
});
if (this.shouldUseDistinct()) {
const joinsOrderByColumns = state.joins.flatMap((join)=>{
return _.keys(join.orderBy).map((key)=>this.aliasColumn(key, join.alias));
});
const orderByColumns = state.orderBy.map(({ column })=>column);
state.select = _.uniq([
...joinsOrderByColumns,
...orderByColumns,
...state.select
]);
}
},
getKnexQuery () {
if (!state.type) {
this.select('*');
}
const aliasedTableName = this.mustUseAlias() ? `${tableName} as ${this.alias}` : tableName;
const qb = db.getConnection(aliasedTableName);
// The state should always be processed before calling shouldUseSubQuery as it
// relies on the presence or absence of joins to determine the need of a subquery
this.processState();
if (this.shouldUseSubQuery()) {
return this.runSubQuery();
}
switch(state.type){
case 'select':
{
qb.select(state.select.map((column)=>this.aliasColumn(column)));
if (this.shouldUseDistinct()) {
qb.distinct();
}
break;
}
case 'count':
{
const dbColumnName = this.aliasColumn(toColumnName(meta, state.count));
if (this.shouldUseDistinct()) {
qb.countDistinct({
count: dbColumnName
});
} else {
qb.count({
count: dbColumnName
});
}
break;
}
case 'max':
{
const dbColumnName = this.aliasColumn(toColumnName(meta, state.max));
qb.max({
max: dbColumnName
});
break;
}
case 'insert':
{
qb.insert(state.data);
if (db.dialect.useReturning() && _.has('id', meta.attributes)) {
qb.returning('id');
}
break;
}
case 'update':
{
if (state.data) {
qb.update(state.data);
}
break;
}
case 'delete':
{
qb.delete();
break;
}
case 'truncate':
{
qb.truncate();
break;
}
default:
{
throw new Error('Unknown query type');
}
}
if (state.transaction) {
qb.transacting(state.transaction);
}
if (state.forUpdate) {
qb.forUpdate();
}
if (!_.isEmpty(state.increments)) {
state.increments.forEach((incr)=>qb.increment(incr.column, incr.amount));
}
if (!_.isEmpty(state.decrements)) {
state.decrements.forEach((decr)=>qb.decrement(decr.column, decr.amount));
}
if (state.onConflict) {
if (state.merge) {
qb.onConflict(state.onConflict).merge(state.merge);
} else if (state.ignore) {
qb.onConflict(state.onConflict).ignore();
}
}
if (state.limit) {
qb.limit(state.limit);
}
if (state.offset) {
qb.offset(state.offset);
}
if (state.orderBy.length > 0) {
qb.orderBy(state.orderBy);
}
if (state.first) {
qb.first();
}
if (state.groupBy.length > 0) {
qb.groupBy(state.groupBy);
}
// if there are joins and it is a delete or update use a sub query
if (state.where) {
applyWhere(qb, state.where);
}
// if there are joins and it is a delete or update use a sub query
if (state.search) {
qb.where((subQb)=>{
applySearch(subQb, state.search, {
qb: this,
db,
uid
});
});
}
if (state.joins.length > 0) {
applyJoins(qb, state.joins);
}
if (this.shouldUseDeepSort()) {
return wrapWithDeepSort(qb, {
qb: this,
db,
uid
});
}
return qb;
},
async execute ({ mapResults = true } = {}) {
try {
const qb = this.getKnexQuery();
const transaction = transactionCtx.get();
if (transaction) {
qb.transacting(transaction);
}
const rows = await qb;
if (state.populate && !_.isNil(rows)) {
await applyPopulate(_.castArray(rows), state.populate, {
qb: this,
uid,
db
});
}
let results = rows;
if (mapResults && state.type === 'select') {
results = fromRow(meta, rows);
}
return results;
} catch (error) {
if (error instanceof Error) {
db.dialect.transformErrors(error);
} else {
throw error;
}
}
},
stream ({ mapResults = true } = {}) {
if (state.type === 'select') {
return new ReadableStrapiQuery({
qb: this,
db,
uid,
mapResults
});
}
throw new DatabaseError(`query-builder.stream() has been called with an unsupported query type: "${state.type}"`);
}
};
};
export { createQueryBuilder as default };
//# sourceMappingURL=query-builder.mjs.map