diff options
Diffstat (limited to 'subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java')
-rw-r--r-- | subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java new file mode 100644 index 00000000..1ae24d2d --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java | |||
@@ -0,0 +1,532 @@ | |||
1 | /******************************************************************************* | ||
2 | * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. | ||
3 | * This program and the accompanying materials are made available under the | ||
4 | * terms of the Eclipse Public License v. 2.0 which is available at | ||
5 | * http://www.eclipse.org/legal/epl-v20.html. | ||
6 | * | ||
7 | * SPDX-License-Identifier: EPL-2.0 | ||
8 | *******************************************************************************/ | ||
9 | package tools.refinery.viatra.runtime.localsearch.matcher.integration; | ||
10 | |||
11 | import java.lang.reflect.InvocationTargetException; | ||
12 | import java.util.ArrayList; | ||
13 | import java.util.Collection; | ||
14 | import java.util.Collections; | ||
15 | import java.util.HashMap; | ||
16 | import java.util.HashSet; | ||
17 | import java.util.LinkedHashSet; | ||
18 | import java.util.LinkedList; | ||
19 | import java.util.List; | ||
20 | import java.util.Map; | ||
21 | import java.util.Objects; | ||
22 | import java.util.Optional; | ||
23 | import java.util.Queue; | ||
24 | import java.util.Set; | ||
25 | import java.util.concurrent.Callable; | ||
26 | import java.util.stream.Collectors; | ||
27 | import java.util.stream.IntStream; | ||
28 | import java.util.stream.Stream; | ||
29 | |||
30 | import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException; | ||
31 | import tools.refinery.viatra.runtime.localsearch.matcher.CallWithAdornment; | ||
32 | import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; | ||
33 | import tools.refinery.viatra.runtime.localsearch.matcher.LocalSearchMatcher; | ||
34 | import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference; | ||
35 | import tools.refinery.viatra.runtime.localsearch.plan.IPlanDescriptor; | ||
36 | import tools.refinery.viatra.runtime.localsearch.plan.IPlanProvider; | ||
37 | import tools.refinery.viatra.runtime.localsearch.plan.SearchPlan; | ||
38 | import tools.refinery.viatra.runtime.localsearch.plan.SearchPlanForBody; | ||
39 | import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler; | ||
40 | import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; | ||
41 | import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; | ||
42 | import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; | ||
43 | import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; | ||
44 | import tools.refinery.viatra.runtime.matchers.backend.IUpdateable; | ||
45 | import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; | ||
46 | import tools.refinery.viatra.runtime.matchers.backend.QueryHintOption; | ||
47 | import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor; | ||
48 | import tools.refinery.viatra.runtime.matchers.context.IInputKey; | ||
49 | import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; | ||
50 | import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; | ||
51 | import tools.refinery.viatra.runtime.matchers.context.IndexingService; | ||
52 | import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; | ||
53 | import tools.refinery.viatra.runtime.matchers.planning.helpers.FunctionalDependencyHelper; | ||
54 | import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference; | ||
55 | import tools.refinery.viatra.runtime.matchers.psystem.PBody; | ||
56 | import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; | ||
57 | import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; | ||
58 | import tools.refinery.viatra.runtime.matchers.psystem.queries.PQueries; | ||
59 | import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; | ||
60 | import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IFlattenCallPredicate; | ||
61 | import tools.refinery.viatra.runtime.matchers.tuple.ITuple; | ||
62 | import tools.refinery.viatra.runtime.matchers.tuple.Tuple; | ||
63 | import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; | ||
64 | import tools.refinery.viatra.runtime.matchers.util.Accuracy; | ||
65 | |||
66 | /** | ||
67 | * @author Zoltan Ujhelyi | ||
68 | * @since 1.7 | ||
69 | * | ||
70 | */ | ||
71 | public abstract class AbstractLocalSearchResultProvider implements IQueryResultProvider { | ||
72 | |||
73 | protected final LocalSearchBackend backend; | ||
74 | protected final IQueryBackendContext backendContext; | ||
75 | protected final IQueryRuntimeContext runtimeContext; | ||
76 | protected final PQuery query; | ||
77 | protected final QueryEvaluationHint userHints; | ||
78 | protected final Map<PQuery, LocalSearchHints> hintCache = new HashMap<>(); | ||
79 | protected final IPlanProvider planProvider; | ||
80 | private static final String PLAN_CACHE_KEY = AbstractLocalSearchResultProvider.class.getName() + "#planCache"; | ||
81 | private final Map<MatcherReference, IPlanDescriptor> planCache; | ||
82 | protected final ISearchContext searchContext; | ||
83 | /** | ||
84 | * @since 2.1 | ||
85 | */ | ||
86 | protected ResultProviderRequestor resultProviderRequestor; | ||
87 | |||
88 | /** | ||
89 | * @since 1.5 | ||
90 | */ | ||
91 | @SuppressWarnings({ "unchecked"}) | ||
92 | public AbstractLocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query, | ||
93 | IPlanProvider planProvider, QueryEvaluationHint userHints) { | ||
94 | this.backend = backend; | ||
95 | this.backendContext = context; | ||
96 | this.query = query; | ||
97 | |||
98 | this.planProvider = planProvider; | ||
99 | this.userHints = userHints; | ||
100 | this.runtimeContext = context.getRuntimeContext(); | ||
101 | this.resultProviderRequestor = backend.getResultProviderRequestor(query, userHints); | ||
102 | this.searchContext = new ISearchContext.SearchContext(backendContext, backend.getCache(), resultProviderRequestor); | ||
103 | this.planCache = backend.getCache().getValue(PLAN_CACHE_KEY, Map.class, HashMap::new); | ||
104 | } | ||
105 | |||
106 | protected abstract IOperationCompiler getOperationCompiler(IQueryBackendContext backendContext, LocalSearchHints configuration); | ||
107 | |||
108 | private IQueryRuntimeContext getRuntimeContext() { | ||
109 | return backend.getRuntimeContext(); | ||
110 | } | ||
111 | |||
112 | private LocalSearchMatcher createMatcher(IPlanDescriptor plan, final ISearchContext searchContext) { | ||
113 | List<SearchPlan> executors = plan.getPlan().stream() | ||
114 | .map(input -> new SearchPlan(input.getBody(), input.getCompiledOperations(), input.calculateParameterMask(), | ||
115 | input.getVariableKeys())) | ||
116 | .collect(Collectors.toList()); | ||
117 | return new LocalSearchMatcher(searchContext, plan, executors); | ||
118 | } | ||
119 | |||
120 | private IPlanDescriptor getOrCreatePlan(MatcherReference key, IQueryBackendContext backendContext, IOperationCompiler compiler, LocalSearchHints configuration, IPlanProvider planProvider) { | ||
121 | if (planCache.containsKey(key)){ | ||
122 | return planCache.get(key); | ||
123 | } else { | ||
124 | IPlanDescriptor plan = planProvider.getPlan(backendContext, compiler, | ||
125 | resultProviderRequestor, configuration, key); | ||
126 | planCache.put(key, plan); | ||
127 | return plan; | ||
128 | } | ||
129 | } | ||
130 | |||
131 | private IPlanDescriptor getOrCreatePlan(MatcherReference key, IPlanProvider planProvider) { | ||
132 | if (planCache.containsKey(key)){ | ||
133 | return planCache.get(key); | ||
134 | } else { | ||
135 | LocalSearchHints configuration = overrideDefaultHints(key.getQuery()); | ||
136 | IOperationCompiler compiler = getOperationCompiler(backendContext, configuration); | ||
137 | IPlanDescriptor plan = planProvider.getPlan(backendContext, compiler, | ||
138 | resultProviderRequestor, configuration, key); | ||
139 | planCache.put(key, plan); | ||
140 | return plan; | ||
141 | } | ||
142 | } | ||
143 | |||
144 | private LocalSearchHints overrideDefaultHints(PQuery pQuery) { | ||
145 | if (hintCache.containsKey(pQuery)) { | ||
146 | return hintCache.get(pQuery); | ||
147 | } else { | ||
148 | LocalSearchHints hint = LocalSearchHints.getDefaultOverriddenBy( | ||
149 | computeOverridingHints(pQuery)); | ||
150 | hintCache.put(pQuery, hint); | ||
151 | return hint; | ||
152 | } | ||
153 | } | ||
154 | |||
155 | /** | ||
156 | * Combine with {@link QueryHintOption#getValueOrDefault(QueryEvaluationHint)} to access | ||
157 | * hint settings not covered by {@link LocalSearchHints} | ||
158 | */ | ||
159 | private QueryEvaluationHint computeOverridingHints(PQuery pQuery) { | ||
160 | return backendContext.getHintProvider().getQueryEvaluationHint(pQuery).overrideBy(userHints); | ||
161 | } | ||
162 | |||
163 | /** | ||
164 | * Prepare this result provider. This phase is separated from the constructor to allow the backend to cache its instance before | ||
165 | * requesting preparation for its dependencies. | ||
166 | * @since 1.5 | ||
167 | */ | ||
168 | public void prepare() { | ||
169 | try { | ||
170 | runtimeContext.coalesceTraversals(() -> { | ||
171 | LocalSearchHints configuration = overrideDefaultHints(query); | ||
172 | if (configuration.isUseBase()) { | ||
173 | indexInitializationBeforePlanning(); | ||
174 | } | ||
175 | prepareDirectDependencies(); | ||
176 | runtimeContext.executeAfterTraversal(AbstractLocalSearchResultProvider.this::preparePlansForExpectedAdornments); | ||
177 | return null; | ||
178 | }); | ||
179 | } catch (InvocationTargetException e) { | ||
180 | throw new QueryProcessingException("Error while building required indexes: {1}", new String[]{e.getTargetException().getMessage()}, "Error while building required indexes.", query, e); | ||
181 | } | ||
182 | } | ||
183 | |||
184 | protected void preparePlansForExpectedAdornments() { | ||
185 | // Plan for possible adornments | ||
186 | for (Set<PParameter> adornment : overrideDefaultHints(query).getAdornmentProvider().getAdornments(query)) { | ||
187 | MatcherReference reference = new MatcherReference(query, adornment, userHints); | ||
188 | LocalSearchHints configuration = overrideDefaultHints(query); | ||
189 | IOperationCompiler compiler = getOperationCompiler(backendContext, configuration); | ||
190 | IPlanDescriptor plan = getOrCreatePlan(reference, backendContext, compiler, configuration, planProvider); | ||
191 | // Index keys | ||
192 | try { | ||
193 | if (configuration.isUseBase()) { | ||
194 | indexKeys(plan.getIteratedKeys()); | ||
195 | } | ||
196 | } catch (InvocationTargetException e) { | ||
197 | throw new QueryProcessingException(e.getMessage(), null, e.getMessage(), query, e); | ||
198 | } | ||
199 | //Prepare dependencies | ||
200 | for(SearchPlanForBody body: plan.getPlan()){ | ||
201 | for(CallWithAdornment dependency : body.getDependencies()){ | ||
202 | searchContext.getMatcher(dependency); | ||
203 | } | ||
204 | } | ||
205 | } | ||
206 | } | ||
207 | |||
208 | protected void prepareDirectDependencies() { | ||
209 | // Do not prepare for any adornment at this point | ||
210 | IAdornmentProvider adornmentProvider = input -> Collections.emptySet(); | ||
211 | QueryEvaluationHint adornmentHint = IAdornmentProvider.toHint(adornmentProvider); | ||
212 | |||
213 | for(IQueryReference call : getDirectDependencies()){ | ||
214 | resultProviderRequestor.requestResultProvider(call, adornmentHint); | ||
215 | } | ||
216 | } | ||
217 | |||
218 | /** | ||
219 | * This method is called before planning start to allow indexing. It is important to note that this method is called | ||
220 | * inside a coalesceTraversals block, meaning (1) it is safe to add multiple registration requests as necessary, but | ||
221 | * (2) no value or statistics is available from the index. | ||
222 | * | ||
223 | * @throws ViatraQueryRuntimeException | ||
224 | */ | ||
225 | protected void indexInitializationBeforePlanning() { | ||
226 | // By default, no indexing is necessary | ||
227 | } | ||
228 | |||
229 | /** | ||
230 | * Collects and indexes all types _directly_ referred by the PQuery {@link #query}. Types indirect | ||
231 | * @param requiredIndexingServices | ||
232 | */ | ||
233 | protected void indexReferredTypesOfQuery(PQuery query, IndexingService requiredIndexingServices) { | ||
234 | PQueries.directlyRequiredTypesOfQuery(query, true /*only enumerables are considered for indexing */).forEach( | ||
235 | inputKey -> runtimeContext.ensureIndexed(inputKey, requiredIndexingServices) | ||
236 | ); | ||
237 | } | ||
238 | |||
239 | private Set<IQueryReference> getDirectDependencies() { | ||
240 | IFlattenCallPredicate flattenPredicate = overrideDefaultHints(query).getFlattenCallPredicate(); | ||
241 | Queue<PQuery> queue = new LinkedList<>(); | ||
242 | Set<PQuery> visited = new HashSet<>(); | ||
243 | Set<IQueryReference> result = new HashSet<>(); | ||
244 | queue.add(query); | ||
245 | |||
246 | while(!queue.isEmpty()){ | ||
247 | PQuery next = queue.poll(); | ||
248 | visited.add(next); | ||
249 | for(PBody body : next.getDisjunctBodies().getBodies()){ | ||
250 | for (IQueryReference call : body.getConstraintsOfType(IQueryReference.class)) { | ||
251 | if (call instanceof PositivePatternCall && | ||
252 | flattenPredicate.shouldFlatten((PositivePatternCall) call)) | ||
253 | { | ||
254 | PQuery dep = ((PositivePatternCall) call).getReferredQuery(); | ||
255 | if (!visited.contains(dep)){ | ||
256 | queue.add(dep); | ||
257 | } | ||
258 | } else { | ||
259 | result.add(call); | ||
260 | } | ||
261 | } | ||
262 | } | ||
263 | } | ||
264 | return result; | ||
265 | } | ||
266 | |||
267 | private LocalSearchMatcher initializeMatcher(Object[] parameters) { | ||
268 | return newLocalSearchMatcher(parameters); | ||
269 | } | ||
270 | |||
271 | private LocalSearchMatcher initializeMatcher(TupleMask parameterSeedMask) { | ||
272 | return newLocalSearchMatcher(parameterSeedMask.transformUnique(query.getParameters())); | ||
273 | |||
274 | } | ||
275 | |||
276 | |||
277 | /** | ||
278 | * @throws ViatraQueryRuntimeException | ||
279 | */ | ||
280 | public LocalSearchMatcher newLocalSearchMatcher(ITuple parameters) { | ||
281 | final Set<PParameter> adornment = new HashSet<>(); | ||
282 | for (int i = 0; i < parameters.getSize(); i++) { | ||
283 | if (parameters.get(i) != null) { | ||
284 | adornment.add(query.getParameters().get(i)); | ||
285 | } | ||
286 | } | ||
287 | |||
288 | return newLocalSearchMatcher(adornment); | ||
289 | } | ||
290 | |||
291 | /** | ||
292 | * @throws ViatraQueryRuntimeException | ||
293 | */ | ||
294 | public LocalSearchMatcher newLocalSearchMatcher(Object[] parameters) { | ||
295 | final Set<PParameter> adornment = new HashSet<>(); | ||
296 | for (int i = 0; i < parameters.length; i++) { | ||
297 | if (parameters[i] != null) { | ||
298 | adornment.add(query.getParameters().get(i)); | ||
299 | } | ||
300 | } | ||
301 | |||
302 | return newLocalSearchMatcher(adornment); | ||
303 | } | ||
304 | |||
305 | private LocalSearchMatcher newLocalSearchMatcher(final Set<PParameter> adornment) { | ||
306 | final MatcherReference reference = new MatcherReference(query, adornment, userHints); | ||
307 | |||
308 | IPlanDescriptor plan = getOrCreatePlan(reference, planProvider); | ||
309 | if (overrideDefaultHints(reference.getQuery()).isUseBase()){ | ||
310 | try { | ||
311 | indexKeys(plan.getIteratedKeys()); | ||
312 | } catch (InvocationTargetException e) { | ||
313 | throw new LocalSearchException("Could not index keys", e); | ||
314 | } | ||
315 | } | ||
316 | |||
317 | LocalSearchMatcher matcher = createMatcher(plan, searchContext); | ||
318 | matcher.addAdapters(backend.getAdapters()); | ||
319 | return matcher; | ||
320 | } | ||
321 | |||
322 | private void indexKeys(final Iterable<IInputKey> keys) throws InvocationTargetException { | ||
323 | final IQueryRuntimeContext qrc = getRuntimeContext(); | ||
324 | qrc.coalesceTraversals(new Callable<Void>() { | ||
325 | |||
326 | @Override | ||
327 | public Void call() throws Exception { | ||
328 | for(IInputKey key : keys){ | ||
329 | if (key.isEnumerable()) { | ||
330 | qrc.ensureIndexed(key, IndexingService.INSTANCES); | ||
331 | } | ||
332 | } | ||
333 | return null; | ||
334 | } | ||
335 | }); | ||
336 | } | ||
337 | |||
338 | @Override | ||
339 | public boolean hasMatch(Object[] parameters) { | ||
340 | final LocalSearchMatcher matcher = initializeMatcher(parameters); | ||
341 | return matcher.streamMatches(parameters).findAny().isPresent(); | ||
342 | } | ||
343 | |||
344 | @Override | ||
345 | public boolean hasMatch(TupleMask parameterSeedMask, ITuple parameters) { | ||
346 | final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask); | ||
347 | return matcher.streamMatches(parameterSeedMask, parameters).findAny().isPresent(); | ||
348 | } | ||
349 | |||
350 | @Override | ||
351 | public Optional<Tuple> getOneArbitraryMatch(Object[] parameters) { | ||
352 | final LocalSearchMatcher matcher = initializeMatcher(parameters); | ||
353 | return matcher.streamMatches(parameters).findAny(); | ||
354 | } | ||
355 | |||
356 | @Override | ||
357 | public Optional<Tuple> getOneArbitraryMatch(TupleMask parameterSeedMask, ITuple parameters) { | ||
358 | final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask); | ||
359 | return matcher.streamMatches(parameterSeedMask, parameters).findAny(); | ||
360 | } | ||
361 | |||
362 | @Override | ||
363 | public int countMatches(Object[] parameters) { | ||
364 | final LocalSearchMatcher matcher = initializeMatcher(parameters); | ||
365 | // Count returns long; casting to int - in case of integer overflow casting will throw the exception | ||
366 | return (int) matcher.streamMatches(parameters).count(); | ||
367 | } | ||
368 | |||
369 | @Override | ||
370 | public int countMatches(TupleMask parameterSeedMask, ITuple parameters) { | ||
371 | final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask); | ||
372 | // Count returns long; casting to int - in case of integer overflow casting will throw the exception | ||
373 | return (int) matcher.streamMatches(parameterSeedMask, parameters).count(); | ||
374 | } | ||
375 | |||
376 | private static final double ESTIMATE_CEILING = Long.MAX_VALUE / 16.0; | ||
377 | |||
378 | @Override | ||
379 | public Optional<Long> estimateCardinality(TupleMask groupMask, Accuracy requiredAccuracy) { | ||
380 | if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { // approximate using parameter types | ||
381 | final List<PParameter> parameters = query.getParameters(); | ||
382 | final Map<Set<Integer>, Set<Integer>> dependencies = backendContext.getQueryAnalyzer() | ||
383 | .getProjectedFunctionalDependencies(query, false); | ||
384 | |||
385 | List<Integer> projectionIndices = groupMask.getIndicesAsList(); | ||
386 | |||
387 | return estimateParameterCombinations(requiredAccuracy, parameters, dependencies, | ||
388 | projectionIndices, | ||
389 | Collections.emptySet() /* No parameters with fixed value */).map(Double::longValue); | ||
390 | } | ||
391 | else return Optional.empty(); | ||
392 | } | ||
393 | |||
394 | @Override | ||
395 | public Optional<Double> estimateAverageBucketSize(TupleMask groupMask, Accuracy requiredAccuracy) { | ||
396 | if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { // approximate using parameter types | ||
397 | final List<PParameter> parameters = query.getParameters(); | ||
398 | final Map<Set<Integer>, Set<Integer>> dependencies = backendContext.getQueryAnalyzer() | ||
399 | .getProjectedFunctionalDependencies(query, false); | ||
400 | |||
401 | // all parameters used for the estimation - determinized order | ||
402 | final List<Integer> allParameterIndices = | ||
403 | IntStream.range(0, parameters.size()).boxed().collect(Collectors.toList()); | ||
404 | |||
405 | // some free parameters are functionally determined by bound parameters | ||
406 | final Set<Integer> boundOrImplied = FunctionalDependencyHelper.closureOf(groupMask.getIndicesAsList(), | ||
407 | dependencies); | ||
408 | |||
409 | return estimateParameterCombinations(requiredAccuracy, parameters, dependencies, | ||
410 | allParameterIndices, | ||
411 | boundOrImplied); | ||
412 | } | ||
413 | else return Optional.empty(); | ||
414 | } | ||
415 | |||
416 | /** | ||
417 | * @since 2.1 | ||
418 | * @noreference This method is not intended to be referenced by clients. | ||
419 | */ | ||
420 | public double estimateCost(TupleMask inputBindingMask) { | ||
421 | // TODO this is currently an abstract cost, not really a branching factor | ||
422 | |||
423 | HashSet<PParameter> adornment = new HashSet<>(inputBindingMask.transform(query.getParameters())); | ||
424 | final MatcherReference reference = new MatcherReference(query, adornment, userHints); | ||
425 | IPlanDescriptor plan = getOrCreatePlan(reference, planProvider); | ||
426 | |||
427 | return plan.getPlan().stream().mapToDouble(SearchPlanForBody::getCost).sum(); | ||
428 | } | ||
429 | |||
430 | /** | ||
431 | * Approximates using parameter types | ||
432 | */ | ||
433 | private Optional<Double> estimateParameterCombinations( | ||
434 | Accuracy requiredAccuracy, | ||
435 | final List<PParameter> parameters, | ||
436 | final Map<Set<Integer>, Set<Integer>> functionalDependencies, | ||
437 | final Collection<Integer> parameterIndicesToEstimate, | ||
438 | final Set<Integer> otherDeterminingIndices) | ||
439 | { | ||
440 | // keep order deterministic | ||
441 | LinkedHashSet<Integer> freeParameterIndices = new LinkedHashSet<>(parameterIndicesToEstimate); | ||
442 | |||
443 | // determining indices are bound | ||
444 | freeParameterIndices.removeAll(otherDeterminingIndices); | ||
445 | |||
446 | // some free parameters are functionally determined by other free parameters | ||
447 | for (Integer candidateForRemoval : new ArrayList<>(freeParameterIndices)) { | ||
448 | List<Integer> others = Stream.concat( | ||
449 | otherDeterminingIndices.stream(), | ||
450 | freeParameterIndices.stream().filter(index -> !Objects.equals(index, candidateForRemoval)) | ||
451 | ).collect(Collectors.toList()); | ||
452 | Set<Integer> othersClosure = FunctionalDependencyHelper.closureOf(others, functionalDependencies); | ||
453 | if (othersClosure.contains(candidateForRemoval)) { | ||
454 | // other parameters functionally determine this mone, does not count towards estimate | ||
455 | freeParameterIndices.remove(candidateForRemoval); | ||
456 | } | ||
457 | } | ||
458 | |||
459 | |||
460 | Optional<Double> result = Optional.of(1.0); | ||
461 | // TODO this is currently works with declared types only. For better results, information from | ||
462 | // the Type inferrer should be included in the PSystem | ||
463 | for (int i = 0; (i < parameters.size()); i++) { | ||
464 | final IInputKey type = parameters.get(i).getDeclaredUnaryType(); | ||
465 | if (freeParameterIndices.contains(i) && type != null) { | ||
466 | result = result.flatMap(accumulator -> | ||
467 | runtimeContext.estimateCardinality(type, TupleMask.identity(1), requiredAccuracy).map(multiplier -> | ||
468 | Math.min(accumulator * multiplier, ESTIMATE_CEILING /* avoid overflow */) | ||
469 | )); | ||
470 | } | ||
471 | } | ||
472 | // TODO better approximate cardinality based on plan, branching factors, etc. | ||
473 | return result; | ||
474 | } | ||
475 | |||
476 | |||
477 | @Override | ||
478 | public Stream<Tuple> getAllMatches(Object[] parameters) { | ||
479 | final LocalSearchMatcher matcher = initializeMatcher(parameters); | ||
480 | return matcher.streamMatches(parameters); | ||
481 | } | ||
482 | |||
483 | @Override | ||
484 | public Stream<Tuple> getAllMatches(TupleMask parameterSeedMask, ITuple parameters) { | ||
485 | final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask); | ||
486 | return matcher.streamMatches(parameterSeedMask, parameters); | ||
487 | } | ||
488 | |||
489 | @Override | ||
490 | public IQueryBackend getQueryBackend() { | ||
491 | return backend; | ||
492 | } | ||
493 | |||
494 | @Override | ||
495 | public void addUpdateListener(IUpdateable listener, Object listenerTag, boolean fireNow) { | ||
496 | // throw new UnsupportedOperationException(UPDATE_LISTENER_NOT_SUPPORTED); | ||
497 | } | ||
498 | |||
499 | @Override | ||
500 | public void removeUpdateListener(Object listenerTag) { | ||
501 | // throw new UnsupportedOperationException(UPDATE_LISTENER_NOT_SUPPORTED); | ||
502 | } | ||
503 | |||
504 | /** | ||
505 | * @since 1.4 | ||
506 | */ | ||
507 | public IMatcherCapability getCapabilites() { | ||
508 | LocalSearchHints configuration = overrideDefaultHints(query); | ||
509 | return configuration; | ||
510 | } | ||
511 | |||
512 | /** | ||
513 | * Forgets all stored plans in this result provider. If no plans are stored, nothing happens. | ||
514 | * | ||
515 | * @since 2.0 | ||
516 | * @noreference This method is not intended to be referenced by clients; it should only used by {@link LocalSearchBackend}. | ||
517 | */ | ||
518 | public void forgetAllPlans() { | ||
519 | planCache.clear(); | ||
520 | } | ||
521 | |||
522 | /** | ||
523 | * Returns a search plan for a given adornment if exists | ||
524 | * | ||
525 | * @return a search plan for the pattern with the given adornment, or null if none exists | ||
526 | * @since 2.0 | ||
527 | * @noreference This method is not intended to be referenced by clients; it should only used by {@link LocalSearchBackend}. | ||
528 | */ | ||
529 | public IPlanDescriptor getSearchPlan(Set<PParameter> adornment) { | ||
530 | return planCache.get(new MatcherReference(query, adornment)); | ||
531 | } | ||
532 | } \ No newline at end of file | ||