From 4bdcf219f2caa6cb512a9049a5c10b0bc01dc5f4 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 19 Aug 2024 16:47:54 +0200 Subject: [PATCH] datetime filters added to standard filters --- packages/filterparser/src/parseFilter.ts | 244 ++++++++++++++++++++++- packages/tools/src/filterBehaviours.ts | 2 +- 2 files changed, 244 insertions(+), 2 deletions(-) diff --git a/packages/filterparser/src/parseFilter.ts b/packages/filterparser/src/parseFilter.ts index 10e1a73a..8939e07d 100644 --- a/packages/filterparser/src/parseFilter.ts +++ b/packages/filterparser/src/parseFilter.ts @@ -1,10 +1,11 @@ import P from 'parsimmon'; +import moment from 'moment'; import { Condition } from 'dbgate-sqltree'; import { interpretEscapes, token, word, whitespace } from './common'; import { mongoParser } from './mongoParser'; import { datetimeParser } from './datetimeParser'; import { hexStringToArray } from 'dbgate-tools'; -import { FilterBehaviour } from 'dbgate-types'; +import { FilterBehaviour, TransformType } from 'dbgate-types'; const binaryCondition = operator => value => ({ conditionType: 'binary', @@ -128,6 +129,163 @@ const sqlTemplate = templateSql => { }; }; +function getTransformCondition(transform: TransformType, value) { + return { + conditionType: 'binary', + operator: '=', + left: { + exprType: 'transform', + transform, + expr: { + exprType: 'placeholder', + }, + }, + right: { + exprType: 'value', + value, + }, + }; +} + +const yearCondition = () => value => { + return getTransformCondition('YEAR', value); +}; + +const yearMonthCondition = () => value => { + const m = value.match(/(\d\d\d\d)-(\d\d?)/); + + return { + conditionType: 'and', + conditions: [getTransformCondition('YEAR', m[1]), getTransformCondition('MONTH', m[2])], + }; +}; + +const yearMonthDayCondition = () => value => { + const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)/); + + return { + conditionType: 'and', + conditions: [ + getTransformCondition('YEAR', m[1]), + getTransformCondition('MONTH', m[2]), + getTransformCondition('DAY', m[3]), + ], + }; +}; + +const yearEdge = edgeFunction => value => { + return moment(new Date(parseInt(value), 0, 1)) + [edgeFunction]('year') + .format('YYYY-MM-DDTHH:mm:ss.SSS'); +}; + +const yearMonthEdge = edgeFunction => value => { + const m = value.match(/(\d\d\d\d)-(\d\d?)/); + + return moment(new Date(parseInt(m[1]), parseInt(m[2]) - 1, 1)) + [edgeFunction]('month') + .format('YYYY-MM-DDTHH:mm:ss.SSS'); +}; + +const yearMonthDayEdge = edgeFunction => value => { + const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)/); + + return moment(new Date(parseInt(m[1]), parseInt(m[2]) - 1, parseInt(m[3]))) + [edgeFunction]('day') + .format('YYYY-MM-DDTHH:mm:ss.SSS'); +}; + +const yearMonthDayMinuteEdge = edgeFunction => value => { + const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)\s+(\d\d?):(\d\d?)/); + const year = m[1]; + const month = m[2]; + const day = m[3]; + const hour = m[4]; + const minute = m[5]; + const dateObject = new Date(year, month - 1, day, hour, minute); + + return moment(dateObject)[edgeFunction]('minute').format('YYYY-MM-DDTHH:mm:ss.SSS'); +}; + +const yearMonthDayMinuteSecondEdge = edgeFunction => value => { + const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)(T|\s+)(\d\d?):(\d\d?):(\d\d?)/); + const year = m[1]; + const month = m[2]; + const day = m[3]; + const hour = m[5]; + const minute = m[6]; + const second = m[7]; + const dateObject = new Date(year, month - 1, day, hour, minute, second); + + return moment(dateObject)[edgeFunction]('second').format('YYYY-MM-DDTHH:mm:ss.SSS'); +}; + +const createIntervalCondition = (start, end) => { + return { + conditionType: 'and', + conditions: [ + { + conditionType: 'binary', + operator: '>=', + left: { + exprType: 'placeholder', + }, + right: { + exprType: 'value', + value: start, + }, + }, + { + conditionType: 'binary', + operator: '<=', + left: { + exprType: 'placeholder', + }, + right: { + exprType: 'value', + value: end, + }, + }, + ], + }; +}; + +const createDateIntervalCondition = (start, end) => { + return createIntervalCondition(start.format('YYYY-MM-DDTHH:mm:ss.SSS'), end.format('YYYY-MM-DDTHH:mm:ss.SSS')); +}; + +const fixedMomentIntervalCondition = (intervalType, diff) => () => { + return createDateIntervalCondition( + moment().add(intervalType, diff).startOf(intervalType), + moment().add(intervalType, diff).endOf(intervalType) + ); +}; + +const yearMonthDayMinuteCondition = () => value => { + const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)\s+(\d\d?):(\d\d?)/); + const year = m[1]; + const month = m[2]; + const day = m[3]; + const hour = m[4]; + const minute = m[5]; + const dateObject = new Date(year, month - 1, day, hour, minute); + + return createDateIntervalCondition(moment(dateObject).startOf('minute'), moment(dateObject).endOf('minute')); +}; + +const yearMonthDaySecondCondition = () => value => { + const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)(T|\s+)(\d\d?):(\d\d?):(\d\d?)/); + const year = m[1]; + const month = m[2]; + const day = m[3]; + const hour = m[5]; + const minute = m[6]; + const second = m[7]; + const dateObject = new Date(year, month - 1, day, hour, minute, second); + + return createDateIntervalCondition(moment(dateObject).startOf('second'), moment(dateObject).endOf('second')); +}; + const createParser = (filterBehaviour: FilterBehaviour) => { const langDef = { string1: () => @@ -184,6 +342,66 @@ const createParser = (filterBehaviour: FilterBehaviour) => { emptyArray: r => r.empty.then(r.array).map(specificPredicateCondition('emptyArray')), exists: () => word('EXISTS').map(specificPredicateCondition('exists')), + this: () => word('THIS'), + last: () => word('LAST'), + next: () => word('NEXT'), + week: () => word('WEEK'), + month: () => word('MONTH'), + year: () => word('YEAR'), + + yesterday: () => word('YESTERDAY').map(fixedMomentIntervalCondition('day', -1)), + today: () => word('TODAY').map(fixedMomentIntervalCondition('day', 0)), + tomorrow: () => word('TOMORROW').map(fixedMomentIntervalCondition('day', 1)), + + lastWeek: r => r.last.then(r.week).map(fixedMomentIntervalCondition('week', -1)), + thisWeek: r => r.this.then(r.week).map(fixedMomentIntervalCondition('week', 0)), + nextWeek: r => r.next.then(r.week).map(fixedMomentIntervalCondition('week', 1)), + + lastMonth: r => r.last.then(r.month).map(fixedMomentIntervalCondition('month', -1)), + thisMonth: r => r.this.then(r.month).map(fixedMomentIntervalCondition('month', 0)), + nextMonth: r => r.next.then(r.month).map(fixedMomentIntervalCondition('month', 1)), + + lastYear: r => r.last.then(r.year).map(fixedMomentIntervalCondition('year', -1)), + thisYear: r => r.this.then(r.year).map(fixedMomentIntervalCondition('year', 0)), + nextYear: r => r.next.then(r.year).map(fixedMomentIntervalCondition('year', 1)), + + dateValueStart: r => + P.alt( + r.yearMonthDayMinuteSecondStart, + r.yearMonthDayMinuteStart, + r.yearMonthDayStart, + r.yearMonthStart, + r.yearNumStart + ), + dateValueEnd: r => + P.alt(r.yearMonthDayMinuteSecondEnd, r.yearMonthDayMinuteEnd, r.yearMonthDayEnd, r.yearMonthEnd, r.yearNumEnd), + + dateLe: r => word('<=').then(r.dateValueEnd).map(binaryCondition('<=')), + dateGe: r => word('>=').then(r.dateValueStart).map(binaryCondition('>=')), + dateLt: r => word('<').then(r.dateValueStart).map(binaryCondition('<')), + dateGt: r => word('>').then(r.dateValueEnd).map(binaryCondition('>')), + + yearNum: () => P.regexp(/\d\d\d\d/).map(yearCondition()), + 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()), + + yearNumStart: () => P.regexp(/\d\d\d\d/).map(yearEdge('startOf')), + yearNumEnd: () => P.regexp(/\d\d\d\d/).map(yearEdge('endOf')), + yearMonthStart: () => P.regexp(/\d\d\d\d-\d\d?/).map(yearMonthEdge('startOf')), + yearMonthEnd: () => P.regexp(/\d\d\d\d-\d\d?/).map(yearMonthEdge('endOf')), + yearMonthDayStart: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?/).map(yearMonthDayEdge('startOf')), + yearMonthDayEnd: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?/).map(yearMonthDayEdge('endOf')), + yearMonthDayMinuteStart: () => + P.regexp(/\d\d\d\d-\d\d?-\d\d?\s+\d\d?:\d\d?/).map(yearMonthDayMinuteEdge('startOf')), + yearMonthDayMinuteEnd: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?\s+\d\d?:\d\d?/).map(yearMonthDayMinuteEdge('endOf')), + yearMonthDayMinuteSecondStart: () => + P.regexp(/\d\d\d\d-\d\d?-\d\d?(\s+|T)\d\d?:\d\d?:\d\d?/).map(yearMonthDayMinuteSecondEdge('startOf')), + yearMonthDayMinuteSecondEnd: () => + P.regexp(/\d\d\d\d-\d\d?-\d\d?(\s+|T)\d\d?:\d\d?:\d\d?/).map(yearMonthDayMinuteSecondEdge('endOf')), + comma: () => word(','), not: () => word('NOT'), empty: () => word('EMPTY'), @@ -227,6 +445,30 @@ const createParser = (filterBehaviour: FilterBehaviour) => { const allowedElements = []; + if (filterBehaviour.supportDatetimeComparison) { + allowedElements.push('yearMonthDaySecond', 'yearMonthDayMinute', 'yearMonthDayNum', 'yearMonthNum', 'yearNum'); + } + + if (filterBehaviour.supportDatetimeSymbols) { + allowedElements.push( + 'today', + 'tomorrow', + 'lastWeek', + 'thisWeek', + 'nextWeek', + 'lastMonth', + 'thisMonth', + 'nextMonth', + 'lastYear', + 'thisYear', + 'nextYear' + ); + } + + if (filterBehaviour.supportDatetimeComparison) { + allowedElements.push('dateLe', 'dateGe', 'dateLt', 'dateGt'); + } + if (filterBehaviour.supportExistsTesting) { allowedElements.push('exists', 'notExists'); } diff --git a/packages/tools/src/filterBehaviours.ts b/packages/tools/src/filterBehaviours.ts index 80009546..c6cbbd7a 100644 --- a/packages/tools/src/filterBehaviours.ts +++ b/packages/tools/src/filterBehaviours.ts @@ -31,7 +31,7 @@ export const logicalFilterBehaviour: FilterBehaviour = { }; export const datetimeFilterBehaviour: FilterBehaviour = { - compilerType: 'datetime', + compilerType: 'sqlTree', supportNullTesting: true, supportSqlCondition: true, supportDatetimeSymbols: true,