mongo filtering via sql tree

This commit is contained in:
Jan Prochazka 2024-08-19 16:25:16 +02:00
parent 9fedfcbb0e
commit 303bd659ad
6 changed files with 129 additions and 7 deletions

View File

@ -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 => { const sqlTemplate = templateSql => {
return { return {
conditionType: 'rawTemplate', conditionType: 'rawTemplate',
@ -104,6 +155,8 @@ const createParser = (filterBehaviour: FilterBehaviour) => {
.map(Number) .map(Number)
.desc('number'), .desc('number'),
objectid: () => token(P.regexp(/ObjectId\(['"]?[0-9a-f]{24}['"]?\)/)).desc('ObjectId'),
hexstring: () => hexstring: () =>
token(P.regexp(/0x(([0-9a-fA-F][0-9a-fA-F])+)/, 1)) token(P.regexp(/0x(([0-9a-fA-F][0-9a-fA-F])+)/, 1))
.map(x => ({ .map(x => ({
@ -123,13 +176,22 @@ const createParser = (filterBehaviour: FilterBehaviour) => {
valueTestEq: r => r.value.map(binaryCondition('=')), valueTestEq: r => r.value.map(binaryCondition('=')),
hexTestEq: r => r.hexstring.map(binaryCondition('=')), hexTestEq: r => r.hexstring.map(binaryCondition('=')),
valueTestStr: r => r.value.map(likeCondition('like', '%#VALUE#%')), 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(','), comma: () => word(','),
not: () => word('NOT'), not: () => word('NOT'),
empty: () => word('EMPTY'),
array: () => word('ARRAY'),
notNull: r => r.not.then(r.null).map(unaryCondition('isNotNull')), notNull: r => r.not.then(r.null).map(unaryCondition('isNotNull')),
null: () => word('NULL').map(unaryCondition('isNull')), null: () => word('NULL').map(unaryCondition('isNull')),
empty: () => word('EMPTY').map(unaryCondition('isEmpty')), isEmpty: r => r.empty.map(unaryCondition('isEmpty')),
notEmpty: r => r.not.then(r.empty).map(unaryCondition('isNotEmpty')), isNotEmpty: r => r.not.then(r.empty).map(unaryCondition('isNotEmpty')),
true: () => P.regexp(/true/i).map(binaryFixedValueCondition('1')), true: () => P.regexp(/true/i).map(binaryFixedValueCondition('1')),
false: () => P.regexp(/false/i).map(binaryFixedValueCondition('0')), false: () => P.regexp(/false/i).map(binaryFixedValueCondition('0')),
trueNum: () => word('1').map(binaryFixedValueCondition('1')), trueNum: () => word('1').map(binaryFixedValueCondition('1')),
@ -155,6 +217,7 @@ const createParser = (filterBehaviour: FilterBehaviour) => {
}; };
const allowedValues = []; // 'string1', 'string2', 'number', 'noQuotedString']; const allowedValues = []; // 'string1', 'string2', 'number', 'noQuotedString'];
if (filterBehaviour.allowStringToken) { if (filterBehaviour.allowStringToken) {
allowedValues.push('string1', 'string2', 'noQuotedString'); allowedValues.push('string1', 'string2', 'noQuotedString');
} }
@ -164,6 +227,14 @@ const createParser = (filterBehaviour: FilterBehaviour) => {
const allowedElements = []; const allowedElements = [];
if (filterBehaviour.supportExistsTesting) {
allowedElements.push('exists', 'notExists');
}
if (filterBehaviour.supportArrayTesting) {
allowedElements.push('emptyArray', 'notEmptyArray');
}
if (filterBehaviour.supportNullTesting) { if (filterBehaviour.supportNullTesting) {
allowedElements.push('null', 'notNull'); allowedElements.push('null', 'notNull');
} }
@ -181,7 +252,7 @@ const createParser = (filterBehaviour: FilterBehaviour) => {
} }
if (filterBehaviour.supportEmpty) { if (filterBehaviour.supportEmpty) {
allowedElements.push('empty', 'notEmpty'); allowedElements.push('isEmpty', 'isNotEmpty');
} }
if (filterBehaviour.allowHexString) { 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 // must be last
if (filterBehaviour.allowStringToken) { if (filterBehaviour.allowStringToken) {
allowedElements.push('valueTestStr'); allowedElements.push('valueTestStr');

View File

@ -79,6 +79,11 @@ export interface TestCondition extends UnaryCondition {
conditionType: 'isNull' | 'isNotNull' | 'isEmpty' | 'isNotEmpty'; conditionType: 'isNull' | 'isNotNull' | 'isEmpty' | 'isNotEmpty';
} }
export interface SpecificPredicateCondition extends UnaryCondition {
conditionType: 'specificPredicate';
predicate: string;
}
export interface CompoudCondition { export interface CompoudCondition {
conditionType: 'and' | 'or'; conditionType: 'and' | 'or';
conditions: Condition[]; conditions: Condition[];
@ -135,7 +140,8 @@ export type Condition =
| InCondition | InCondition
| NotInCondition | NotInCondition
| RawTemplateCondition | RawTemplateCondition
| AnyColumnPassEvalOnlyCondition; | AnyColumnPassEvalOnlyCondition
| SpecificPredicateCondition;
export interface Source { export interface Source {
name?: NamedObjectInfo; name?: NamedObjectInfo;

View File

@ -48,6 +48,8 @@ export const mongoFilterBehaviour: FilterBehaviour = {
supportExistsTesting: true, supportExistsTesting: true,
allowStringToken: true, allowStringToken: true,
allowNumberDualTesting: true,
allowObjectIdTesting: true,
}; };
export const evalFilterBehaviour: FilterBehaviour = { export const evalFilterBehaviour: FilterBehaviour = {

View File

@ -18,4 +18,6 @@ export interface FilterBehaviour {
allowStringToken?: boolean; allowStringToken?: boolean;
allowNumberToken?: boolean; allowNumberToken?: boolean;
allowHexString?: boolean; allowHexString?: boolean;
allowNumberDualTesting?: boolean;
allowObjectIdTesting?: boolean;
} }

View File

@ -270,10 +270,11 @@ const driver = {
return res.databases; return res.databases;
}, },
async readCollection(pool, options) { async readCollection(pool, options) {
const mongoCondition = convertToMongoCondition(options.condition);
console.log('******************* mongoCondition *****************')
console.log(JSON.stringify(mongoCondition, undefined, 2));
try { try {
const mongoCondition = convertToMongoCondition(options.condition);
// console.log('******************* mongoCondition *****************');
// console.log(JSON.stringify(mongoCondition, undefined, 2));
const collection = pool.__getDatabase().collection(options.pureName); const collection = pool.__getDatabase().collection(options.pureName);
if (options.countDocuments) { if (options.countDocuments) {
const count = await collection.countDocuments(convertObjectId(mongoCondition) || {}); const count = await collection.countDocuments(convertObjectId(mongoCondition) || {});

View File

@ -89,6 +89,38 @@ function convertToMongoCondition(filter) {
$options: 'i', $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: default:
throw new Error(`Unknown condition type ${filter.conditionType}`); throw new Error(`Unknown condition type ${filter.conditionType}`);
} }