Skip to content
Snippets Groups Projects
Commit 6b5ea957 authored by Leonardo's avatar Leonardo
Browse files

feat: date logic pill support

parent e1b04ded
No related branches found
No related tags found
1 merge request!36feat: date logic pill support
Pipeline #146280 passed
import type { AllLogicStatement, AnyStatement, BackendQueryFormat, NodeStruct, QueryStruct } from 'ts-common';
import type { QueryText } from '../model';
import { extractLogicSQL } from './convertLogicToSQL';
import type { QueryCacheData } from '../cypher/converter/model';
function cleanStringOfNewLinesAndTabs(str: string): string {
if (!str) {
return '';
}
let trimmedSQL = str.replace(/\n/g, ' ');
trimmedSQL = trimmedSQL.replaceAll(/ {2,50}/g, ' ');
trimmedSQL = trimmedSQL.replace(/\t+/g, '');
return trimmedSQL.trim();
}
// function buildNodeCTE(path: QueryStruct, logic: AnyStatement | undefined): { node: string; edges: string; rows: string[] } {
// const node = path.node;
// const pathId = path.id;
// if (!node || !node.label || !node.id) {
// return { node: '', edges: '', rows: [] };
// }
// const loopThoughNodes = (node: NodeStruct): NodeStruct[] => {
// if (node.relation && node.relation.node) {
// return [node, ...loopThoughNodes(node.relation.node)];
// }
// return [node];
// };
// const allNodes = loopThoughNodes(node);
// const where = logic ? extractLogicSQL(logic, { nodes: allNodes }).logic : undefined;
// const rowList = allNodes.map(n => `${n.id}_row`);
// const select = `
// SELECT ${[
// ...allNodes.map(n => `to_jsonb(${n.id}) AS ${n.id}_row`),
// ...allNodes
// .filter(n => n.relation && n!.relation!.node)
// .map(
// n =>
// `${n.id}.${n!.relation!.label!.split('.')[0]}::text AS ${n.id}_${n!.relation!.label!.split('.')[0]}, ${n!.relation!.node!.id}.${
// n!.relation!.label!.split('.')[1]
// }::text AS ${n!.relation!.node!.id}_${n!.relation!.label!.split('.')[1]}`,
// ),
// ].join(', ')}
// FROM ${node.label} ${node.id}
// ${allNodes
// .filter(n => n?.relation?.node?.label && n?.relation?.node?.id)
// .map(
// n =>
// `INNER JOIN ${n!.relation!.node!.label} ${n!.relation!.node!.id} ON ${n.id}.${n!.relation!.label!.split('.')[0]}::text = ${
// n!.relation!.node!.id
// }.${n!.relation!.label!.split('.')[1]}::text`,
// )
// .join('\n')}
// ${where ? `WHERE ${where}` : ''}`;
// const edges = buildEdgesCTE(node, rowList, pathId).join(' UNION ALL ');
// return { node: select, edges, rows: rowList };
// }
// function buildEdgesCTE(node: NodeStruct | undefined, rowList: string[], pathId: string): string[] {
// if (!node || !node.relation || !node.relation.node || !node.id || !node.relation.node.id || !node.relation.label) {
// return [];
// }
// const edge = `
// SELECT jsonb_build_object(
// '_id', concat('${node.label}-', ${node.id}_${node.relation.label.split('.')[0]}::text, '-${node.relation.node.label}-', ${
// node.relation.node.id
// }_${node.relation.label.split('.')[1]}::text),
// 'label', '${node.relation.label}',
// 'attributes', '{}'::jsonb,
// 'from', ${node.id}_${node.relation.label.split('.')[0]}::text,
// 'to', ${node.relation.node.id}_${node.relation.label.split('.')[1]}::text
// ) AS edge,
// ${rowList.join(',\n')}
// FROM ${pathId}`;
// const ret = cleanStringOfNewLinesAndTabs(edge);
// return [ret, ...buildEdgesCTE(node.relation.node, rowList, pathId)];
// }
// function buildNodeCountsCTE(node: NodeStruct | undefined): string[] {
// if (!node || !node.label || !node.id) {
// return [];
// }
// const nodeParts: string[] = [];
// const mainNode = `SELECT '${node.label}' AS label FROM ${node.label}`;
// nodeParts.push(cleanStringOfNewLinesAndTabs(mainNode));
// buildNodeCountsCTE(node.relation?.node).forEach(part => nodeParts.push(part));
// return nodeParts;
// }
// function buildEdgeCountsCTE(node: NodeStruct | undefined): string[] {
// if (!node || !node.relation || !node.relation.node || !node.id || !node.relation.node.id || !node.relation.label) {
// return [];
// }
// const edge = `
// SELECT '${node.relation.label}' AS label, COUNT(*) AS count
// FROM ${node.label} ${node.id}
// JOIN ${node.relation.node.label} ${node.relation.node.id}
// ON ${node.id}.${node.relation.label.split('.')[0]} = ${node.relation.node.id}.${node.relation.label.split('.')[1]}
// GROUP BY label`;
// const ret = cleanStringOfNewLinesAndTabs(edge);
// return [ret, ...buildEdgeCountsCTE(node.relation.node)];
// }
export function query2SQL(query: BackendQueryFormat): QueryText {
const loopThoughNodes = (node: NodeStruct): NodeStruct[] => {
if (node.relation && node.relation.node) {
return [node, ...loopThoughNodes(node.relation.node)];
}
return [node];
};
const allNodes = query.query.flatMap(path => loopThoughNodes(path.node));
const allUniqueNodes: Record<
string,
{ label: string; joins: { from: string; leftJoin: string; rightJoin: string; to: string; fromLabel: string; toLabel: string }[] }
> = Object.fromEntries(
allNodes
.map(n => [n.id, { label: n.label, joins: [] }])
.filter((node, index, self) => {
return index === self.findIndex(t => t[0] === node[0] && t[1] === node[1]);
}),
);
allNodes.forEach(n => {
if (n.id && n.relation && n.relation.node && n.relation.node.id && n.label && n.relation.node.label) {
allUniqueNodes[n.id].joins.push({
from: n.id,
leftJoin: n!.relation!.label!.split('.')[0],
rightJoin: n!.relation!.label!.split('.')[1],
to: n!.relation!.node!.id,
fromLabel: n!.label,
toLabel: n!.relation!.node!.label,
});
}
});
console.log(JSON.stringify(allNodes, null, 2));
const nodeEntries = Object.entries(allUniqueNodes);
const where = query.logic ? extractLogicSQL(query.logic, { nodes: allNodes }).logic : undefined;
const firstNode = nodeEntries[0][1];
const relIDSql = Object.values(allUniqueNodes)
.map(n =>
n.joins.map(j => `${j.from}.${j.leftJoin} AS ${j.from}_${j.leftJoin}, ${j.to}.${j.rightJoin} AS ${j.to}_${j.rightJoin}`).join(', '),
)
.filter(s => s.length > 0);
const select = [...nodeEntries.map(n => `to_jsonb(${n[0]}) AS ${n[0]}_row`), ...relIDSql].join(', ');
let sql = `SELECT ${select} FROM ${firstNode.label} ${nodeEntries[0][0]}`;
// let sql = `SELECT jsonb_agg(row_to_json(t)) FROM ${firstNode.label} ${nodeEntries[0][0]}`;
let joins = '';
for (let i = 0; i < nodeEntries.length; i++) {
const [nodeId, node] = nodeEntries[i];
if (node.joins) {
node.joins.forEach(j => {
joins += ` INNER JOIN ${allUniqueNodes[j.to].label} ${j.to} ON ${j.from}.${j.leftJoin}::text = ${j.to}.${j.rightJoin}::text`;
});
}
}
sql += joins;
sql += where ? ` WHERE ${where}` : '';
const sqlNoLimit = sql;
sql += query.limit ? ` LIMIT ${query.limit}` : '';
const nodes_cte = `SELECT jsonb_agg(row_to_json(t)) AS nodes FROM (${nodeEntries
.map(n => `SELECT DISTINCT ${n[0]}_row AS node FROM combined`)
.join(' UNION ALL ')}) AS t`;
const allUniqueEdges = Object.values(allUniqueNodes)
.map(n =>
n.joins
.map(j => {
return `SELECT jsonb_build_object(
'_id', concat('${n.label}-', md5(${j.from}_row::text), '-${allUniqueNodes[j.to].label}-', md5(${j.to}_row::text)),
'label', '${j.leftJoin}.${j.rightJoin}',
'attributes', '{}'::jsonb,
'from', md5(${j.from}_row::text),
'to', md5(${j.to}_row::text)
) AS edge
FROM combined`;
})
.filter(s => s.length > 0)
.join(' UNION ALL '),
)
.filter(s => s.length > 0)
.join(' UNION ALL ');
// console.log(JSON.stringify(allUniqueNodes, null, 2));
// const rowList = allNodes.map(n => `${n.id}_row`);
// const select = `
// SELECT ${allNodes.map(n => `to_jsonb(${n.id}) AS ${n.id}_row`).join(', ')},
// ${allNodes
// .filter(n => n.relation && n!.relation!.node)
// .map(
// n =>
// `${n.id}.${n!.relation!.label!.split('.')[0]}::text AS ${n.id}_${n!.relation!.label!.split('.')[0]}, ${n!.relation!.node!.id}.${
// n!.relation!.label!.split('.')[1]
// }::text AS ${n!.relation!.node!.id}_${n!.relation!.label!.split('.')[1]}`,
// )
// .join(', ')}
// FROM ${node.label} ${node.id}
// ${allNodes
// .filter(n => n?.relation?.node?.label && n?.relation?.node?.id)
// .map(
// n =>
// `INNER JOIN ${n!.relation!.node!.label} ${n!.relation!.node!.id} ON ${n.id}.${n!.relation!.label!.split('.')[0]}::text = ${
// n!.relation!.node!.id
// }.${n!.relation!.label!.split('.')[1]}::text`,
// )
// .join('\n')}
// ${where ? `WHERE ${where}` : ''}`;
// const edges = buildEdgesCTE(node, rowList, pathId).join(' UNION ALL ');
// const nodesCTE = query.query.flatMap(path => ({ ...buildNodeCTE(path, query.logic), path: path }));
// const nodeCountsCTE = [...new Set(query.query.flatMap(path => buildNodeCountsCTE(path.node)))];
// const edgeCountsCTE = [...new Set(query.query.flatMap(path => buildEdgeCountsCTE(path.node)))];
const mainQuery = `WITH combined AS (${sql}),
nodes_cte AS (
SELECT jsonb_agg(row_to_json(t)) AS nodes FROM (${nodeEntries
.map(
([nodeId, node]) => `
SELECT
md5(${nodeId}_row::text) AS _id,
'${node.label}' AS label,
${nodeId}_row - 'id' AS attributes
FROM combined`,
)
.join(' UNION ALL ')}) AS t
),
edges_cte AS (
SELECT jsonb_agg(edge) AS edges
FROM (
${allUniqueEdges.length > 0 ? allUniqueEdges : 'SELECT null AS edge WHERE FALSE'}
) AS e
),
node_counts AS (
SELECT jsonb_agg(jsonb_build_object('label', label, 'count', count)) AS nodeCounts
FROM (
SELECT label, COUNT(*) AS count
FROM (
${[...new Set(nodeEntries.map(([_, node]) => node.label))]
.map(label => `SELECT '${label}' AS label FROM ${label}`)
.join(' UNION ALL ')}
) AS node_labels
GROUP BY label
) AS counts
)
SELECT jsonb_build_object(
'nodes', nodes_cte.nodes,
'edges', COALESCE(edges_cte.edges, '[]'::jsonb),
'nodeCounts', COALESCE((SELECT nodeCounts FROM node_counts), '[]'::jsonb)
) AS result FROM nodes_cte, edges_cte;`;
// 'nodeCounts', COALESCE( (SELECT jsonb_agg(jsonb_build_object('label', label, 'count', count))
// FROM node_counts), '[]'::jsonb ),
// 'edgeCounts', COALESCE( (SELECT jsonb_agg(jsonb_build_object('label', label, 'count', count))
// FROM edge_counts), '[]'::jsonb )
// node_counts AS (
// SELECT label, COUNT(*) AS count
// FROM (
// ${nodesCTE.map(node => node.node).join(' UNION ALL ')}
// ) t
// GROUP BY label
// ),
// edge_counts AS (
// ${nodesCTE.map(node => node.edges).join(' UNION ALL ')}
// )
return {
query: cleanStringOfNewLinesAndTabs(mainQuery),
countQuery: '',
};
}
import { StringFilterTypes, type AnyStatement } from 'ts-common/src/model/query/logic/general';
import { type AllLogicStatement, type AnyStatement } from 'ts-common/src/model/query/logic/general';
import type { QueryCacheData } from './model';
import { log } from 'ts-common/src/logger/logger';
import { NumberAggregationTypes, NumberFilterTypes, NumberFunctionTypes } from 'ts-common/src/model/query/logic/number';
import { StringFilterTypes, StringFunctionTypes } from 'ts-common/src/model/query/logic/string';
import { DateFilterTypes, DateFunctionTypes } from 'ts-common/src/model/query/logic/date/types';
export function createWhereLogic(
op: string,
......@@ -8,7 +11,8 @@ export function createWhereLogic(
whereLogic: Set<string>,
cacheData: QueryCacheData,
): { logic: string; where: Set<string> } {
const newWhereLogic = `${left.replace('.', '_')}_${op}`;
const newOp = op.replace('_', '').toLowerCase();
const newWhereLogic = `${left.replace('.', '_')}_${newOp}`;
// TODO: Relation temporarily ignored due to unnecessary added complexity in the query
// for (const relation of cacheData.relations) {
......@@ -17,7 +21,7 @@ export function createWhereLogic(
// }
// }
whereLogic.add(`${op}(${left}) AS ${newWhereLogic}`);
whereLogic.add(`${newOp}(${left}) AS ${newWhereLogic}`);
for (const entity of cacheData.entities) {
if (entity.id !== left) {
whereLogic.add(entity.id);
......@@ -31,24 +35,28 @@ export function extractLogicCypher(logicQuery: AnyStatement, cacheData: QueryCac
switch (typeof logicQuery) {
case 'object':
if (Array.isArray(logicQuery)) {
let op = logicQuery[0].replace('_', '').toLowerCase();
let right = logicQuery?.[2];
const op = logicQuery[0];
let newOp: string = op;
const right = logicQuery?.[2];
const { logic: left, where: whereLogic } = extractLogicCypher(logicQuery[1], cacheData);
switch (op) {
case '!=':
op = '<>';
case NumberFilterTypes.EQUAL:
case StringFilterTypes.EQUAL:
case DateFilterTypes.EQUAL:
newOp = '=';
break;
case '==':
if (right === '".*"') op = '=~'; // in case "equals to <empty string>", this is what happens
else op = '=';
case NumberFilterTypes.NOT_EQUAL:
case StringFilterTypes.NOT_EQUAL:
case DateFilterTypes.NOT_EQUAL:
if (right === '".*"') newOp = '=~'; // in case "equals to <empty string>", this is what happens
else newOp = '<>';
break;
case 'like':
op = '=~';
case StringFilterTypes.LIKE:
newOp = '=~';
break;
case 'in':
case 'present in list':
op = 'IN';
case StringFilterTypes.IN:
newOp = 'IN';
if (typeof right === 'string') {
return {
logic: `(${left} IN [${right
......@@ -60,9 +68,8 @@ export function extractLogicCypher(logicQuery: AnyStatement, cacheData: QueryCac
};
}
break;
case 'not in':
case 'not in list':
op = 'NOT IN';
case StringFilterTypes.NOT_IN:
newOp = 'NOT IN';
if (typeof right === 'string') {
return {
logic: `(NOT ${left} IN [${right
......@@ -74,20 +81,34 @@ export function extractLogicCypher(logicQuery: AnyStatement, cacheData: QueryCac
};
}
break;
case 'isempty':
case StringFilterTypes.EMPTY:
return { logic: `(${left} IS NULL OR ${left} = "")`, where: whereLogic };
case 'isnotempty':
case StringFilterTypes.NOT_EMPTY:
return { logic: `(${left} IS NOT NULL AND ${left} <> "")`, where: whereLogic };
case 'lower':
case StringFunctionTypes.LOWER:
return { logic: `toLower(${left})`, where: whereLogic };
case 'upper':
case StringFunctionTypes.UPPER:
return { logic: `toUpper(${left})`, where: whereLogic };
case 'avg':
case 'count':
case 'max':
case 'min':
case 'sum':
case NumberAggregationTypes.AVG:
case NumberAggregationTypes.COUNT:
case NumberAggregationTypes.MAX:
case NumberAggregationTypes.MIN:
case NumberAggregationTypes.SUM:
return createWhereLogic(op, left, whereLogic, cacheData);
case DateFunctionTypes.YEAR:
return { logic: `${left}.year`, where: whereLogic };
case DateFunctionTypes.MONTH:
return { logic: `${left}.month`, where: whereLogic };
case DateFunctionTypes.DAY:
return { logic: `${left}.day`, where: whereLogic };
case DateFunctionTypes.HOUR:
return { logic: `${left}.hour`, where: whereLogic };
case DateFunctionTypes.MINUTE:
return { logic: `${left}.minute`, where: whereLogic };
case DateFunctionTypes.SECOND:
return { logic: `${left}.seconds`, where: whereLogic };
case DateFunctionTypes.DAY_OF_WEEK:
return { logic: `${left}.weekDay`, where: whereLogic };
}
if (logicQuery.length > 2 && logicQuery[2]) {
const { logic: rightLogic, where: whereLogicRight } = extractLogicCypher(logicQuery[2], cacheData);
......@@ -96,12 +117,12 @@ export function extractLogicCypher(logicQuery: AnyStatement, cacheData: QueryCac
for (const where of whereLogicRight) {
whereLogic.add(where);
}
if (op === '=~') {
if (newOp === '=~') {
rightMutable = `(".*" + ${rightMutable} + ".*")`;
}
return { logic: `(${left} ${op} ${rightMutable})`, where: whereLogic };
return { logic: `(${left} ${newOp.replace('_', '').toLowerCase()} ${rightMutable})`, where: whereLogic };
}
return { logic: `(${op} ${left})`, where: whereLogic };
return { logic: `(${newOp.replace('_', '').toLowerCase()} ${left})`, where: whereLogic };
}
return { logic: logicQuery, where: new Set() };
case 'string':
......
import type { BackendQueryFormat } from 'ts-common';
import { StringFilterTypes, StringFunctionTypes } from 'ts-common/src/model/query/logic/string';
import { query2Cypher } from './queryConverter';
import { StringFilterTypes, type BackendQueryFormat } from 'ts-common';
import { expect, test, describe, it } from 'bun:test';
import { NumberAggregationTypes, NumberFilterTypes, NumberFunctionTypes } from 'ts-common/src/model/query/logic/number';
function fixCypherSpaces(cypher?: string | null): string {
if (!cypher) {
......@@ -71,7 +73,7 @@ describe('query2Cypher', () => {
const query: BackendQueryFormat = {
saveStateID: 'test',
return: ['*'],
logic: ['!=', '@p1.name', '"Raymond Campbell"'],
logic: [NumberFilterTypes.NOT_EQUAL, '@p1.name', '"Raymond Campbell"'],
query: [
{
id: 'path1',
......@@ -122,7 +124,7 @@ describe('query2Cypher', () => {
const query: BackendQueryFormat = {
saveStateID: 'test',
limit: 5000,
logic: ['And', ['<', '@movie.imdbRating', 7.5], ['==', 'p2.age', 'p1.age']],
logic: ['And', [NumberFilterTypes.LESS_THAN, '@movie.imdbRating', 7.5], [NumberFilterTypes.EQUAL, 'p2.age', 'p1.age']],
query: [
{
id: 'path1',
......@@ -165,7 +167,7 @@ describe('query2Cypher', () => {
const query: BackendQueryFormat = {
saveStateID: 'test',
limit: 5000,
logic: ['<', ['-', '@movie.year', 'p1.year'], 10],
logic: [NumberFilterTypes.LESS_THAN, [NumberFunctionTypes.SUBTRACT, '@movie.year', 'p1.year'], 10],
query: [
{
id: 'path1',
......@@ -199,7 +201,7 @@ describe('query2Cypher', () => {
const query: BackendQueryFormat = {
saveStateID: 'test',
limit: 5000,
logic: ['And', ['<', '@movie.imdbRating', 7.5], ['==', 'p2.age', 'p1.age']],
logic: ['And', [NumberFilterTypes.LESS_THAN, '@movie.imdbRating', 7.5], [NumberFilterTypes.EQUAL, 'p2.age', 'p1.age']],
query: [
{
id: 'path1',
......@@ -238,7 +240,7 @@ describe('query2Cypher', () => {
const query: BackendQueryFormat = {
saveStateID: 'test',
limit: 5000,
logic: ['<', '@p1.age', ['Avg', '@p1.age']],
logic: [NumberFilterTypes.LESS_THAN, '@p1.age', [NumberAggregationTypes.AVG, '@p1.age']],
query: [
{
id: 'path1',
......@@ -275,7 +277,7 @@ describe('query2Cypher', () => {
const query: BackendQueryFormat = {
saveStateID: 'test',
limit: 5000,
logic: ['<', '@p1.age', ['Avg', '@p1.age']],
logic: [NumberFilterTypes.LESS_THAN, '@p1.age', [NumberAggregationTypes.AVG, '@p1.age']],
query: [
{
id: 'path1',
......@@ -330,7 +332,7 @@ describe('query2Cypher', () => {
const query: BackendQueryFormat = {
saveStateID: 'test',
limit: 5000,
logic: ['Like', ['Lower', '@p1.name'], '"john"'],
logic: [StringFilterTypes.LIKE, [StringFunctionTypes.LOWER, '@p1.name'], '"john"'],
query: [
{
id: 'path1',
......@@ -355,7 +357,7 @@ describe('query2Cypher', () => {
const query: BackendQueryFormat = {
saveStateID: 'test',
limit: 500,
logic: ['Like', '@id_1691576718400.title', '"ale"'],
logic: [StringFilterTypes.LIKE, '@id_1691576718400.title', '"ale"'],
query: [
{
id: 'path_0',
......@@ -386,7 +388,7 @@ describe('query2Cypher', () => {
const query: BackendQueryFormat = {
saveStateID: 'test',
limit: 500,
logic: ['Like', '@id_1691576718400.title', '"ale"'],
logic: [StringFilterTypes.LIKE, '@id_1691576718400.title', '"ale"'],
query: [
{
id: 'path_0',
......@@ -417,7 +419,7 @@ describe('query2Cypher', () => {
const query: BackendQueryFormat = {
saveStateID: 'test',
limit: 500,
logic: ['<', '@id_1698231933579.unitPrice', '10'],
logic: [NumberFilterTypes.LESS_THAN, '@id_1698231933579.unitPrice', '10'],
query: [
{
id: 'path_0',
......@@ -447,7 +449,7 @@ describe('query2Cypher', () => {
const query: BackendQueryFormat = {
saveStateID: 'test',
return: ['*'],
logic: ['>', ['Count', '@p1'], '1'],
logic: [NumberFilterTypes.GREATER_THAN, [NumberAggregationTypes.COUNT, '@p1'], '1'],
query: [
{
id: 'path1',
......@@ -523,7 +525,7 @@ describe('query2Cypher', () => {
},
},
],
logic: ['Upper', '@id_1731428699410.name'],
logic: [StringFunctionTypes.UPPER, '@id_1731428699410.name'],
return: ['*'],
};
......@@ -618,7 +620,11 @@ describe('query2Cypher', () => {
limit: 100,
return: ['*'],
cached: false,
logic: ['And', ['==', '@id_1737115397423.GO', '"852349"'], ['==', '@id_1737115886717.goodFlow', 'false']],
logic: [
'And',
[NumberFilterTypes.EQUAL, '@id_1737115397423.GO', '"852349"'],
[NumberFilterTypes.EQUAL, '@id_1737115886717.goodFlow', 'false'],
],
};
const cypher = query2Cypher(query);
......
......@@ -2,7 +2,7 @@ import type { SerializedNode } from 'graphology-types';
import { hasCycle } from 'graphology-dag';
import Graph from 'graphology';
import { allSimplePaths } from 'graphology-simple-path';
import type { BackendQueryFormat, NodeStruct, QueryStruct, RelationStruct } from 'ts-common';
import { AllLogicMap, type BackendQueryFormat, type NodeStruct, type QueryStruct, type RelationStruct } from 'ts-common';
import type { MachineLearning } from 'ts-common/src/model/query/queryRequestModel';
import type {
......@@ -44,7 +44,7 @@ const traverseEntityRelationPaths = (
x: node.attributes.x,
y: node.attributes.x,
depth: { min: settings.depth.min, max: settings.depth.max },
direction: 'both',
direction: QueryRelationDirection.BOTH,
attributes: [],
});
} else {
......@@ -98,9 +98,17 @@ function calculateQueryLogic(
node: SerializedNode<LogicNodeAttributes>,
graph: QueryMultiGraph,
logics: SerializedNode<LogicNodeAttributes>[],
): AllLogicStatement {
): AllLogicStatement | undefined {
if (!node?.attributes) throw Error('Malformed Graph! Node has no attributes');
const connectionsToLeft = graph.edges.filter(e => e.target === node.key);
const connectionsToRight = graph.edges.filter(e => e.source === node.key);
const first = node.attributes.logic.logic[0];
const logicMap = Object.values(AllLogicMap).find(value => value.type === first);
if (logicMap && logicMap.ignoreNoOutput && connectionsToRight.length === 0) {
log.debug('AllLogicMap with ignored output due to missing third term:', first, AllLogicMap[first], node.attributes.logic.logic);
return undefined; // Return a neutral value to ignore this logic
}
const ret = [...node.attributes.logic.logic].map(l => {
if (typeof l !== 'string') throw Error('Malformed Graph! Logic node has no logic');
......@@ -159,7 +167,7 @@ function queryLogicUnion(
): AllLogicStatement | undefined {
const graphLogicChunks = nodes.map(node => calculateQueryLogic(node, graph, logics));
if (graphLogicChunks.length === 0) return undefined;
if (graphLogicChunks.length === 0 || !graphLogicChunks[0]) return undefined;
if (graphLogicChunks.length === 1) return graphLogicChunks[0];
const constraintNodeId = nodes[0].key;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment