|
|
@ -80,8 +80,13 @@ function addJoin(knexQuery: any, base: ObjectType, other: ObjectType) { |
|
|
|
.join(otherTable, { [otherTable + '.id']: linkTable + '.' + linkingTableIdNames[other] }); |
|
|
|
.join(otherTable, { [otherTable + '.id']: linkTable + '.' + linkingTableIdNames[other] }); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function addLeafWhere(knexQuery: any, queryElem: api.QueryElem) { |
|
|
|
enum WhereType { |
|
|
|
const simpleLeafOps = { |
|
|
|
And = 0, |
|
|
|
|
|
|
|
Or, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function addLeafWhere(knexQuery: any, queryElem: api.QueryElem, type: WhereType) { |
|
|
|
|
|
|
|
const simpleLeafOps: Record<any, string> = { |
|
|
|
[api.QueryFilterOp.Eq]: "=", |
|
|
|
[api.QueryFilterOp.Eq]: "=", |
|
|
|
[api.QueryFilterOp.Ne]: "!=", |
|
|
|
[api.QueryFilterOp.Ne]: "!=", |
|
|
|
[api.QueryFilterOp.Like]: "like", |
|
|
|
[api.QueryFilterOp.Like]: "like", |
|
|
@ -101,21 +106,61 @@ function addLeafWhere(knexQuery: any, queryElem: api.QueryElem) { |
|
|
|
const b = queryElem.propOperand || ""; |
|
|
|
const b = queryElem.propOperand || ""; |
|
|
|
|
|
|
|
|
|
|
|
if (Object.keys(simpleLeafOps).includes(operator)) { |
|
|
|
if (Object.keys(simpleLeafOps).includes(operator)) { |
|
|
|
return knexQuery.where(a, operator, b); |
|
|
|
if (type == WhereType.And) { |
|
|
|
|
|
|
|
return knexQuery.andWhere(a, simpleLeafOps[operator], b); |
|
|
|
|
|
|
|
} else if (type == WhereType.Or) { |
|
|
|
|
|
|
|
return knexQuery.orWhere(a, simpleLeafOps[operator], b); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else if (operator == api.QueryFilterOp.In) { |
|
|
|
|
|
|
|
if (type == WhereType.And) { |
|
|
|
|
|
|
|
return knexQuery.whereIn(a, b); |
|
|
|
|
|
|
|
} else if (type == WhereType.Or) { |
|
|
|
|
|
|
|
return knexQuery.orWhereIn(a, b); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else if (operator == api.QueryFilterOp.NotIn) { |
|
|
|
|
|
|
|
if (type == WhereType.And) { |
|
|
|
|
|
|
|
return knexQuery.whereNotIn(a, b); |
|
|
|
|
|
|
|
} else if (type == WhereType.Or) { |
|
|
|
|
|
|
|
return knexQuery.orWhereNotIn(a, b); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
throw "Query filter not implemented"; |
|
|
|
throw "Query filter not implemented"; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function addWhere(knexQuery: any, queryElem: api.QueryElem) { |
|
|
|
function addBranchWhere(knexQuery: any, queryElem: api.QueryElem, type: WhereType) { |
|
|
|
|
|
|
|
if (queryElem.children && queryElem.childrenOperator === api.QueryElemOp.And) { |
|
|
|
|
|
|
|
var q = knexQuery; |
|
|
|
|
|
|
|
queryElem.children.forEach((child: api.QueryElem) => { |
|
|
|
|
|
|
|
q = addWhere(q, child, type); |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
return q; |
|
|
|
|
|
|
|
} else if (queryElem.children && queryElem.childrenOperator === api.QueryElemOp.Or) { |
|
|
|
|
|
|
|
var q = knexQuery; |
|
|
|
|
|
|
|
const c = queryElem.children; |
|
|
|
|
|
|
|
const f = function (this: any) { |
|
|
|
|
|
|
|
for (var i = 0; i < c.length; i++) { |
|
|
|
|
|
|
|
addWhere(this, c[i], WhereType.Or); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (type == WhereType.And) { |
|
|
|
|
|
|
|
return q.where(f); |
|
|
|
|
|
|
|
} else if (type == WhereType.Or) { |
|
|
|
|
|
|
|
return q.orWhere(f); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function addWhere(knexQuery: any, queryElem: api.QueryElem, type: WhereType) { |
|
|
|
if (queryElem.prop) { |
|
|
|
if (queryElem.prop) { |
|
|
|
// Leaf node.
|
|
|
|
// Leaf node.
|
|
|
|
return addLeafWhere(knexQuery, queryElem); |
|
|
|
return addLeafWhere(knexQuery, queryElem, type); |
|
|
|
} else if (queryElem.children) { |
|
|
|
} else if (queryElem.children) { |
|
|
|
// Branch node.
|
|
|
|
// Branch node.
|
|
|
|
|
|
|
|
return addBranchWhere(knexQuery, queryElem, type); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
throw "Query filter not implemented."; |
|
|
|
return knexQuery; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const objectColumns = { |
|
|
|
const objectColumns = { |
|
|
@ -125,7 +170,8 @@ const objectColumns = { |
|
|
|
[ObjectType.Tag]: ['tags.id as tags.id', 'tags.name as tags.name', 'tags.parentId as tags.parentId'] |
|
|
|
[ObjectType.Tag]: ['tags.id as tags.id', 'tags.name as tags.name', 'tags.parentId as tags.parentId'] |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
function constructQuery(knex: Knex, queryFor: ObjectType, queryElem: api.QueryElem) { |
|
|
|
function constructQuery(knex: Knex, queryFor: ObjectType, queryElem: api.QueryElem, ordering: api.Ordering, |
|
|
|
|
|
|
|
offset: number, limit: number) { |
|
|
|
const joinObjects = getRequiredDatabaseObjects(queryElem); |
|
|
|
const joinObjects = getRequiredDatabaseObjects(queryElem); |
|
|
|
joinObjects.delete(queryFor); // We are already querying this object in the base query.
|
|
|
|
joinObjects.delete(queryFor); // We are already querying this object in the base query.
|
|
|
|
|
|
|
|
|
|
|
@ -145,9 +191,17 @@ function constructQuery(knex: Knex, queryFor: ObjectType, queryElem: api.QueryEl |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
// Apply filtering.
|
|
|
|
// Apply filtering.
|
|
|
|
q = addWhere(q, queryElem); |
|
|
|
q = addWhere(q, queryElem, WhereType.And); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Apply ordering
|
|
|
|
|
|
|
|
const orderKeys = { |
|
|
|
|
|
|
|
[api.OrderByType.Name]: objectTables[queryFor] + '.' + ((queryFor === ObjectType.Song) ? 'title' : 'name') |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
q = q.orderBy(orderKeys[ordering.orderBy.type], |
|
|
|
|
|
|
|
(ordering.ascending ? 'asc' : 'desc')); |
|
|
|
|
|
|
|
|
|
|
|
// TODO: add ordering, limiting
|
|
|
|
// Apply limiting.
|
|
|
|
|
|
|
|
q = q.limit(limit).offset(offset); |
|
|
|
|
|
|
|
|
|
|
|
return q; |
|
|
|
return q; |
|
|
|
} |
|
|
|
} |
|
|
@ -167,40 +221,6 @@ async function getLinkedObjects(knex: Knex, base: ObjectType, linked: ObjectType |
|
|
|
return result; |
|
|
|
return result; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// // Returns the "where" clauses for Sequelize, per object type.
|
|
|
|
|
|
|
|
// const getSequelizeWhere = (queryElem: api.QueryElem, type: QueryType) => {
|
|
|
|
|
|
|
|
// var where: any = {
|
|
|
|
|
|
|
|
// [Op.and]: []
|
|
|
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if (queryElem.prop && queryElem.propOperator && queryElem.propOperand) {
|
|
|
|
|
|
|
|
// // Visit a filter-like subquery leaf.
|
|
|
|
|
|
|
|
// where[Op.and].push({
|
|
|
|
|
|
|
|
// [sequelizeProps[type][queryElem.prop]]: {
|
|
|
|
|
|
|
|
// [sequelizeOps[queryElem.propOperator]]: queryElem.propOperand
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// if (queryElem.childrenOperator && queryElem.children) {
|
|
|
|
|
|
|
|
// // Recursively visit a nested subquery.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// const children = queryElem.children.map((child: api.QueryElem) => getSequelizeWhere(child, type));
|
|
|
|
|
|
|
|
// where[Op.and].push({
|
|
|
|
|
|
|
|
// [sequelizeOps[queryElem.childrenOperator]]: children
|
|
|
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// return where;
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// function getSequelizeOrder(order: api.Ordering, type: QueryType) {
|
|
|
|
|
|
|
|
// const ascstring = order.ascending ? 'ASC' : 'DESC';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// return [
|
|
|
|
|
|
|
|
// [ sequelizeOrderColumns[type][order.orderBy.type], ascstring ]
|
|
|
|
|
|
|
|
// ];
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => { |
|
|
|
export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => { |
|
|
|
if (!api.checkQueryRequest(req.body)) { |
|
|
|
if (!api.checkQueryRequest(req.body)) { |
|
|
|
const e: EndpointError = { |
|
|
|
const e: EndpointError = { |
|
|
@ -210,6 +230,7 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, |
|
|
|
throw e; |
|
|
|
throw e; |
|
|
|
} |
|
|
|
} |
|
|
|
const reqObject: api.QueryRequest = req.body; |
|
|
|
const reqObject: api.QueryRequest = req.body; |
|
|
|
|
|
|
|
console.log("Query: ", reqObject); |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
const songLimit = reqObject.offsetsLimits.songLimit; |
|
|
|
const songLimit = reqObject.offsetsLimits.songLimit; |
|
|
@ -220,18 +241,33 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, |
|
|
|
const artistOffset = reqObject.offsetsLimits.artistOffset; |
|
|
|
const artistOffset = reqObject.offsetsLimits.artistOffset; |
|
|
|
|
|
|
|
|
|
|
|
const artistsPromise: Promise<any> = (artistLimit && artistLimit > 0) ? |
|
|
|
const artistsPromise: Promise<any> = (artistLimit && artistLimit > 0) ? |
|
|
|
constructQuery(knex, ObjectType.Artist, reqObject.query) |
|
|
|
constructQuery(knex, |
|
|
|
.offset(artistOffset || 0).limit(artistLimit) : |
|
|
|
ObjectType.Artist, |
|
|
|
|
|
|
|
reqObject.query, |
|
|
|
|
|
|
|
reqObject.ordering, |
|
|
|
|
|
|
|
artistOffset || 0, |
|
|
|
|
|
|
|
artistLimit |
|
|
|
|
|
|
|
) : |
|
|
|
(async () => [])(); |
|
|
|
(async () => [])(); |
|
|
|
|
|
|
|
|
|
|
|
const songsPromise: Promise<any> = (songLimit && songLimit > 0) ? |
|
|
|
const songsPromise: Promise<any> = (songLimit && songLimit > 0) ? |
|
|
|
constructQuery(knex, ObjectType.Song, reqObject.query) |
|
|
|
constructQuery(knex, |
|
|
|
.offset(songOffset || 0).limit(songLimit) : |
|
|
|
ObjectType.Song, |
|
|
|
|
|
|
|
reqObject.query, |
|
|
|
|
|
|
|
reqObject.ordering, |
|
|
|
|
|
|
|
songOffset || 0, |
|
|
|
|
|
|
|
songLimit |
|
|
|
|
|
|
|
) : |
|
|
|
(async () => [])(); |
|
|
|
(async () => [])(); |
|
|
|
|
|
|
|
|
|
|
|
const tagsPromise: Promise<any> = (tagLimit && tagLimit > 0) ? |
|
|
|
const tagsPromise: Promise<any> = (tagLimit && tagLimit > 0) ? |
|
|
|
constructQuery(knex, ObjectType.Tag, reqObject.query) |
|
|
|
constructQuery(knex, |
|
|
|
.offset(tagOffset || 0).limit(tagLimit) : |
|
|
|
ObjectType.Tag, |
|
|
|
|
|
|
|
reqObject.query, |
|
|
|
|
|
|
|
reqObject.ordering, |
|
|
|
|
|
|
|
tagOffset || 0, |
|
|
|
|
|
|
|
tagLimit |
|
|
|
|
|
|
|
) : |
|
|
|
(async () => [])(); |
|
|
|
(async () => [])(); |
|
|
|
|
|
|
|
|
|
|
|
// For some objects, we want to return linked information as well.
|
|
|
|
// For some objects, we want to return linked information as well.
|
|
|
|