All tests working again.

pull/10/head
Sander Vocke 5 years ago
parent 1d5df9b43b
commit 4d20c72889
  1. 3
      client/src/api.ts
  2. 134
      server/endpoints/QueryEndpointHandler.ts
  3. 15
      server/test/integration/flows/QueryFlow.js

@ -75,8 +75,6 @@ export enum QueryElemProperty {
} }
export enum OrderByType { export enum OrderByType {
Name = 0, Name = 0,
ArtistRanking,
TagRanking
} }
export interface QueryElem { export interface QueryElem {
prop?: QueryElemProperty, prop?: QueryElemProperty,
@ -88,7 +86,6 @@ export interface QueryElem {
export interface Ordering { export interface Ordering {
orderBy: { orderBy: {
type: OrderByType, type: OrderByType,
itemId?: number,
} }
ascending: boolean, ascending: boolean,
} }

@ -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.

@ -54,11 +54,12 @@ describe('POST /query with several songs and filters', () => {
artists: [ artists: [
{ {
artistId: 1, artistId: 1,
name: 'Artist1' name: 'Artist1',
storeLinks: [],
} }
], ],
tags: [], tags: [],
rankings: [] albums: []
}; };
const song2 = { const song2 = {
songId: 2, songId: 2,
@ -67,11 +68,12 @@ describe('POST /query with several songs and filters', () => {
artists: [ artists: [
{ {
artistId: 1, artistId: 1,
name: 'Artist1' name: 'Artist1',
storeLinks: [],
} }
], ],
tags: [], tags: [],
rankings: [] albums: []
}; };
const song3 = { const song3 = {
songId: 3, songId: 3,
@ -80,11 +82,12 @@ describe('POST /query with several songs and filters', () => {
artists: [ artists: [
{ {
artistId: 2, artistId: 2,
name: 'Artist2' name: 'Artist2',
storeLinks: [],
} }
], ],
tags: [], tags: [],
rankings: [] albums: []
}; };
async function checkAllSongs(req) { async function checkAllSongs(req) {

Loading…
Cancel
Save