|
|
|
@ -46,9 +46,9 @@ export function isLeafElem(q: QueryElem): q is QueryLeafElem { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export enum QueryNodeOp { |
|
|
|
|
And = 0, |
|
|
|
|
Or, |
|
|
|
|
Not, |
|
|
|
|
And = "AND", |
|
|
|
|
Or = "OR", |
|
|
|
|
Not = "NOT", |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export interface QueryNodeElem { |
|
|
|
@ -83,7 +83,7 @@ export function queryNot(arg: QueryElem) { |
|
|
|
|
|
|
|
|
|
export type QueryElem = QueryLeafElem | QueryNodeElem; |
|
|
|
|
|
|
|
|
|
function mapToServerProperty(l: QueryLeafBy, queryFor: QueryFor | null) : |
|
|
|
|
function mapToServerProperty(l: QueryLeafBy, queryFor: QueryFor | null): |
|
|
|
|
serverApi.QueryElemProperty | null { |
|
|
|
|
return { |
|
|
|
|
[QueryLeafBy.TrackName]: serverApi.QueryElemProperty.trackName, |
|
|
|
@ -95,15 +95,15 @@ function mapToServerProperty(l: QueryLeafBy, queryFor: QueryFor | null) : |
|
|
|
|
[QueryLeafBy.TrackId]: serverApi.QueryElemProperty.trackId, |
|
|
|
|
[QueryLeafBy.StoreLinks]: |
|
|
|
|
(queryFor == QueryFor.Albums) ? serverApi.QueryElemProperty.albumStoreLinks : |
|
|
|
|
(queryFor == QueryFor.Artists) ? serverApi.QueryElemProperty.artistStoreLinks : |
|
|
|
|
(queryFor == QueryFor.Tracks) ? serverApi.QueryElemProperty.trackStoreLinks : |
|
|
|
|
null, |
|
|
|
|
(queryFor == QueryFor.Artists) ? serverApi.QueryElemProperty.artistStoreLinks : |
|
|
|
|
(queryFor == QueryFor.Tracks) ? serverApi.QueryElemProperty.trackStoreLinks : |
|
|
|
|
null, |
|
|
|
|
[QueryLeafBy.TagInfo]: null, |
|
|
|
|
[QueryLeafBy.NotApplicable]: null, |
|
|
|
|
}[l]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function mapToServerLeafOp(l: QueryLeafOp, queryFor: QueryFor | null) : |
|
|
|
|
function mapToServerLeafOp(l: QueryLeafOp, queryFor: QueryFor | null): |
|
|
|
|
serverApi.QueryLeafOp | null { |
|
|
|
|
return { |
|
|
|
|
[QueryLeafOp.Equals]: serverApi.QueryLeafOp.Eq, |
|
|
|
@ -112,7 +112,7 @@ function mapToServerLeafOp(l: QueryLeafOp, queryFor: QueryFor | null) : |
|
|
|
|
}[l]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function mapToServerNodeOp(l: QueryNodeOp, queryFor: QueryFor | null) : |
|
|
|
|
function mapToServerNodeOp(l: QueryNodeOp, queryFor: QueryFor | null): |
|
|
|
|
serverApi.QueryNodeOp | null { |
|
|
|
|
return { |
|
|
|
|
[QueryNodeOp.And]: serverApi.QueryNodeOp.And, |
|
|
|
@ -131,7 +131,7 @@ export function addPlaceholders( |
|
|
|
|
inNode: null | QueryNodeOp, |
|
|
|
|
): QueryElem { |
|
|
|
|
|
|
|
|
|
const makePlaceholder : () => QueryElem = () => { |
|
|
|
|
const makePlaceholder: () => QueryElem = () => { |
|
|
|
|
return { |
|
|
|
|
a: QueryLeafBy.NotApplicable, |
|
|
|
|
leafOp: QueryLeafOp.Placeholder, |
|
|
|
@ -147,7 +147,19 @@ export function addPlaceholders( |
|
|
|
|
|
|
|
|
|
if (q == null) { |
|
|
|
|
return makePlaceholder(); |
|
|
|
|
} else if (isNodeElem(q)) { |
|
|
|
|
} else if (isNodeElem(q) && q.nodeOp == QueryNodeOp.Not && |
|
|
|
|
isLeafElem(q.operands[0]) && |
|
|
|
|
inNode !== null) { |
|
|
|
|
// Not only modifies its sub-node, so this is handled like a leaf.
|
|
|
|
|
return { operands: [q, makePlaceholder()], nodeOp: otherOp[inNode] }; |
|
|
|
|
} else if (isNodeElem(q) && q.nodeOp == QueryNodeOp.Not && |
|
|
|
|
isLeafElem(q.operands[0]) && |
|
|
|
|
inNode === null) { |
|
|
|
|
// Not only modifies its sub-node, so this is handled like a leaf.
|
|
|
|
|
return { operands: [q, makePlaceholder()], nodeOp: QueryNodeOp.And }; |
|
|
|
|
} else if (isNodeElem(q) && q.nodeOp != QueryNodeOp.Not) { |
|
|
|
|
// Combinational operators.
|
|
|
|
|
|
|
|
|
|
var operands = q.operands.map((op: any, idx: number) => { |
|
|
|
|
return addPlaceholders(op, q.nodeOp); |
|
|
|
|
}); |
|
|
|
@ -195,7 +207,7 @@ export function removePlaceholders(q: QueryElem | null): QueryElem | null { |
|
|
|
|
if (newOperands.length === 0) { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
if (newOperands.length === 1) { |
|
|
|
|
if ((newOperands.length === 1 && [QueryNodeOp.Or, QueryNodeOp.And].includes(q.nodeOp))) { |
|
|
|
|
return newOperands[0]; |
|
|
|
|
} |
|
|
|
|
return { operands: newOperands, nodeOp: q.nodeOp }; |
|
|
|
@ -206,34 +218,26 @@ export function removePlaceholders(q: QueryElem | null): QueryElem | null { |
|
|
|
|
return q; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Note: null means an invalidating node. It should make the whole query invalid, so it should
|
|
|
|
|
// be propagated to the root.
|
|
|
|
|
export function simplify(q: QueryElem | null, queryFor: QueryFor | null): QueryElem | null { |
|
|
|
|
// TODO: null should not be a valid input. Instead we should have
|
|
|
|
|
// constant true, constant false values.
|
|
|
|
|
|
|
|
|
|
if (q && isNodeElem(q)) { |
|
|
|
|
var newOperands: QueryElem[] = []; |
|
|
|
|
q.operands.forEach((o: QueryElem) => { |
|
|
|
|
const s = simplify(o, queryFor); |
|
|
|
|
if (s !== null) { newOperands.push(s); } |
|
|
|
|
}) |
|
|
|
|
if (newOperands.length === 0) { return null; } |
|
|
|
|
|
|
|
|
|
// AND/OR optimization
|
|
|
|
|
if ((newOperands.length === 1 && q.nodeOp == QueryNodeOp.And) || |
|
|
|
|
(newOperands.length === 1 && q.nodeOp == QueryNodeOp.Or)) {
|
|
|
|
|
return newOperands[0];
|
|
|
|
|
var newOperands: (QueryElem | null)[] = q.operands.map((op: QueryElem) => simplify(op, queryFor)); |
|
|
|
|
if (newOperands.filter((op: QueryElem | null) => op === null).length > 0) { |
|
|
|
|
console.log("nullifying op:", q, queryFor) |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return { operands: newOperands, nodeOp: q.nodeOp }; |
|
|
|
|
return { operands: newOperands as QueryElem[], nodeOp: q.nodeOp }; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// This shouldn't be part of simplification.
|
|
|
|
|
// if (q && isLeafElem(q)) {
|
|
|
|
|
// if (mapToServerLeafOp(q.leafOp, queryFor) === null ||
|
|
|
|
|
// mapToServerProperty(q.a, queryFor) === null) {
|
|
|
|
|
// return null;
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// Nullify any queries that contain operations which are invalid
|
|
|
|
|
// for the current queried object type.
|
|
|
|
|
if (q && isLeafElem(q) && queryFor !== null && |
|
|
|
|
(mapToServerLeafOp(q.leafOp, queryFor) === null || |
|
|
|
|
mapToServerProperty(q.a, queryFor) === null)) { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return q; |
|
|
|
|
} |
|
|
|
@ -253,6 +257,7 @@ export function toApiQuery(q: QueryElem, queryFor: QueryFor | null): serverApi.Q |
|
|
|
|
let a = mapToServerProperty(q.a, queryFor); |
|
|
|
|
let op = mapToServerLeafOp(q.leafOp, queryFor); |
|
|
|
|
if (a === null || op === null) { |
|
|
|
|
console.log("Error details:", q, queryFor); |
|
|
|
|
throw 'Found a null leaf in query tree. Was it simplified first?'; |
|
|
|
|
} |
|
|
|
|
// "Regular" queries
|
|
|
|
|