From e1c9c4505dbdfc29223fb5b95cca3fc4471b8c4e Mon Sep 17 00:00:00 2001 From: ahrefabhi Date: Fri, 27 Jun 2025 11:44:04 +0530 Subject: [PATCH] feat: add support for negation context in query processing --- frontend/src/utils/antlrQueryUtils.ts | 88 ++++++++++++++++---- frontend/src/utils/queryContextUtils.ts | 102 ++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 14 deletions(-) diff --git a/frontend/src/utils/antlrQueryUtils.ts b/frontend/src/utils/antlrQueryUtils.ts index 64d5bc42c35f..46fc442a3334 100644 --- a/frontend/src/utils/antlrQueryUtils.ts +++ b/frontend/src/utils/antlrQueryUtils.ts @@ -248,9 +248,9 @@ export function findKeyOperatorValueTriplet( FilterQueryLexer.GT, FilterQueryLexer.GE, FilterQueryLexer.LIKE, - FilterQueryLexer.NOT_LIKE, + // FilterQueryLexer.NOT_LIKE, FilterQueryLexer.ILIKE, - FilterQueryLexer.NOT_ILIKE, + // FilterQueryLexer.NOT_ILIKE, FilterQueryLexer.BETWEEN, FilterQueryLexer.EXISTS, FilterQueryLexer.REGEXP, @@ -375,6 +375,7 @@ export function getQueryContextAtCursor( currentToken: '', isInValue: false, isInKey: true, // Default to key context when input is empty + isInNegation: false, isInOperator: false, isInFunction: false, isInConjunction: false, @@ -388,6 +389,7 @@ export function getQueryContextAtCursor( stop: cursorIndex, currentToken: '', isInValue: false, + isInNegation: false, isInKey: false, isInOperator: false, isInFunction: false, @@ -418,6 +420,8 @@ export function getQueryContextAtCursor( const isInKey = currentToken.type === FilterQueryLexer.KEY; + const isInNegation = currentToken.type === FilterQueryLexer.NOT; + const isInOperator = [ FilterQueryLexer.EQUALS, FilterQueryLexer.NOT_EQUALS, @@ -427,9 +431,9 @@ export function getQueryContextAtCursor( FilterQueryLexer.GT, FilterQueryLexer.GE, FilterQueryLexer.LIKE, - FilterQueryLexer.NOT_LIKE, + // FilterQueryLexer.NOT_LIKE, FilterQueryLexer.ILIKE, - FilterQueryLexer.NOT_ILIKE, + // FilterQueryLexer.NOT_ILIKE, FilterQueryLexer.BETWEEN, FilterQueryLexer.EXISTS, FilterQueryLexer.REGEXP, @@ -442,7 +446,7 @@ export function getQueryContextAtCursor( FilterQueryLexer.HAS, FilterQueryLexer.HASANY, FilterQueryLexer.HASALL, - FilterQueryLexer.HASNONE, + // FilterQueryLexer.HASNONE, ].includes(currentToken.type); // Get the context-related tokens (key, operator, value) @@ -473,6 +477,7 @@ export function getQueryContextAtCursor( currentToken: currentToken.text, isInValue: false, isInKey: false, + isInNegation: false, isInOperator: true, isInFunction: false, isInConjunction: false, @@ -491,6 +496,7 @@ export function getQueryContextAtCursor( currentToken: currentToken.text, isInValue: true, isInKey: false, + isInNegation: false, isInOperator: false, isInFunction: false, isInConjunction: false, @@ -509,6 +515,7 @@ export function getQueryContextAtCursor( currentToken: currentToken.text, isInValue: false, isInKey: false, + isInNegation: false, isInOperator: false, isInFunction: false, isInConjunction: true, @@ -526,6 +533,7 @@ export function getQueryContextAtCursor( stop: currentToken.stop, currentToken: currentToken.text, isInValue: false, + isInNegation: false, isInKey: true, isInOperator: false, isInFunction: false, @@ -546,6 +554,7 @@ export function getQueryContextAtCursor( stop: currentToken.stop, currentToken: currentToken.text, isInValue: false, + isInNegation: false, isInKey: true, isInOperator: false, isInFunction: false, @@ -567,6 +576,7 @@ export function getQueryContextAtCursor( stop: currentToken.stop, currentToken: currentToken.text, isInValue: false, + isInNegation: false, isInKey: false, isInOperator: false, isInFunction: false, @@ -585,6 +595,7 @@ export function getQueryContextAtCursor( stop: currentToken.stop, currentToken: currentToken.text, isInValue: true, + isInNegation: false, isInKey: false, isInOperator: false, isInFunction: false, @@ -612,6 +623,25 @@ export function getQueryContextAtCursor( currentToken: currentToken.text, isInValue: false, isInKey: true, + isInNegation: false, + isInOperator: false, + isInFunction: false, + isInConjunction: false, + isInParenthesis: false, + ...relationTokens, // Include related tokens + }; + } + + if (isInNegation && nextToken.type === FilterQueryLexer.NOT) { + return { + tokenType: currentToken.type, + text: currentToken.text, + start: currentToken.start, + stop: currentToken.stop, + currentToken: currentToken.text, + isInValue: false, + isInKey: false, + isInNegation: true, isInOperator: false, isInFunction: false, isInConjunction: false, @@ -636,6 +666,7 @@ export function getQueryContextAtCursor( currentToken: currentToken.text, isInValue: false, isInKey: false, + isInNegation: false, isInOperator: true, isInFunction: false, isInConjunction: false, @@ -660,6 +691,7 @@ export function getQueryContextAtCursor( currentToken: currentToken.text, isInValue: true, isInKey: false, + isInNegation: false, isInOperator: false, isInFunction: false, isInConjunction: false, @@ -684,6 +716,7 @@ export function getQueryContextAtCursor( currentToken: currentToken.text, isInValue: false, isInKey: false, + isInNegation: false, isInOperator: false, isInFunction: false, isInConjunction: true, @@ -703,6 +736,25 @@ export function getQueryContextAtCursor( currentToken: '', isInValue: false, isInKey: true, + isInNegation: false, + isInOperator: false, + isInFunction: false, + isInConjunction: false, + isInParenthesis: false, + ...relationTokens, // Include related tokens + }; + } + + if (nextToken.type === FilterQueryLexer.NOT) { + return { + tokenType: -1, + text: '', + start: cursorIndex, + stop: cursorIndex, + currentToken: '', + isInValue: false, + isInKey: false, + isInNegation: true, isInOperator: false, isInFunction: false, isInConjunction: false, @@ -729,6 +781,7 @@ export function getQueryContextAtCursor( currentToken: '', isInValue: false, isInKey: false, + isInNegation: false, isInOperator: true, isInFunction: false, isInConjunction: false, @@ -750,6 +803,7 @@ export function getQueryContextAtCursor( start: cursorIndex, stop: cursorIndex, currentToken: '', + isInNegation: false, isInValue: true, isInKey: false, isInOperator: false, @@ -769,6 +823,7 @@ export function getQueryContextAtCursor( currentToken: '', isInValue: false, isInKey: false, + isInNegation: false, isInOperator: false, isInFunction: false, isInConjunction: true, @@ -794,6 +849,7 @@ export function getQueryContextAtCursor( currentToken: '', isInValue: false, isInKey: false, + isInNegation: false, isInOperator: false, isInFunction: false, isInConjunction: false, @@ -812,6 +868,7 @@ export function getQueryContextAtCursor( currentToken: currentToken.text, isInValue, isInKey, + isInNegation, isInOperator, isInFunction, isInConjunction, @@ -828,6 +885,7 @@ export function getQueryContextAtCursor( currentToken: '', isInValue: false, isInKey: false, + isInNegation: false, isInOperator: false, isInFunction: false, isInConjunction: false, @@ -836,6 +894,16 @@ export function getQueryContextAtCursor( } } +export const negationQueryOperatorSuggestions = [ + { label: 'LIKE', type: 'operator', info: 'Like' }, + { label: 'ILIKE', type: 'operator', info: 'Case insensitive like' }, + { label: 'EXISTS', type: 'operator', info: 'Exists' }, + { label: 'BETWEEN', type: 'operator', info: 'Between' }, + { label: 'IN', type: 'operator', info: 'In' }, + { label: 'REGEXP', type: 'operator', info: 'Regular expression' }, + { label: 'CONTAINS', type: 'operator', info: 'Contains' }, +]; + export const queryOperatorSuggestions = [ { label: '=', type: 'operator', info: 'Equal to' }, { label: '!=', type: 'operator', info: 'Not equal to' }, @@ -843,14 +911,6 @@ export const queryOperatorSuggestions = [ { label: '<', type: 'operator', info: 'Less than' }, { label: '>=', type: 'operator', info: 'Greater than or equal to' }, { label: '<=', type: 'operator', info: 'Less than or equal to' }, - { label: 'LIKE', type: 'operator', info: 'Like' }, - { label: 'ILIKE', type: 'operator', info: 'Case insensitive like' }, - { label: 'BETWEEN', type: 'operator', info: 'Between' }, - { label: 'EXISTS', type: 'operator', info: 'Exists' }, - { label: 'NOT_EXISTS', type: 'operator', info: 'Not Exists' }, - { label: 'REGEXP', type: 'operator', info: 'Regular expression' }, - { label: 'CONTAINS', type: 'operator', info: 'Contains' }, - { label: 'IN', type: 'operator', info: 'In' }, { label: 'NOT', type: 'operator', info: 'Not' }, - { label: 'NOT_LIKE', type: 'operator', info: 'Not like' }, + ...negationQueryOperatorSuggestions, ]; diff --git a/frontend/src/utils/queryContextUtils.ts b/frontend/src/utils/queryContextUtils.ts index 2ec89afda39d..c68c795c4bb1 100644 --- a/frontend/src/utils/queryContextUtils.ts +++ b/frontend/src/utils/queryContextUtils.ts @@ -51,6 +51,7 @@ function normalizeSpaces(query: string): string { export function createContext( token: Token, isInKey: boolean, + isInNegation: boolean, isInOperator: boolean, isInValue: boolean, keyToken?: string, @@ -66,6 +67,7 @@ export function createContext( stop: token.stop, currentToken: token.text || '', isInKey, + isInNegation, isInOperator, isInValue, isInFunction: false, @@ -85,6 +87,7 @@ function determineTokenContext( query: string, ): { isInKey: boolean; + isInNegation: boolean; isInOperator: boolean; isInValue: boolean; isInFunction: boolean; @@ -92,6 +95,7 @@ function determineTokenContext( isInParenthesis: boolean; } { let isInKey: boolean = false; + let isInNegation: boolean = false; let isInOperator: boolean = false; let isInValue: boolean = false; let isInFunction: boolean = false; @@ -126,6 +130,9 @@ function determineTokenContext( } } + // Negation context + isInNegation = tokenType === FilterQueryLexer.NOT; + // Function context isInFunction = isFunctionToken(tokenType); @@ -137,6 +144,7 @@ function determineTokenContext( return { isInKey, + isInNegation, isInOperator, isInValue, isInFunction, @@ -156,6 +164,7 @@ function determineContextBoundaries( operatorContext: { start: number; end: number } | null; valueContext: { start: number; end: number } | null; conjunctionContext: { start: number; end: number } | null; + negationContext: { start: number; end: number } | null; bracketContext: { start: number; end: number; isForList: boolean } | null; } { // Find the current query pair based on cursor position @@ -343,6 +352,12 @@ function determineContextBoundaries( if (currentPair) { const { position } = currentPair; + // Negation context: from negationStart to negationEnd + const negationContext = { + start: position.negationStart ?? 0, + end: position.negationEnd ?? 0, + }; + // Key context: from keyStart to keyEnd const keyContext = { start: position.keyStart, @@ -412,6 +427,7 @@ function determineContextBoundaries( return { keyContext, + negationContext, operatorContext, valueContext, conjunctionContext, @@ -433,6 +449,18 @@ function determineContextBoundaries( if (tokenAtCursor.type === FilterQueryLexer.KEY) { return { keyContext: { start: tokenAtCursor.start, end: tokenAtCursor.stop }, + negationContext: null, + operatorContext: null, + valueContext: null, + conjunctionContext: null, + bracketContext, + }; + } + + if (tokenAtCursor.type === FilterQueryLexer.NOT) { + return { + keyContext: null, + negationContext: { start: tokenAtCursor.start, end: tokenAtCursor.stop }, operatorContext: null, valueContext: null, conjunctionContext: null, @@ -443,6 +471,7 @@ function determineContextBoundaries( if (isOperatorToken(tokenAtCursor.type)) { return { keyContext: null, + negationContext: null, operatorContext: { start: tokenAtCursor.start, end: tokenAtCursor.stop }, valueContext: null, conjunctionContext: null, @@ -453,6 +482,7 @@ function determineContextBoundaries( if (isValueToken(tokenAtCursor.type)) { return { keyContext: null, + negationContext: null, operatorContext: null, valueContext: { start: tokenAtCursor.start, end: tokenAtCursor.stop }, conjunctionContext: null, @@ -463,6 +493,7 @@ function determineContextBoundaries( if (isConjunctionToken(tokenAtCursor.type)) { return { keyContext: null, + negationContext: null, operatorContext: null, valueContext: null, conjunctionContext: { start: tokenAtCursor.start, end: tokenAtCursor.stop }, @@ -474,6 +505,7 @@ function determineContextBoundaries( // If no current pair, return null for all contexts except possibly bracket context return { keyContext: null, + negationContext: null, operatorContext: null, valueContext: null, conjunctionContext: null, @@ -515,6 +547,7 @@ export function getQueryContextAtCursor( stop: cursorIndex, currentToken: '', isInKey: true, + isInNegation: false, isInOperator: false, isInValue: false, isInFunction: false, @@ -660,6 +693,12 @@ export function getQueryContextAtCursor( adjustedCursorIndex <= contextBoundaries.keyContext.end) || adjustedCursorIndex === contextBoundaries.keyContext.end + 1); + const isInNegationBoundary = + contextBoundaries.negationContext && + ((adjustedCursorIndex >= contextBoundaries.negationContext.start && + adjustedCursorIndex <= contextBoundaries.negationContext.end) || + adjustedCursorIndex === contextBoundaries.negationContext.end + 1); + const isInOperatorBoundary = contextBoundaries.operatorContext && ((adjustedCursorIndex >= contextBoundaries.operatorContext.start && @@ -703,6 +742,7 @@ export function getQueryContextAtCursor( // If cursor is within a specific context boundary, this takes precedence if ( isInKeyBoundary || + isInNegationBoundary || isInOperatorBoundary || isInValueBoundary || isInConjunctionBoundary || @@ -736,6 +776,7 @@ export function getQueryContextAtCursor( stop: adjustedCursorIndex, currentToken: '', isInKey: isInKeyBoundary || false, + isInNegation: isInNegationBoundary || false, isInOperator: isInOperatorBoundary || false, isInValue: finalIsInValue || false, isInConjunction: finalIsInConjunction || false, @@ -824,6 +865,7 @@ export function getQueryContextAtCursor( stop: adjustedCursorIndex, currentToken: '', isInKey: true, // Default to key context when input is empty + isInNegation: false, isInOperator: false, isInValue: false, isInFunction: false, @@ -853,6 +895,7 @@ export function getQueryContextAtCursor( stop: adjustedCursorIndex, currentToken: lastTokenBeforeCursor.text, isInKey: false, + isInNegation: false, isInOperator: true, // After key + space, should be operator context isInValue: false, isInFunction: false, @@ -865,6 +908,27 @@ export function getQueryContextAtCursor( }; } + if (lastTokenContext.isInNegation) { + return { + tokenType: lastTokenBeforeCursor.type, + text: lastTokenBeforeCursor.text, + start: adjustedCursorIndex, + stop: adjustedCursorIndex, + currentToken: lastTokenBeforeCursor.text, + isInKey: false, + isInNegation: false, + isInOperator: true, // After key + space + NOT, should be operator context + isInValue: false, + isInFunction: false, + isInConjunction: false, + isInParenthesis: false, + isInBracketList: false, + keyToken: lastTokenBeforeCursor.text, + queryPairs: queryPairs, + currentPair: currentPair, + }; + } + if (lastTokenContext.isInOperator) { // If we just typed an operator and then a space, we move to value context const keyFromPair = currentPair?.key || ''; @@ -876,6 +940,7 @@ export function getQueryContextAtCursor( stop: adjustedCursorIndex, currentToken: lastTokenBeforeCursor.text, isInKey: false, + isInNegation: false, isInOperator: false, isInValue: !isNonValueToken, // After operator + space, should be value context isInFunction: false, @@ -900,6 +965,7 @@ export function getQueryContextAtCursor( stop: adjustedCursorIndex, currentToken: lastTokenBeforeCursor.text, isInKey: false, + isInNegation: false, isInOperator: false, isInValue: false, isInFunction: false, @@ -923,6 +989,7 @@ export function getQueryContextAtCursor( stop: adjustedCursorIndex, currentToken: lastTokenBeforeCursor.text, isInKey: true, // After conjunction + space, should be key context + isInNegation: false, isInOperator: false, isInValue: false, isInFunction: false, @@ -1016,6 +1083,7 @@ export function getQueryContextAtCursor( stop: adjustedCursorIndex, currentToken: previousToken.text, isInKey: false, + isInNegation: false, isInOperator: false, isInValue: true, // Always in value context after operator isInFunction: false, @@ -1038,6 +1106,7 @@ export function getQueryContextAtCursor( stop: adjustedCursorIndex, currentToken: previousToken.text, isInKey: false, + isInNegation: false, isInOperator: true, // After key, progress to operator context isInValue: false, isInFunction: false, @@ -1058,6 +1127,7 @@ export function getQueryContextAtCursor( stop: adjustedCursorIndex, currentToken: previousToken.text, isInKey: false, + isInNegation: false, isInOperator: false, isInValue: false, isInFunction: false, @@ -1080,6 +1150,7 @@ export function getQueryContextAtCursor( stop: adjustedCursorIndex, currentToken: previousToken.text, isInKey: true, // After conjunction, progress back to key context + isInNegation: false, isInOperator: false, isInValue: false, isInFunction: false, @@ -1100,6 +1171,7 @@ export function getQueryContextAtCursor( stop: adjustedCursorIndex, currentToken: '', isInKey: true, + isInNegation: false, isInOperator: false, isInValue: false, isInFunction: false, @@ -1119,6 +1191,7 @@ export function getQueryContextAtCursor( currentToken: '', isInValue: false, isInKey: true, // Default to key context on error + isInNegation: false, isInOperator: false, isInFunction: false, isInConjunction: false, @@ -1185,6 +1258,7 @@ export function extractQueryPairs(query: string): IQueryPair[] { key: currentPair.key, operator: currentPair.operator || '', value: currentPair.value, + hasNegation: currentPair.hasNegation || false, position: { keyStart: currentPair.position?.keyStart || 0, keyEnd: currentPair.position?.keyEnd || 0, @@ -1192,6 +1266,8 @@ export function extractQueryPairs(query: string): IQueryPair[] { operatorEnd: currentPair.position?.operatorEnd || 0, valueStart: currentPair.position?.valueStart, valueEnd: currentPair.position?.valueEnd, + negationStart: currentPair.position?.negationStart || 0, + negationEnd: currentPair.position?.negationEnd || 0, }, isComplete: !!( currentPair.key && @@ -1212,6 +1288,22 @@ export function extractQueryPairs(query: string): IQueryPair[] { }, }; } + // If NOT token comes set hasNegation to true + else if (token.type === FilterQueryLexer.NOT && currentPair) { + currentPair.hasNegation = true; + + currentPair.position = { + keyStart: currentPair.position?.keyStart || 0, + keyEnd: currentPair.position?.keyEnd || 0, + operatorStart: currentPair.position?.operatorStart || 0, + operatorEnd: currentPair.position?.operatorEnd || 0, + valueStart: currentPair.position?.valueStart, + valueEnd: currentPair.position?.valueEnd, + negationStart: token.start, + negationEnd: token.stop, + }; + } + // If token is an operator and we have a key, add the operator else if ( isOperatorToken(token.type) && @@ -1228,6 +1320,8 @@ export function extractQueryPairs(query: string): IQueryPair[] { operatorEnd: token.stop, valueStart: currentPair.position?.valueStart, valueEnd: currentPair.position?.valueEnd, + negationStart: currentPair.position?.negationStart || 0, + negationEnd: currentPair.position?.negationEnd || 0, }; } // If token is a value and we have a key and operator, add the value @@ -1247,6 +1341,8 @@ export function extractQueryPairs(query: string): IQueryPair[] { operatorEnd: currentPair.position?.operatorEnd || 0, valueStart: token.start, valueEnd: token.stop, + negationStart: currentPair.position?.negationStart || 0, + negationEnd: currentPair.position?.negationEnd || 0, }; } // If token is a conjunction (AND/OR), finalize the current pair @@ -1255,6 +1351,7 @@ export function extractQueryPairs(query: string): IQueryPair[] { key: currentPair.key, operator: currentPair.operator || '', value: currentPair.value, + hasNegation: currentPair.hasNegation || false, position: { keyStart: currentPair.position?.keyStart || 0, keyEnd: currentPair.position?.keyEnd || 0, @@ -1262,6 +1359,8 @@ export function extractQueryPairs(query: string): IQueryPair[] { operatorEnd: currentPair.position?.operatorEnd || 0, valueStart: currentPair.position?.valueStart, valueEnd: currentPair.position?.valueEnd, + negationStart: currentPair.position?.negationStart || 0, + negationEnd: currentPair.position?.negationEnd || 0, }, isComplete: !!( currentPair.key && @@ -1281,6 +1380,7 @@ export function extractQueryPairs(query: string): IQueryPair[] { key: currentPair.key, operator: currentPair.operator || '', value: currentPair.value, + hasNegation: currentPair.hasNegation || false, position: { keyStart: currentPair.position?.keyStart || 0, keyEnd: currentPair.position?.keyEnd || 0, @@ -1288,6 +1388,8 @@ export function extractQueryPairs(query: string): IQueryPair[] { operatorEnd: currentPair.position?.operatorEnd || 0, valueStart: currentPair.position?.valueStart, valueEnd: currentPair.position?.valueEnd, + negationStart: currentPair.position?.negationStart || 0, + negationEnd: currentPair.position?.negationEnd || 0, }, isComplete: !!( currentPair.key &&