diff --git a/packages/datalib/src/CollectionGridDisplay.ts b/packages/datalib/src/CollectionGridDisplay.ts
index dcf801c0b..5987df743 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 f078e28a1..179271179 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 000000000..1eece2154
--- /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 000000000..454d74388
--- /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 947ade58a..75dd85ab3 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 8292fd139..447b49a9d 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 97b1d2a0d..a99b48790 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 c3c9691a0..8c440fe1d 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))}