Skip to content

Commit e6eb808

Browse files
authored
Enhance dynamic source clause to support only metadata filters (#4554)
Signed-off-by: Vamsi Manohar <[email protected]>
1 parent fea679d commit e6eb808

File tree

2 files changed

+200
-64
lines changed

2 files changed

+200
-64
lines changed

ppl/src/main/antlr/OpenSearchPPLParser.g4

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -531,21 +531,13 @@ tableSourceClause
531531
;
532532

533533
dynamicSourceClause
534-
: LT_SQR_PRTHS sourceReferences (COMMA sourceFilterArgs)? RT_SQR_PRTHS
535-
;
536-
537-
sourceReferences
538-
: sourceReference (COMMA sourceReference)*
534+
: LT_SQR_PRTHS (sourceReference | sourceFilterArg) (COMMA (sourceReference | sourceFilterArg))* RT_SQR_PRTHS
539535
;
540536

541537
sourceReference
542538
: (CLUSTER)? wcQualifiedName
543539
;
544540

545-
sourceFilterArgs
546-
: sourceFilterArg (COMMA sourceFilterArg)*
547-
;
548-
549541
sourceFilterArg
550542
: ident EQUAL literalValue
551543
| ident IN valueList

ppl/src/test/java/org/opensearch/sql/ppl/antlr/PPLSyntaxParserTest.java

Lines changed: 199 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55

66
package org.opensearch.sql.ppl.antlr;
77

8-
import static org.junit.Assert.assertEquals;
9-
import static org.junit.Assert.assertNotEquals;
10-
import static org.junit.Assert.assertNotNull;
11-
import static org.junit.Assert.assertThrows;
12-
import static org.junit.Assert.assertTrue;
8+
import static org.junit.Assert.*;
139

1410
import java.util.List;
1511
import org.antlr.v4.runtime.CommonTokenStream;
@@ -140,22 +136,22 @@ public void testDynamicSourceClauseParseTreeStructure() {
140136
searchFrom.fromClause().dynamicSourceClause();
141137

142138
// Verify source references size and text
143-
assertEquals(
144-
"Should have 2 source references",
145-
2,
146-
dynamicSource.sourceReferences().sourceReference().size());
139+
assertEquals("Should have 2 source references", 2, dynamicSource.sourceReference().size());
147140
assertEquals(
148141
"Source references text should match",
149-
"myindex,logs",
150-
dynamicSource.sourceReferences().getText());
142+
"myindex",
143+
dynamicSource.sourceReference(0).getText());
151144

152-
// Verify filter args size and text
153145
assertEquals(
154-
"Should have 2 filter args", 2, dynamicSource.sourceFilterArgs().sourceFilterArg().size());
146+
"Source references text should match", "logs", dynamicSource.sourceReference(1).getText());
147+
// Verify filter args size and text
148+
assertEquals("Should have 2 filter args", 2, dynamicSource.sourceFilterArg().size());
155149
assertEquals(
156150
"Filter args text should match",
157-
"fieldIndex=\"test\",count=100",
158-
dynamicSource.sourceFilterArgs().getText());
151+
"fieldIndex=\"test\"",
152+
dynamicSource.sourceFilterArg(0).getText());
153+
assertEquals(
154+
"Filter args text should match", "count=100", dynamicSource.sourceFilterArg(1).getText());
159155
}
160156

161157
@Test
@@ -173,22 +169,21 @@ public void testDynamicSourceWithComplexFilters() {
173169
searchFrom.fromClause().dynamicSourceClause();
174170

175171
// Verify source references
172+
assertEquals("Should have 2 source references", 2, dynamicSource.sourceReference().size());
176173
assertEquals(
177-
"Should have 2 source references",
178-
2,
179-
dynamicSource.sourceReferences().sourceReference().size());
174+
"First source reference text", "vpc.flow_logs", dynamicSource.sourceReference(0).getText());
180175
assertEquals(
181-
"Source references text",
182-
"vpc.flow_logs,api.gateway",
183-
dynamicSource.sourceReferences().getText());
176+
"Second source reference text", "api.gateway", dynamicSource.sourceReference(1).getText());
184177

185178
// Verify filter args
179+
assertEquals("Should have 3 filter args", 3, dynamicSource.sourceFilterArg().size());
186180
assertEquals(
187-
"Should have 3 filter args", 3, dynamicSource.sourceFilterArgs().sourceFilterArg().size());
181+
"First filter arg text",
182+
"region=\"us-east-1\"",
183+
dynamicSource.sourceFilterArg(0).getText());
188184
assertEquals(
189-
"Filter args text",
190-
"region=\"us-east-1\",env=\"prod\",count=5",
191-
dynamicSource.sourceFilterArgs().getText());
185+
"Second filter arg text", "env=\"prod\"", dynamicSource.sourceFilterArg(1).getText());
186+
assertEquals("Third filter arg text", "count=5", dynamicSource.sourceFilterArg(2).getText());
192187
}
193188

194189
@Test
@@ -204,24 +199,61 @@ public void testDynamicSourceWithSingleSource() {
204199
OpenSearchPPLParser.DynamicSourceClauseContext dynamicSource =
205200
searchFrom.fromClause().dynamicSourceClause();
206201

202+
assertEquals("Should have 1 source reference", 1, dynamicSource.sourceReference().size());
203+
assertEquals("Source reference text", "ds:myindex", dynamicSource.sourceReference(0).getText());
204+
205+
assertEquals("Should have 1 filter arg", 1, dynamicSource.sourceFilterArg().size());
207206
assertEquals(
208-
"Should have 1 source reference",
209-
1,
210-
dynamicSource.sourceReferences().sourceReference().size());
211-
assertEquals("Source reference text", "ds:myindex", dynamicSource.sourceReferences().getText());
207+
"Filter arg text", "fieldIndex=\"test\"", dynamicSource.sourceFilterArg(0).getText());
208+
}
209+
210+
@Test
211+
public void testDynamicSourceWithoutSource() {
212+
String query = "source=[fieldIndex=\"httpStatus\", region=\"us-west-2\"]";
213+
OpenSearchPPLLexer lexer = new OpenSearchPPLLexer(new CaseInsensitiveCharStream(query));
214+
OpenSearchPPLParser parser = new OpenSearchPPLParser(new CommonTokenStream(lexer));
212215

216+
OpenSearchPPLParser.RootContext root = parser.root();
217+
OpenSearchPPLParser.SearchFromContext searchFrom =
218+
(OpenSearchPPLParser.SearchFromContext)
219+
root.pplStatement().queryStatement().pplCommands().searchCommand();
220+
OpenSearchPPLParser.DynamicSourceClauseContext dynamicSource =
221+
searchFrom.fromClause().dynamicSourceClause();
222+
assertEquals("Should have no source references", 0, dynamicSource.sourceReference().size());
223+
assertEquals("Should have 2 filter arg", 2, dynamicSource.sourceFilterArg().size());
213224
assertEquals(
214-
"Should have 1 filter arg", 1, dynamicSource.sourceFilterArgs().sourceFilterArg().size());
225+
"First filter arg text",
226+
"fieldIndex=\"httpStatus\"",
227+
dynamicSource.sourceFilterArg(0).getText());
215228
assertEquals(
216-
"Filter arg text", "fieldIndex=\"test\"", dynamicSource.sourceFilterArgs().getText());
229+
"Second filter arg text",
230+
"region=\"us-west-2\"",
231+
dynamicSource.sourceFilterArg(1).getText());
217232
}
218233

219234
@Test
220-
public void testDynamicSourceRequiresAtLeastOneSource() {
221-
// The grammar requires at least one source reference before optional filter args
222-
// This test documents that behavior
223-
exceptionRule.expect(RuntimeException.class);
224-
new PPLSyntaxParser().parse("source=[fieldIndex=\"httpStatus\", region=\"us-west-2\"]");
235+
public void testDynamicSourceWithAllSources() {
236+
String query = "source=[`*`, fieldIndex=\"httpStatus\", region=\"us-west-2\"]";
237+
OpenSearchPPLLexer lexer = new OpenSearchPPLLexer(new CaseInsensitiveCharStream(query));
238+
OpenSearchPPLParser parser = new OpenSearchPPLParser(new CommonTokenStream(lexer));
239+
240+
OpenSearchPPLParser.RootContext root = parser.root();
241+
OpenSearchPPLParser.SearchFromContext searchFrom =
242+
(OpenSearchPPLParser.SearchFromContext)
243+
root.pplStatement().queryStatement().pplCommands().searchCommand();
244+
OpenSearchPPLParser.DynamicSourceClauseContext dynamicSource =
245+
searchFrom.fromClause().dynamicSourceClause();
246+
assertEquals("Should have 1 source reference", 1, dynamicSource.sourceReference().size());
247+
assertEquals("Source reference text", "`*`", dynamicSource.sourceReference(0).getText());
248+
assertEquals("Should have 2 filter arg", 2, dynamicSource.sourceFilterArg().size());
249+
assertEquals(
250+
"First filter arg text",
251+
"fieldIndex=\"httpStatus\"",
252+
dynamicSource.sourceFilterArg(0).getText());
253+
assertEquals(
254+
"Second filter arg text",
255+
"region=\"us-west-2\"",
256+
dynamicSource.sourceFilterArg(1).getText());
225257
}
226258

227259
@Test
@@ -237,18 +269,16 @@ public void testDynamicSourceWithDottedNames() {
237269
OpenSearchPPLParser.DynamicSourceClauseContext dynamicSource =
238270
searchFrom.fromClause().dynamicSourceClause();
239271

272+
assertEquals("Should have 2 source references", 2, dynamicSource.sourceReference().size());
240273
assertEquals(
241-
"Should have 2 source references",
242-
2,
243-
dynamicSource.sourceReferences().sourceReference().size());
274+
"First source reference text", "vpc.flow_logs", dynamicSource.sourceReference(0).getText());
244275
assertEquals(
245-
"Source references text",
246-
"vpc.flow_logs,api.gateway.logs",
247-
dynamicSource.sourceReferences().getText());
276+
"Second source reference text",
277+
"api.gateway.logs",
278+
dynamicSource.sourceReference(1).getText());
248279

249-
assertEquals(
250-
"Should have 1 filter arg", 1, dynamicSource.sourceFilterArgs().sourceFilterArg().size());
251-
assertEquals("Filter arg text", "env=\"prod\"", dynamicSource.sourceFilterArgs().getText());
280+
assertEquals("Should have 1 filter arg", 1, dynamicSource.sourceFilterArg().size());
281+
assertEquals("Filter arg text", "env=\"prod\"", dynamicSource.sourceFilterArg(0).getText());
252282
}
253283

254284
@Test
@@ -264,15 +294,11 @@ public void testDynamicSourceWithSimpleFilter() {
264294
OpenSearchPPLParser.DynamicSourceClauseContext dynamicSource =
265295
searchFrom.fromClause().dynamicSourceClause();
266296

267-
assertEquals(
268-
"Should have 1 source reference",
269-
1,
270-
dynamicSource.sourceReferences().sourceReference().size());
271-
assertEquals("Source reference text", "logs", dynamicSource.sourceReferences().getText());
297+
assertEquals("Should have 1 source reference", 1, dynamicSource.sourceReference().size());
298+
assertEquals("Source reference text", "logs", dynamicSource.sourceReference(0).getText());
272299

273-
assertEquals(
274-
"Should have 1 filter arg", 1, dynamicSource.sourceFilterArgs().sourceFilterArg().size());
275-
assertEquals("Filter arg text", "count=100", dynamicSource.sourceFilterArgs().getText());
300+
assertEquals("Should have 1 filter arg", 1, dynamicSource.sourceFilterArg().size());
301+
assertEquals("Filter arg text", "count=100", dynamicSource.sourceFilterArg(0).getText());
276302
}
277303

278304
@Test
@@ -293,12 +319,12 @@ public void testDynamicSourceWithInClause() {
293319
searchFrom.fromClause().dynamicSourceClause();
294320

295321
assertNotNull("Dynamic source should exist", dynamicSource);
296-
assertNotNull("Filter args should exist", dynamicSource.sourceFilterArgs());
322+
assertNotNull("Filter args should exist", dynamicSource);
297323

298324
// The IN clause should be parsed as a sourceFilterArg
299325
assertTrue(
300326
"Should have at least one filter arg with IN clause",
301-
dynamicSource.sourceFilterArgs().sourceFilterArg().size() >= 1);
327+
dynamicSource.sourceFilterArg().size() >= 1);
302328
}
303329

304330
@Test
@@ -717,6 +743,124 @@ public void testBlockCommentShouldPass() {
717743
"""));
718744
}
719745

746+
@Test
747+
public void testDynamicSourceWithIntermixedSourcesAndFilters() {
748+
// Test intermixed sources and filters in various orders
749+
String query = "source=[myindex, region=\"us-east-1\", logs, count=100, api.gateway]";
750+
OpenSearchPPLLexer lexer = new OpenSearchPPLLexer(new CaseInsensitiveCharStream(query));
751+
OpenSearchPPLParser parser = new OpenSearchPPLParser(new CommonTokenStream(lexer));
752+
753+
OpenSearchPPLParser.RootContext root = parser.root();
754+
OpenSearchPPLParser.SearchFromContext searchFrom =
755+
(OpenSearchPPLParser.SearchFromContext)
756+
root.pplStatement().queryStatement().pplCommands().searchCommand();
757+
OpenSearchPPLParser.DynamicSourceClauseContext dynamicSource =
758+
searchFrom.fromClause().dynamicSourceClause();
759+
760+
// Verify we have 3 source references
761+
assertEquals("Should have 3 source references", 3, dynamicSource.sourceReference().size());
762+
assertEquals("First source reference", "myindex", dynamicSource.sourceReference(0).getText());
763+
assertEquals("Second source reference", "logs", dynamicSource.sourceReference(1).getText());
764+
assertEquals(
765+
"Third source reference", "api.gateway", dynamicSource.sourceReference(2).getText());
766+
767+
// Verify we have 2 filter args
768+
assertEquals("Should have 2 filter args", 2, dynamicSource.sourceFilterArg().size());
769+
assertEquals(
770+
"First filter arg", "region=\"us-east-1\"", dynamicSource.sourceFilterArg(0).getText());
771+
assertEquals("Second filter arg", "count=100", dynamicSource.sourceFilterArg(1).getText());
772+
}
773+
774+
@Test
775+
public void testDynamicSourceStartingWithFilter() {
776+
// Test starting with a filter argument instead of source reference
777+
String query = "source=[region=\"us-west-1\", myindex, env=\"prod\", logs]";
778+
OpenSearchPPLLexer lexer = new OpenSearchPPLLexer(new CaseInsensitiveCharStream(query));
779+
OpenSearchPPLParser parser = new OpenSearchPPLParser(new CommonTokenStream(lexer));
780+
781+
OpenSearchPPLParser.RootContext root = parser.root();
782+
OpenSearchPPLParser.SearchFromContext searchFrom =
783+
(OpenSearchPPLParser.SearchFromContext)
784+
root.pplStatement().queryStatement().pplCommands().searchCommand();
785+
OpenSearchPPLParser.DynamicSourceClauseContext dynamicSource =
786+
searchFrom.fromClause().dynamicSourceClause();
787+
788+
// Verify source references
789+
assertEquals("Should have 2 source references", 2, dynamicSource.sourceReference().size());
790+
assertEquals("First source reference", "myindex", dynamicSource.sourceReference(0).getText());
791+
assertEquals("Second source reference", "logs", dynamicSource.sourceReference(1).getText());
792+
793+
// Verify filter args
794+
assertEquals("Should have 2 filter args", 2, dynamicSource.sourceFilterArg().size());
795+
assertEquals(
796+
"First filter arg", "region=\"us-west-1\"", dynamicSource.sourceFilterArg(0).getText());
797+
assertEquals("Second filter arg", "env=\"prod\"", dynamicSource.sourceFilterArg(1).getText());
798+
}
799+
800+
@Test
801+
public void testDynamicSourceAlternatingPattern() {
802+
// Test alternating pattern of sources and filters
803+
String query =
804+
"source=[ds:index1, type=\"logs\", ds:index2, count=50, ds:index3, region=\"us-east-1\"]";
805+
OpenSearchPPLLexer lexer = new OpenSearchPPLLexer(new CaseInsensitiveCharStream(query));
806+
OpenSearchPPLParser parser = new OpenSearchPPLParser(new CommonTokenStream(lexer));
807+
808+
OpenSearchPPLParser.RootContext root = parser.root();
809+
OpenSearchPPLParser.SearchFromContext searchFrom =
810+
(OpenSearchPPLParser.SearchFromContext)
811+
root.pplStatement().queryStatement().pplCommands().searchCommand();
812+
OpenSearchPPLParser.DynamicSourceClauseContext dynamicSource =
813+
searchFrom.fromClause().dynamicSourceClause();
814+
815+
// Verify source references
816+
assertEquals("Should have 3 source references", 3, dynamicSource.sourceReference().size());
817+
assertEquals("First source reference", "ds:index1", dynamicSource.sourceReference(0).getText());
818+
assertEquals(
819+
"Second source reference", "ds:index2", dynamicSource.sourceReference(1).getText());
820+
assertEquals("Third source reference", "ds:index3", dynamicSource.sourceReference(2).getText());
821+
822+
// Verify filter args
823+
assertEquals("Should have 3 filter args", 3, dynamicSource.sourceFilterArg().size());
824+
assertEquals("First filter arg", "type=\"logs\"", dynamicSource.sourceFilterArg(0).getText());
825+
assertEquals("Second filter arg", "count=50", dynamicSource.sourceFilterArg(1).getText());
826+
assertEquals(
827+
"Third filter arg", "region=\"us-east-1\"", dynamicSource.sourceFilterArg(2).getText());
828+
}
829+
830+
@Test
831+
public void testDynamicSourceWithComplexIntermixedIN() {
832+
// Test intermixed with IN clause filter
833+
String query =
834+
"source=[logs, fieldIndex IN (\"status\", \"error\"), api.gateway, region=\"us-east-1\"]";
835+
OpenSearchPPLLexer lexer = new OpenSearchPPLLexer(new CaseInsensitiveCharStream(query));
836+
OpenSearchPPLParser parser = new OpenSearchPPLParser(new CommonTokenStream(lexer));
837+
838+
OpenSearchPPLParser.RootContext root = parser.root();
839+
assertNotNull("Query should parse successfully", root);
840+
841+
OpenSearchPPLParser.SearchFromContext searchFrom =
842+
(OpenSearchPPLParser.SearchFromContext)
843+
root.pplStatement().queryStatement().pplCommands().searchCommand();
844+
OpenSearchPPLParser.DynamicSourceClauseContext dynamicSource =
845+
searchFrom.fromClause().dynamicSourceClause();
846+
847+
assertNotNull("Dynamic source should exist", dynamicSource);
848+
849+
// Verify source references
850+
assertEquals("Should have 2 source references", 2, dynamicSource.sourceReference().size());
851+
assertEquals("First source reference", "logs", dynamicSource.sourceReference(0).getText());
852+
assertEquals(
853+
"Second source reference", "api.gateway", dynamicSource.sourceReference(1).getText());
854+
855+
// Verify filter args - should have IN clause and region filter
856+
assertEquals("Should have 2 filter args", 2, dynamicSource.sourceFilterArg().size());
857+
assertTrue(
858+
"First filter should contain IN clause",
859+
dynamicSource.sourceFilterArg(0).getText().contains("IN"));
860+
assertEquals(
861+
"Second filter arg", "region=\"us-east-1\"", dynamicSource.sourceFilterArg(1).getText());
862+
}
863+
720864
@Test
721865
public void testWhereCommand() {
722866
assertNotEquals(null, new PPLSyntaxParser().parse("SOURCE=test | WHERE x"));

0 commit comments

Comments
 (0)