From 475f82a35ed7870e5d78d8f71129b9c8a1caae3d Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 4 Apr 2021 10:37:24 +0200 Subject: [PATCH] mongo conditions support --- packages/datalib/src/CollectionGridDisplay.ts | 1 + packages/datalib/src/GridDisplay.ts | 1 + packages/filterparser/src/common.ts | 32 +++++ packages/filterparser/src/mongoParser.ts | 121 ++++++++++++++++++ packages/filterparser/src/parseFilter.ts | 38 +----- packages/filterparser/src/types.ts | 2 +- .../datagrid/CollectionDataGridCore.svelte | 30 ++++- .../web/src/datagrid/DataFilterControl.svelte | 24 ++++ packages/web/src/datagrid/DataGridCell.svelte | 24 +++- packages/web/src/datagrid/DataGridCore.svelte | 6 +- packages/web/src/datagrid/DataGridRow.svelte | 2 + 11 files changed, 241 insertions(+), 40 deletions(-) create mode 100644 packages/filterparser/src/common.ts create mode 100644 packages/filterparser/src/mongoParser.ts diff --git a/packages/datalib/src/CollectionGridDisplay.ts b/packages/datalib/src/CollectionGridDisplay.ts index dcf801c0..5987df74 100644 --- a/packages/datalib/src/CollectionGridDisplay.ts +++ b/packages/datalib/src/CollectionGridDisplay.ts @@ -94,6 +94,7 @@ export class CollectionGridDisplay extends GridDisplay { uniquePath, isStructured: true, parentHeaderText: createHeaderText(basePath), + filterType: 'mongo', }; } } diff --git a/packages/datalib/src/GridDisplay.ts b/packages/datalib/src/GridDisplay.ts index f078e28a..17927117 100644 --- a/packages/datalib/src/GridDisplay.ts +++ b/packages/datalib/src/GridDisplay.ts @@ -22,6 +22,7 @@ export interface DisplayColumn { isChecked?: boolean; hintColumnName?: string; dataType?: string; + filterType?: boolean; isStructured?: boolean; } diff --git a/packages/filterparser/src/common.ts b/packages/filterparser/src/common.ts new file mode 100644 index 00000000..1eece215 --- /dev/null +++ b/packages/filterparser/src/common.ts @@ -0,0 +1,32 @@ +import P from 'parsimmon'; + +export const whitespace = P.regexp(/\s*/m); + +export function token(parser) { + return parser.skip(whitespace); +} + +export function word(str) { + return P.string(str).thru(token); +} + +export function interpretEscapes(str) { + let escapes = { + b: '\b', + f: '\f', + n: '\n', + r: '\r', + t: '\t', + }; + return str.replace(/\\(u[0-9a-fA-F]{4}|[^u])/, (_, escape) => { + let type = escape.charAt(0); + let hex = escape.slice(1); + if (type === 'u') { + return String.fromCharCode(parseInt(hex, 16)); + } + if (escapes.hasOwnProperty(type)) { + return escapes[type]; + } + return type; + }); +} diff --git a/packages/filterparser/src/mongoParser.ts b/packages/filterparser/src/mongoParser.ts new file mode 100644 index 00000000..454d7438 --- /dev/null +++ b/packages/filterparser/src/mongoParser.ts @@ -0,0 +1,121 @@ +import P from 'parsimmon'; +import { interpretEscapes, token, word, whitespace } from './common'; + +const operatorCondition = operator => value => ({ + __placeholder__: { + [operator]: value, + }, +}); + +const regexCondition = regexString => value => ({ + __placeholder__: { + $regex: regexString.replace('#VALUE#', value), + $options: 'i', + }, +}); + +const numberTestCondition = () => value => ({ + $or: [ + { + __placeholder__: { + $regex: `.*${value}.*`, + $options: 'i', + }, + }, + { + __placeholder__: value, + }, + ], +}); + +const testCondition = (operator, value) => () => ({ + __placeholder__: { + [operator]: value, + }, +}); + +const compoudCondition = conditionType => conditions => { + if (conditions.length == 1) return conditions[0]; + return { + [conditionType]: conditions, + }; +}; + +const negateCondition = condition => ({ + __placeholder__: { + $not: condition.__placeholder__, + }, +}); + +const createParser = () => { + const langDef = { + string1: () => + token(P.regexp(/"((?:\\.|.)*?)"/, 1)) + .map(interpretEscapes) + .desc('string quoted'), + + string2: () => + token(P.regexp(/'((?:\\.|.)*?)'/, 1)) + .map(interpretEscapes) + .desc('string quoted'), + + number: () => + token(P.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/)) + .map(Number) + .desc('number'), + + noQuotedString: () => P.regexp(/[^\s^,^'^"]+/).desc('string unquoted'), + + value: r => P.alt(r.string1, r.string2, r.number, r.noQuotedString), + valueTestNum: r => r.number.map(numberTestCondition()), + valueTest: r => r.value.map(regexCondition('.*#VALUE#.*')), + + comma: () => word(','), + not: () => word('NOT'), + notExists: r => r.not.then(r.exists).map(testCondition('$exists', false)), + exists: () => word('EXISTS').map(testCondition('$exists', true)), + true: () => word('TRUE').map(testCondition('$eq', true)), + false: () => word('FALSE').map(testCondition('$eq', false)), + + eq: r => word('=').then(r.value).map(operatorCondition('$eq')), + ne: r => word('!=').then(r.value).map(operatorCondition('$ne')), + lt: r => word('<').then(r.value).map(operatorCondition('$lt')), + gt: r => word('>').then(r.value).map(operatorCondition('$gt')), + le: r => word('<=').then(r.value).map(operatorCondition('$lte')), + ge: r => word('>=').then(r.value).map(operatorCondition('$gte')), + startsWith: r => word('^').then(r.value).map(regexCondition('#VALUE#.*')), + endsWith: r => word('$').then(r.value).map(regexCondition('.*#VALUE#')), + contains: r => word('+').then(r.value).map(regexCondition('.*#VALUE#.*')), + startsWithNot: r => word('!^').then(r.value).map(regexCondition('#VALUE#.*')).map(negateCondition), + endsWithNot: r => word('!$').then(r.value).map(regexCondition('.*#VALUE#')).map(negateCondition), + containsNot: r => word('~').then(r.value).map(regexCondition('.*#VALUE#.*')).map(negateCondition), + + element: r => + P.alt( + r.exists, + r.notExists, + r.true, + r.false, + r.eq, + r.ne, + r.lt, + r.gt, + r.le, + r.ge, + r.startsWith, + r.endsWith, + r.contains, + r.startsWithNot, + r.endsWithNot, + r.containsNot, + r.valueTestNum, + r.valueTest + ).trim(whitespace), + factor: r => r.element.sepBy(whitespace).map(compoudCondition('$and')), + list: r => r.factor.sepBy(r.comma).map(compoudCondition('$or')), + }; + + return P.createLanguage(langDef); +}; + +export const mongoParser = createParser(); diff --git a/packages/filterparser/src/parseFilter.ts b/packages/filterparser/src/parseFilter.ts index 947ade58..75dd85ab 100644 --- a/packages/filterparser/src/parseFilter.ts +++ b/packages/filterparser/src/parseFilter.ts @@ -3,37 +3,8 @@ import moment from 'moment'; import { FilterType } from './types'; import { Condition } from 'dbgate-sqltree'; import { TransformType } from 'dbgate-types'; - -const whitespace = P.regexp(/\s*/m); - -function token(parser) { - return parser.skip(whitespace); -} - -function word(str) { - return P.string(str).thru(token); -} - -function interpretEscapes(str) { - let escapes = { - b: '\b', - f: '\f', - n: '\n', - r: '\r', - t: '\t', - }; - return str.replace(/\\(u[0-9a-fA-F]{4}|[^u])/, (_, escape) => { - let type = escape.charAt(0); - let hex = escape.slice(1); - if (type === 'u') { - return String.fromCharCode(parseInt(hex, 16)); - } - if (escapes.hasOwnProperty(type)) { - return escapes[type]; - } - return type; - }); -} +import { interpretEscapes, token, word, whitespace } from './common'; +import { mongoParser } from './mongoParser'; const binaryCondition = operator => value => ({ conditionType: 'binary', @@ -239,7 +210,8 @@ const createParser = (filterType: FilterType) => { yearMonthNum: () => P.regexp(/\d\d\d\d-\d\d?/).map(yearMonthCondition()), yearMonthDayNum: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?/).map(yearMonthDayCondition()), yearMonthDayMinute: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?\s+\d\d?:\d\d?/).map(yearMonthDayMinuteCondition()), - yearMonthDaySecond: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?(\s+|T)\d\d?:\d\d?:\d\d?/).map(yearMonthDaySecondCondition()), + yearMonthDaySecond: () => + P.regexp(/\d\d\d\d-\d\d?-\d\d?(\s+|T)\d\d?:\d\d?:\d\d?/).map(yearMonthDaySecondCondition()), value: r => P.alt(...allowedValues.map(x => r[x])), valueTestEq: r => r.value.map(binaryCondition('=')), @@ -348,9 +320,11 @@ const parsers = { string: createParser('string'), datetime: createParser('datetime'), logical: createParser('logical'), + mongo: mongoParser, }; export function parseFilter(value: string, filterType: FilterType): Condition { + // console.log('PARSING', value, 'WITH', filterType); const ast = parsers[filterType].list.tryParse(value); // console.log('AST', ast); return ast; diff --git a/packages/filterparser/src/types.ts b/packages/filterparser/src/types.ts index 8292fd13..447b49a9 100644 --- a/packages/filterparser/src/types.ts +++ b/packages/filterparser/src/types.ts @@ -1,3 +1,3 @@ // import types from 'dbgate-types'; -export type FilterType = 'number' | 'string' | 'datetime' | 'logical'; +export type FilterType = 'number' | 'string' | 'datetime' | 'logical' | 'mongo'; diff --git a/packages/web/src/datagrid/CollectionDataGridCore.svelte b/packages/web/src/datagrid/CollectionDataGridCore.svelte index 97b1d2a0..a99b4879 100644 --- a/packages/web/src/datagrid/CollectionDataGridCore.svelte +++ b/packages/web/src/datagrid/CollectionDataGridCore.svelte @@ -1,6 +1,26 @@ @@ -66,12 +67,26 @@ {:else if _.isDate(value)} {moment(value).format('YYYY-MM-DD HH:mm:ss')} {:else if value === true} - 1 + {#if isDynamicStructure} + true + {:else} + 1 + {/if} {:else if value === false} - 0 + {#if isDynamicStructure} + false + {:else} + 0 + {/if} {:else if _.isNumber(value)} {#if value >= 10000 || value <= -10000} - {value.toLocaleString()} + {#if isDynamicStructure} + {value.toLocaleString()} + {:else} + {value.toLocaleString()} + {/if} + {:else if isDynamicStructure} + {value.toString()} {:else} {value.toString()} {/if} @@ -157,4 +172,7 @@ color: var(--theme-font-3); font-style: italic; } + .value { + color: var(--theme-icon-green); + } diff --git a/packages/web/src/datagrid/DataGridCore.svelte b/packages/web/src/datagrid/DataGridCore.svelte index c3c9691a..8c440fe1 100644 --- a/packages/web/src/datagrid/DataGridCore.svelte +++ b/packages/web/src/datagrid/DataGridCore.svelte @@ -245,7 +245,6 @@ import openReferenceForm, { openPrimaryKeyForm } from '../formview/openReferenceForm'; import openNewTab from '../utility/openNewTab'; import ErrorInfo from '../elements/ErrorInfo.svelte'; - import resizeObserver from '../utility/resizeObserver'; import { dataGridRowHeight } from './DataGridRowHeightMeter.svelte'; export let onLoadNextData = undefined; @@ -1033,7 +1032,7 @@ {#if !display || (!isDynamicStructure && (!columns || columns.length == 0))} {:else if errorMessage} - + {:else if grider.errors && grider.errors.length > 0}
{#each grider.errors as err} @@ -1121,7 +1120,7 @@ > (domFilterControlsRef.get()[col.uniqueName] = value)} - filterType={getFilterType(col.dataType)} + filterType={col.filterType || getFilterType(col.dataType)} filter={display.getFilter(col.uniqueName)} setFilter={value => display.setFilter(col.uniqueName, value)} showResizeSplitter @@ -1146,6 +1145,7 @@ {visibleRealColumns} {rowHeight} {autofillSelectedCells} + {isDynamicStructure} selectedCells={filterCellsForRow(selectedCells, rowIndex)} autofillMarkerCell={filterCellForRow(autofillMarkerCell, rowIndex)} focusedColumn={display.focusedColumn} diff --git a/packages/web/src/datagrid/DataGridRow.svelte b/packages/web/src/datagrid/DataGridRow.svelte index 458ec140..36e8b425 100644 --- a/packages/web/src/datagrid/DataGridRow.svelte +++ b/packages/web/src/datagrid/DataGridRow.svelte @@ -19,6 +19,7 @@ export let inplaceEditorState; export let dispatchInsplaceEditor; export let onSetFormView; + export let isDynamicStructure = false; $: rowData = grider.getRowData(rowIndex); $: rowStatus = grider.getRowStatus(rowIndex); @@ -62,6 +63,7 @@ isDeleted={rowStatus.status == 'deleted' || (rowStatus.deletedFields && rowStatus.deletedFields.has(col.uniqueName))} {onSetFormView} + {isDynamicStructure} /> {/if} {/each}