@@ -110,6 +110,36 @@ void assemblyOptionalMinusUsesDevelopPlanShape(@TempDir Path dataDir) throws Exc
110110 }
111111 }
112112
113+ @ Test
114+ void componentNameFilterUsesFiniteValuesAnchor (@ TempDir Path dataDir ) throws Exception {
115+ Theme theme = Theme .ENGINEERING ;
116+ Path themeDir = dataDir .resolve (theme .name ());
117+ try {
118+ LmdbStore store = new LmdbStore (themeDir .toFile (), ConfigUtil .createConfig ());
119+ SailRepository repository = new SailRepository (store );
120+ try {
121+ BenchmarkJoinEstimatorSupport .prepareEstimatorForBulkLoad (repository , store );
122+ loadData (repository , theme );
123+ BenchmarkJoinEstimatorSupport .persistEstimatorAfterBulkLoad (repository , store );
124+ primeLearnedFilterStats (repository , theme , 4 );
125+ BenchmarkJoinEstimatorSupport .persistStoreStatistics (store );
126+ } finally {
127+ shutdownAndRelease (repository , store );
128+ }
129+
130+ store = new LmdbStore (themeDir .toFile (), ConfigUtil .createConfig ());
131+ repository = new SailRepository (store );
132+ try {
133+ OptimizerSnapshot snapshot = explainOptimized (repository , theme , 4 );
134+ assertEngineeringQ4FastPlanShape (snapshot .renderedQuery ().trim (), snapshot .plan ());
135+ } finally {
136+ shutdownAndRelease (repository , store );
137+ }
138+ } finally {
139+ BenchmarkJoinEstimatorSupport .deleteStoreDirectory (themeDir );
140+ }
141+ }
142+
113143 private static void loadData (SailRepository repository , Theme theme ) throws IOException {
114144 try (SailRepositoryConnection connection = repository .getConnection ()) {
115145 connection .begin (IsolationLevels .NONE );
@@ -160,18 +190,26 @@ private static void shutdownAndRelease(SailRepository repository, LmdbStore stor
160190 private static void assertEngineeringQ7DevelopPlanShape (String renderedQuery , String plan ) {
161191 assertContains (renderedQuery , "VALUES ?name { \" REQ-1000\" \" REQ-1001\" }" );
162192 assertBefore (renderedQuery ,
163- "?requirement a <http://example.com/theme/engineering/Requirement> . " ,
193+ "VALUES ?name { \" REQ-1000 \" \" REQ-1001 \" } " ,
164194 "?requirement <http://example.com/theme/engineering/name> ?name ." ,
165- "Engineering q7 should keep the Requirement rdf:type anchor before the bound name filter\n " + plan );
195+ "Engineering q7 should bind the finite requirement-name set before the name lookup\n " + plan );
196+ assertBefore (renderedQuery ,
197+ "?requirement <http://example.com/theme/engineering/name> ?name ." ,
198+ "?requirement a <http://example.com/theme/engineering/Requirement> ." ,
199+ "Engineering q7 should use the bound name lookup before the rdf:type direct lookup\n " + plan );
166200 assertBefore (renderedQuery ,
167201 "?requirement <http://example.com/theme/engineering/name> ?name ." ,
168202 "FILTER EXISTS" ,
169203 "Engineering q7 should apply the name filter before evaluating EXISTS\n " + plan );
170204 assertBefore (plan ,
171- "value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type" ,
205+ "BindingSetAssignment ([[name=\" REQ-1000\" ], [name=\" REQ-1001\" ]])" ,
206+ "value=http://example.com/theme/engineering/name" ,
207+ "Engineering q7 should anchor the finite name set before the name access" );
208+ assertBefore (plan ,
172209 "value=http://example.com/theme/engineering/name" ,
173- "Engineering q7 plan should keep the Requirement rdf:type anchor before the name filter" );
174- assertNamePatternUsesBoundSubject (plan , "Engineering q7" , "requirement" );
210+ "value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type" ,
211+ "Engineering q7 plan should use the bound name lookup before the rdf:type direct lookup" );
212+ assertNamePatternUsesBoundValue (plan , "Engineering q7" , false );
175213 assertContains (plan , "ValueConstant (value=\" REQ-1000\" )" );
176214 }
177215
@@ -189,6 +227,23 @@ private static void assertEngineeringQ10DevelopPlanShape(String plan) {
189227 assertMinusSatisfiesStaysNewScope (plan );
190228 }
191229
230+ private static void assertEngineeringQ4FastPlanShape (String renderedQuery , String plan ) {
231+ assertContains (renderedQuery , "VALUES ?name { \" Component 1\" \" Component 2\" }" );
232+ assertBefore (renderedQuery ,
233+ "VALUES ?name { \" Component 1\" \" Component 2\" }" ,
234+ "?component <http://example.com/theme/engineering/name> ?name ." ,
235+ "Engineering q4 should bind the finite component-name set before the name lookup\n " + plan );
236+ assertBefore (renderedQuery ,
237+ "?component <http://example.com/theme/engineering/name> ?name ." ,
238+ "?component a <http://example.com/theme/engineering/Component> ." ,
239+ "Engineering q4 should use the bound name lookup before broad rdf:type scan\n " + plan );
240+ assertBefore (plan ,
241+ "BindingSetAssignment ([[name=\" Component 1\" ], [name=\" Component 2\" ]])" ,
242+ "value=http://example.com/theme/engineering/name" ,
243+ "Engineering q4 should anchor the finite name set before the name access" );
244+ assertNamePatternUsesBoundValue (plan , "Engineering q4" , false );
245+ }
246+
192247 private static void assertDevelopOperatorSkeleton (String plan ) {
193248 assertOrdered (plan ,
194249 "Projection" ,
@@ -203,7 +258,7 @@ private static void assertDevelopOperatorSkeleton(String plan) {
203258 "LeftJoin" ,
204259 "Join (JoinIterator)" ,
205260 "BindingSetAssignment ([[name=\" Assembly 1\" ], [name=\" Assembly 2\" ]])" ,
206- "deferredFilterScope=localPattern) [right] " ,
261+ "deferredFilterScope=localPattern)" ,
207262 "Or" ,
208263 "ValueConstant (value=\" Assembly 1\" )" ,
209264 "ValueConstant (value=\" Assembly 2\" )" ,
@@ -221,15 +276,21 @@ private static void assertDevelopOperatorSkeleton(String plan) {
221276 }
222277
223278 private static void assertNamePatternUsesBoundValue (String plan , String label ) {
279+ assertNamePatternUsesBoundValue (plan , label , true );
280+ }
281+
282+ private static void assertNamePatternUsesBoundValue (String plan , String label , boolean requirePlannerMetrics ) {
224283 int predicateIndex = plan .indexOf ("value=http://example.com/theme/engineering/name" );
225284 if (predicateIndex < 0 ) {
226285 throw new AssertionError (label + " plan should include the name pattern:\n " + plan );
227286 }
228287
229288 String pattern = statementPatternWindow (plan , predicateIndex , "StatementPattern" );
230289 assertContains (pattern , "o: Var (name=name) (bindingState=bound)" );
231- assertContains (pattern , "plannedBoundVars=[name]" );
232- assertContains (pattern , "plannedLookupComponents=[P, O]" );
290+ if (requirePlannerMetrics ) {
291+ assertContainsAny (pattern , "plannedBoundVars=[name]" , "plannedBoundVars=name" );
292+ assertContains (pattern , "plannedLookupComponents=[P, O]" );
293+ }
233294 }
234295
235296 private static void assertNamePatternUsesBoundSubject (String plan , String label , String subjectName ) {
@@ -284,6 +345,16 @@ private static void assertContains(String value, String expected) {
284345 }
285346 }
286347
348+ private static void assertContainsAny (String value , String ... expectedTokens ) {
349+ for (String expected : expectedTokens ) {
350+ if (value .contains (expected )) {
351+ return ;
352+ }
353+ }
354+ throw new AssertionError ("Expected to find one of `" + String .join ("`, `" , expectedTokens ) + "` in:\n "
355+ + value );
356+ }
357+
287358 private static void assertBefore (String value , String first , String second , String message ) {
288359 int firstIndex = value .indexOf (first );
289360 int secondIndex = value .indexOf (second );
0 commit comments