diff --git a/src/components/definitionExpressionManager.test.ts b/src/components/definitionExpressionManager.test.ts index 20da722..a45c695 100644 --- a/src/components/definitionExpressionManager.test.ts +++ b/src/components/definitionExpressionManager.test.ts @@ -89,13 +89,29 @@ describe('createDefinitionExpression', () => { poly: '', }); }); + it('should request all records when all type filters are selected', () => { + // using 'all' and all keys + const state: State = { + projects: new Set(['Proposed']), + features: new Set(featureTypes.map(({ featureType }) => featureType)), + join: or, + }; + const result = generateDefinitionExpression(state); + + expect(result).toEqual({ + centroids: "Status in('Proposed')", + point: "StatusDescription in('Proposed')", + line: "StatusDescription in('Proposed')", + poly: "StatusDescription in('Proposed')", + }); + }); it('should use project status values when selecting project status', () => { const state: State = { projects: new Set(['Proposed', 'Current']), features: all, join: or, }; - const result = generateDefinitionExpression(state); + let result = generateDefinitionExpression(state); expect(result).toEqual({ centroids: "Status in('Proposed','Current')", @@ -103,6 +119,16 @@ describe('createDefinitionExpression', () => { line: "StatusDescription in('Proposed','Current')", poly: "StatusDescription in('Proposed','Current')", }); + + state.join = and; + + result = generateDefinitionExpression(state); + expect(result).toEqual({ + centroids: "Status in('Proposed','Current')", + point: "StatusDescription in('Proposed','Current')", + line: "StatusDescription in('Proposed','Current')", + poly: "StatusDescription in('Proposed','Current')", + }); }); it('should use feature type codes when selecting feature types', () => { const state: State = { @@ -110,7 +136,7 @@ describe('createDefinitionExpression', () => { features: new Set(['Terrestrial Treatment Area']), join: or, }; - const result = generateDefinitionExpression(state); + let result = generateDefinitionExpression(state); expect(result).toEqual({ centroids: 'Project_ID in(select Project_ID from POLY where TypeCode in(1))', @@ -118,6 +144,27 @@ describe('createDefinitionExpression', () => { line: '1=0', poly: 'TypeCode in(1)', }); + + state.features = new Set(['Terrestrial Treatment Area', 'Fish passage structure', 'Dam']); + result = generateDefinitionExpression(state); + + expect(result).toEqual({ + centroids: + 'Project_ID in(select Project_ID from POINT where TypeCode in(9) union select Project_ID from LINE where TypeCode in(12) union select Project_ID from POLY where TypeCode in(1))', + point: 'TypeCode in(9)', + line: 'TypeCode in(12)', + poly: 'TypeCode in(1)', + }); + + state.features = new Set(['Dam']); + result = generateDefinitionExpression(state); + + expect(result).toEqual({ + centroids: 'Project_ID in(select Project_ID from LINE where TypeCode in(12))', + point: '1=0', + line: 'TypeCode in(12)', + poly: '1=0', + }); }); it('should only apply selected feature types to its containing table', () => { const state: State = { @@ -154,17 +201,27 @@ describe('createDefinitionExpression', () => { it('should use the user input join value intersect when selecting feature types with and', () => { const state: State = { projects: all, - features: new Set(['Fence', 'Dam']), + features: new Set(['Fish passage structure', 'Dam']), join: and, }; - const result = generateDefinitionExpression(state); + let result = generateDefinitionExpression(state); expect(result).toEqual({ centroids: - "((Project_ID in(select Project_ID from POINT where TypeDescription='Dam' intersect Project_ID in(select Project_ID from POINT where TypeDescription='Fence')))", + 'Project_ID in(select Project_ID from POINT where TypeCode=9 intersect select Project_ID from LINE where TypeCode=12)', point: - "(Project_ID in(select Project_ID from POINT where TypeDescription='Dam' intersect Project_ID in(select Project_ID from POINT where TypeDescription='Fence')))", - line: '1=0', + 'TypeCode in(9) and Project_ID in(select Project_ID from POINT where TypeCode=9 intersect select Project_ID from LINE where TypeCode=12)', + line: 'TypeCode in(12) and Project_ID in(select Project_ID from POINT where TypeCode=9 intersect select Project_ID from LINE where TypeCode=12)', + poly: '1=0', + }); + + state.features = new Set(['Dam']); + result = generateDefinitionExpression(state); + + expect(result).toEqual({ + centroids: 'Project_ID in(select Project_ID from LINE where TypeCode=12)', + point: '1=0', + line: 'TypeCode in(12) and Project_ID in(select Project_ID from LINE where TypeCode=12)', poly: '1=0', }); }); diff --git a/src/components/definitionExpressionManager.ts b/src/components/definitionExpressionManager.ts index 2cfda04..956c3a4 100644 --- a/src/components/definitionExpressionManager.ts +++ b/src/components/definitionExpressionManager.ts @@ -4,8 +4,14 @@ import { featureTypes, projectStatus } from './data/filters'; const allRecords = ''; const noRecords = '1=0' as const; const all = 'all' as const; -const and = 'and' as const; const or = 'or' as const; +const addPossibleConjunction = (phrase: string) => { + if (!phrase || [allRecords, noRecords].includes(phrase)) { + return phrase; + } + + return `${phrase} and `; +}; const full = featureTypes.reduce( (acc, { type }) => { @@ -67,22 +73,22 @@ const generateExpressions = ( projectPredicate: string, featurePredicates: Record< 'point' | 'line' | 'poly', - { - code: number; - type: string; - }[] + | { + code: number; + type: string; + }[] + | string >, join: 'and' | 'or', ) => { - // if the join style is or, we can use sql in statements for each table - if (join === or) { - const result = { - centroids: '', - point: '', - line: '', - poly: '', - }; + const result = { + centroids: '', + point: '', + line: '', + poly: '', + }; + if (join === or) { // if there is a project status filter, we can use it for all tables if (projectPredicate) { const featureStatusExpression = `StatusDescription in(${projectPredicate})`; @@ -95,13 +101,9 @@ const generateExpressions = ( const expressions = []; - if ((featurePredicates?.point ?? 0).length) { - if (featurePredicates.point.length === full.point) { - result.point = allRecords; - } else { - if (result.point) { - result.point += ' and '; - } + if (Array.isArray(featurePredicates.point)) { + if (featurePredicates.point.length !== full.point) { + result.point = addPossibleConjunction(result.point); const codes = featurePredicates.point.map(({ code }) => code).join(','); @@ -112,13 +114,9 @@ const generateExpressions = ( result.point = typeof featurePredicates?.point === 'string' ? result.point : '1=0'; } - if ((featurePredicates?.line ?? 0).length) { - if (featurePredicates.line.length === full.line) { - result.point = allRecords; - } else { - if (result.line) { - result.line += ' and '; - } + if (Array.isArray(featurePredicates.line)) { + if (featurePredicates.line.length !== full.line) { + result.line = addPossibleConjunction(result.line); const codes = featurePredicates.line.map(({ code }) => code).join(','); @@ -129,13 +127,9 @@ const generateExpressions = ( result.line = typeof featurePredicates?.line === 'string' ? result.line : '1=0'; } - if ((featurePredicates?.poly ?? 0).length) { - if (featurePredicates.poly.length === full.poly) { - result.point = allRecords; - } else { - if (result.poly) { - result.poly += ' and '; - } + if (Array.isArray(featurePredicates.poly)) { + if (featurePredicates.poly.length !== full.poly) { + result.poly = addPossibleConjunction(result.poly); const codes = featurePredicates.poly.map(({ code }) => code).join(','); @@ -152,7 +146,7 @@ const generateExpressions = ( if (result.centroids) { if (expressions.length > 0) { - result.centroids += ' and '; + result.centroids = addPossibleConjunction(result.centroids); } if (expressions.length === 1) { @@ -160,13 +154,83 @@ const generateExpressions = ( return result; } + } + + result.centroids += `Project_ID in(${expressions.join(` union `)})`; + + return result; + } else { + if (projectPredicate) { + const featureStatusExpression = `StatusDescription in(${projectPredicate})`; + + result.centroids = `Status in(${projectPredicate})`; + result.point = featureStatusExpression; + result.line = featureStatusExpression; + result.poly = featureStatusExpression; + } + + const expressions = []; - result.centroids += `Project_ID in(${expressions.join(` union `)})`; + if (Array.isArray(featurePredicates.point)) { + result.point = addPossibleConjunction(result.point); + const predicate = featurePredicates.point + .map(({ code }) => `select Project_ID from POINT where TypeCode=${code}`) + .join(' intersect '); + + expressions.push(predicate); + result.point += `TypeCode in${featurePredicates.point.map(({ code }) => `(${code})`).join(',')}`; + } else { + result.point = typeof featurePredicates?.point === 'string' ? result.point : '1=0'; + } + + if (Array.isArray(featurePredicates.line)) { + result.line = addPossibleConjunction(result.line); + + const predicate = featurePredicates.line + .map(({ code }) => `select Project_ID from LINE where TypeCode=${code}`) + .join(' intersect '); + + expressions.push(predicate); + result.line += `TypeCode in${featurePredicates.line.map(({ code }) => `(${code})`).join(',')}`; + } else { + result.line = typeof featurePredicates?.line === 'string' ? result.line : '1=0'; + } + + if (Array.isArray(featurePredicates.poly)) { + result.poly = addPossibleConjunction(result.poly); + + const predicate = featurePredicates.poly + .map(({ code }) => `select Project_ID from POLY where TypeCode=${code}`) + .join(' intersect '); + + expressions.push(predicate); + result.poly += `TypeCode in${featurePredicates.poly.map(({ code }) => `(${code})`).join(',')}`; + } else { + result.poly = typeof featurePredicates?.poly === 'string' ? result.poly : '1=0'; + } + + if (expressions.length === 0) { return result; } - result.centroids += `Project_ID in(${expressions.join(` union `)})`; + result.centroids = addPossibleConjunction(result.centroids); + result.point = addPossibleConjunction(result.point); + result.line = addPossibleConjunction(result.line); + result.poly = addPossibleConjunction(result.poly); + + const expression = `Project_ID in(${expressions.join(` intersect `)})`; + result.centroids += expression; + + if (result.point && result.point != noRecords) { + result.point += expression; + } + if (result.line && result.line != noRecords) { + result.line += expression; + } + if (result.poly && result.poly != noRecords) { + result.poly += expression; + } return result; } @@ -194,7 +258,6 @@ export const generateDefinitionExpression = ({ }; } - // empty set mean no filters so no records can be matched if ( projectPredicate === allRecords && Object.entries(featurePredicates).every(([key, value]) => value.length === full[key as keyof typeof full])