diff --git a/packages/filterparser/src/parseFilter.ts b/packages/filterparser/src/parseFilter.ts index 9850462c..10e1a73a 100644 --- a/packages/filterparser/src/parseFilter.ts +++ b/packages/filterparser/src/parseFilter.ts @@ -67,6 +67,57 @@ const negateCondition = condition => { }; }; +const numberTestCondition = () => value => { + return { + conditionType: 'or', + conditions: [ + { + conditionType: 'like', + left: { + exprType: 'placeholder', + }, + right: { + exprType: 'value', + value: `.*${value}.*`, + }, + }, + { + conditionType: 'binary', + operator: '=', + left: { + exprType: 'placeholder', + }, + right: { + exprType: 'value', + value, + }, + }, + ], + }; +}; + +const idRegex = /[('"]([0-9a-f]{24})['")]/; + +const objectIdTestCondition = () => value => ({ + conditionType: 'binary', + operator: '=', + left: { + exprType: 'placeholder', + }, + right: { + exprType: 'value', + value: { $oid: value.match(idRegex)[1] }, + }, +}); + +const specificPredicateCondition = predicate => () => ({ + conditionType: 'specificPredicate', + predicate, + expr: { + exprType: 'placeholder', + }, +}); + const sqlTemplate = templateSql => { return { conditionType: 'rawTemplate', @@ -104,6 +155,8 @@ const createParser = (filterBehaviour: FilterBehaviour) => { .map(Number) .desc('number'), + objectid: () => token(P.regexp(/ObjectId\(['"]?[0-9a-f]{24}['"]?\)/)).desc('ObjectId'), + hexstring: () => token(P.regexp(/0x(([0-9a-fA-F][0-9a-fA-F])+)/, 1)) .map(x => ({ @@ -123,13 +176,22 @@ const createParser = (filterBehaviour: FilterBehaviour) => { valueTestEq: r => r.value.map(binaryCondition('=')), hexTestEq: r => r.hexstring.map(binaryCondition('=')), valueTestStr: r => r.value.map(likeCondition('like', '%#VALUE#%')), + valueTestNum: r => r.number.map(numberTestCondition()), + valueTestObjectId: r => r.objectid.map(objectIdTestCondition()), + + notExists: r => r.not.then(r.exists).map(specificPredicateCondition('notExists')), + notEmptyArray: r => r.not.then(r.empty).then(r.array).map(specificPredicateCondition('notEmptyArray')), + emptyArray: r => r.empty.then(r.array).map(specificPredicateCondition('emptyArray')), + exists: () => word('EXISTS').map(specificPredicateCondition('exists')), comma: () => word(','), not: () => word('NOT'), + empty: () => word('EMPTY'), + array: () => word('ARRAY'), notNull: r => r.not.then(r.null).map(unaryCondition('isNotNull')), null: () => word('NULL').map(unaryCondition('isNull')), - empty: () => word('EMPTY').map(unaryCondition('isEmpty')), - notEmpty: r => r.not.then(r.empty).map(unaryCondition('isNotEmpty')), + isEmpty: r => r.empty.map(unaryCondition('isEmpty')), + isNotEmpty: r => r.not.then(r.empty).map(unaryCondition('isNotEmpty')), true: () => P.regexp(/true/i).map(binaryFixedValueCondition('1')), false: () => P.regexp(/false/i).map(binaryFixedValueCondition('0')), trueNum: () => word('1').map(binaryFixedValueCondition('1')), @@ -155,6 +217,7 @@ const createParser = (filterBehaviour: FilterBehaviour) => { }; const allowedValues = []; // 'string1', 'string2', 'number', 'noQuotedString']; + if (filterBehaviour.allowStringToken) { allowedValues.push('string1', 'string2', 'noQuotedString'); } @@ -164,6 +227,14 @@ const createParser = (filterBehaviour: FilterBehaviour) => { const allowedElements = []; + if (filterBehaviour.supportExistsTesting) { + allowedElements.push('exists', 'notExists'); + } + + if (filterBehaviour.supportArrayTesting) { + allowedElements.push('emptyArray', 'notEmptyArray'); + } + if (filterBehaviour.supportNullTesting) { allowedElements.push('null', 'notNull'); } @@ -181,7 +252,7 @@ const createParser = (filterBehaviour: FilterBehaviour) => { } if (filterBehaviour.supportEmpty) { - allowedElements.push('empty', 'notEmpty'); + allowedElements.push('isEmpty', 'isNotEmpty'); } if (filterBehaviour.allowHexString) { @@ -199,6 +270,14 @@ const createParser = (filterBehaviour: FilterBehaviour) => { } } + if (filterBehaviour.allowNumberDualTesting) { + allowedElements.push('valueTestNum'); + } + + if (filterBehaviour.allowObjectIdTesting) { + allowedElements.push('valueTestObjectId'); + } + // must be last if (filterBehaviour.allowStringToken) { allowedElements.push('valueTestStr'); diff --git a/packages/sqltree/src/types.ts b/packages/sqltree/src/types.ts index 5a8390e1..db2b609d 100644 --- a/packages/sqltree/src/types.ts +++ b/packages/sqltree/src/types.ts @@ -79,6 +79,11 @@ export interface TestCondition extends UnaryCondition { conditionType: 'isNull' | 'isNotNull' | 'isEmpty' | 'isNotEmpty'; } +export interface SpecificPredicateCondition extends UnaryCondition { + conditionType: 'specificPredicate'; + predicate: string; +} + export interface CompoudCondition { conditionType: 'and' | 'or'; conditions: Condition[]; @@ -135,7 +140,8 @@ export type Condition = | InCondition | NotInCondition | RawTemplateCondition - | AnyColumnPassEvalOnlyCondition; + | AnyColumnPassEvalOnlyCondition + | SpecificPredicateCondition; export interface Source { name?: NamedObjectInfo; diff --git a/packages/tools/src/filterBehaviours.ts b/packages/tools/src/filterBehaviours.ts index 61963901..80009546 100644 --- a/packages/tools/src/filterBehaviours.ts +++ b/packages/tools/src/filterBehaviours.ts @@ -48,6 +48,8 @@ export const mongoFilterBehaviour: FilterBehaviour = { supportExistsTesting: true, allowStringToken: true, + allowNumberDualTesting: true, + allowObjectIdTesting: true, }; export const evalFilterBehaviour: FilterBehaviour = { diff --git a/packages/types/filter-type.d.ts b/packages/types/filter-type.d.ts index 2fa50647..522490f3 100644 --- a/packages/types/filter-type.d.ts +++ b/packages/types/filter-type.d.ts @@ -18,4 +18,6 @@ export interface FilterBehaviour { allowStringToken?: boolean; allowNumberToken?: boolean; allowHexString?: boolean; + allowNumberDualTesting?: boolean; + allowObjectIdTesting?: boolean; } diff --git a/plugins/dbgate-plugin-mongo/src/backend/driver.js b/plugins/dbgate-plugin-mongo/src/backend/driver.js index e9ecbede..b548b470 100644 --- a/plugins/dbgate-plugin-mongo/src/backend/driver.js +++ b/plugins/dbgate-plugin-mongo/src/backend/driver.js @@ -270,10 +270,11 @@ const driver = { return res.databases; }, async readCollection(pool, options) { - const mongoCondition = convertToMongoCondition(options.condition); - console.log('******************* mongoCondition *****************') - console.log(JSON.stringify(mongoCondition, undefined, 2)); try { + const mongoCondition = convertToMongoCondition(options.condition); + // console.log('******************* mongoCondition *****************'); + // console.log(JSON.stringify(mongoCondition, undefined, 2)); + const collection = pool.__getDatabase().collection(options.pureName); if (options.countDocuments) { const count = await collection.countDocuments(convertObjectId(mongoCondition) || {}); diff --git a/plugins/dbgate-plugin-mongo/src/frontend/convertToMongoCondition.js b/plugins/dbgate-plugin-mongo/src/frontend/convertToMongoCondition.js index 536f538a..1ac599d9 100644 --- a/plugins/dbgate-plugin-mongo/src/frontend/convertToMongoCondition.js +++ b/plugins/dbgate-plugin-mongo/src/frontend/convertToMongoCondition.js @@ -89,6 +89,38 @@ function convertToMongoCondition(filter) { $options: 'i', }, }; + + case 'specificPredicate': + switch (filter.predicate) { + case 'exists': + return { + [convertLeftOperandToMongoColumn(filter.expr)]: { + $exists: true, + }, + }; + case 'notExists': + return { + [convertLeftOperandToMongoColumn(filter.expr)]: { + $exists: false, + }, + }; + case 'emptyArray': + return { + [convertLeftOperandToMongoColumn(filter.expr)]: { + $exists: true, + $eq: [], + }, + }; + case 'notEmptyArray': + return { + [convertLeftOperandToMongoColumn(filter.expr)]: { + $exists: true, + $type: 'array', + $ne: [], + }, + }; + } + default: throw new Error(`Unknown condition type ${filter.conditionType}`); }