From 876d9dd1d1a867afe1ccfe9e89d817df8b8867b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?= Date: Mon, 26 Oct 2026 17:00:50 +0100 Subject: [PATCH 1/7] refactor(query): decompose Eval and Exp god classes and clear all SonarLint alerts in core.next - Extract 7 SRP pattern evaluators from Eval.java into dedicated components: EvalMinus, EvalUnion, EvalService, EvalValues, EvalFilter, EvalBind, EvalSubQuery - Extract variable scoping and pattern traversal from Exp.java into ExpPatternScope.java - Add unit test suites for all extracted evaluators and scope analyzer - Fix 172 SonarLint issues across 32 test files in core.next (naming S100/S101, text blocks S6203, constants S1170, scope S1450/S3398, escapes S5665, whitespace S2479) - Zero SonarLint warnings across all 974 files (main + test) in core.next - Fixes #650 --- .../next/query/impl/engine/eval/Eval.java | 609 +----------------- .../next/query/impl/engine/eval/EvalBind.java | 168 +++++ .../query/impl/engine/eval/EvalFilter.java | 111 ++++ .../query/impl/engine/eval/EvalMinus.java | 66 ++ .../query/impl/engine/eval/EvalService.java | 80 +++ .../query/impl/engine/eval/EvalSubQuery.java | 104 +++ .../query/impl/engine/eval/EvalUnion.java | 90 +++ .../query/impl/engine/eval/EvalValues.java | 80 +++ .../next/query/impl/engine/pattern/Exp.java | 343 +--------- .../impl/engine/pattern/ExpPatternScope.java | 344 ++++++++++ .../query/impl/engine/pattern/QueryPlan.java | 4 + .../data/api/factory/ValueFactoryTest.java | 2 +- .../data/api/io/format/FileFormatTest.java | 24 +- .../data/api/literal/CoreDatatypesTest.java | 8 +- .../parser/DefaultRDFParserFactoryTest.java | 14 +- .../impl/io/parser/rdfa/RDFaParserTest.java | 40 +- .../DefaultRDFSerializerFactoryTest.java | 38 +- .../nquads/NQuadsSerializerOptionsTest.java | 20 +- .../NTriplesSerializerOptionsTest.java | 20 +- .../rdfc10/RDFC10SerializerOptionsTest.java | 6 +- .../rdfxml/RDFXMLSerializerOptionsTest.java | 44 +- .../trig/TriGSerializerOptionsTest.java | 44 +- .../turtle/TurtleSerializerOptionsTest.java | 44 +- .../query/impl/engine/eval/EvalBindTest.java | 78 +++ .../impl/engine/eval/EvalFilterTest.java | 64 ++ .../query/impl/engine/eval/EvalMinusTest.java | 62 ++ .../impl/engine/eval/EvalServiceTest.java | 73 +++ .../impl/engine/eval/EvalSubQueryTest.java | 46 ++ .../query/impl/engine/eval/EvalUnionTest.java | 56 ++ .../impl/engine/eval/EvalValuesTest.java | 78 +++ .../engine/pattern/ExpPatternScopeTest.java | 169 +++++ ...seRepositoryConnectionTransactionTest.java | 3 +- .../csv/CsvTupleResultSerializerTest.java | 6 +- .../json/JsonBooleanResultSerializerTest.java | 6 +- .../parser/SparqlAstBuilderDescribeTest.java | 20 +- .../parser/SparqlAstBuilderUnionTest.java | 39 +- .../parser/SparqlParserBnodeBgpScopeTest.java | 30 +- .../sparql/parser/SparqlParserIfTest.java | 7 +- .../sparql/parser/SparqlParserInTest.java | 7 +- .../sparql/parser/SparqlParserMinusTest.java | 7 +- .../parser/SparqlParserReplaceTest.java | 7 +- .../sparql/parser/SparqlParserStrDtTest.java | 7 +- .../parser/SparqlParserValidationTest.java | 48 +- 43 files changed, 1977 insertions(+), 1139 deletions(-) create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalBind.java create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalFilter.java create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalMinus.java create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalService.java create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalSubQuery.java create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalUnion.java create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalValues.java create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/ExpPatternScope.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalBindTest.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalFilterTest.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalMinusTest.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalServiceTest.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalSubQueryTest.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalUnionTest.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalValuesTest.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/ExpPatternScopeTest.java diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Eval.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Eval.java index 42eb60c4c..4672ab8d3 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Eval.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Eval.java @@ -82,6 +82,13 @@ public final class Eval implements ExpType { EvalGraph graphEvaluator; EvalJoin join; EvalOptional optional; + EvalMinus minusEvaluator; + EvalUnion unionEvaluator; + EvalService serviceEvaluator; + EvalValues valuesEvaluator; + EvalFilter filterEvaluator; + EvalBind bindEvaluator; + EvalSubQuery subQueryEvaluator; // subeval = false: eval query/subquery // subeval = true: eval statement (union, optional, minus, graph, join) boolean isSubEval = false; @@ -107,6 +114,13 @@ public Eval(Producer p, Evaluator e, Matcher m) { graphEvaluator = new EvalGraph(this); join = new EvalJoin(this); optional = new EvalOptional(this); + minusEvaluator = new EvalMinus(this); + unionEvaluator = new EvalUnion(this); + serviceEvaluator = new EvalService(this); + valuesEvaluator = new EvalValues(this); + filterEvaluator = new EvalFilter(this); + bindEvaluator = new EvalBind(this); + subQueryEvaluator = new EvalSubQuery(this); } @@ -488,7 +502,7 @@ Eval copy(Memory m, Producer p, Evaluator e, boolean extern) { * copy memory for sub query copy sub query select variables that are * already bound in current memory Use case: subquery and exists */ - private Memory copyMemory(Memory memory, QueryPlan query, QueryPlan sub, Exp exp) { + Memory copyMemory(Memory memory, QueryPlan query, QueryPlan sub, Exp exp) { Memory mem = new Memory(match, evaluator); getEvaluator().init(mem); if (sub == null) { @@ -749,7 +763,7 @@ int eval(Producer p, Node graphNode, Stack stack, Mappings map, int n) { // we pass mappings only for variables that are in-scope in // both branches of the union // it can be bypassed with values var (undef) - backtrack = service(p, graphNode, exp, map, stack, n); + backtrack = serviceEvaluator.eval(p, graphNode, exp, map, stack, n); break; case GRAPH: @@ -758,26 +772,26 @@ int eval(Producer p, Node graphNode, Stack stack, Mappings map, int n) { break; case UNION: - backtrack = union(p, graphNode, exp, map, stack, n); + backtrack = unionEvaluator.eval(p, graphNode, exp, map, stack, n); break; case OPTIONAL: backtrack = optional.eval(p, graphNode, exp, map, stack, n); break; case MINUS: - backtrack = minus(p, graphNode, exp, map, stack, n); + backtrack = minusEvaluator.eval(p, graphNode, exp, map, stack, n); break; case JOIN: backtrack = join.eval(p, graphNode, exp, map, stack, n); break; case QUERY: - backtrack = query(p, graphNode, exp, map, stack, n); + backtrack = subQueryEvaluator.eval(p, graphNode, exp, map, stack, n); break; case FILTER: - backtrack = filter(p, graphNode, exp, stack, n); + backtrack = filterEvaluator.eval(p, graphNode, exp, stack, n); break; case BIND: - backtrack = bind(p, graphNode, exp, map, stack, n); + backtrack = bindEvaluator.eval(p, graphNode, exp, map, stack, n); break; @@ -786,12 +800,12 @@ int eval(Producer p, Node graphNode, Stack stack, Mappings map, int n) { break; case VALUES: - backtrack = values(p, graphNode, exp, stack, n); + backtrack = valuesEvaluator.eval(p, graphNode, exp, stack, n); break; case OPT_BIND: // Binding constraints introduced by the filter optimizer. - backtrack = optBind(p, graphNode, exp, stack, n); + backtrack = bindEvaluator.optBind(p, graphNode, exp, stack, n); break; case ACCEPT: @@ -817,128 +831,6 @@ int eval(Producer p, Node graphNode, Stack stack, Mappings map, int n) { return backtrack; } - private int minus(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) { - int backtrack = n - 1; - Memory env = getMemory(); - - Mappings map1 = subEval(p, graphNode, null, exp.first(), exp, data); - if (state.isStopped()) { - return STOP; - } - if (map1.isEmpty()) { - return backtrack; - } - - MappingSet set1 = new MappingSet(map1); - Exp rest = exp.rest(); - Mappings minusMappings = set1.prepareMappingsRest(rest); - - Mappings map2 = subEval(p, graphNode, null, rest, exp, minusMappings); - - getVisitor().minus(this, getGraphNode(graphNode), exp, map1, map2); - - MappingSet set = new MappingSet(exp, set1, new MappingSet(map2)); - set.start(); - - for (Mapping map : map1) { - if (state.isStopped()) { - return STOP; - } - boolean ok = !set.minusCompatible(map); - if ((ok) && (env.push(map, n))) { - backtrack = eval(p, graphNode, stack, n + 1); - env.pop(map); - if (backtrack < n) { - return backtrack; - } - } - } - return backtrack; - } - - boolean isFederate(Exp exp) { - if (getQuery().getGlobalQuery().isFederate()) { - return true; - } - return exp.isRecFederate(); - } - - private int union(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) { - int backtrack = n - 1; - // join(A, union(B, C)) ; map = eval(A).distinct(inscopenodes()) - - Mappings map1 = unionBranch(p, graphNode, exp.first(), exp, data); - if (state.isStopped()) { - return STOP; - } - Mappings map2 = unionBranch(p, graphNode, exp.rest(), exp, data); - - getVisitor().union(this, getGraphNode(graphNode), exp, map1, map2); - Mappings alternatives = new Mappings(); - alternatives.add(map1); - alternatives.add(map2); - return evalUnionMappings(p, graphNode, stack, n, alternatives, backtrack); - } - - /** Continues the enclosing query once for every solution of a UNION branch. */ - private int evalUnionMappings( - Producer p, Node graphNode, Stack stack, int n, Mappings mappings, int backtrack) { - Memory environment = getMemory(); - for (Mapping mapping : mappings) { - if (state.isStopped()) { - return STOP; - } - if (environment.push(mapping, n, false)) { - backtrack = eval(p, graphNode, stack, n + 1); - environment.pop(mapping); - if (backtrack < n) { - return backtrack; - } - } - } - return backtrack; - } - - /** - * Eval one exp of union main map is partial solution Mappings resulting - * from previous statement evaluation, typically eval(A) in join(A, union(B, - * C)) In federated case (or if exp is itself a union), map is passed to - * subEval(exp, map), it may be taken into account by service in exp In non - * federated case, map is included in copy of exp as a values(var, map) - * clause - */ - Mappings unionBranch(Producer p, Node graphNode, Exp exp, Exp main, Mappings data) { - if (exp.isFirstWith()) { - // union in union: eval inner union with parameter data as is - return subEval(p, graphNode, null, exp, main, data); - } else if (isFederate(exp) || exp.isUnion() || isParameterUnionMappings()) { - Mappings unionData = unionData(exp, data); - return subEval(p, graphNode, null, exp, main, unionData); - } else { - // exp += values(var, map) - Exp ee = exp.complete(data); - return subEval(p, graphNode, null, ee, main, null); - } - } - - /** - * exp is a branch of union - * extract from data relevant mappings for exp in-scope variables - */ - Mappings unionData(Exp exp, Mappings data) { - if (data != null) { - if (data.getNodeList() == null) { - MappingSet ms = new MappingSet(data); - return ms.prepareMappingsRest(exp); - } else if (data.getJoinMappings() != null) { - // select relevant variables from original join Mappings - return unionData(exp, data.getJoinMappings()); - } - } - return data; - } - - private int and(Producer p, Node graphNode, Exp exp, Stack stack, Mappings data, int n) { getVisitor().bgp(this, getGraphNode(graphNode), exp, null); @@ -977,63 +869,6 @@ private int bgp(Producer p, Node graphNode, Exp exp, Stack stack, int n) { return backtrack; } - private int service(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) { - Memory env = getMemory(); - Node serv = exp.first().getNode(); - Node node = serv.isVariable() ? env.getNode(serv) : serv; - - if (getProvider() != null) { - Mappings map = getProvider().service(node, exp, selectQueryMappings(data), this); - return processServiceMappings(p, graphNode, stack, n, env, map); - } else if (exp.isSilent()) { - return eval(p, graphNode, stack, n + 1); - } else { - throw new QueryEvaluationException( - "Federated SPARQL SERVICE execution failed: no Provider configured to query endpoint " + node); - } - } - - private int processServiceMappings(Producer p, Node graphNode, Stack stack, int n, Memory env, Mappings map) { - int backtrack = n - 1; - if (map == null) { - return backtrack; - } - for (Mapping m : map) { - if (state.isStopped()) { - return STOP; - } - // push each Mapping in memory and continue - complete(getQuery(), m, false); - - if (env.push(m, n, false, false)) { - backtrack = eval(p, graphNode, stack, n + 1); - env.pop(m, false); - if (backtrack < n) { - return backtrack; - } - } - } - return backtrack; - } - - // process additional variable provided by service - // such as ?_server_0 in federated mode - void complete(QueryPlan q, Mapping map, boolean addNode) { - int i = 0; - for (Node node : map.getQueryNodes()) { - Node out = q.getOuterNode(node); - // Preserve auxiliary service bindings absent from the outer projection. - if (out == null) { - out = node; - if (addNode && !q.getSelect().contains(node)) { - q.getSelect().add(node); - } - } - map.getQueryNodes()[i] = out; - i++; - } - } - Node getNode(Producer p, Node gNode) { if (gNode.isConstant()) { return p.getNode(gNode); @@ -1045,229 +880,40 @@ Node getGraphNode(Node node) { return node; } - /** - * bind(exp as var) - */ - private int bind(Producer p, Node graphNode, Exp exp, Mappings map, Stack stack, int n) { - if (exp.isFunctional()) { - return extBind(p, graphNode, exp, stack, n); - } - - int backtrack = n - 1; - Memory env = getMemory(); - Node node; - try { - node = eval(graphNode, exp.getFilter(), env, p); - } catch (QueryTypeErrorException error) { - // SPARQL Extend keeps the input solution when its expression errors; - // only the target variable is left unbound. Unsupported features and - // programming failures deliberately still propagate. - getVisitor().bind(this, getGraphNode(graphNode), exp, null); - return eval(p, graphNode, stack, map, n + 1); - } - - getVisitor().bind(this, getGraphNode(graphNode), exp, node == null ? null : node.getDatatypeValue()); - - if (node == null) { - backtrack = eval(p, graphNode, stack, map, n + 1); - } else if (env.push(exp.getNode(), node, n)) { - // kgram authorize to bind a variable "again" - // hence push may fail if value is not the same - backtrack = eval(p, graphNode, stack, map, n + 1); - env.pop(exp.getNode()); - } - - return backtrack; - } - - /** - * values (?x ?y) { unnest(exp) } - * compiled as extended bind - */ - private int extBind(Producer p, Node graphNode, Exp exp, Stack stack, int n) { - int backtrack = n - 1; - Memory env = getMemory(); - env.setGraphNode(graphNode); - Mappings map = eval(exp.getFilter(), env, exp.getNodeList()); - env.setGraphNode(null); - getVisitor().values(this, getGraphNode(graphNode), exp, map); - - if (map != null) { - HashMap tab = toMap(exp.getNodeList()); - for (Mapping m : map) { - if (state.isStopped()) { - return STOP; - } - if (env.push(tab, m, n)) { - backtrack = eval(p, graphNode, stack, n + 1); - env.pop(tab, m); - if (backtrack < n) { - return backtrack; - } - } - } - } - - return backtrack; + boolean isFederate(Exp exp) { + return unionEvaluator.isFederate(exp); } - HashMap toMap(List list) { - HashMap m = new HashMap<>(); - for (Node node : list) { - m.put(node.getLabel(), node); - } - return m; + boolean valuesBinding(List varList, Mapping map, int n) { + return valuesEvaluator.valuesBinding(varList, map, n); } - /** - * - */ - private int filter(Producer p, Node graphNode, Exp exp, Stack stack, int n) { - int backtrack = n - 1; - Memory env = getMemory(); - boolean success = true; - - if (!exp.isPostpone()) { - success = test(graphNode, exp.getFilter(), env, p); - - if (visitors.hasFilter()) { - success = getVisitor().filter(this, getGraphNode(graphNode), exp.getFilter().getExp(), success); - } - } - - if (success) { - backtrack = eval(p, graphNode, stack, n + 1); - } - - return backtrack; + void free(List varList, Mapping map) { + valuesEvaluator.free(varList, map); } boolean test(Node graphNode, Filter f, Environment env, Producer p) { - try { - env.setGraphNode(graphNode); - DatatypeValue dt = eval(f, env, p); - return isTrue(dt); - } catch (QueryTypeErrorException error) { - // A SPARQL filter expression error makes that solution fail; it is - // not an engine failure and must not abort the whole query. - return false; - } finally { - env.setGraphNode(null); - } - } - - boolean isTrue(DatatypeValue dt) { - if (dt == null) { - return false; - } - return dt.isTrue(); + return filterEvaluator.test(graphNode, f, env, p); } public Node eval(Node graphNode, Filter f, Environment env, Producer p) { - try { - env.setGraphNode(graphNode); - return p.getNode(eval(f.getExp(), env, p)); - } finally { - env.setGraphNode(null); - } + return filterEvaluator.eval(graphNode, f, env, p); } - public DatatypeValue eval(Filter f, Environment env, Producer p) { - return f.getExp().evalWE(getEvaluator(), env.getBind(), env, p); + return filterEvaluator.eval(f, env, p); } public DatatypeValue eval(Expr e, Environment env, Producer p) { - return e.evalWE(getEvaluator(), env.getBind(), env, p); + return filterEvaluator.eval(e, env, p); } - // values var ( unnext(exp) ) - // Note: Producer is not current producer but global producer public Mappings eval(Filter f, Environment env, List nodes) { - return eval(f, env, getProducer(), nodes); + return filterEvaluator.eval(f, env, getProducer(), nodes); } public Mappings eval(Filter f, Environment env, Producer p, List nodes) { - - int n = 1; - Expr exp = f.getExp(); - if (exp.oper() == UNNEST) { - if (visitors.hasListener()) { - visitors.listen(exp); - } - if (exp.arity() == 2) { - // unnest(exp, 2) - DatatypeValue dt = eval(exp.getExp(1), env, p); - if (dt == null) { - return new Mappings(); - } - n = dt.intValue(); - } - exp = exp.getExp(0); - } - DatatypeValue res = eval(exp, env, p); - if (res == null) { - return new Mappings(); - } - // use std producer to map in case local p cannot map - return getProducer().map(nodes, res, n); - } - - private int values(Producer p, Node graphNode, Exp exp, Stack stack, int n) { - int backtrack = n - 1; - getVisitor().values(this, getGraphNode(graphNode), exp, exp.getMappings()); - - for (Mapping map : exp.getMappings()) { - if (state.isStopped()) { - return STOP; - } - if (valuesBinding(exp.getNodeList(), map, n)) { - backtrack = eval(p, graphNode, stack, n + 1); - free(exp.getNodeList(), map); - - if (backtrack < n) { - return backtrack; - } - } - } - - return backtrack; - - } - - boolean valuesBinding(List varList, Mapping map, int n) { - int i = 0; - for (Node qNode : varList) { - Node node = map.getNode(qNode); - if (node != null) { - Node value = getProducer().getNode(node.getValue()); - boolean suc = getMemory().push(qNode, value, n); - if (!suc) { - popBinding(varList, map, i); - return false; - } - } - i++; - } - return true; - } - - void popBinding(List varList, Mapping map, int i) { - for (int index = 0; index < i; index++) { - Node variable = varList.get(index); - if (map.getNode(variable) != null) { - getMemory().pop(variable); - } - } - } - - /** Releases only bindings contributed by the current VALUES row. */ - void free(List varList, Mapping map) { - for (Node qNode : varList) { - if (map.getNode(qNode) != null) { - getMemory().pop(qNode); - } - } + return filterEvaluator.eval(f, env, p, nodes); } /** @@ -1388,45 +1034,6 @@ private int edge(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack return backtrack; } - /** - * subquery - * select * where {{select * where {?x p ?y}} . ?y q ?z} - * new eval, new memory, share only sub query select variables - */ - private int query(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) { - int backtrack = n - 1; - QueryPlan subQuery = exp.getQuery(); - Memory env = getMemory(); - getVisitor().start(subQuery); - - // copy current Eval, new stack - // bind sub query select nodes in new memory - Eval ev = copy(copyMemory(env, getQuery(), subQuery, null), p, getEvaluator(), false); - Mappings lMap = ev.eval(graphNode, subQuery, null, selectQueryMappings(data)); - - getVisitor().query(this, getGraphNode(graphNode), exp, lMap); - getVisitor().finish(lMap); - - // enumerate the result of the sub query - // bind the select nodes into the stack - for (Mapping map : lMap) { - if (state.isStopped()) { - return STOP; - } - boolean bmatch = push(subQuery, map, n); - - if (bmatch) { - backtrack = eval(p, graphNode, stack, n + 1); - pop(subQuery, map); - if (backtrack < n) { - return backtrack; - } - } - } - - return backtrack; - } - /** * Skip Mappings with null nodeList for subquery and service */ @@ -1444,61 +1051,6 @@ Mappings selectQueryMappings(Mappings data) { return data; } - /** - * res is a result of sub query bind the select nodes of sub query into - * current memory retrieve outer node that correspond to sub node - */ - private boolean push(QueryPlan subQuery, Mapping res, int n) { - int k = 0; - Memory env = memory; - Matcher mm = match; - QueryPlan qq = query; - - for (Exp exp : subQuery.getSelectFun()) { - Node subNode = exp.getNode(); - Node node = res.getNode(subNode); - Node outNode; - if (exp.size() == 0) { - // store outer node for next time - outNode = qq.getOuterNodeSelf(subNode); //ici - exp.add(outNode); - } else { - outNode = exp.get(0).getNode(); - } - - if (node != null && !(mm.match(outNode, node, env) && env.push(outNode, node, n))) { - // a value may be null because of an optional pattern - for (int i = 0; i < k; i++) { - subNode = subQuery.getSelect().get(i); - outNode = qq.getOuterNodeSelf(subNode); - Node value = res.getNode(subNode); - if (value != null) { - env.pop(outNode); - } - } - return false; - } - k++; - } - - return true; - } - - /** - * pop select nodes of sub query - */ - private void pop(QueryPlan subQuery, Mapping ans) { - Memory env = memory; - QueryPlan qq = query; - - for (Node subNode : subQuery.getSelect()) { - if (ans.isBound(subNode)) { - Node outNode = qq.getOuterNodeSelf(subNode); - env.pop(outNode); - } - } - } - /** * Store a new result */ @@ -1599,97 +1151,6 @@ public void finish() { optional.setStop(true); } - /** - * use case: - *

- * (n) EDGE{?x ?q ?z} (n+1) FILTER{?x = ?y} with BIND {?x := ?y} compiled - * as: - *

- * (n) BIND {?x := ?y} (n+1) EDGE{?x ?q ?z} (n+2) FILTER{?x = ?y} - */ - private int optBind(Producer p, Node gNode, Exp exp, Stack stack, int n) { - Memory env = getMemory(); - int backtrack; - if (exp.isBindCst()) { - backtrack = cbind(p, gNode, exp, stack, n); - return backtrack; - } else { - - // ?x = ?y - int i = 0; - int j = 1; - Node node = env.getNode(exp.get(i).getNode()); - if (node == null) { - i = 1; - j = 0; - node = env.getNode(exp.get(i).getNode()); - if (node == null) { - // no binding: continue - backtrack = eval(p, gNode, stack, n + 1); - return backtrack; - } - } - - Node qNode = exp.get(j).getNode(); - if (!env.isBound(qNode) && getProducer().isBindable(node)) { - // bind qNode with same index as other variable - env.push(qNode, node, env.getIndex(exp.get(i).getNode())); - backtrack = eval(p, gNode, stack, n + 1); - env.pop(qNode); - } else { - backtrack = eval(p, gNode, stack, n + 1); - } - - return backtrack; - } - } - - /** - * exp : BIND{?x = cst1 || ?x = cst2} Bind ?x with all its values - */ - private int cbind(Producer p, Node gNode, Exp exp, Stack stack, int n) { - Node qNode = exp.get(0).getNode(); - if (!exp.status() || memory.isBound(qNode)) { - return eval(p, gNode, stack, n + 1); - } - - if (exp.getNodeList() == null) { - initBindNodes(exp, getProducer()); - } - - if (exp.getNodeList() != null) { - return evalBindNodes(p, gNode, exp, stack, n, qNode); - } - return eval(p, gNode, stack, n + 1); - } - - private void initBindNodes(Exp exp, Producer prod) { - for (Object value : exp.getObjectValues()) { - Expr cst = (Expr) value; - Node node = prod.getNode(cst.getValue()); - if (node != null && prod.isBindable(node)) { - exp.addNode(node); - } else { - exp.setNodeList(null); - exp.status(false); - break; - } - } - } - - private int evalBindNodes(Producer p, Node gNode, Exp exp, Stack stack, int n, Node qNode) { - Memory env = memory; - for (Node node : exp.getNodeList()) { - env.push(qNode, node, n); - int backtrack = eval(p, gNode, stack, n + 1); - env.pop(qNode); - if (backtrack < n) { - return backtrack; - } - } - return n - 1; - } - /** * Draft extension where a Visitor provides Edge iterator */ diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalBind.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalBind.java new file mode 100644 index 000000000..2f7cd874b --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalBind.java @@ -0,0 +1,168 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.data.api.model.DatatypeValue; +import fr.inria.corese.core.next.query.api.exception.QueryTypeErrorException; +import fr.inria.corese.core.next.query.impl.engine.model.Expr; +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; +import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; +import fr.inria.corese.core.next.query.impl.engine.solution.Memory; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static fr.inria.corese.core.next.query.impl.engine.eval.Eval.STOP; + +/** + * Evaluates SPARQL {@code BIND} and assignment operators for {@link Eval}. + * + *

Handles standard {@code BIND(expr AS ?var)}, functional/unnesting bindings, + * constant equality optimizations ({@code OPT_BIND} / {@code cbind}), and binds + * target variables into memory with error suppression for unbound results.

+ */ +final class EvalBind { + + private final Eval engine; + + EvalBind(Eval engine) { + this.engine = engine; + } + + int eval(Producer p, Node graphNode, Exp exp, Mappings map, Stack stack, int n) { + if (exp.isFunctional()) { + return extBind(p, graphNode, exp, stack, n); + } + + int backtrack = n - 1; + Memory env = engine.getMemory(); + Node node; + try { + node = engine.eval(graphNode, exp.getFilter(), env, p); + } catch (QueryTypeErrorException error) { + engine.getVisitor().bind(engine, engine.getGraphNode(graphNode), exp, null); + return engine.eval(p, graphNode, stack, map, n + 1); + } + + DatatypeValue dt = node == null ? null : node.getDatatypeValue(); + engine.getVisitor().bind(engine, engine.getGraphNode(graphNode), exp, dt); + + if (node == null) { + backtrack = engine.eval(p, graphNode, stack, map, n + 1); + } else if (env.push(exp.getNode(), node, n)) { + backtrack = engine.eval(p, graphNode, stack, map, n + 1); + env.pop(exp.getNode()); + } + + return backtrack; + } + + int extBind(Producer p, Node graphNode, Exp exp, Stack stack, int n) { + int backtrack = n - 1; + Memory env = engine.getMemory(); + env.setGraphNode(graphNode); + Mappings map = engine.eval(exp.getFilter(), env, exp.getNodeList()); + env.setGraphNode(null); + engine.getVisitor().values(engine, engine.getGraphNode(graphNode), exp, map); + + if (map != null) { + Map tab = toMap(exp.getNodeList()); + for (Mapping m : map) { + if (engine.getState().isStopped()) { + return STOP; + } + if (env.push(tab, m, n)) { + backtrack = engine.eval(p, graphNode, stack, n + 1); + env.pop(tab, m); + if (backtrack < n) { + return backtrack; + } + } + } + } + + return backtrack; + } + + Map toMap(List list) { + Map m = new HashMap<>(); + for (Node node : list) { + m.put(node.getLabel(), node); + } + return m; + } + + int optBind(Producer p, Node gNode, Exp exp, Stack stack, int n) { + Memory env = engine.getMemory(); + if (exp.isBindCst()) { + return cbind(p, gNode, exp, stack, n); + } + + int i = 0; + int j = 1; + Node node = env.getNode(exp.get(i).getNode()); + if (node == null) { + i = 1; + j = 0; + node = env.getNode(exp.get(i).getNode()); + if (node == null) { + return engine.eval(p, gNode, stack, n + 1); + } + } + + Node qNode = exp.get(j).getNode(); + if (!env.isBound(qNode) && engine.getProducer().isBindable(node)) { + env.push(qNode, node, env.getIndex(exp.get(i).getNode())); + int backtrack = engine.eval(p, gNode, stack, n + 1); + env.pop(qNode); + return backtrack; + } else { + return engine.eval(p, gNode, stack, n + 1); + } + } + + int cbind(Producer p, Node gNode, Exp exp, Stack stack, int n) { + Node qNode = exp.get(0).getNode(); + if (!exp.status() || engine.getMemory().isBound(qNode)) { + return engine.eval(p, gNode, stack, n + 1); + } + + if (exp.getNodeList() == null) { + initBindNodes(exp, engine.getProducer()); + } + + if (exp.getNodeList() != null) { + return evalBindNodes(p, gNode, exp, stack, n, qNode); + } + return engine.eval(p, gNode, stack, n + 1); + } + + void initBindNodes(Exp exp, Producer prod) { + for (Object value : exp.getObjectValues()) { + Expr cst = (Expr) value; + Node node = prod.getNode(cst.getValue()); + if (node != null && prod.isBindable(node)) { + exp.addNode(node); + } else { + exp.setNodeList(null); + exp.status(false); + break; + } + } + } + + int evalBindNodes(Producer p, Node gNode, Exp exp, Stack stack, int n, Node qNode) { + Memory env = engine.getMemory(); + for (Node node : exp.getNodeList()) { + env.push(qNode, node, n); + int backtrack = engine.eval(p, gNode, stack, n + 1); + env.pop(qNode); + if (backtrack < n) { + return backtrack; + } + } + return n - 1; + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalFilter.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalFilter.java new file mode 100644 index 000000000..14e40df03 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalFilter.java @@ -0,0 +1,111 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.data.api.model.DatatypeValue; +import fr.inria.corese.core.next.query.api.exception.QueryTypeErrorException; +import fr.inria.corese.core.next.query.impl.engine.model.Expr; +import fr.inria.corese.core.next.query.impl.engine.model.Filter; +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; +import fr.inria.corese.core.next.query.impl.engine.solution.Memory; +import fr.inria.corese.core.next.query.impl.engine.spi.Environment; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; + +import java.util.List; + +import static fr.inria.corese.core.next.query.impl.engine.model.ExprType.UNNEST; + +/** + * Evaluates SPARQL {@code FILTER} expressions and constraints for {@link Eval}. + * + *

Handles expression evaluation, three-valued Boolean logic testing, + * error isolation (SPARQL filter errors evaluate to false rather than query abort), + * and listener/visitor notifications.

+ */ +final class EvalFilter { + + private final Eval engine; + + EvalFilter(Eval engine) { + this.engine = engine; + } + + int eval(Producer p, Node graphNode, Exp exp, Stack stack, int n) { + int backtrack = n - 1; + Memory env = engine.getMemory(); + boolean success = true; + + if (!exp.isPostpone()) { + success = test(graphNode, exp.getFilter(), env, p); + + if (engine.getVisitorDispatcher().hasFilter()) { + success = engine.getVisitor().filter(engine, engine.getGraphNode(graphNode), exp.getFilter().getExp(), success); + } + } + + if (success) { + backtrack = engine.eval(p, graphNode, stack, n + 1); + } + + return backtrack; + } + + boolean test(Node graphNode, Filter f, Environment env, Producer p) { + try { + env.setGraphNode(graphNode); + DatatypeValue dt = eval(f, env, p); + return isTrue(dt); + } catch (QueryTypeErrorException error) { + return false; + } finally { + env.setGraphNode(null); + } + } + + boolean isTrue(DatatypeValue dt) { + if (dt == null) { + return false; + } + return dt.isTrue(); + } + + Node eval(Node graphNode, Filter f, Environment env, Producer p) { + try { + env.setGraphNode(graphNode); + return p.getNode(eval(f.getExp(), env, p)); + } finally { + env.setGraphNode(null); + } + } + + DatatypeValue eval(Filter f, Environment env, Producer p) { + return f.getExp().evalWE(engine.getEvaluator(), env.getBind(), env, p); + } + + DatatypeValue eval(Expr e, Environment env, Producer p) { + return e.evalWE(engine.getEvaluator(), env.getBind(), env, p); + } + + Mappings eval(Filter f, Environment env, Producer p, List nodes) { + int n = 1; + Expr exp = f.getExp(); + if (exp.oper() == UNNEST) { + if (engine.getVisitorDispatcher().hasListener()) { + engine.getVisitorDispatcher().listen(exp); + } + if (exp.arity() == 2) { + DatatypeValue dt = eval(exp.getExp(1), env, p); + if (dt == null) { + return new Mappings(); + } + n = dt.intValue(); + } + exp = exp.getExp(0); + } + DatatypeValue res = eval(exp, env, p); + if (res == null) { + return new Mappings(); + } + return engine.getProducer().map(nodes, res, n); + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalMinus.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalMinus.java new file mode 100644 index 000000000..ec6626dd1 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalMinus.java @@ -0,0 +1,66 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; +import fr.inria.corese.core.next.query.impl.engine.solution.MappingSet; +import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; +import fr.inria.corese.core.next.query.impl.engine.solution.Memory; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; + +import static fr.inria.corese.core.next.query.impl.engine.eval.Eval.STOP; + +/** + * Evaluates SPARQL {@code MINUS} pattern operations for {@link Eval}. + * + *

Evaluates the left branch, prepares compatibility constraints for the + * right branch, evaluates the right branch, and filters out incompatible solutions + * before continuing backtracking.

+ */ +final class EvalMinus { + + private final Eval engine; + + EvalMinus(Eval engine) { + this.engine = engine; + } + + int eval(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) { + int backtrack = n - 1; + Memory env = engine.getMemory(); + + Mappings map1 = engine.subEval(p, graphNode, null, exp.first(), exp, data); + if (engine.getState().isStopped()) { + return STOP; + } + if (map1.isEmpty()) { + return backtrack; + } + + MappingSet set1 = new MappingSet(map1); + Exp rest = exp.rest(); + Mappings minusMappings = set1.prepareMappingsRest(rest); + + Mappings map2 = engine.subEval(p, graphNode, null, rest, exp, minusMappings); + + engine.getVisitor().minus(engine, engine.getGraphNode(graphNode), exp, map1, map2); + + MappingSet set = new MappingSet(exp, set1, new MappingSet(map2)); + set.start(); + + for (Mapping map : map1) { + if (engine.getState().isStopped()) { + return STOP; + } + boolean ok = !set.minusCompatible(map); + if (ok && env.push(map, n)) { + backtrack = engine.eval(p, graphNode, stack, n + 1); + env.pop(map); + if (backtrack < n) { + return backtrack; + } + } + } + return backtrack; + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalService.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalService.java new file mode 100644 index 000000000..bfb5a7296 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalService.java @@ -0,0 +1,80 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.query.api.exception.QueryEvaluationException; +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; +import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; +import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; +import fr.inria.corese.core.next.query.impl.engine.solution.Memory; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; + +import static fr.inria.corese.core.next.query.impl.engine.eval.Eval.STOP; + +/** + * Evaluates federated SPARQL {@code SERVICE} pattern operations for {@link Eval}. + * + *

Delegates remote query invocation to the configured {@link fr.inria.corese.core.next.query.impl.engine.spi.Provider}, + * verifies endpoints, maps returned solutions into memory, and enforces the non-silent error policy.

+ */ +final class EvalService { + + private final Eval engine; + + EvalService(Eval engine) { + this.engine = engine; + } + + int eval(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) { + Memory env = engine.getMemory(); + Node serv = exp.first().getNode(); + Node node = serv.isVariable() ? env.getNode(serv) : serv; + + if (engine.getProvider() != null) { + Mappings map = engine.getProvider().service(node, exp, engine.selectQueryMappings(data), engine); + return processServiceMappings(p, graphNode, stack, n, env, map); + } else if (exp.isSilent()) { + return engine.eval(p, graphNode, stack, n + 1); + } else { + throw new QueryEvaluationException( + "Federated SPARQL SERVICE execution failed: no Provider configured to query endpoint " + node); + } + } + + private int processServiceMappings(Producer p, Node graphNode, Stack stack, int n, Memory env, Mappings map) { + int backtrack = n - 1; + if (map == null) { + return backtrack; + } + for (Mapping m : map) { + if (engine.getState().isStopped()) { + return STOP; + } + complete(engine.getQuery(), m, false); + + if (env.push(m, n, false, false)) { + backtrack = engine.eval(p, graphNode, stack, n + 1); + env.pop(m, false); + if (backtrack < n) { + return backtrack; + } + } + } + return backtrack; + } + + void complete(QueryPlan q, Mapping map, boolean addNode) { + int i = 0; + for (Node node : map.getQueryNodes()) { + Node out = q.getOuterNode(node); + if (out == null) { + out = node; + if (addNode && !q.getSelect().contains(node)) { + q.getSelect().add(node); + } + } + map.getQueryNodes()[i] = out; + i++; + } + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalSubQuery.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalSubQuery.java new file mode 100644 index 000000000..56970f259 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalSubQuery.java @@ -0,0 +1,104 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; +import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; +import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; +import fr.inria.corese.core.next.query.impl.engine.solution.Memory; +import fr.inria.corese.core.next.query.impl.engine.spi.Matcher; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; + +import static fr.inria.corese.core.next.query.impl.engine.eval.Eval.STOP; + +/** + * Evaluates nested SPARQL subqueries for {@link Eval}. + * + *

Executes the subquery in an isolated child evaluation frame with cloned + * memory, captures projected results, binds selected outer variables into the + * parent frame, and unbinds them upon backtracking.

+ */ +final class EvalSubQuery { + + private final Eval engine; + + EvalSubQuery(Eval engine) { + this.engine = engine; + } + + int eval(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) { + int backtrack = n - 1; + QueryPlan subQuery = exp.getQuery(); + Memory env = engine.getMemory(); + engine.getVisitor().start(subQuery); + + Eval ev = engine.copy(engine.copyMemory(env, engine.getQuery(), subQuery, null), p, engine.getEvaluator(), false); + Mappings lMap = ev.eval(graphNode, subQuery, null, engine.selectQueryMappings(data)); + + engine.getVisitor().query(engine, engine.getGraphNode(graphNode), exp, lMap); + engine.getVisitor().finish(lMap); + + for (Mapping map : lMap) { + if (engine.getState().isStopped()) { + return STOP; + } + boolean bmatch = push(subQuery, map, n); + + if (bmatch) { + backtrack = engine.eval(p, graphNode, stack, n + 1); + pop(subQuery, map); + if (backtrack < n) { + return backtrack; + } + } + } + + return backtrack; + } + + private boolean push(QueryPlan subQuery, Mapping res, int n) { + int k = 0; + Memory env = engine.getMemory(); + Matcher mm = engine.getMatcher(); + QueryPlan qq = engine.getQuery(); + + for (Exp exp : subQuery.getSelectFun()) { + Node subNode = exp.getNode(); + Node node = res.getNode(subNode); + Node outNode; + if (exp.size() == 0) { + outNode = qq.getOuterNodeSelf(subNode); + exp.add(outNode); + } else { + outNode = exp.get(0).getNode(); + } + + if (node != null && !(mm.match(outNode, node, env) && env.push(outNode, node, n))) { + for (int i = 0; i < k; i++) { + subNode = subQuery.getSelect().get(i); + outNode = qq.getOuterNodeSelf(subNode); + Node value = res.getNode(subNode); + if (value != null) { + env.pop(outNode); + } + } + return false; + } + k++; + } + + return true; + } + + private void pop(QueryPlan subQuery, Mapping ans) { + Memory env = engine.getMemory(); + QueryPlan qq = engine.getQuery(); + + for (Node subNode : subQuery.getSelect()) { + if (ans.isBound(subNode)) { + Node outNode = qq.getOuterNodeSelf(subNode); + env.pop(outNode); + } + } + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalUnion.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalUnion.java new file mode 100644 index 000000000..895d16be5 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalUnion.java @@ -0,0 +1,90 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; +import fr.inria.corese.core.next.query.impl.engine.solution.MappingSet; +import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; +import fr.inria.corese.core.next.query.impl.engine.solution.Memory; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; + +import static fr.inria.corese.core.next.query.impl.engine.eval.Eval.STOP; + +/** + * Evaluates SPARQL {@code UNION} pattern operations for {@link Eval}. + * + *

Evaluates alternatives sequentially, passing context mappings for federated + * or nested branches, and feeds solutions into the evaluation continuation.

+ */ +final class EvalUnion { + + private final Eval engine; + + EvalUnion(Eval engine) { + this.engine = engine; + } + + int eval(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack, int n) { + Mappings map1 = unionBranch(p, graphNode, exp.first(), exp, data); + if (engine.getState().isStopped()) { + return STOP; + } + Mappings map2 = unionBranch(p, graphNode, exp.rest(), exp, data); + + engine.getVisitor().union(engine, engine.getGraphNode(graphNode), exp, map1, map2); + Mappings alternatives = new Mappings(); + alternatives.add(map1); + alternatives.add(map2); + return evalUnionMappings(p, graphNode, stack, n, alternatives, n - 1); + } + + private int evalUnionMappings( + Producer p, Node graphNode, Stack stack, int n, Mappings mappings, int backtrack) { + Memory environment = engine.getMemory(); + int currentBacktrack = backtrack; + for (Mapping mapping : mappings) { + if (engine.getState().isStopped()) { + return STOP; + } + if (environment.push(mapping, n, false)) { + currentBacktrack = engine.eval(p, graphNode, stack, n + 1); + environment.pop(mapping); + if (currentBacktrack < n) { + return currentBacktrack; + } + } + } + return currentBacktrack; + } + + Mappings unionBranch(Producer p, Node graphNode, Exp exp, Exp main, Mappings data) { + if (exp.isFirstWith()) { + return engine.subEval(p, graphNode, null, exp, main, data); + } else if (isFederate(exp) || exp.isUnion() || engine.isParameterUnionMappings()) { + Mappings unionData = unionData(exp, data); + return engine.subEval(p, graphNode, null, exp, main, unionData); + } else { + Exp ee = exp.complete(data); + return engine.subEval(p, graphNode, null, ee, main, null); + } + } + + Mappings unionData(Exp exp, Mappings data) { + if (data != null) { + if (data.getNodeList() == null) { + MappingSet ms = new MappingSet(data); + return ms.prepareMappingsRest(exp); + } else if (data.getJoinMappings() != null) { + return unionData(exp, data.getJoinMappings()); + } + } + return data; + } + + boolean isFederate(Exp exp) { + if (engine.getQuery().getGlobalQuery().isFederate()) { + return true; + } + return exp.isRecFederate(); + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalValues.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalValues.java new file mode 100644 index 000000000..c7412e113 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalValues.java @@ -0,0 +1,80 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; + +import java.util.List; + +import static fr.inria.corese.core.next.query.impl.engine.eval.Eval.STOP; + +/** + * Evaluates inline SPARQL {@code VALUES} data blocks for {@link Eval}. + * + *

Iterates across declared solution mappings, binds inline variables into + * memory, pushes execution onto the stack, and unbinds values on backtrack.

+ */ +final class EvalValues { + + private final Eval engine; + + EvalValues(Eval engine) { + this.engine = engine; + } + + int eval(Producer p, Node graphNode, Exp exp, Stack stack, int n) { + int backtrack = n - 1; + engine.getVisitor().values(engine, engine.getGraphNode(graphNode), exp, exp.getMappings()); + + for (Mapping map : exp.getMappings()) { + if (engine.getState().isStopped()) { + return STOP; + } + if (valuesBinding(exp.getNodeList(), map, n)) { + backtrack = engine.eval(p, graphNode, stack, n + 1); + free(exp.getNodeList(), map); + + if (backtrack < n) { + return backtrack; + } + } + } + + return backtrack; + } + + boolean valuesBinding(List varList, Mapping map, int n) { + int i = 0; + for (Node qNode : varList) { + Node node = map.getNode(qNode); + if (node != null) { + Node value = engine.getProducer().getNode(node.getValue()); + boolean suc = engine.getMemory().push(qNode, value, n); + if (!suc) { + popBinding(varList, map, i); + return false; + } + } + i++; + } + return true; + } + + void popBinding(List varList, Mapping map, int i) { + for (int index = 0; index < i; index++) { + Node variable = varList.get(index); + if (map.getNode(variable) != null) { + engine.getMemory().pop(variable); + } + } + } + + void free(List varList, Mapping map) { + for (Node qNode : varList) { + if (map.getNode(qNode) != null) { + engine.getMemory().pop(qNode); + } + } + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/Exp.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/Exp.java index b3e751cbe..25e6d92c0 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/Exp.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/Exp.java @@ -886,87 +886,15 @@ public boolean contains(Node node) { * optional, no union */ public void share(List filterVar, List expVar) { - switch (type()) { - case FILTER, OPT_BIND, OPTION: - break; - - case OPTIONAL, MINUS: - first().share(filterVar, expVar); - break; - - case UNION: - shareUnion(filterVar, expVar); - break; - - case QUERY: - shareQuery(filterVar, expVar); - break; - - case BIND: - // A FILTER in the same group is evaluated after a preceding BIND - // when it references the BIND target. If the expression errors, - // the target remains unbound and the relocated filter rejects the - // solution, as required by SPARQL's error semantics. - share(getNode(), filterVar, expVar); - break; - - case EDGE: - for (int i = 0; i < nbNode(); i++) { - Node nodePath = getNode(i); - share(nodePath, filterVar, expVar); - } - break; - - case NODE: - share(getNode(), filterVar, expVar); - break; - - default: - for (Exp exp : this) { - exp.share(filterVar, expVar); - } - } + ExpPatternScope.share(this, filterVar, expVar); } void share(Node node, List fVar, List eVar) { - if (node != null && node.isVariable() - && fVar.contains(node.getLabel()) - && !eVar.contains(node.getLabel())) { - eVar.add(node.getLabel()); - } - } - - private void shareUnion(List filterVar, List expVar) { - ArrayList lVar1 = new ArrayList<>(); - ArrayList lVar2 = new ArrayList<>(); - first().share(filterVar, lVar1); - rest().share(filterVar, lVar2); - for (String varString : lVar1) { - if (lVar2.contains(varString) && !expVar.contains(varString)) { - expVar.add(varString); - } - } - } - - private void shareQuery(List filterVar, List expVar) { - ArrayList lVar = new ArrayList<>(); - getQuery().getBody().share(filterVar, lVar); - - for (Exp exp : getQuery().getSelectFun()) { - String name = exp.getNode().getLabel(); - if ((lVar.contains(name) || exp.getFilter() != null) && !expVar.contains(name)) { - expVar.add(name); - } - } + ExpPatternScope.share(node, fVar, eVar); } public boolean bound(List fvec, List evec) { - for (String varString : fvec) { - if (!evec.contains(varString)) { - return false; - } - } - return true; + return ExpPatternScope.bound(fvec, evec); } /** @@ -977,128 +905,7 @@ public boolean bound(List fvec, List evec) { * optional = true : we are inside an optional */ void getNodes(ExpNodeCollector h) { - switch (type()) { - case FILTER: - // get exists () nodes - if (h.isExist()) { - getExistNodes(getFilter().getExp(), h, h.getExistNodeList()); - } - break; - - case NODE, ACCEPT: - //use case: join() check connection, need all variables - h.add(getNode()); - break; - - case EDGE: - collectEdgeNodes(h); - break; - - case XPATH, EVAL: - for (int i = 0; i < nbNode(); i++) { - Node nodeEval = getNode(i); - h.add(nodeEval); - } - break; - - case VALUES: - for (Node varNode : getNodeList()) { - h.add(varNode); - } - break; - - case MINUS: - // second argument does not bind anything: skip it - if (first() != null) { - first().getNodes(h); - } - break; - - case OPTIONAL: - collectOptionalNodes(h); - break; - - case GRAPH: - h.add(getGraphName()); - if (size() > 1) { - rest().getNodes(h); - } - break; - - case UNION: - collectUnionNodes(h); - break; - - case BIND: - collectBindNodes(h); - break; - - case QUERY: - queryNodeList(h); - break; - - default: - collectDefaultNodes(h); - break; - } - } - - private void collectEdgeNodes(ExpNodeCollector h) { - Edge pathEdge = getEdge(); - h.add(pathEdge.getNode(0)); - if (pathEdge.getEdgeVariable() != null) { - h.add(pathEdge.getEdgeVariable()); - } - h.add(pathEdge.getNode(1)); - for (int i = 2; i < pathEdge.nbNode(); i++) { - h.add(pathEdge.getNode(i)); - } - } - - private void collectOptionalNodes(ExpNodeCollector h) { - boolean b = h.isOptional(); - first().getNodes(h.setOptional(true)); - if (!h.isInSubScope()) { - rest().getNodes(h); - } - h.setOptional(b); - } - - private void collectUnionNodes(ExpNodeCollector h) { - if (h.isInSubScope()) { - List left = first().getTheNodes(h.copy()); - List right = rest().getTheNodes(h.copy()); - for (Node nodeLeft : left) { - if (right.contains(nodeLeft)) { - h.add(nodeLeft); - } - } - } else { - for (Exp ee : this) { - ee.getNodes(h); - } - } - } - - private void collectBindNodes(ExpNodeCollector h) { - if (h.isBind()) { - if (getNodeList() == null) { - h.add(getNode()); - } else { - for (Node nodeBind : getNodeList()) { - h.add(nodeBind); - } - } - } - } - - private void collectDefaultNodes(ExpNodeCollector h) { - for (Exp ee : this) { - ee.getNodes(h); - if (h.isInSubScopeSample() && ee.isSkipStatement()) { - break; - } - } + ExpPatternScope.getNodes(this, h); } boolean isSkipStatement() { @@ -1108,30 +915,6 @@ boolean isSkipStatement() { }; } - /** - * complete handler selectList with this query selectList - */ - void queryNodeList(ExpNodeCollector h) { - List selectList = h.getSelectNodeList(); - List subSelectList = getQuery().getSelect(); - - if (h.isInSubScope()) { - // focus on left optional etc. in query body - // because select * includes right optional etc. - List scopeList = getQuery().getBody().getTheNodes(h.copy()); - - for (Node nodeScope : scopeList) { - if (subSelectList.contains(nodeScope)) { - add(selectList, nodeScope); - } - } - } else { - for (Node nodeSubSelect : subSelectList) { - add(selectList, nodeSubSelect); - } - } - } - /** * For modularity reasons, Pattern is stored as ExpPattern interface */ @@ -1139,35 +922,15 @@ public Exp getPattern(Expr exp) { return exp.getPattern(); } - /** - * This is a filter get exists{} nodes if any - */ - void getExistNodes(Expr exp, ExpNodeCollector h, List lExistNode) { - if (exp.oper() == ExprType.EXIST) { - Exp pat = getPattern(exp); - List lNode = pat.getTheNodes(h.copy()); - for (Node exprNode : lNode) { - add(lExistNode, exprNode); - } - } else { - for (Expr ee : exp.getExpList()) { - getExistNodes(ee, h, lExistNode); - } - } - } - /** * Compute inscope variables for variable binding for MappingSet */ public List getRecordInScopeNodes(boolean bind) { - if (getInScopeNodeList() == null) { - setInScopeNodeList(getInScopeNodes(bind)); - } - return getInScopeNodeList(); + return ExpPatternScope.getRecordInScopeNodes(this, bind); } public List getRecordInScopeNodesWithoutBind() { - return getRecordInScopeNodes(false); + return ExpPatternScope.getRecordInScopeNodesWithoutBind(this); } /** @@ -1175,41 +938,34 @@ public List getRecordInScopeNodesWithoutBind() { * setAll(true) ::= for all exp in this exp */ public List getAllInScopeNodes() { - return getTheNodes(handler(true, true).all()); + return ExpPatternScope.getAllInScopeNodes(this); } /** * in scope + bind */ public List getNodes() { - return getTheNodes(handler(false, true).sample()); + return ExpPatternScope.getNodes(this); } /** * in subscope + bind */ public List getInScopeNodes() { - return getTheNodes(handler(true, true).sample()); + return ExpPatternScope.getInScopeNodes(this); } // in subscope with/without bind List getInScopeNodes(boolean bind) { - return getTheNodes(handler(true, bind).sample()); + return ExpPatternScope.getInScopeNodes(this, true, bind); } public List getTheNodes(ExpNodeCollector h) { - getNodes(h); - return h.getNodes(); - } - - ExpNodeCollector handler(boolean inSubScope, boolean bind) { - return new ExpNodeCollector(inSubScope, bind); + return ExpPatternScope.getTheNodes(this, h); } void add(List lNode, Node node) { - if (node != null && node.isVariable() && !node.isBlank() && !lNode.contains(node)) { - lNode.add(node); - } + ExpPatternScope.add(lNode, node); } boolean doesNotContain(List lExp, Node node) { @@ -1360,100 +1116,41 @@ public void optional() { * in BGP1 before testing filter. see EvalOptional */ void inscopeFilter() { - List l1 = first().getRecordInScopeNodesWithoutBind(); - List l2 = rest().getRecordInScopeNodesWithoutBind(); - - for (Exp exp : rest()) { - if (exp.isFilter() && !exp.getFilter().isRecExist()) { - List lvar = exp.getFilter().getVariables(); - if (bind(l1, lvar) && bind(l2, lvar)) { - getCreateInscopeFilter().add(exp); - } - } - } + ExpPatternScope.inscopeFilter(this); } boolean bind(List lnode, List lvar) { - for (String variable : lvar) { - boolean suc = false; - for (Node bindNode : lnode) { - if (bindNode.getLabel().equals(variable)) { - suc = true; - break; - } - } - if (!suc) { - return false; - } - } - return true; + return ExpPatternScope.bind(lnode, lvar); } /** * Check if filter variables are NOT bound by triple, path, values or bind, locally in this Exp */ boolean hasUnboundVariables(Filter f) { - List varList = f.getVariables(); - List nodeList = getNodeVariables(); - - for (String variable : varList) { - if (!nodeList.contains(variable)) { - return true; - } - } - - return false; + return ExpPatternScope.hasUnboundVariables(this, f); } /** * Node variables of edge, path, bind, values */ List getNodeVariables() { - List list = new ArrayList<>(); - for (Exp exp : this) { - switch (exp.type()) { - case EDGE: - exp.getEdgeVariables(list); - break; - case BIND: - exp.getBindVariables(list); - break; - case VALUES: - exp.getValuesVariables(list); - break; - default: - break; - } - } - return list; + return ExpPatternScope.getNodeVariables(this); } void getBindVariables(List list) { - if (getNodeList() == null) { - addVariable(list, getNode()); - } else { - getValuesVariables(list); - } + ExpPatternScope.getBindVariables(this, list); } void getValuesVariables(List list) { - for (Node valuesNode : getNodeList()) { - addVariable(list, valuesNode); - } + ExpPatternScope.getValuesVariables(this, list); } void getEdgeVariables(List list) { - addVariable(list, getEdge().getNode(0)); - addVariable(list, getEdge().getNode(1)); - if (!isPath()) { - addVariable(list, getEdge().getProperty()); - } + ExpPatternScope.getEdgeVariables(this, list); } void addVariable(List list, Node node) { - if (node.isVariable() && !list.contains(node.getLabel())) { - list.add(node.getLabel()); - } + ExpPatternScope.addVariable(list, node); } // optional postponed filters diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/ExpPatternScope.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/ExpPatternScope.java new file mode 100644 index 000000000..80a57137d --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/ExpPatternScope.java @@ -0,0 +1,344 @@ +package fr.inria.corese.core.next.query.impl.engine.pattern; + +import fr.inria.corese.core.next.query.impl.engine.model.Edge; +import fr.inria.corese.core.next.query.impl.engine.model.Expr; +import fr.inria.corese.core.next.query.impl.engine.model.ExprType; +import fr.inria.corese.core.next.query.impl.engine.model.Filter; +import fr.inria.corese.core.next.query.impl.engine.model.Node; + +import java.util.ArrayList; +import java.util.List; + +/** + * Encapsulates variable scope analysis, in-scope node collection, and filter variable + * sharing across compiled SPARQL pattern trees ({@link Exp}). + */ +public final class ExpPatternScope { + + private ExpPatternScope() { + // Utility class + } + + /** + * Traverses the given expression pattern and populates the collector with visible nodes. + */ + public static void getNodes(Exp exp, ExpNodeCollector h) { + switch (exp.type()) { + case FILTER -> { + if (h.isExist()) { + getExistNodes(exp.getFilter().getExp(), h, h.getExistNodeList()); + } + } + case NODE, ACCEPT -> h.add(exp.getNode()); + case EDGE -> collectEdgeNodes(exp, h); + case XPATH, EVAL -> { + for (int i = 0; i < exp.nbNode(); i++) { + h.add(exp.getNode(i)); + } + } + case VALUES -> { + if (exp.getNodeList() != null) { + for (Node varNode : exp.getNodeList()) { + h.add(varNode); + } + } + } + case MINUS -> { + if (exp.first() != null) { + getNodes(exp.first(), h); + } + } + case OPTIONAL -> collectOptionalNodes(exp, h); + case GRAPH -> { + h.add(exp.getGraphName()); + if (exp.size() > 1) { + getNodes(exp.rest(), h); + } + } + case UNION -> collectUnionNodes(exp, h); + case BIND -> collectBindNodes(exp, h); + case QUERY -> queryNodeList(exp, h); + default -> collectDefaultNodes(exp, h); + } + } + + private static void collectEdgeNodes(Exp exp, ExpNodeCollector h) { + Edge pathEdge = exp.getEdge(); + h.add(pathEdge.getNode(0)); + if (pathEdge.getEdgeVariable() != null) { + h.add(pathEdge.getEdgeVariable()); + } + h.add(pathEdge.getNode(1)); + for (int i = 2; i < pathEdge.nbNode(); i++) { + h.add(pathEdge.getNode(i)); + } + } + + private static void collectOptionalNodes(Exp exp, ExpNodeCollector h) { + boolean b = h.isOptional(); + getNodes(exp.first(), h.setOptional(true)); + if (!h.isInSubScope()) { + getNodes(exp.rest(), h); + } + h.setOptional(b); + } + + private static void collectUnionNodes(Exp exp, ExpNodeCollector h) { + if (h.isInSubScope()) { + List left = getTheNodes(exp.first(), h.copy()); + List right = getTheNodes(exp.rest(), h.copy()); + for (Node nodeLeft : left) { + if (right.contains(nodeLeft)) { + h.add(nodeLeft); + } + } + } else { + for (Exp ee : exp) { + getNodes(ee, h); + } + } + } + + private static void collectBindNodes(Exp exp, ExpNodeCollector h) { + if (h.isBind()) { + if (exp.getNodeList() == null) { + h.add(exp.getNode()); + } else { + for (Node nodeBind : exp.getNodeList()) { + h.add(nodeBind); + } + } + } + } + + private static void collectDefaultNodes(Exp exp, ExpNodeCollector h) { + for (Exp ee : exp) { + getNodes(ee, h); + if (h.isInSubScopeSample() && ee.isSkipStatement()) { + break; + } + } + } + + static void queryNodeList(Exp exp, ExpNodeCollector h) { + List selectList = h.getSelectNodeList(); + List subSelectList = exp.getQuery().getSelect(); + + if (h.isInSubScope()) { + List scopeList = getTheNodes(exp.getQuery().getBody(), h.copy()); + for (Node nodeScope : scopeList) { + if (subSelectList.contains(nodeScope)) { + add(selectList, nodeScope); + } + } + } else { + for (Node nodeSubSelect : subSelectList) { + add(selectList, nodeSubSelect); + } + } + } + + static void getExistNodes(Expr expr, ExpNodeCollector h, List lExistNode) { + if (expr.oper() == ExprType.EXIST) { + Exp pat = expr.getPattern(); + List lNode = getTheNodes(pat, h.copy()); + for (Node exprNode : lNode) { + add(lExistNode, exprNode); + } + } else { + for (Expr ee : expr.getExpList()) { + getExistNodes(ee, h, lExistNode); + } + } + } + + public static List getTheNodes(Exp exp, ExpNodeCollector h) { + getNodes(exp, h); + return h.getNodes(); + } + + public static List getRecordInScopeNodes(Exp exp, boolean bind) { + if (exp.getInScopeNodeList() == null) { + exp.setInScopeNodeList(getInScopeNodes(exp, true, bind)); + } + return exp.getInScopeNodeList(); + } + + public static List getRecordInScopeNodesWithoutBind(Exp exp) { + return getRecordInScopeNodes(exp, false); + } + + public static List getAllInScopeNodes(Exp exp) { + return getTheNodes(exp, new ExpNodeCollector(true, true).all()); + } + + public static List getNodes(Exp exp) { + return getTheNodes(exp, new ExpNodeCollector(false, true).sample()); + } + + public static List getInScopeNodes(Exp exp) { + return getInScopeNodes(exp, true, true); + } + + public static List getInScopeNodes(Exp exp, boolean inSubScope, boolean bind) { + return getTheNodes(exp, new ExpNodeCollector(inSubScope, bind).sample()); + } + + public static void add(List lNode, Node node) { + if (node != null && node.isVariable() && !node.isBlank() && !lNode.contains(node)) { + lNode.add(node); + } + } + + public static void share(Exp exp, List filterVar, List expVar) { + switch (exp.type()) { + case FILTER, OPT_BIND, OPTION -> { + // Do not bind + } + case OPTIONAL, MINUS -> share(exp.first(), filterVar, expVar); + case UNION -> shareUnion(exp, filterVar, expVar); + case QUERY -> shareQuery(exp, filterVar, expVar); + case BIND -> share(exp.getNode(), filterVar, expVar); + case EDGE -> { + for (int i = 0; i < exp.nbNode(); i++) { + share(exp.getNode(i), filterVar, expVar); + } + } + case NODE -> share(exp.getNode(), filterVar, expVar); + default -> { + for (Exp e : exp) { + share(e, filterVar, expVar); + } + } + } + } + + public static void share(Node node, List fVar, List eVar) { + if (node != null && node.isVariable() + && fVar.contains(node.getLabel()) + && !eVar.contains(node.getLabel())) { + eVar.add(node.getLabel()); + } + } + + public static void shareUnion(Exp exp, List filterVar, List expVar) { + List lVar1 = new ArrayList<>(); + List lVar2 = new ArrayList<>(); + share(exp.first(), filterVar, lVar1); + share(exp.rest(), filterVar, lVar2); + for (String varString : lVar1) { + if (lVar2.contains(varString) && !expVar.contains(varString)) { + expVar.add(varString); + } + } + } + + public static void shareQuery(Exp exp, List filterVar, List expVar) { + List lVar = new ArrayList<>(); + share(exp.getQuery().getBody(), filterVar, lVar); + + for (Exp child : exp.getQuery().getSelectFun()) { + String name = child.getNode().getLabel(); + if ((lVar.contains(name) || child.getFilter() != null) && !expVar.contains(name)) { + expVar.add(name); + } + } + } + + public static boolean bound(List fvec, List evec) { + for (String varString : fvec) { + if (!evec.contains(varString)) { + return false; + } + } + return true; + } + + public static void inscopeFilter(Exp exp) { + List l1 = getRecordInScopeNodesWithoutBind(exp.first()); + List l2 = getRecordInScopeNodesWithoutBind(exp.rest()); + + for (Exp child : exp.rest()) { + if (child.isFilter() && !child.getFilter().isRecExist()) { + List lvar = child.getFilter().getVariables(); + if (bind(l1, lvar) && bind(l2, lvar)) { + exp.getCreateInscopeFilter().add(child); + } + } + } + } + + public static boolean bind(List lnode, List lvar) { + for (String variable : lvar) { + boolean suc = false; + for (Node bindNode : lnode) { + if (bindNode.getLabel().equals(variable)) { + suc = true; + break; + } + } + if (!suc) { + return false; + } + } + return true; + } + + public static boolean hasUnboundVariables(Exp exp, Filter f) { + List varList = f.getVariables(); + List nodeList = getNodeVariables(exp); + + for (String variable : varList) { + if (!nodeList.contains(variable)) { + return true; + } + } + + return false; + } + + public static List getNodeVariables(Exp exp) { + List list = new ArrayList<>(); + for (Exp child : exp) { + switch (child.type()) { + case EDGE -> getEdgeVariables(child, list); + case BIND -> getBindVariables(child, list); + case VALUES -> getValuesVariables(child, list); + default -> { + // other types don't declare direct node variables + } + } + } + return list; + } + + public static void getBindVariables(Exp exp, List list) { + if (exp.getNodeList() == null) { + addVariable(list, exp.getNode()); + } else { + getValuesVariables(exp, list); + } + } + + public static void getValuesVariables(Exp exp, List list) { + if (exp.getNodeList() != null) { + for (Node valuesNode : exp.getNodeList()) { + addVariable(list, valuesNode); + } + } + } + + public static void getEdgeVariables(Exp exp, List list) { + addVariable(list, exp.getEdge().getNode(0)); + addVariable(list, exp.getEdge().getNode(1)); + if (!exp.isPath()) { + addVariable(list, exp.getEdge().getProperty()); + } + } + + public static void addVariable(List list, Node node) { + if (node != null && node.isVariable() && !list.contains(node.getLabel())) { + list.add(node.getLabel()); + } + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java index 43c6027d1..606ac2af9 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java @@ -1274,6 +1274,10 @@ int newGlobalNodeIndex() { return getOuterQuery().newNodeIndex(); } + public int allocateNodeIndex() { + return layout.allocateNodeIndex(); + } + int newNodeIndex() { return layout.allocateNodeIndex(); } diff --git a/src/test/java/fr/inria/corese/core/next/data/api/factory/ValueFactoryTest.java b/src/test/java/fr/inria/corese/core/next/data/api/factory/ValueFactoryTest.java index ded7fd90b..65c92a829 100644 --- a/src/test/java/fr/inria/corese/core/next/data/api/factory/ValueFactoryTest.java +++ b/src/test/java/fr/inria/corese/core/next/data/api/factory/ValueFactoryTest.java @@ -235,7 +235,7 @@ void testCreateLiteralBigDecimal() { @Test void testCreateLiteralBigInteger() { - BigInteger bi = new BigInteger("1234567890123456789"); + BigInteger bi = BigInteger.valueOf(1234567890123456789L); Literal literal = this.valueFactory.createLiteral(bi); assertEquals(bi, literal.integerValue()); } diff --git a/src/test/java/fr/inria/corese/core/next/data/api/io/format/FileFormatTest.java b/src/test/java/fr/inria/corese/core/next/data/api/io/format/FileFormatTest.java index 1a2edfce4..e25773078 100644 --- a/src/test/java/fr/inria/corese/core/next/data/api/io/format/FileFormatTest.java +++ b/src/test/java/fr/inria/corese/core/next/data/api/io/format/FileFormatTest.java @@ -52,7 +52,7 @@ private static Stream validFormats() { @ParameterizedTest(name = "Create FileFormat: {0}") @MethodSource("validFormats") @DisplayName("Constructor populates fields correctly") - void constructor_sets_all_fields(String name, + void constructorSetsAllFields(String name, List extensions, List mimeTypes) { @@ -72,37 +72,37 @@ void constructor_sets_all_fields(String name, * ---------------------------------------------------------- */ @Nested - class Constructor_argument_validation { + class ConstructorArgumentValidation { private final List ext = List.of("ttl"); private final List mime = List.of("text/turtle"); @Test - void null_name_throws_NPE() { + void nullNameThrowsNpe() { assertThrows(NullPointerException.class, () -> new FileFormat(null, ext, mime)); } @Test - void null_extensions_throws_NPE() { + void nullExtensionsThrowsNpe() { assertThrows(NullPointerException.class, () -> new FileFormat("Turtle", null, mime)); } @Test - void null_mimeTypes_throws_NPE() { + void nullMimeTypesThrowsNpe() { assertThrows(NullPointerException.class, () -> new FileFormat("Turtle", ext, null)); } @Test - void empty_extensions_throws_IAE() { + void emptyExtensionsThrowsIae() { assertThrows(IllegalArgumentException.class, () -> new FileFormat("Turtle", List.of(), mime)); } @Test - void empty_mimeTypes_throws_IAE() { + void emptyMimeTypesThrowsIae() { assertThrows(IllegalArgumentException.class, () -> new FileFormat("Turtle", ext, List.of())); } @@ -114,7 +114,7 @@ void empty_mimeTypes_throws_IAE() { * ---------------------------------------------------------- */ @Test - void internal_lists_are_immutable_and_defensively_copied() { + void internalListsAreImmutableAndDefensivelyCopied() { // Build mutable lists List ext = new ArrayList<>(List.of("ttl")); List mime = new ArrayList<>(List.of("text/turtle")); @@ -144,12 +144,12 @@ void internal_lists_are_immutable_and_defensively_copied() { * ---------------------------------------------------------- */ @Nested - class Equals_and_hashCode_contract { + class EqualsAndHashCodeContract { private final FileFormat base = new FileFormat("Turtle", List.of("ttl"), List.of("text/turtle")); @Test - void symmetry_and_case_insensitivity() { + void symmetryAndCaseInsensitivity() { FileFormat sameDifferentCase = new FileFormat("tUrTlE", List.of("ttl"), List.of("text/turtle")); assertEquals(base, sameDifferentCase); @@ -170,7 +170,7 @@ void transitivity() { } @Test - void inequality_when_any_field_differs() { + void inequalityWhenAnyFieldDiffers() { FileFormat diffName = new FileFormat("N-Triples", List.of("ttl"), List.of("text/turtle")); FileFormat diffExt = new FileFormat("Turtle", List.of("nt"), List.of("text/turtle")); FileFormat diffMime = new FileFormat("Turtle", List.of("ttl"), List.of("application/n-triples")); @@ -190,7 +190,7 @@ void inequality_when_any_field_differs() { * ---------------------------------------------------------- */ @Test - void toString_contains_all_relevant_information() { + void toStringContainsAllRelevantInformation() { FileFormat format = new FileFormat("Turtle", List.of("ttl"), List.of("text/turtle")); String out = format.toString(); diff --git a/src/test/java/fr/inria/corese/core/next/data/api/literal/CoreDatatypesTest.java b/src/test/java/fr/inria/corese/core/next/data/api/literal/CoreDatatypesTest.java index e59613e00..7fa0e88bb 100644 --- a/src/test/java/fr/inria/corese/core/next/data/api/literal/CoreDatatypesTest.java +++ b/src/test/java/fr/inria/corese/core/next/data/api/literal/CoreDatatypesTest.java @@ -9,7 +9,7 @@ class CoreDatatypesTest { @Test - void testGetDatatypeFromIRI_ValidXsdDatatype() { + void testGetDatatypeFromIriValidXsdDatatype() { // Test valid XSD datatype IRI xsdStringIri = new SimpleIRI("http://www.w3.org/2001/XMLSchema#string"); CoreDatatype datatype = CoreDatatypes.from(xsdStringIri); @@ -17,7 +17,7 @@ void testGetDatatypeFromIRI_ValidXsdDatatype() { } @Test - void testGetDatatypeFromIRI_ValidRdfDatatype() { + void testGetDatatypeFromIriValidRdfDatatype() { // Test valid RDF datatype (langString) IRI langStringIri = new SimpleIRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString"); CoreDatatype datatype = CoreDatatypes.from(langStringIri); @@ -25,7 +25,7 @@ void testGetDatatypeFromIRI_ValidRdfDatatype() { } @Test - void testGetDatatypeFromIRI_InvalidDatatype() { + void testGetDatatypeFromIriInvalidDatatype() { // Test unknown datatype IRI invalidIri = new SimpleIRI("http://example.com/unknownDatatype"); CoreDatatype datatype = CoreDatatypes.from(invalidIri); @@ -34,7 +34,7 @@ void testGetDatatypeFromIRI_InvalidDatatype() { } @Test - void testGetDatatypeFromIRI_NullDatatype() { + void testGetDatatypeFromIriNullDatatype() { // Test passing a null IRI CoreDatatype datatype = CoreDatatypes.from(null); diff --git a/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/DefaultRDFParserFactoryTest.java b/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/DefaultRDFParserFactoryTest.java index 6230bbfde..bfee9a8ef 100644 --- a/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/DefaultRDFParserFactoryTest.java +++ b/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/DefaultRDFParserFactoryTest.java @@ -45,7 +45,7 @@ void setUp() { @Test @DisplayName("createRDFParser (with config) should return JSONLDParser for JSONLD format") - void testCreateRDFParserWithConfig_JSONLD() { + void testCreateRDFParserWithConfigJsonLd() { RDFParser parser = parserFactory.createRDFParser(RDFFormat.JSONLD, mockModel, mockValueFactory, parserOptions); assertNotNull(parser); assertInstanceOf(JSONLDParser.class, parser); @@ -53,7 +53,7 @@ void testCreateRDFParserWithConfig_JSONLD() { @Test @DisplayName("createRDFParser (with config) should return ANTLRTurtleParser for TURTLE format") - void testCreateRDFParserWithConfig_TURTLE() { + void testCreateRDFParserWithConfigTurtle() { RDFParser parser = parserFactory.createRDFParser(RDFFormat.TURTLE, mockModel, mockValueFactory, parserOptions); assertNotNull(parser); assertInstanceOf(TurtleParser.class, parser); @@ -61,7 +61,7 @@ void testCreateRDFParserWithConfig_TURTLE() { @Test @DisplayName("createRDFParser (with config) should return ANTLRNTriplesParser for N-TRIPLES format") - void testCreateRDFParserWithConfig_NTRIPLES() { + void testCreateRDFParserWithConfigNTriples() { RDFParser parser = parserFactory.createRDFParser(RDFFormat.NTRIPLES, mockModel, mockValueFactory, parserOptions); assertNotNull(parser); assertInstanceOf(NTriplesParser.class, parser); @@ -69,7 +69,7 @@ void testCreateRDFParserWithConfig_NTRIPLES() { @Test @DisplayName("createRDFParser (with config) should return ANTLRNQuadsParser for N-QUADS format") - void testCreateRDFParserWithConfig_NQUADS() { + void testCreateRDFParserWithConfigNQuads() { RDFParser parser = parserFactory.createRDFParser(RDFFormat.NQUADS, mockModel, mockValueFactory, parserOptions); assertNotNull(parser); assertInstanceOf(NQuadsParser.class, parser); @@ -78,7 +78,7 @@ void testCreateRDFParserWithConfig_NQUADS() { @Test @DisplayName("createRDFParser (without config) should return JSONLDParser for JSONLD format") - void testCreateRDFParserWithoutConfig_JSONLD() { + void testCreateRDFParserWithoutConfigJsonLd() { RDFParser parser = parserFactory.createRDFParser(RDFFormat.JSONLD, mockModel, mockValueFactory); assertNotNull(parser); assertInstanceOf(JSONLDParser.class, parser); @@ -86,7 +86,7 @@ void testCreateRDFParserWithoutConfig_JSONLD() { @Test @DisplayName("createRDFParser (without config) should return ANTLRTurtleParser for TURTLE format") - void testCreateRDFParserWithoutConfig_TURTLE() { + void testCreateRDFParserWithoutConfigTurtle() { RDFParser parser = parserFactory.createRDFParser(RDFFormat.TURTLE, mockModel, mockValueFactory); assertNotNull(parser); assertInstanceOf(TurtleParser.class, parser); @@ -94,7 +94,7 @@ void testCreateRDFParserWithoutConfig_TURTLE() { @Test @DisplayName("createRDFParser (without config) should return ANTLRNTriplesParser for N-TRIPLES format") - void testCreateRDFParserWithoutConfig_NTRIPLES() { + void testCreateRDFParserWithoutConfigNTriples() { RDFParser parser = parserFactory.createRDFParser(RDFFormat.NTRIPLES, mockModel, mockValueFactory); assertNotNull(parser); assertInstanceOf(NTriplesParser.class, parser); diff --git a/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/rdfa/RDFaParserTest.java b/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/rdfa/RDFaParserTest.java index a056e1012..a4a36ed87 100644 --- a/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/rdfa/RDFaParserTest.java +++ b/src/test/java/fr/inria/corese/core/next/data/impl/io/parser/rdfa/RDFaParserTest.java @@ -32,21 +32,21 @@ class RDFaParserTest extends ParserTestBase { private final DefaultRDFParserFactory parserFactory = new DefaultRDFParserFactory(); private final ValueFactory valueFactory = Values.factory(); - private final String defaultTurtlePrefixes = """ - @prefix bibo: . - @prefix cc: . - @prefix dbp: . - @prefix dbp-owl: . - @prefix dbr: . - @prefix dc: . - @prefix ex: . - @prefix foaf: . - @prefix owl: . - @prefix rdf: . - @prefix rdfa: . - @prefix rdfs: . - @prefix xhv: . - @prefix xsd: . + private static final String DEFAULT_TURTLE_PREFIXES = """ + @prefix bibo: . + @prefix cc: . + @prefix dbp: . + @prefix dbp-owl: . + @prefix dbr: . + @prefix dc: . + @prefix ex: . + @prefix foaf: . + @prefix owl: . + @prefix rdf: . + @prefix rdfa: . + @prefix rdfs: . + @prefix xhv: . + @prefix xsd: . """; @Test @@ -142,7 +142,7 @@ private void assertRdfaLists(String body, String expectedTurtle) { "http://example.org/document"); Model expected = createTestModel(); RDFParser turtle = parserFactory.createRDFParser(RDFFormat.TURTLE, expected, valueFactory); - turtle.parse(new ByteArrayInputStream((defaultTurtlePrefixes + expectedTurtle) + turtle.parse(new ByteArrayInputStream((DEFAULT_TURTLE_PREFIXES + expectedTurtle) .getBytes(java.nio.charset.StandardCharsets.UTF_8)), "http://example.org/document"); assertModelsIsomorphic(expected, actual); } @@ -181,7 +181,7 @@ void parseCurrentSubjectCreatorHead() { """; - String currentSubjectNTriples = defaultTurtlePrefixes + """ + String currentSubjectNTriples = DEFAULT_TURTLE_PREFIXES + """ foaf:primaryTopic . dc:creator "Jo" . """; @@ -226,7 +226,7 @@ void parseCurrentSubjectCreatorMiddle() { """; - String currentSubjectNTriples = defaultTurtlePrefixes + """ + String currentSubjectNTriples = DEFAULT_TURTLE_PREFIXES + """ <> dc:creator "Jo" . """; @@ -670,7 +670,7 @@ void inheritSubjectTest() { - Test 0020 + Test 0020
@@ -721,7 +721,7 @@ void multiplePrefixDeclaration() { - Test 0020 + Test 0020
diff --git a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/DefaultRDFSerializerFactoryTest.java b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/DefaultRDFSerializerFactoryTest.java index 45311e2dd..7145fddf0 100644 --- a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/DefaultRDFSerializerFactoryTest.java +++ b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/DefaultRDFSerializerFactoryTest.java @@ -47,7 +47,7 @@ void setUp() { @Test @DisplayName("createSerializer should return TurtleSerializer for TURTLE format") - void createSerializer_shouldReturnTurtleSerializer_forTurtleFormat() { + void createSerializerShouldreturnturtleserializerForturtleformat() { TurtleSerializerOptions config = TurtleSerializerOptions.defaultConfig(); try (MockedConstruction mockedConstruction = mockConstruction(TurtleSerializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.TURTLE, mockModel, config); @@ -61,7 +61,7 @@ void createSerializer_shouldReturnTurtleSerializer_forTurtleFormat() { @Test @DisplayName("createSerializer should return NTriplesSerializer for NTRIPLES format") - void createSerializer_shouldReturnNTriplesSerializer_forNTriplesFormat() { + void createSerializerShouldreturnntriplesserializerForntriplesformat() { NTriplesSerializerOptions config = NTriplesSerializerOptions.defaultConfig(); try (MockedConstruction mockedConstruction = mockConstruction(NTriplesSerializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.NTRIPLES, mockModel, config); @@ -75,7 +75,7 @@ void createSerializer_shouldReturnNTriplesSerializer_forNTriplesFormat() { @Test @DisplayName("createSerializer should return NQuadsSerializer for NQUADS format") - void createSerializer_shouldReturnNQuadsSerializer_forNQuadsFormat() { + void createSerializerShouldreturnnquadsserializerFornquadsformat() { NQuadsSerializerOptions config = NQuadsSerializerOptions.defaultConfig(); try (MockedConstruction mockedConstruction = mockConstruction(NQuadsSerializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.NQUADS, mockModel, config); @@ -89,7 +89,7 @@ void createSerializer_shouldReturnNQuadsSerializer_forNQuadsFormat() { @Test @DisplayName("createSerializer should return TriGSerializer for TRIG format") - void createSerializer_shouldReturnTriGSerializer_forTriGFormat() { + void createSerializerShouldreturntrigserializerFortrigformat() { TriGSerializerOptions config = TriGSerializerOptions.defaultConfig(); try (MockedConstruction mockedConstruction = mockConstruction(TriGSerializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.TRIG, mockModel, config); @@ -103,7 +103,7 @@ void createSerializer_shouldReturnTriGSerializer_forTriGFormat() { @Test @DisplayName("createSerializer should return XmlSerializer for RDFXML format") - void createSerializer_shouldReturnXmlSerializer_forRdfXmlFormat() { + void createSerializerShouldreturnxmlserializerForrdfxmlformat() { RDFXMLSerializerOptions config = RDFXMLSerializerOptions.defaultConfig(); try (MockedConstruction mockedConstruction = mockConstruction(RDFXMLSerializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.RDFXML, mockModel, config); @@ -116,7 +116,7 @@ void createSerializer_shouldReturnXmlSerializer_forRdfXmlFormat() { @Test @DisplayName("createSerializer should return CanonicalSerializer for CANONICAL_RDF format") - void createSerializer_shouldReturnCanonicalSerializer_forCanonicalRdfFormat() { + void createSerializerShouldreturncanonicalserializerForcanonicalrdfformat() { RDFC10SerializerOptions config = RDFC10SerializerOptions.defaultConfig(); try (MockedConstruction mockedConstruction = mockConstruction(RDFC10Serializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.RDFC_1_0, mockModel, config); @@ -129,7 +129,7 @@ void createSerializer_shouldReturnCanonicalSerializer_forCanonicalRdfFormat() { @Test @DisplayName("createSerializer should return JSONLDSerializer for JSONLD format") - void createSerializer_shouldReturnJSONLDSerializer_forJSONLDFormat() { + void createSerializerShouldreturnjsonldserializerForjsonldformat() { JSONLDOptions config = new JSONLDOptions.Builder().build(); try (MockedConstruction mockedConstruction = mockConstruction(JSONLDSerializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.JSONLD, mockModel, config); @@ -143,7 +143,7 @@ void createSerializer_shouldReturnJSONLDSerializer_forJSONLDFormat() { @Test @DisplayName("createSerializer should throw NullPointerException for a null format") - void createSerializer_shouldThrowNPE_forNullFormat() { + void createSerializerShouldthrownpeFornullformat() { TurtleSerializerOptions config = TurtleSerializerOptions.defaultConfig(); assertThrows(NullPointerException.class, () -> factory.createSerializer(null, mockModel, config), @@ -152,7 +152,7 @@ void createSerializer_shouldThrowNPE_forNullFormat() { @Test @DisplayName("createSerializer should throw NullPointerException for a null model") - void createSerializer_shouldThrowNPE_forNullModel() { + void createSerializerShouldthrownpeFornullmodel() { TurtleSerializerOptions config = TurtleSerializerOptions.defaultConfig(); assertThrows(NullPointerException.class, () -> factory.createSerializer(RDFFormat.TURTLE, null, config), @@ -161,7 +161,7 @@ void createSerializer_shouldThrowNPE_forNullModel() { @Test @DisplayName("createSerializer should throw NullPointerException for a null config") - void createSerializer_shouldThrowNPE_forNullConfig() { + void createSerializerShouldthrownpeFornullconfig() { assertThrows(NullPointerException.class, () -> factory.createSerializer(RDFFormat.TURTLE, mockModel, null), "Should throw NullPointerException for null SerializationConfig"); @@ -169,7 +169,7 @@ void createSerializer_shouldThrowNPE_forNullConfig() { @Test @DisplayName("createSerializer without config should return TurtleSerializer for TURTLE format") - void createSerializerWithoutConfig_shouldReturnTurtleSerializer_forTurtleFormat() { + void createSerializerWithoutConfigShouldreturnturtleserializerForturtleformat() { try (MockedConstruction mockedConstruction = mockConstruction(TurtleSerializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.TURTLE, mockModel); @@ -182,7 +182,7 @@ void createSerializerWithoutConfig_shouldReturnTurtleSerializer_forTurtleFormat( @Test @DisplayName("createSerializer without config should return NTriplesSerializer for NTRIPLES format") - void createSerializerWithoutConfig_shouldReturnNTriplesSerializer_forNTriplesFormat() { + void createSerializerWithoutConfigShouldreturnntriplesserializerForntriplesformat() { try (MockedConstruction mockedConstruction = mockConstruction(NTriplesSerializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.NTRIPLES, mockModel); @@ -195,7 +195,7 @@ void createSerializerWithoutConfig_shouldReturnNTriplesSerializer_forNTriplesFor @Test @DisplayName("createSerializer without config should return NQuadsSerializer for NQUADS format") - void createSerializerWithoutConfig_shouldReturnNQuadsSerializer_forNQuadsFormat() { + void createSerializerWithoutConfigShouldreturnnquadsserializerFornquadsformat() { try (MockedConstruction mockedConstruction = mockConstruction(NQuadsSerializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.NQUADS, mockModel); @@ -208,7 +208,7 @@ void createSerializerWithoutConfig_shouldReturnNQuadsSerializer_forNQuadsFormat( @Test @DisplayName("createSerializer without config should return TriGSerializer for TRIG format") - void createSerializerWithoutConfig_shouldReturnTriGSerializer_forTriGFormat() { + void createSerializerWithoutConfigShouldreturntrigserializerFortrigformat() { try (MockedConstruction mockedConstruction = mockConstruction(TriGSerializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.TRIG, mockModel); @@ -221,7 +221,7 @@ void createSerializerWithoutConfig_shouldReturnTriGSerializer_forTriGFormat() { @Test @DisplayName("createSerializer without config should return XmlSerializer for RDFXML format") - void createSerializerWithoutConfig_shouldReturnXmlSerializer_forRdfXmlFormat() { + void createSerializerWithoutConfigShouldreturnxmlserializerForrdfxmlformat() { try (MockedConstruction mockedConstruction = mockConstruction(RDFXMLSerializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.RDFXML, mockModel); @@ -234,7 +234,7 @@ void createSerializerWithoutConfig_shouldReturnXmlSerializer_forRdfXmlFormat() { @Test @DisplayName("createSerializer without config should return CanonicalSerializer for CANONICAL_RDF format") - void createSerializerWithoutConfig_shouldReturnCanonicalSerializer_forCanonicalRdfFormat() { + void createSerializerWithoutConfigShouldreturncanonicalserializerForcanonicalrdfformat() { try (MockedConstruction mockedConstruction = mockConstruction(RDFC10Serializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.RDFC_1_0, mockModel); @@ -247,7 +247,7 @@ void createSerializerWithoutConfig_shouldReturnCanonicalSerializer_forCanonicalR @Test @DisplayName("createSerializer without config should return JSONLDSerializer for JSONLD format") - void createSerializerWithoutConfig_shouldReturnJSONLDSerializer_forJSONLDFormat() { + void createSerializerWithoutConfigShouldreturnjsonldserializerForjsonldformat() { try (MockedConstruction mockedConstruction = mockConstruction(JSONLDSerializer.class)) { RDFSerializer serializer = factory.createSerializer(RDFFormat.JSONLD, mockModel); @@ -260,7 +260,7 @@ void createSerializerWithoutConfig_shouldReturnJSONLDSerializer_forJSONLDFormat( @Test @DisplayName("createSerializer without config should throw NullPointerException for a null format") - void createSerializerWithoutConfig_shouldThrowNPE_forNullFormat() { + void createSerializerWithoutConfigShouldthrownpeFornullformat() { assertThrows(NullPointerException.class, () -> factory.createSerializer(null, mockModel), "Should throw NullPointerException for null RDFFormat"); @@ -268,7 +268,7 @@ void createSerializerWithoutConfig_shouldThrowNPE_forNullFormat() { @Test @DisplayName("createSerializer without config should throw NullPointerException for a null model") - void createSerializerWithoutConfig_shouldThrowNPE_forNullModel() { + void createSerializerWithoutConfigShouldthrownpeFornullmodel() { assertThrows(NullPointerException.class, () -> factory.createSerializer(RDFFormat.TURTLE, null), "Should throw NullPointerException for null Model"); diff --git a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/nquads/NQuadsSerializerOptionsTest.java b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/nquads/NQuadsSerializerOptionsTest.java index 8a3d78e48..3224c0f2e 100644 --- a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/nquads/NQuadsSerializerOptionsTest.java +++ b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/nquads/NQuadsSerializerOptionsTest.java @@ -15,7 +15,7 @@ class NQuadsSerializerOptionsTest { @Test @DisplayName("defaultConfig() should return a config with expected N-Quads defaults") - void defaultConfig_shouldReturnExpectedDefaults() { + void defaultConfigShouldreturnexpecteddefaults() { NQuadsSerializerOptions config = NQuadsSerializerOptions.defaultConfig(); assertNotNull(config, "Default config should not be null"); @@ -31,7 +31,7 @@ void defaultConfig_shouldReturnExpectedDefaults() { @Test @DisplayName("Builder should allow overriding includeContext") - void builder_shouldAllowOverridingIncludeContext() { + void builderShouldallowoverridingincludecontext() { NQuadsSerializerOptions config = NQuadsSerializerOptions.builder() .includeContext(false) .build(); @@ -41,7 +41,7 @@ void builder_shouldAllowOverridingIncludeContext() { @Test @DisplayName("Builder should allow overriding literalDatatypePolicy") - void builder_shouldAllowOverridingLiteralDatatypePolicy() { + void builderShouldallowoverridingliteraldatatypepolicy() { NQuadsSerializerOptions config = NQuadsSerializerOptions.builder() .literalDatatypePolicy(LiteralDatatypePolicy.MINIMAL) .build(); @@ -51,7 +51,7 @@ void builder_shouldAllowOverridingLiteralDatatypePolicy() { @Test @DisplayName("Builder should allow overriding escapeUnicode") - void builder_shouldAllowOverridingEscapeUnicode() { + void builderShouldallowoverridingescapeunicode() { NQuadsSerializerOptions config = NQuadsSerializerOptions.builder() .escapeUnicode(false) .build(); @@ -61,7 +61,7 @@ void builder_shouldAllowOverridingEscapeUnicode() { @Test @DisplayName("Builder should allow overriding strictMode") - void builder_shouldAllowOverridingStrictMode() { + void builderShouldallowoverridingstrictmode() { NQuadsSerializerOptions config = NQuadsSerializerOptions.builder() .strictMode(false) .build(); @@ -71,7 +71,7 @@ void builder_shouldAllowOverridingStrictMode() { @Test @DisplayName("Builder should allow setting baseIRI") - void builder_shouldAllowSettingBaseIRI() { + void builderShouldallowsettingbaseiri() { String testBaseIRI = "http://example.org/base/"; NQuadsSerializerOptions config = NQuadsSerializerOptions.builder() .baseIRI(testBaseIRI) @@ -82,7 +82,7 @@ void builder_shouldAllowSettingBaseIRI() { @Test @DisplayName("Builder should allow setting lineEnding") - void builder_shouldAllowSettingLineEnding() { + void builderShouldallowsettinglineending() { String customLineEnding = "\r\n"; NQuadsSerializerOptions config = NQuadsSerializerOptions.builder() .lineEnding(customLineEnding) @@ -93,7 +93,7 @@ void builder_shouldAllowSettingLineEnding() { @Test @DisplayName("Builder should allow overriding validateURIs") - void builder_shouldAllowOverridingValidateURIs() { + void builderShouldallowoverridingvalidateuris() { NQuadsSerializerOptions config = NQuadsSerializerOptions.builder() .validateURIs(true) .build(); @@ -103,7 +103,7 @@ void builder_shouldAllowOverridingValidateURIs() { @Test @DisplayName("Builder should allow overriding stableBlankNodeIds") - void builder_shouldAllowOverridingStableBlankNodeIds() { + void builderShouldallowoverridingstableblanknodeids() { NQuadsSerializerOptions config = NQuadsSerializerOptions.builder() .stableBlankNodeIds(true) .build(); @@ -113,7 +113,7 @@ void builder_shouldAllowOverridingStableBlankNodeIds() { @Test @DisplayName("Builder should handle null values for optional fields gracefully (e.g., baseIRI)") - void builder_shouldHandleNullForOptionalFields() { + void builderShouldhandlenullforoptionalfields() { NQuadsSerializerOptions config = NQuadsSerializerOptions.builder() .baseIRI(null) .build(); diff --git a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/ntriples/NTriplesSerializerOptionsTest.java b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/ntriples/NTriplesSerializerOptionsTest.java index 92265b5e8..d0e49af6c 100644 --- a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/ntriples/NTriplesSerializerOptionsTest.java +++ b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/ntriples/NTriplesSerializerOptionsTest.java @@ -15,7 +15,7 @@ class NTriplesSerializerOptionsTest { @Test @DisplayName("defaultConfig() should return a config with expected N-Triples defaults") - void defaultConfig_shouldReturnExpectedDefaults() { + void defaultConfigShouldreturnexpecteddefaults() { NTriplesSerializerOptions config = NTriplesSerializerOptions.defaultConfig(); assertNotNull(config, "Default config should not be null"); @@ -31,7 +31,7 @@ void defaultConfig_shouldReturnExpectedDefaults() { @Test @DisplayName("Builder should allow overriding includeContext") - void builder_shouldAllowOverridingIncludeContext() { + void builderShouldallowoverridingincludecontext() { NTriplesSerializerOptions config = NTriplesSerializerOptions.builder() .includeContext(true) .build(); @@ -41,7 +41,7 @@ void builder_shouldAllowOverridingIncludeContext() { @Test @DisplayName("Builder should allow overriding literalDatatypePolicy") - void builder_shouldAllowOverridingLiteralDatatypePolicy() { + void builderShouldallowoverridingliteraldatatypepolicy() { NTriplesSerializerOptions config = NTriplesSerializerOptions.builder() .literalDatatypePolicy(LiteralDatatypePolicy.MINIMAL) .build(); @@ -51,7 +51,7 @@ void builder_shouldAllowOverridingLiteralDatatypePolicy() { @Test @DisplayName("Builder should allow overriding escapeUnicode") - void builder_shouldAllowOverridingEscapeUnicode() { + void builderShouldallowoverridingescapeunicode() { NTriplesSerializerOptions config = NTriplesSerializerOptions.builder() .escapeUnicode(false) .build(); @@ -61,7 +61,7 @@ void builder_shouldAllowOverridingEscapeUnicode() { @Test @DisplayName("Builder should allow overriding strictMode") - void builder_shouldAllowOverridingStrictMode() { + void builderShouldallowoverridingstrictmode() { NTriplesSerializerOptions config = NTriplesSerializerOptions.builder() .strictMode(false) .build(); @@ -71,7 +71,7 @@ void builder_shouldAllowOverridingStrictMode() { @Test @DisplayName("Builder should allow setting baseIRI") - void builder_shouldAllowSettingBaseIRI() { + void builderShouldallowsettingbaseiri() { String testBaseIRI = "http://example.org/base/"; NTriplesSerializerOptions config = NTriplesSerializerOptions.builder() .baseIRI(testBaseIRI) @@ -82,7 +82,7 @@ void builder_shouldAllowSettingBaseIRI() { @Test @DisplayName("Builder should allow setting lineEnding") - void builder_shouldAllowSettingLineEnding() { + void builderShouldallowsettinglineending() { String customLineEnding = "\r\n"; NTriplesSerializerOptions config = NTriplesSerializerOptions.builder() .lineEnding(customLineEnding) @@ -93,7 +93,7 @@ void builder_shouldAllowSettingLineEnding() { @Test @DisplayName("Builder should allow overriding validateURIs") - void builder_shouldAllowOverridingValidateURIs() { + void builderShouldallowoverridingvalidateuris() { NTriplesSerializerOptions config = NTriplesSerializerOptions.builder() .validateURIs(true) .build(); @@ -103,7 +103,7 @@ void builder_shouldAllowOverridingValidateURIs() { @Test @DisplayName("Builder should allow overriding stableBlankNodeIds") - void builder_shouldAllowOverridingStableBlankNodeIds() { + void builderShouldallowoverridingstableblanknodeids() { NTriplesSerializerOptions config = NTriplesSerializerOptions.builder() .stableBlankNodeIds(true) .build(); @@ -113,7 +113,7 @@ void builder_shouldAllowOverridingStableBlankNodeIds() { @Test @DisplayName("Builder should handle null values for optional fields gracefully (e.g., baseIRI)") - void builder_shouldHandleNullForOptionalFields() { + void builderShouldhandlenullforoptionalfields() { NTriplesSerializerOptions config = NTriplesSerializerOptions.builder() .baseIRI(null) .build(); diff --git a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/rdfc10/RDFC10SerializerOptionsTest.java b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/rdfc10/RDFC10SerializerOptionsTest.java index 527a7f602..c4b0cf7a3 100644 --- a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/rdfc10/RDFC10SerializerOptionsTest.java +++ b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/rdfc10/RDFC10SerializerOptionsTest.java @@ -14,7 +14,7 @@ class RDFC10SerializerOptionsTest { @Test @DisplayName("defaultConfig should return an instance with expected default values") - void defaultConfig_shouldReturnExpectedValues() { + void defaultConfigShouldreturnexpectedvalues() { RDFC10SerializerOptions config = RDFC10SerializerOptions.defaultConfig(); assertNotNull(config, "Default config should not be null"); @@ -27,7 +27,7 @@ void defaultConfig_shouldReturnExpectedValues() { @Test @DisplayName("builder should allow setting custom options") - void builder_shouldAllowCustomOptions() { + void builderShouldallowcustomoptions() { RDFC10SerializerOptions customConfig = RDFC10SerializerOptions.builder() .strictMode(false) .validateURIs(false) @@ -46,7 +46,7 @@ void builder_shouldAllowCustomOptions() { @Test @DisplayName("builder should use default values for un-set options") - void builder_shouldUseDefaultValues_forUnsetOptions() { + void builderShouldusedefaultvaluesForunsetoptions() { RDFC10SerializerOptions config = RDFC10SerializerOptions.builder() .strictMode(false) .build(); diff --git a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/rdfxml/RDFXMLSerializerOptionsTest.java b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/rdfxml/RDFXMLSerializerOptionsTest.java index 9b339307a..530df910e 100644 --- a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/rdfxml/RDFXMLSerializerOptionsTest.java +++ b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/rdfxml/RDFXMLSerializerOptionsTest.java @@ -25,7 +25,7 @@ class RDFXMLSerializerOptionsTest { @Test @DisplayName("defaultConfig() should return a config with expected RDF/XML defaults") - void defaultConfig_shouldReturnExpectedDefaults() { + void defaultConfigShouldreturnexpecteddefaults() { RDFXMLSerializerOptions config = RDFXMLSerializerOptions.defaultConfig(); assertNotNull(config, "Default config should not be null"); @@ -63,7 +63,7 @@ void defaultConfig_shouldReturnExpectedDefaults() { @Test @DisplayName("Builder should allow overriding usePrefixes") - void builder_shouldAllowOverridingUsePrefixes() { + void builderShouldallowoverridinguseprefixes() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .usePrefixes(false) .build(); @@ -72,7 +72,7 @@ void builder_shouldAllowOverridingUsePrefixes() { @Test @DisplayName("Builder should allow overriding autoDeclarePrefixes") - void builder_shouldAllowOverridingAutoDeclarePrefixes() { + void builderShouldallowoverridingautodeclareprefixes() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .autoDeclarePrefixes(false) .build(); @@ -81,7 +81,7 @@ void builder_shouldAllowOverridingAutoDeclarePrefixes() { @Test @DisplayName("Builder should allow overriding prefixOrdering") - void builder_shouldAllowOverridingPrefixOrdering() { + void builderShouldallowoverridingprefixordering() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .prefixOrdering(PrefixOrdering.USAGE_ORDER) .build(); @@ -90,7 +90,7 @@ void builder_shouldAllowOverridingPrefixOrdering() { @Test @DisplayName("Builder should allow adding custom prefixes via PrefixHandler") - void builder_shouldAllowAddingCustomPrefixes() { + void builderShouldallowaddingcustomprefixes() { String customPrefix = "my"; String customNamespace = "http://my.example.org/"; @@ -109,7 +109,7 @@ void builder_shouldAllowAddingCustomPrefixes() { @Test @DisplayName("Builder should allow setting custom PrefixHandler") - void builder_shouldAllowSettingCustomPrefixHandler() { + void builderShouldallowsettingcustomprefixhandler() { PrefixHandler customHandler = new PrefixHandler(false); // sans vocabulaires standards customHandler.setPrefix("ex", "http://example.org/"); customHandler.setPrefix("custom", "http://custom.org/"); @@ -127,7 +127,7 @@ void builder_shouldAllowSettingCustomPrefixHandler() { @Test @DisplayName("Builder should allow adding multiple prefixes at once") - void builder_shouldAllowAddingMultiplePrefixes() { + void builderShouldallowaddingmultipleprefixes() { Map customPrefixes = new HashMap<>(); customPrefixes.put("ex", "http://example.org/"); customPrefixes.put("custom", "http://custom.org/"); @@ -145,7 +145,7 @@ void builder_shouldAllowAddingMultiplePrefixes() { @Test @DisplayName("Builder should allow overriding prettyPrint") - void builder_shouldAllowOverridingPrettyPrint() { + void builderShouldallowoverridingprettyprint() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .prettyPrint(false) .build(); @@ -154,7 +154,7 @@ void builder_shouldAllowOverridingPrettyPrint() { @Test @DisplayName("Builder should allow overriding indent") - void builder_shouldAllowOverridingIndent() { + void builderShouldallowoverridingindent() { String customIndent = "\t"; RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .indent(customIndent) @@ -164,7 +164,7 @@ void builder_shouldAllowOverridingIndent() { @Test @DisplayName("Builder should allow overriding maxLineLength") - void builder_shouldAllowOverridingMaxLineLength() { + void builderShouldallowoverridingmaxlinelength() { int customLength = 120; RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .maxLineLength(customLength) @@ -174,7 +174,7 @@ void builder_shouldAllowOverridingMaxLineLength() { @Test @DisplayName("Builder should allow overriding sortSubjects") - void builder_shouldAllowOverridingSortSubjects() { + void builderShouldallowoverridingsortsubjects() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .sortSubjects(true) .build(); @@ -183,7 +183,7 @@ void builder_shouldAllowOverridingSortSubjects() { @Test @DisplayName("Builder should allow overriding sortPredicates") - void builder_shouldAllowOverridingSortPredicates() { + void builderShouldallowoverridingsortpredicates() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .sortPredicates(true) .build(); @@ -192,7 +192,7 @@ void builder_shouldAllowOverridingSortPredicates() { @Test @DisplayName("Builder should allow overriding useMultilineLiterals") - void builder_shouldAllowOverridingUseMultilineLiterals() { + void builderShouldallowoverridingusemultilineliterals() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .useMultilineLiterals(false) .build(); @@ -201,7 +201,7 @@ void builder_shouldAllowOverridingUseMultilineLiterals() { @Test @DisplayName("Builder should allow overriding strictMode") - void builder_shouldAllowOverridingStrictMode() { + void builderShouldallowoverridingstrictmode() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .strictMode(false) .build(); @@ -210,7 +210,7 @@ void builder_shouldAllowOverridingStrictMode() { @Test @DisplayName("Builder should allow overriding escapeUnicode") - void builder_shouldAllowOverridingEscapeUnicode() { + void builderShouldallowoverridingescapeunicode() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .escapeUnicode(true) .build(); @@ -219,7 +219,7 @@ void builder_shouldAllowOverridingEscapeUnicode() { @Test @DisplayName("Builder should allow overriding literalDatatypePolicy") - void builder_shouldAllowOverridingLiteralDatatypePolicy() { + void builderShouldallowoverridingliteraldatatypepolicy() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .literalDatatypePolicy(LiteralDatatypePolicy.MINIMAL) .build(); @@ -228,7 +228,7 @@ void builder_shouldAllowOverridingLiteralDatatypePolicy() { @Test @DisplayName("Builder should allow setting baseIRI") - void builder_shouldAllowSettingBaseIRI() { + void builderShouldallowsettingbaseiri() { String testBaseIRI = "http://example.org/base/"; RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .baseIRI(testBaseIRI) @@ -238,7 +238,7 @@ void builder_shouldAllowSettingBaseIRI() { @Test @DisplayName("Builder should allow overriding lineEnding") - void builder_shouldAllowOverridingLineEnding() { + void builderShouldallowoverridinglineending() { String customLineEnding = "\r\n"; RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .lineEnding(customLineEnding) @@ -248,7 +248,7 @@ void builder_shouldAllowOverridingLineEnding() { @Test @DisplayName("Builder should allow overriding validateURIs") - void builder_shouldAllowOverridingValidateURIs() { + void builderShouldallowoverridingvalidateuris() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .validateURIs(true) .build(); @@ -257,7 +257,7 @@ void builder_shouldAllowOverridingValidateURIs() { @Test @DisplayName("Builder should allow overriding stableBlankNodeIds") - void builder_shouldAllowOverridingStableBlankNodeIds() { + void builderShouldallowoverridingstableblanknodeids() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .stableBlankNodeIds(false) .build(); @@ -266,7 +266,7 @@ void builder_shouldAllowOverridingStableBlankNodeIds() { @Test @DisplayName("Builder should allow overriding includeContext") - void builder_shouldAllowOverridingIncludeContext() { + void builderShouldallowoverridingincludecontext() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .includeContext(true) .build(); @@ -275,7 +275,7 @@ void builder_shouldAllowOverridingIncludeContext() { @Test @DisplayName("Builder should maintain default prefixes when adding custom ones") - void builder_shouldMaintainDefaultPrefixesWhenAddingCustomOnes() { + void builderShouldmaintaindefaultprefixeswhenaddingcustomones() { RDFXMLSerializerOptions config = new RDFXMLSerializerOptions.Builder() .addPrefix("ex", "http://example.org/") .build(); diff --git a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/trig/TriGSerializerOptionsTest.java b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/trig/TriGSerializerOptionsTest.java index 5093d21b5..96eeced0c 100644 --- a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/trig/TriGSerializerOptionsTest.java +++ b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/trig/TriGSerializerOptionsTest.java @@ -24,7 +24,7 @@ class TriGSerializerOptionsTest { @Test @DisplayName("defaultConfig() should return a config with expected TriG defaults") @SuppressWarnings("java:S5961") - void defaultConfig_shouldReturnExpectedDefaults() { + void defaultConfigShouldreturnexpecteddefaults() { TriGSerializerOptions config = TriGSerializerOptions.defaultConfig(); assertNotNull(config, "Default config should not be null"); @@ -67,7 +67,7 @@ void defaultConfig_shouldReturnExpectedDefaults() { @Test @DisplayName("Builder should allow overriding includeContext") - void builder_shouldAllowOverridingIncludeContext() { + void builderShouldallowoverridingincludecontext() { TriGSerializerOptions config = TriGSerializerOptions.builder() .includeContext(false) .build(); @@ -76,7 +76,7 @@ void builder_shouldAllowOverridingIncludeContext() { @Test @DisplayName("Builder should allow overriding blankNodeStyle") - void builder_shouldAllowOverridingBlankNodeStyle() { + void builderShouldallowoverridingblanknodestyle() { TriGSerializerOptions config = TriGSerializerOptions.builder() .blankNodeStyle(BlankNodeStyle.ANONYMOUS) .build(); @@ -85,7 +85,7 @@ void builder_shouldAllowOverridingBlankNodeStyle() { @Test @DisplayName("Builder should allow overriding useCollections") - void builder_shouldAllowOverridingUseCollections() { + void builderShouldallowoverridingusecollections() { TriGSerializerOptions config = TriGSerializerOptions.builder() .useCollections(true) .build(); @@ -95,7 +95,7 @@ void builder_shouldAllowOverridingUseCollections() { @Test @DisplayName("Builder should allow overriding usePrefixes") - void builder_shouldAllowOverridingUsePrefixes() { + void builderShouldallowoverridinguseprefixes() { TriGSerializerOptions config = TriGSerializerOptions.builder() .usePrefixes(false) .build(); @@ -104,7 +104,7 @@ void builder_shouldAllowOverridingUsePrefixes() { @Test @DisplayName("Builder should allow overriding autoDeclarePrefixes") - void builder_shouldAllowOverridingAutoDeclarePrefixes() { + void builderShouldallowoverridingautodeclareprefixes() { TriGSerializerOptions config = TriGSerializerOptions.builder() .autoDeclarePrefixes(false) .build(); @@ -113,7 +113,7 @@ void builder_shouldAllowOverridingAutoDeclarePrefixes() { @Test @DisplayName("Builder should allow overriding prefixOrdering") - void builder_shouldAllowOverridingPrefixOrdering() { + void builderShouldallowoverridingprefixordering() { TriGSerializerOptions config = TriGSerializerOptions.builder() .prefixOrdering(PrefixOrdering.USAGE_ORDER) .build(); @@ -122,7 +122,7 @@ void builder_shouldAllowOverridingPrefixOrdering() { @Test @DisplayName("Builder should allow overriding useCompactTriples") - void builder_shouldAllowOverridingUseCompactTriples() { + void builderShouldallowoverridingusecompacttriples() { TriGSerializerOptions config = TriGSerializerOptions.builder() .useCompactTriples(false) .build(); @@ -131,7 +131,7 @@ void builder_shouldAllowOverridingUseCompactTriples() { @Test @DisplayName("Builder should allow overriding useRdfTypeShortcut") - void builder_shouldAllowOverridingUseRdfTypeShortcut() { + void builderShouldallowoverridinguserdftypeshortcut() { TriGSerializerOptions config = TriGSerializerOptions.builder() .useRdfTypeShortcut(false) .build(); @@ -140,7 +140,7 @@ void builder_shouldAllowOverridingUseRdfTypeShortcut() { @Test @DisplayName("Builder should allow overriding useMultilineLiterals") - void builder_shouldAllowOverridingUseMultilineLiterals() { + void builderShouldallowoverridingusemultilineliterals() { TriGSerializerOptions config = TriGSerializerOptions.builder() .useMultilineLiterals(false) .build(); @@ -149,7 +149,7 @@ void builder_shouldAllowOverridingUseMultilineLiterals() { @Test @DisplayName("Builder should allow overriding prettyPrint") - void builder_shouldAllowOverridingPrettyPrint() { + void builderShouldallowoverridingprettyprint() { TriGSerializerOptions config = TriGSerializerOptions.builder() .prettyPrint(false) .build(); @@ -158,7 +158,7 @@ void builder_shouldAllowOverridingPrettyPrint() { @Test @DisplayName("Builder should allow overriding indent") - void builder_shouldAllowOverridingIndent() { + void builderShouldallowoverridingindent() { String customIndent = "\t"; TriGSerializerOptions config = TriGSerializerOptions.builder() .indent(customIndent) @@ -168,7 +168,7 @@ void builder_shouldAllowOverridingIndent() { @Test @DisplayName("Builder should allow overriding maxLineLength") - void builder_shouldAllowOverridingMaxLineLength() { + void builderShouldallowoverridingmaxlinelength() { int customLength = 120; TriGSerializerOptions config = TriGSerializerOptions.builder() .maxLineLength(customLength) @@ -178,7 +178,7 @@ void builder_shouldAllowOverridingMaxLineLength() { @Test @DisplayName("Builder should allow overriding groupBySubject") - void builder_shouldAllowOverridingGroupBySubject() { + void builderShouldallowoverridinggroupbysubject() { TriGSerializerOptions config = TriGSerializerOptions.builder() .groupBySubject(false) .build(); @@ -187,7 +187,7 @@ void builder_shouldAllowOverridingGroupBySubject() { @Test @DisplayName("Builder should allow overriding sortSubjects") - void builder_shouldAllowOverridingSortSubjects() { + void builderShouldallowoverridingsortsubjects() { TriGSerializerOptions config = TriGSerializerOptions.builder() .sortSubjects(true) .build(); @@ -196,7 +196,7 @@ void builder_shouldAllowOverridingSortSubjects() { @Test @DisplayName("Builder should allow overriding sortPredicates") - void builder_shouldAllowOverridingSortPredicates() { + void builderShouldallowoverridingsortpredicates() { TriGSerializerOptions config = TriGSerializerOptions.builder() .sortPredicates(true) .build(); @@ -205,7 +205,7 @@ void builder_shouldAllowOverridingSortPredicates() { @Test @DisplayName("Builder should allow overriding strictMode") - void builder_shouldAllowOverridingStrictMode() { + void builderShouldallowoverridingstrictmode() { TriGSerializerOptions config = TriGSerializerOptions.builder() .strictMode(false) .build(); @@ -215,7 +215,7 @@ void builder_shouldAllowOverridingStrictMode() { @Test @DisplayName("Builder should allow overriding literalDatatypePolicy") - void builder_shouldAllowOverridingLiteralDatatypePolicy() { + void builderShouldallowoverridingliteraldatatypepolicy() { TriGSerializerOptions config = TriGSerializerOptions.builder() .literalDatatypePolicy(LiteralDatatypePolicy.ALWAYS_TYPED) .build(); @@ -224,7 +224,7 @@ void builder_shouldAllowOverridingLiteralDatatypePolicy() { @Test @DisplayName("Builder should allow setting baseIRI") - void builder_shouldAllowSettingBaseIRI() { + void builderShouldallowsettingbaseiri() { String testBaseIRI = "http://example.org/base/"; TriGSerializerOptions config = TriGSerializerOptions.builder() .baseIRI(testBaseIRI) @@ -234,7 +234,7 @@ void builder_shouldAllowSettingBaseIRI() { @Test @DisplayName("Builder should allow overriding lineEnding") - void builder_shouldAllowOverridingLineEnding() { + void builderShouldallowoverridinglineending() { String customLineEnding = "\r\n"; TriGSerializerOptions config = TriGSerializerOptions.builder() .lineEnding(customLineEnding) @@ -244,7 +244,7 @@ void builder_shouldAllowOverridingLineEnding() { @Test @DisplayName("Builder should allow overriding validateURIs") - void builder_shouldAllowOverridingValidateURIs() { + void builderShouldallowoverridingvalidateuris() { TriGSerializerOptions config = TriGSerializerOptions.builder() .validateURIs(true) .build(); @@ -253,7 +253,7 @@ void builder_shouldAllowOverridingValidateURIs() { @Test @DisplayName("Builder should allow overriding stableBlankNodeIds") - void builder_shouldAllowOverridingStableBlankNodeIds() { + void builderShouldallowoverridingstableblanknodeids() { TriGSerializerOptions config = TriGSerializerOptions.builder() .stableBlankNodeIds(true) .build(); diff --git a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/turtle/TurtleSerializerOptionsTest.java b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/turtle/TurtleSerializerOptionsTest.java index 6c863192c..05964e1db 100644 --- a/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/turtle/TurtleSerializerOptionsTest.java +++ b/src/test/java/fr/inria/corese/core/next/data/impl/io/serializer/turtle/TurtleSerializerOptionsTest.java @@ -24,7 +24,7 @@ class TurtleSerializerOptionsTest { @Test @DisplayName("defaultConfig() should return a config with expected Turtle defaults") @SuppressWarnings("java:S5961") - void defaultConfig_shouldReturnExpectedDefaults() { + void defaultConfigShouldreturnexpecteddefaults() { TurtleSerializerOptions config = TurtleSerializerOptions.defaultConfig(); assertNotNull(config, "Default config should not be null"); @@ -68,7 +68,7 @@ void defaultConfig_shouldReturnExpectedDefaults() { @Test @DisplayName("Builder should allow overriding useCollections") - void builder_shouldAllowOverridingUseCollections() { + void builderShouldallowoverridingusecollections() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .useCollections(false) .build(); @@ -77,7 +77,7 @@ void builder_shouldAllowOverridingUseCollections() { @Test @DisplayName("Builder should allow overriding blankNodeStyle") - void builder_shouldAllowOverridingBlankNodeStyle() { + void builderShouldallowoverridingblanknodestyle() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .blankNodeStyle(BlankNodeStyle.NAMED) .build(); @@ -88,7 +88,7 @@ void builder_shouldAllowOverridingBlankNodeStyle() { @Test @DisplayName("Builder should allow overriding usePrefixes") - void builder_shouldAllowOverridingUsePrefixes() { + void builderShouldallowoverridinguseprefixes() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .usePrefixes(false) .build(); @@ -97,7 +97,7 @@ void builder_shouldAllowOverridingUsePrefixes() { @Test @DisplayName("Builder should allow overriding autoDeclarePrefixes") - void builder_shouldAllowOverridingAutoDeclarePrefixes() { + void builderShouldallowoverridingautodeclareprefixes() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .autoDeclarePrefixes(false) .build(); @@ -106,7 +106,7 @@ void builder_shouldAllowOverridingAutoDeclarePrefixes() { @Test @DisplayName("Builder should allow overriding prefixOrdering") - void builder_shouldAllowOverridingPrefixOrdering() { + void builderShouldallowoverridingprefixordering() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .prefixOrdering(PrefixOrdering.USAGE_ORDER) .build(); @@ -115,7 +115,7 @@ void builder_shouldAllowOverridingPrefixOrdering() { @Test @DisplayName("Builder should allow overriding useCompactTriples") - void builder_shouldAllowOverridingUseCompactTriples() { + void builderShouldallowoverridingusecompacttriples() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .useCompactTriples(false) .build(); @@ -124,7 +124,7 @@ void builder_shouldAllowOverridingUseCompactTriples() { @Test @DisplayName("Builder should allow overriding useRdfTypeShortcut") - void builder_shouldAllowOverridingUseRdfTypeShortcut() { + void builderShouldallowoverridinguserdftypeshortcut() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .useRdfTypeShortcut(false) .build(); @@ -133,7 +133,7 @@ void builder_shouldAllowOverridingUseRdfTypeShortcut() { @Test @DisplayName("Builder should allow overriding useMultilineLiterals") - void builder_shouldAllowOverridingUseMultilineLiterals() { + void builderShouldallowoverridingusemultilineliterals() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .useMultilineLiterals(false) .build(); @@ -142,7 +142,7 @@ void builder_shouldAllowOverridingUseMultilineLiterals() { @Test @DisplayName("Builder should allow overriding prettyPrint") - void builder_shouldAllowOverridingPrettyPrint() { + void builderShouldallowoverridingprettyprint() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .prettyPrint(false) .build(); @@ -151,7 +151,7 @@ void builder_shouldAllowOverridingPrettyPrint() { @Test @DisplayName("Builder should allow overriding indent") - void builder_shouldAllowOverridingIndent() { + void builderShouldallowoverridingindent() { String customIndent = "\t"; TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .indent(customIndent) @@ -161,7 +161,7 @@ void builder_shouldAllowOverridingIndent() { @Test @DisplayName("Builder should allow overriding maxLineLength") - void builder_shouldAllowOverridingMaxLineLength() { + void builderShouldallowoverridingmaxlinelength() { int customLength = 120; TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .maxLineLength(customLength) @@ -171,7 +171,7 @@ void builder_shouldAllowOverridingMaxLineLength() { @Test @DisplayName("Builder should allow overriding groupBySubject") - void builder_shouldAllowOverridingGroupBySubject() { + void builderShouldallowoverridinggroupbysubject() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .groupBySubject(false) .build(); @@ -180,7 +180,7 @@ void builder_shouldAllowOverridingGroupBySubject() { @Test @DisplayName("Builder should allow overriding sortSubjects") - void builder_shouldAllowOverridingSortSubjects() { + void builderShouldallowoverridingsortsubjects() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .sortSubjects(true) .build(); @@ -189,7 +189,7 @@ void builder_shouldAllowOverridingSortSubjects() { @Test @DisplayName("Builder should allow overriding sortPredicates") - void builder_shouldAllowOverridingSortPredicates() { + void builderShouldallowoverridingsortpredicates() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .sortPredicates(true) .build(); @@ -198,7 +198,7 @@ void builder_shouldAllowOverridingSortPredicates() { @Test @DisplayName("Builder should allow overriding strictMode") - void builder_shouldAllowOverridingStrictMode() { + void builderShouldallowoverridingstrictmode() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .strictMode(false) .build(); @@ -208,7 +208,7 @@ void builder_shouldAllowOverridingStrictMode() { @Test @DisplayName("Builder should allow overriding literalDatatypePolicy") - void builder_shouldAllowOverridingLiteralDatatypePolicy() { + void builderShouldallowoverridingliteraldatatypepolicy() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .literalDatatypePolicy(LiteralDatatypePolicy.ALWAYS_TYPED) .build(); @@ -217,7 +217,7 @@ void builder_shouldAllowOverridingLiteralDatatypePolicy() { @Test @DisplayName("Builder should allow setting baseIRI") - void builder_shouldAllowSettingBaseIRI() { + void builderShouldallowsettingbaseiri() { String testBaseIRI = "http://example.org/base/"; TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .baseIRI(testBaseIRI) @@ -227,7 +227,7 @@ void builder_shouldAllowSettingBaseIRI() { @Test @DisplayName("Builder should allow overriding lineEnding") - void builder_shouldAllowOverridingLineEnding() { + void builderShouldallowoverridinglineending() { String customLineEnding = "\r\n"; TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .lineEnding(customLineEnding) @@ -237,7 +237,7 @@ void builder_shouldAllowOverridingLineEnding() { @Test @DisplayName("Builder should allow overriding validateURIs") - void builder_shouldAllowOverridingValidateURIs() { + void builderShouldallowoverridingvalidateuris() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .validateURIs(true) .build(); @@ -246,7 +246,7 @@ void builder_shouldAllowOverridingValidateURIs() { @Test @DisplayName("Builder should allow overriding stableBlankNodeIds") - void builder_shouldAllowOverridingStableBlankNodeIds() { + void builderShouldallowoverridingstableblanknodeids() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .stableBlankNodeIds(true) .build(); @@ -255,7 +255,7 @@ void builder_shouldAllowOverridingStableBlankNodeIds() { @Test @DisplayName("Builder should allow overriding includeContext") - void builder_shouldAllowOverridingIncludeContext() { + void builderShouldallowoverridingincludecontext() { TurtleSerializerOptions config = new TurtleSerializerOptions.Builder() .includeContext(true) .build(); diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalBindTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalBindTest.java new file mode 100644 index 000000000..cbcc58f85 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalBindTest.java @@ -0,0 +1,78 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.data.Values; +import fr.inria.corese.core.next.query.impl.engine.model.Filter; +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; +import fr.inria.corese.core.next.query.impl.engine.spi.Evaluator; +import fr.inria.corese.core.next.query.impl.engine.spi.Matcher; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayName("EvalBind Tests") +class EvalBindTest { + + private Producer mockProducer; + private EvalBind evaluator; + + @BeforeEach + void setUp() { + mockProducer = mock(Producer.class); + Evaluator mockEvaluator = mock(Evaluator.class); + Matcher mockMatcher = mock(Matcher.class); + Eval engine = new Eval(mockProducer, mockEvaluator, mockMatcher); + QueryPlan plan = QueryPlan.create(); + engine.init(plan); + evaluator = new EvalBind(engine); + } + + @Test + @DisplayName("Should convert list of nodes to map indexed by label") + void shouldConvertNodesToMap() { + Node n1 = NodeImpl.forVariable("?a"); + Node n2 = NodeImpl.forVariable("?b"); + Map map = evaluator.toMap(List.of(n1, n2)); + + assertEquals(2, map.size()); + assertEquals(n1, map.get("?a")); + assertEquals(n2, map.get("?b")); + } + + @Test + @DisplayName("Should evaluate standard bind when expression is non-null") + void shouldEvaluateStandardBind() { + QueryPlan plan = QueryPlan.create(); + Node varNode = NodeImpl.forVariable("?target"); + varNode.setIndex(plan.allocateNodeIndex()); + Eval eval = new Eval(mockProducer, mock(Evaluator.class), mock(Matcher.class)); + eval.init(plan); + EvalBind customEvaluator = new EvalBind(eval); + + Node valNode = NodeImpl.forLiteral("boundValue", null, null); + when(mockProducer.getNode(any())).thenReturn(valNode); + + Filter mockFilter = mock(Filter.class); + fr.inria.corese.core.next.query.impl.engine.model.Expr mockExpr = mock(fr.inria.corese.core.next.query.impl.engine.model.Expr.class); + when(mockFilter.getExp()).thenReturn(mockExpr); + + Exp bindExp = Exp.create(Exp.Type.BIND, varNode); + bindExp.setFilter(mockFilter); + + Stack stack = new Stack(); + int result = customEvaluator.eval(mockProducer, null, bindExp, null, stack, 1); + assertEquals(1, result); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalFilterTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalFilterTest.java new file mode 100644 index 000000000..6f0af7df9 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalFilterTest.java @@ -0,0 +1,64 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.data.Values; +import fr.inria.corese.core.next.data.api.model.DatatypeValue; +import fr.inria.corese.core.next.query.api.exception.QueryTypeErrorException; +import fr.inria.corese.core.next.query.impl.engine.model.Expr; +import fr.inria.corese.core.next.query.impl.engine.model.Filter; +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; +import fr.inria.corese.core.next.query.impl.engine.solution.Memory; +import fr.inria.corese.core.next.query.impl.engine.spi.Evaluator; +import fr.inria.corese.core.next.query.impl.engine.spi.Matcher; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayName("EvalFilter Tests") +class EvalFilterTest { + + private Eval engine; + private Producer mockProducer; + private EvalFilter evaluator; + + @BeforeEach + void setUp() { + mockProducer = mock(Producer.class); + Evaluator mockEvaluator = mock(Evaluator.class); + Matcher mockMatcher = mock(Matcher.class); + engine = new Eval(mockProducer, mockEvaluator, mockMatcher); + QueryPlan plan = QueryPlan.create(); + engine.init(plan); + evaluator = new EvalFilter(engine); + } + + @Test + @DisplayName("Should evaluate boolean truth cleanly") + void shouldEvaluateBooleanTruth() { + assertFalse(evaluator.isTrue(null)); + assertTrue(evaluator.isTrue(Values.literal(true))); + assertFalse(evaluator.isTrue(Values.literal(false))); + } + + @Test + @DisplayName("Should isolate QueryTypeErrorException as false during filter testing") + void shouldIsolateTypeErrorAsFalse() { + Filter mockFilter = mock(Filter.class); + Expr mockExpr = mock(Expr.class); + when(mockFilter.getExp()).thenReturn(mockExpr); + when(mockExpr.evalWE(any(), any(), any(), any())).thenThrow(new QueryTypeErrorException("type error")); + + Memory env = engine.getMemory(); + boolean result = evaluator.test(null, mockFilter, env, mockProducer); + assertFalse(result); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalMinusTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalMinusTest.java new file mode 100644 index 000000000..c511e7bba --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalMinusTest.java @@ -0,0 +1,62 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; +import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; +import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; +import fr.inria.corese.core.next.query.impl.engine.solution.Memory; +import fr.inria.corese.core.next.query.impl.engine.spi.Evaluator; +import fr.inria.corese.core.next.query.impl.engine.spi.Matcher; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static fr.inria.corese.core.next.query.impl.engine.eval.Eval.STOP; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayName("EvalMinus Tests") +class EvalMinusTest { + + private Producer mockProducer; + private EvalMinus evaluator; + private EvaluationState state; + + @BeforeEach + void setUp() { + mockProducer = mock(Producer.class); + when(mockProducer.getMappings(any(), any(), any(), any())).thenReturn(new Mappings()); + Evaluator mockEvaluator = mock(Evaluator.class); + Matcher mockMatcher = mock(Matcher.class); + Eval mockEngine = new Eval(mockProducer, mockEvaluator, mockMatcher); + QueryPlan plan = QueryPlan.create(); + mockEngine.init(plan); + evaluator = new EvalMinus(mockEngine); + state = mockEngine.getState(); + } + + @Test + @DisplayName("Should return backtrack when left branch produces empty solutions") + void shouldBacktrackWhenLeftBranchIsEmpty() { + Exp minusExp = Exp.create(Exp.Type.MINUS, Exp.create(Exp.Type.BGP), Exp.create(Exp.Type.BGP)); + Stack stack = new Stack(); + int result = evaluator.eval(mockProducer, null, minusExp, null, stack, 2); + assertEquals(1, result); + } + + @Test + @DisplayName("Should return STOP immediately when engine is stopped during evaluation") + void shouldReturnStopWhenEngineIsStopped() { + state.stop(); + Exp minusExp = Exp.create(Exp.Type.MINUS, Exp.create(Exp.Type.BGP), Exp.create(Exp.Type.BGP)); + Stack stack = new Stack(); + int result = evaluator.eval(mockProducer, null, minusExp, null, stack, 2); + assertEquals(STOP, result); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalServiceTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalServiceTest.java new file mode 100644 index 000000000..f88abb0b8 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalServiceTest.java @@ -0,0 +1,73 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.query.api.exception.QueryEvaluationException; +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; +import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; +import fr.inria.corese.core.next.query.impl.engine.spi.Evaluator; +import fr.inria.corese.core.next.query.impl.engine.spi.Matcher; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; + +@DisplayName("EvalService Tests") +class EvalServiceTest { + + private EvalService evaluator; + + @BeforeEach + void setUp() { + Producer mockProducer = mock(Producer.class); + Evaluator mockEvaluator = mock(Evaluator.class); + Matcher mockMatcher = mock(Matcher.class); + Eval engine = new Eval(mockProducer, mockEvaluator, mockMatcher); + QueryPlan plan = QueryPlan.create(); + engine.init(plan); + evaluator = new EvalService(engine); + } + + @Test + @DisplayName("Should throw QueryEvaluationException when SERVICE is executed without Provider") + void shouldThrowWhenNoProvider() { + Node servNode = NodeImpl.forIRI("http://example.org/sparql"); + Exp servExp = Exp.create(Exp.Type.SERVICE, Exp.create(Exp.Type.NODE, servNode), Exp.create(Exp.Type.EMPTY)); + Stack stack = new Stack(); + Producer producer = mock(Producer.class); + + assertThrows(QueryEvaluationException.class, () -> + evaluator.eval(producer, null, servExp, null, stack, 1)); + } + + @Test + @DisplayName("Should silently bypass when SERVICE SILENT is executed without Provider") + void shouldBypassWhenSilentWithoutProvider() { + Node servNode = NodeImpl.forIRI("http://example.org/sparql"); + Exp servExp = Exp.create(Exp.Type.SERVICE, Exp.create(Exp.Type.NODE, servNode), Exp.create(Exp.Type.EMPTY)); + servExp.setSilent(true); + Stack stack = new Stack(); + Producer producer = mock(Producer.class); + + org.junit.jupiter.api.Assertions.assertDoesNotThrow(() -> + evaluator.eval(producer, null, servExp, null, stack, 1)); + } + + @Test + @DisplayName("Should map outer query nodes during complete") + void shouldCompleteOuterQueryNodes() { + QueryPlan plan = QueryPlan.create(); + Node qNode = NodeImpl.forVariable("?x"); + plan.getSelect().add(qNode); + + Mapping mapping = Mapping.create(new Node[]{NodeImpl.forVariable("?x")}, new Node[]{NodeImpl.forIRI("http://example.org/val")}); + evaluator.complete(plan, mapping, false); + + assertEquals("?x", mapping.getQueryNodes()[0].getLabel()); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalSubQueryTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalSubQueryTest.java new file mode 100644 index 000000000..779c97b2b --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalSubQueryTest.java @@ -0,0 +1,46 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; +import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; +import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; +import fr.inria.corese.core.next.query.impl.engine.spi.Evaluator; +import fr.inria.corese.core.next.query.impl.engine.spi.Matcher; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +@DisplayName("EvalSubQuery Tests") +class EvalSubQueryTest { + + private Producer mockProducer; + private EvalSubQuery evaluator; + + @BeforeEach + void setUp() { + mockProducer = mock(Producer.class); + Evaluator mockEvaluator = mock(Evaluator.class); + Matcher mockMatcher = mock(Matcher.class); + Eval engine = new Eval(mockProducer, mockEvaluator, mockMatcher); + QueryPlan plan = QueryPlan.create(); + engine.init(plan); + evaluator = new EvalSubQuery(engine); + } + + @Test + @DisplayName("Should backtrack when subquery produces empty result") + void shouldBacktrackOnEmptySubQuery() { + QueryPlan subQuery = QueryPlan.create(); + subQuery.setBody(Exp.create(Exp.Type.EMPTY)); + + Stack stack = new Stack(); + int result = evaluator.eval(mockProducer, null, subQuery, null, stack, 1); + assertEquals(1, result); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalUnionTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalUnionTest.java new file mode 100644 index 000000000..6c8fe7b83 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalUnionTest.java @@ -0,0 +1,56 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; +import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; +import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; +import fr.inria.corese.core.next.query.impl.engine.spi.Evaluator; +import fr.inria.corese.core.next.query.impl.engine.spi.Matcher; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static fr.inria.corese.core.next.query.impl.engine.eval.Eval.STOP; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +@DisplayName("EvalUnion Tests") +class EvalUnionTest { + + private Eval engine; + private Producer mockProducer; + private EvalUnion evaluator; + + @BeforeEach + void setUp() { + mockProducer = mock(Producer.class); + Evaluator mockEvaluator = mock(Evaluator.class); + Matcher mockMatcher = mock(Matcher.class); + engine = new Eval(mockProducer, mockEvaluator, mockMatcher); + QueryPlan plan = QueryPlan.create(); + engine.init(plan); + evaluator = new EvalUnion(engine); + } + + @Test + @DisplayName("Should detect non-federated expression when global query is not federate") + void shouldDetectNonFederated() { + Exp exp = Exp.create(Exp.Type.BGP); + assertFalse(evaluator.isFederate(exp)); + } + + @Test + @DisplayName("Should return STOP if engine is stopped before evaluating right branch") + void shouldReturnStopWhenStopped() { + engine.getState().stop(); + Exp unionExp = Exp.create(Exp.Type.UNION, Exp.create(Exp.Type.EMPTY), Exp.create(Exp.Type.EMPTY)); + Stack stack = new Stack(); + int result = evaluator.eval(mockProducer, null, unionExp, null, stack, 2); + assertEquals(STOP, result); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalValuesTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalValuesTest.java new file mode 100644 index 000000000..f8daa083b --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalValuesTest.java @@ -0,0 +1,78 @@ +package fr.inria.corese.core.next.query.impl.engine.eval; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; +import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; +import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; +import fr.inria.corese.core.next.query.impl.engine.spi.Evaluator; +import fr.inria.corese.core.next.query.impl.engine.spi.Matcher; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static fr.inria.corese.core.next.query.impl.engine.eval.Eval.STOP; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayName("EvalValues Tests") +class EvalValuesTest { + + private Eval engine; + private Producer mockProducer; + private EvalValues evaluator; + + @BeforeEach + void setUp() { + mockProducer = mock(Producer.class); + Evaluator mockEvaluator = mock(Evaluator.class); + Matcher mockMatcher = mock(Matcher.class); + engine = new Eval(mockProducer, mockEvaluator, mockMatcher); + QueryPlan plan = QueryPlan.create(); + engine.init(plan); + evaluator = new EvalValues(engine); + } + + @Test + @DisplayName("Should return STOP if engine is stopped") + void shouldReturnStopWhenStopped() { + engine.getState().stop(); + Exp valuesExp = Exp.create(Exp.Type.VALUES); + Mappings map = new Mappings(); + map.add(Mapping.create()); + valuesExp.setMappings(map); + valuesExp.setNodeList(List.of(NodeImpl.forVariable("?x"))); + + Stack stack = new Stack(); + int result = evaluator.eval(mockProducer, null, valuesExp, stack, 1); + assertEquals(STOP, result); + } + + @Test + @DisplayName("Should bind and unbind values row") + void shouldBindAndUnbindValuesRow() { + QueryPlan plan = QueryPlan.create(); + Node variable = NodeImpl.forVariable("?x"); + variable.setIndex(plan.allocateNodeIndex()); + Eval eval = new Eval(mockProducer, mock(Evaluator.class), mock(Matcher.class)); + eval.init(plan); + EvalValues customEvaluator = new EvalValues(eval); + + Node val = NodeImpl.forLiteral("test", null, null); + when(mockProducer.getNode(any())).thenReturn(val); + + Mapping row = Mapping.create(new Node[]{variable}, new Node[]{val}); + boolean pushed = customEvaluator.valuesBinding(List.of(variable), row, 1); + assertTrue(pushed); + + customEvaluator.free(List.of(variable), row); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/ExpPatternScopeTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/ExpPatternScopeTest.java new file mode 100644 index 000000000..21b7f153a --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/ExpPatternScopeTest.java @@ -0,0 +1,169 @@ +package fr.inria.corese.core.next.query.impl.engine.pattern; + +import fr.inria.corese.core.next.query.impl.engine.model.Edge; +import fr.inria.corese.core.next.query.impl.engine.model.ExpType; +import fr.inria.corese.core.next.query.impl.engine.model.Filter; +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayName("ExpPatternScope Unit Tests") +class ExpPatternScopeTest { + + @Test + @DisplayName("Should add variable node only when valid and not blank or duplicate") + void testAddVariable() { + List list = new ArrayList<>(); + Node var1 = NodeImpl.forVariable("?x"); + Node varDuplicate = NodeImpl.forVariable("?x"); + Node blank = NodeImpl.forBlank("_:b1"); + + ExpPatternScope.add(list, var1); + ExpPatternScope.add(list, varDuplicate); + ExpPatternScope.add(list, blank); + ExpPatternScope.add(list, null); + + assertEquals(1, list.size()); + assertEquals("?x", list.get(0).getLabel()); + } + + @Test + @DisplayName("Should add variable label string to list") + void testAddVariableString() { + List list = new ArrayList<>(); + Node var1 = NodeImpl.forVariable("?y"); + Node literal = NodeImpl.forLiteral("val", null, null); + + ExpPatternScope.addVariable(list, var1); + ExpPatternScope.addVariable(list, var1); // duplicate + ExpPatternScope.addVariable(list, literal); // not variable + ExpPatternScope.addVariable(list, null); + + assertEquals(1, list.size()); + assertEquals("?y", list.get(0)); + } + + @Test + @DisplayName("Should correctly evaluate bound variables subset check") + void testBound() { + List fvec = List.of("?x", "?y"); + List evecMatch = List.of("?x", "?y", "?z"); + List evecMismatch = List.of("?x", "?z"); + + assertTrue(ExpPatternScope.bound(fvec, evecMatch)); + assertFalse(ExpPatternScope.bound(fvec, evecMismatch)); + } + + @Test + @DisplayName("Should share variables between filter and node") + void testShareNode() { + List filterVar = List.of("?x", "?y"); + List expVar = new ArrayList<>(); + Node varX = NodeImpl.forVariable("?x"); + + ExpPatternScope.share(varX, filterVar, expVar); + assertEquals(1, expVar.size()); + assertEquals("?x", expVar.get(0)); + + // Re-sharing shouldn't duplicate + ExpPatternScope.share(varX, filterVar, expVar); + assertEquals(1, expVar.size()); + } + + @Test + @DisplayName("Should share variables across BGP with NODE and EDGE") + void testShareBgp() { + Exp bgp = Exp.create(ExpType.Type.BGP); + Node varX = NodeImpl.forVariable("?x"); + Node varY = NodeImpl.forVariable("?y"); + + Edge edge = mock(Edge.class); + when(edge.nbNode()).thenReturn(2); + when(edge.getNode(0)).thenReturn(varX); + when(edge.getNode(1)).thenReturn(varY); + + Exp edgeExp = Exp.create(ExpType.Type.EDGE, edge); + bgp.add(edgeExp); + + List filterVar = List.of("?x", "?z"); + List expVar = new ArrayList<>(); + + ExpPatternScope.share(bgp, filterVar, expVar); + + assertEquals(1, expVar.size()); + assertTrue(expVar.contains("?x")); + } + + @Test + @DisplayName("Should collect variables from EDGE, BIND and VALUES") + void testGetNodeVariables() { + Exp bgp = Exp.create(ExpType.Type.BGP); + + Node varX = NodeImpl.forVariable("?x"); + Node varY = NodeImpl.forVariable("?y"); + Node varZ = NodeImpl.forVariable("?z"); + + Edge edge = mock(Edge.class); + when(edge.getNode(0)).thenReturn(varX); + when(edge.getNode(1)).thenReturn(varY); + when(edge.getProperty()).thenReturn(NodeImpl.forIRI("http://example.org/p")); + Exp edgeExp = Exp.create(ExpType.Type.EDGE, edge); + + Exp bindExp = Exp.create(ExpType.Type.BIND, varZ); + + Exp valuesExp = Exp.create(ExpType.Type.VALUES); + valuesExp.setNodeList(List.of(NodeImpl.forVariable("?v"))); + + bgp.add(edgeExp); + bgp.add(bindExp); + bgp.add(valuesExp); + + List vars = ExpPatternScope.getNodeVariables(bgp); + + assertTrue(vars.contains("?x")); + assertTrue(vars.contains("?y")); + assertTrue(vars.contains("?z")); + assertTrue(vars.contains("?v")); + } + + @Test + @DisplayName("Should detect unbound variables for a Filter") + void testHasUnboundVariables() { + Exp bgp = Exp.create(ExpType.Type.BGP); + Node varX = NodeImpl.forVariable("?x"); + bgp.add(Exp.create(ExpType.Type.BIND, varX)); + + Filter filter1 = mock(Filter.class); + when(filter1.getVariables()).thenReturn(List.of("?x")); + + Filter filter2 = mock(Filter.class); + when(filter2.getVariables()).thenReturn(List.of("?x", "?unbound")); + + assertFalse(ExpPatternScope.hasUnboundVariables(bgp, filter1)); + assertTrue(ExpPatternScope.hasUnboundVariables(bgp, filter2)); + } + + @Test + @DisplayName("Should compute in-scope nodes for simple BGP") + void testInScopeNodes() { + Exp bgp = Exp.create(ExpType.Type.BGP); + Node varA = NodeImpl.forVariable("?a"); + bgp.add(Exp.create(ExpType.Type.NODE, varA)); + + List inScope = ExpPatternScope.getInScopeNodes(bgp); + assertEquals(1, inScope.size()); + assertEquals("?a", inScope.get(0).getLabel()); + + List recordInScope = ExpPatternScope.getRecordInScopeNodes(bgp, true); + assertEquals(1, recordInScope.size()); + assertEquals("?a", recordInScope.get(0).getLabel()); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/repository/CoreseRepositoryConnectionTransactionTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/repository/CoreseRepositoryConnectionTransactionTest.java index 819d9d18d..478950772 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/repository/CoreseRepositoryConnectionTransactionTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/repository/CoreseRepositoryConnectionTransactionTest.java @@ -22,7 +22,6 @@ class CoreseRepositoryConnectionTransactionTest { private CoreseRepositoryConnection connection; private Transaction transaction; - private AtomicBoolean active; @BeforeEach void setUp() { @@ -30,7 +29,7 @@ void setUp() { StorageManager storage = mock(StorageManager.class); TransactionManager transactionManager = mock(TransactionManager.class); transaction = mock(Transaction.class); - active = new AtomicBoolean(); + AtomicBoolean active = new AtomicBoolean(); when(repository.isOpen()).thenReturn(true); when(storage.transactions()).thenReturn(transactionManager); diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/io/serializer/csv/CsvTupleResultSerializerTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/io/serializer/csv/CsvTupleResultSerializerTest.java index 7c92dd96b..743214a76 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/io/serializer/csv/CsvTupleResultSerializerTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/io/serializer/csv/CsvTupleResultSerializerTest.java @@ -75,8 +75,8 @@ protected String getResultWithLiteralContainingQuotesString() { return """ name,desc Alice,Literal with a single quote' - Bernard,"Literal with a quote\"\"" - Charles,"Literal both quotes single ' and double \"\"" + Bernard,"Literal with a quote\""" + Charles,"Literal both quotes single ' and double \""" """; } @@ -85,7 +85,7 @@ protected String getSVStandardResultsString() { return """ x,literal http://example/x,String - http://example/x,"String-with-dquote\"\"" + http://example/x,"String-with-dquote\""" _:blank0,Blank node ,Missing 'x' , diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/io/serializer/json/JsonBooleanResultSerializerTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/io/serializer/json/JsonBooleanResultSerializerTest.java index 5d0241837..8c9174caf 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/io/serializer/json/JsonBooleanResultSerializerTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/io/serializer/json/JsonBooleanResultSerializerTest.java @@ -45,14 +45,10 @@ private String getLinksTestResultsString() { "}"; } - private boolean getLinksTestResults() { - return true; - } - @Test @DisplayName("Tests the serialization of results including several links") void linksTest() { - ResultSerializer serializer = getSerializer(getLinksTestResults(), getOptionsWithLinks()); + ResultSerializer serializer = getSerializer(true, getOptionsWithLinks()); StringWriter writer = new StringWriter(); serializer.write(writer); assertEquals(getLinksTestResultsString(), writer.toString()); diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlAstBuilderDescribeTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlAstBuilderDescribeTest.java index 478b36d12..5a6ae4219 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlAstBuilderDescribeTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlAstBuilderDescribeTest.java @@ -33,17 +33,6 @@ private DescribeQueryAst buildWithEmptyWhere() { return (DescribeQueryAst) builder.getResult(); } - /** - * Builds a DescribeQueryAst with a single triple in the WHERE clause. - */ - private DescribeQueryAst buildWithWhere() { - builder.enterGroup(); - builder.enterBgp(); - builder.addTriple(builder.variable("s"), builder.iri("a"), builder.iri("foaf:Person")); - builder.exitBgp(); - builder.exitGroup(); - return (DescribeQueryAst) builder.getResult(); - } @Nested @DisplayName("DESCRIBE *") @@ -164,6 +153,15 @@ void isDescribeAllFalseWhenResourcesPresent() { @DisplayName("DESCRIBE ?s WHERE { ... }") class DescribeWithWhereClause { + private DescribeQueryAst buildWithWhere() { + builder.enterGroup(); + builder.enterBgp(); + builder.addTriple(builder.variable("s"), builder.iri("a"), builder.iri("foaf:Person")); + builder.exitBgp(); + builder.exitGroup(); + return (DescribeQueryAst) builder.getResult(); + } + @Test @DisplayName("WHERE clause is present and contains expected BGP") void whereClauseContainsBgp() { diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlAstBuilderUnionTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlAstBuilderUnionTest.java index a2dec2a9c..8dde4ca00 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlAstBuilderUnionTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlAstBuilderUnionTest.java @@ -25,27 +25,6 @@ void setUp() { builder.enterSelectQuery(); } - /** - * Simulates: outer group open → UNION open → branch { } → branch { } → UNION close → outer group close. - * Each branch is empty (no BGP inside). - */ - private GroupGraphPatternAst buildTwoEmptyBranchUnion() { - builder.enterGroup(); - builder.enterUnion(); - - builder.enterGroup(); - builder.exitGroup(); - builder.collectUnionBranch(); - - builder.enterGroup(); - builder.exitGroup(); - builder.collectUnionBranch(); - - builder.exitUnion(); - builder.exitGroup(); - builder.exitSelectQuery(); - return Objects.requireNonNull((SelectQueryAst)builder.getResult()).whereClause(); - } /** * Builds a BGP with a single triple ?s a inside the current group. @@ -60,6 +39,24 @@ private void addTriple(String object) { @DisplayName("Two-branch UNION") class TwoBranchUnion { + private GroupGraphPatternAst buildTwoEmptyBranchUnion() { + builder.enterGroup(); + builder.enterUnion(); + + builder.enterGroup(); + builder.exitGroup(); + builder.collectUnionBranch(); + + builder.enterGroup(); + builder.exitGroup(); + builder.collectUnionBranch(); + + builder.exitUnion(); + builder.exitGroup(); + builder.exitSelectQuery(); + return Objects.requireNonNull((SelectQueryAst)builder.getResult()).whereClause(); + } + @Test @DisplayName("WHERE clause contains exactly one UnionAst") void whereClauseContainsUnionAst() { diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserBnodeBgpScopeTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserBnodeBgpScopeTest.java index 0054614c5..adb54b045 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserBnodeBgpScopeTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserBnodeBgpScopeTest.java @@ -20,67 +20,73 @@ class SparqlParserBnodeBgpScopeTest extends AbstractSparqlParserFeatureTest { @Test @DisplayName("same blank-node label used twice within one BGP is valid") void sameLabelInOneBgpIsValid() { - assertDoesNotThrow(() -> newParserDefault().parse(""" + String query = """ SELECT * WHERE { _:b ?o . _:b ?x } - """)); + """; + assertDoesNotThrow(() -> newParserDefault().parse(query)); } @Test @DisplayName("two different blank-node labels in one BGP are valid") void differentLabelsInOneBgpAreValid() { - assertDoesNotThrow(() -> newParserDefault().parse(""" + String query = """ SELECT * WHERE { _:b1 _:b2 . _:b1 _:b2 } - """)); + """; + assertDoesNotThrow(() -> newParserDefault().parse(query)); } @Test @DisplayName("same blank-node label in main BGP and OPTIONAL body is rejected") void sameLabelInOptionalIsRejected() { - assertThrows(QuerySyntaxException.class, () -> newParserDefault().parse(""" + String query = """ SELECT * WHERE { _:b ?o . OPTIONAL { _:b ?x } } - """), "A blank-node label must not cross an OPTIONAL boundary"); + """; + assertThrows(QuerySyntaxException.class, () -> newParserDefault().parse(query), "A blank-node label must not cross an OPTIONAL boundary"); } @Test @DisplayName("same blank-node label in two UNION branches is rejected") void sameLabelInUnionBranchesIsRejected() { - assertThrows(QuerySyntaxException.class, () -> newParserDefault().parse(""" + String query = """ SELECT * WHERE { { _:b ?o } UNION { _:b ?x } } - """), "A blank-node label must not appear in both UNION branches"); + """; + assertThrows(QuerySyntaxException.class, () -> newParserDefault().parse(query), "A blank-node label must not appear in both UNION branches"); } @Test @DisplayName("same blank-node label across a GRAPH boundary is rejected") void sameLabelAcrossGraphBoundaryIsRejected() { - assertThrows(QuerySyntaxException.class, () -> newParserDefault().parse(""" + String query = """ SELECT * WHERE { GRAPH { _:b ?o } _:b ?x } - """), "A blank-node label must not cross a GRAPH clause boundary"); + """; + assertThrows(QuerySyntaxException.class, () -> newParserDefault().parse(query), "A blank-node label must not cross a GRAPH clause boundary"); } @Test @DisplayName("same blank-node label in WHERE BGP and MINUS body is rejected") void sameLabelInMinusBodyIsRejected() { - assertThrows(QuerySyntaxException.class, () -> newParserDefault().parse(""" + String query = """ SELECT * WHERE { _:b ?o . MINUS { _:b ?x } } - """), "A blank-node label must not cross a MINUS boundary"); + """; + assertThrows(QuerySyntaxException.class, () -> newParserDefault().parse(query), "A blank-node label must not cross a MINUS boundary"); } } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserIfTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserIfTest.java index 68990621f..e212eb99d 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserIfTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserIfTest.java @@ -104,13 +104,14 @@ void shouldParseNestedIf() { @DisplayName("BIND(IF(?s, IF(?p, ?p, ?o), ?o) AS ?s) — nested IF with already-declared variable should fail") void shouldFailNestedIfWhenBindVariableAlreadyDeclared() { SparqlParser parser = newParserDefault(); - - QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(""" + String query = """ SELECT * WHERE { ?s ?p ?o . BIND(IF(?s, IF(?p, ?p, ?o), ?o) AS ?s) } - """)); + """; + + QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(query)); assertEquals("Variable ?s used in BIND is already declared in the same group graph pattern", exception.getMessage()); } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserInTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserInTest.java index d63db3e63..1ac1d19bb 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserInTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserInTest.java @@ -102,13 +102,14 @@ void shouldParseOrderByInAndValidateVariableScope() { @DisplayName("ORDER BY (?label NOT IN (?altLabel, ?missing)) — should reject variable not visible in WHERE") void shouldRejectOrderByNotInWhenVariableNotVisibleInWhere() { SparqlParser parser = newParserDefault(); - - QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(""" + String query = """ SELECT ?label WHERE { ?s ?p ?label . ?s ?altLabel . } ORDER BY (?label NOT IN (?altLabel, ?missing)) - """)); + """; + + QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(query)); assertEquals("Variable ?missing used in ORDER BY is not visible in WHERE clause", exception.getMessage()); } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserMinusTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserMinusTest.java index 04ad799a1..b0f215b34 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserMinusTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserMinusTest.java @@ -86,12 +86,13 @@ void shouldExcludeMinusVariablesFromSelectAllScope() { @DisplayName("SELECT projection may contain a variable declared only inside MINUS") void shouldAcceptProjectionOfMinusOnlyVariable() { SparqlParser parser = newParserDefault(); - - assertDoesNotThrow(() -> parser.parse(""" + String query = """ SELECT ?hidden WHERE { ?s ?p ?o . MINUS { ?s ?q ?hidden . } } - """)); + """; + + assertDoesNotThrow(() -> parser.parse(query)); } } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserReplaceTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserReplaceTest.java index 57a304c9f..b12b0e61b 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserReplaceTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserReplaceTest.java @@ -94,14 +94,15 @@ void shouldParseOrderByReplaceAndValidateVariableScope() { @DisplayName("ORDER BY REPLACE(?label, ?pattern, ?replacement, ?missingFlags) — should reject variable not visible in WHERE") void shouldRejectOrderByReplaceWhenVariableNotVisibleInWhere() { SparqlParser parser = newParserDefault(); - - QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(""" + String query = """ SELECT ?label WHERE { ?s ?p ?label . ?s ?pattern . ?s ?replacement . } ORDER BY REPLACE(?label, ?pattern, ?replacement, ?missingFlags) - """)); + """; + + QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(query)); assertEquals("Variable ?missingFlags used in ORDER BY is not visible in WHERE clause", exception.getMessage()); } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserStrDtTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserStrDtTest.java index 1244b2ac6..810aeeb5a 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserStrDtTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserStrDtTest.java @@ -104,13 +104,14 @@ void shouldParseOrderByStrDt() { @DisplayName("ORDER BY STRDT(?missing, xsd:string) — should reject variable not visible in WHERE") void shouldRejectOrderByStrDtWhenVariableNotVisibleInWhere() { SparqlParser parser = newParserDefault(); - - QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(""" + String query = """ PREFIX xsd: SELECT ?label WHERE { ?s ?p ?label . } ORDER BY STRDT(?missing, xsd:string) - """)); + """; + + QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(query)); assertEquals("Variable ?missing used in ORDER BY is not visible in WHERE clause", exception.getMessage()); } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserValidationTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserValidationTest.java index 93b8507b8..51147ee87 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserValidationTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/parser/SparqlParserValidationTest.java @@ -70,14 +70,15 @@ class AskValidationTest { @DisplayName("Should reject ASK ORDER BY variable not visible in WHERE") void shouldRejectAskOrderByVariableNotVisibleInWhere() { SparqlParser parser = newParserDefault(); - - QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(""" + String query = """ ASK WHERE { ?s ?p ?o } ORDER BY ?z - """)); + """; + + QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(query)); assertEquals(ORDER_BY_SCOPE_MESSAGE, exception.getMessage()); } @@ -90,8 +91,7 @@ class ConstructValidationTest { @DisplayName("Should reject CONSTRUCT ORDER BY variable not visible in WHERE") void shouldRejectConstructOrderByVariableNotVisibleInWhere() { SparqlParser parser = newParserDefault(); - - QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(""" + String query = """ CONSTRUCT { ?s ?p ?o } @@ -99,7 +99,9 @@ void shouldRejectConstructOrderByVariableNotVisibleInWhere() { ?s ?p ?o } ORDER BY ?z - """)); + """; + + QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(query)); assertEquals(ORDER_BY_SCOPE_MESSAGE, exception.getMessage()); } @@ -112,14 +114,15 @@ class DescribeValidationTest { @DisplayName("Should reject DESCRIBE ORDER BY variable not visible in WHERE") void shouldRejectDescribeOrderByVariableNotVisibleInWhere() { SparqlParser parser = newParserDefault(); - - QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(""" + String query = """ DESCRIBE ?s WHERE { ?s ?p ?o } ORDER BY ?z - """)); + """; + + QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(query)); assertEquals(ORDER_BY_SCOPE_MESSAGE, exception.getMessage()); } @@ -132,13 +135,14 @@ class BindValidationTest { @DisplayName("Should reject BIND when variable is already introduced by a triple pattern") void shouldRejectBindVariableAlreadyVisibleFromTriple() { SparqlParser parser = newParserDefault(); - - QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(""" + String query = """ SELECT * WHERE { ?x ?p ?o . BIND(?o AS ?x) } - """)); + """; + + QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(query)); assertEquals(BIND_SCOPE_MESSAGE, exception.getMessage()); } @@ -147,14 +151,15 @@ void shouldRejectBindVariableAlreadyVisibleFromTriple() { @DisplayName("Should reject BIND when variable is already introduced by a previous BIND") void shouldRejectBindVariableAlreadyVisibleFromPreviousBind() { SparqlParser parser = newParserDefault(); - - QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(""" + String query = """ SELECT * WHERE { ?s ?p ?o . BIND(?s AS ?x) BIND(?p AS ?x) } - """)); + """; + + QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(query)); assertEquals(BIND_SCOPE_MESSAGE, exception.getMessage()); } @@ -163,14 +168,15 @@ void shouldRejectBindVariableAlreadyVisibleFromPreviousBind() { @DisplayName("Should reject BIND when its target is already introduced by inline VALUES") void shouldRejectBindVariableAlreadyVisibleFromInlineValues() { var parser = newParserDefault(); + String query = """ + SELECT * WHERE { + VALUES ?x { 1 } + BIND(2 AS ?x) + } + """; QueryValidationException exception = assertThrows( QueryValidationException.class, - () -> parser.parse(""" - SELECT * WHERE { - VALUES ?x { 1 } - BIND(2 AS ?x) - } - """)); + () -> parser.parse(query)); assertEquals(BIND_SCOPE_MESSAGE, exception.getMessage()); } From 2a581da2846fda2fdd6df37ebe11684616b7e552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?= Date: Mon, 26 Oct 2026 17:25:00 +0100 Subject: [PATCH 2/7] refactor(engine): eliminate legacy Pointerable and PointerObject scaffolding --- .../next/query/impl/engine/eval/Eval.java | 19 +---- .../engine/eval/EvalVisitorDispatcher.java | 21 ----- .../impl/engine/eval/IterableEntity.java | 44 ----------- .../query/impl/engine/eval/PointerObject.java | 12 --- .../impl/engine/event/ProcessVisitor.java | 3 +- .../next/query/impl/engine/model/Edge.java | 3 +- .../next/query/impl/engine/model/EdgeInv.java | 16 +--- .../query/impl/engine/model/Loopable.java | 12 --- .../next/query/impl/engine/model/Node.java | 3 +- .../query/impl/engine/model/NodeImpl.java | 6 -- .../query/impl/engine/model/PointerType.java | 36 --------- .../query/impl/engine/model/Pointerable.java | 77 ------------------- .../next/query/impl/engine/path/Path.java | 32 +------- .../next/query/impl/engine/pattern/Exp.java | 36 ++------- .../query/impl/engine/pattern/QueryPlan.java | 11 --- .../query/impl/engine/solution/Mapping.java | 33 +------- .../query/impl/engine/solution/Mappings.java | 39 +--------- .../query/impl/engine/solution/Memory.java | 13 +--- .../eval/EvalVisitorDispatcherTest.java | 23 ------ .../query/impl/engine/pattern/ExpTest.java | 14 ---- .../impl/engine/pattern/QueryPlanTest.java | 7 -- .../impl/engine/solution/MappingTest.java | 9 --- .../impl/engine/solution/MappingsTest.java | 28 ------- 23 files changed, 15 insertions(+), 482 deletions(-) delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/IterableEntity.java delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/PointerObject.java delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Loopable.java delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/model/PointerType.java delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Pointerable.java diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Eval.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Eval.java index 4672ab8d3..31bbd885a 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Eval.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Eval.java @@ -7,7 +7,6 @@ import fr.inria.corese.core.next.query.impl.engine.model.ExpType; import fr.inria.corese.core.next.query.impl.engine.model.Expr; import fr.inria.corese.core.next.query.impl.engine.model.Filter; -import fr.inria.corese.core.next.query.impl.engine.model.Loopable; import fr.inria.corese.core.next.query.impl.engine.model.Node; import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; @@ -963,16 +962,7 @@ private int edge(Producer p, Node graphNode, Exp exp, Mappings data, Stack stack } } - Iterable entities; - if (visitors.hasProduce()) { - // A query visitor may provide candidates before the producer is consulted. - entities = produce(graphNode, qEdge); - if (entities == null) { - entities = p.getEdges(graphNode, graphNodeList, qEdge, env); - } - } else { - entities = p.getEdges(graphNode, graphNodeList, qEdge, env); - } + Iterable entities = p.getEdges(graphNode, graphNodeList, qEdge, env); try { for (Edge entity : entities) { @@ -1151,13 +1141,6 @@ public void finish() { optional.setStop(true); } - /** - * Draft extension where a Visitor provides Edge iterator - */ - Iterable produce(Node gNode, Edge edge) { - return visitors.produce(this, gNode, edge); - } - public boolean hasFilter() { return visitors.hasFilter(); } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalVisitorDispatcher.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalVisitorDispatcher.java index 1c4bae474..84021d387 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalVisitorDispatcher.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalVisitorDispatcher.java @@ -6,7 +6,6 @@ import fr.inria.corese.core.next.query.impl.engine.event.ResultListener; import fr.inria.corese.core.next.query.impl.engine.model.Edge; import fr.inria.corese.core.next.query.impl.engine.model.Expr; -import fr.inria.corese.core.next.query.impl.engine.model.Loopable; import fr.inria.corese.core.next.query.impl.engine.model.Node; import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; @@ -26,7 +25,6 @@ public final class EvalVisitorDispatcher { private boolean hasListener = false; private boolean hasStatement = false; - private boolean hasProduce = false; private boolean hasCandidate = false; private boolean hasFilter = false; @@ -48,12 +46,10 @@ public void setVisitor(ProcessVisitor visitor) { public void updateVisitorFlags() { if (visitor != null) { this.hasStatement = visitor.statement(); - this.hasProduce = visitor.produce(); this.hasCandidate = visitor.candidate(); this.hasFilter = visitor.filter(); } else { this.hasStatement = false; - this.hasProduce = false; this.hasCandidate = false; this.hasFilter = false; } @@ -63,10 +59,6 @@ public boolean hasStatement() { return hasStatement; } - public boolean hasProduce() { - return hasProduce; - } - public boolean hasCandidate() { return hasCandidate; } @@ -109,17 +101,4 @@ public boolean listen(Exp exp, Edge qEdge, Edge entity) { } return true; } - - // --- Produce helper --- - - public Iterable produce(Eval eval, Node gNode, Edge edge) { - DatatypeValue res = visitor.produce(eval, gNode, edge); - if (res instanceof Loopable loopable) { - Iterable loop = loopable.getLoop(); - if (loop != null) { - return new IterableEntity(loop); - } - } - return null; - } } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/IterableEntity.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/IterableEntity.java deleted file mode 100644 index d9a76b613..000000000 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/IterableEntity.java +++ /dev/null @@ -1,44 +0,0 @@ -package fr.inria.corese.core.next.query.impl.engine.eval; - -import fr.inria.corese.core.next.query.impl.engine.model.Edge; -import fr.inria.corese.core.next.query.impl.engine.model.Node; - -import java.util.Iterator; - -/** - * @author Olivier Corby, Wimmics INRIA I3S, 2015 - */ -final class IterableEntity implements Iterable { - - private final Iterable values; - - IterableEntity(Iterable loop) { - values = loop; - } - - @Override - public Iterator iterator() { - Iterator iterator = values.iterator(); - return new Iterator<>() { - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public Edge next() { - return asEdge(iterator.next()); - } - }; - } - - private static Edge asEdge(Object value) { - if (value instanceof Edge edge) { - return edge; - } - if (value instanceof Node node && node.getNodeObject() instanceof Edge edge) { - return edge; - } - throw new IllegalStateException("Loop item cannot be converted to a KGRAM edge: " + value); - } -} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/PointerObject.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/PointerObject.java deleted file mode 100644 index 7f31a6d41..000000000 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/PointerObject.java +++ /dev/null @@ -1,12 +0,0 @@ -package fr.inria.corese.core.next.query.impl.engine.eval; - -import fr.inria.corese.core.next.query.impl.engine.model.Pointerable; - -/** - * - * @author Olivier Corby, Wimmics INRIA I3S, 2015 - * - */ -public abstract class PointerObject implements Pointerable { - -} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ProcessVisitor.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ProcessVisitor.java index c35e156af..43db1ff78 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ProcessVisitor.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ProcessVisitor.java @@ -9,7 +9,6 @@ import fr.inria.corese.core.next.query.impl.engine.model.Edge; import fr.inria.corese.core.next.query.impl.engine.model.Expr; import fr.inria.corese.core.next.query.impl.engine.model.Node; -import fr.inria.corese.core.next.query.impl.engine.model.Pointerable; import fr.inria.corese.core.next.query.impl.engine.path.Path; import fr.inria.corese.core.next.data.api.model.DatatypeValue; @@ -17,7 +16,7 @@ /** * @author Olivier Corby, Wimmics INRIA I3S, 2018 */ -public interface ProcessVisitor extends Pointerable { +public interface ProcessVisitor { int SLICE_DEFAULT = 20; diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Edge.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Edge.java index b8265351d..b1c4a2d21 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Edge.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Edge.java @@ -8,7 +8,7 @@ * * @author Olivier Corby, Edelweiss, INRIA 2010 */ -public interface Edge extends Pointerable { +public interface Edge { // nb nodes to consider in sparql query processing @@ -61,7 +61,6 @@ default void setEdgeIndex(int n) { Node getGraph(); - @Override Edge getEdge(); default Object getProvenance() { diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/EdgeInv.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/EdgeInv.java index 9847f610d..24c682419 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/EdgeInv.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/EdgeInv.java @@ -1,9 +1,6 @@ package fr.inria.corese.core.next.query.impl.engine.model; -import fr.inria.corese.core.next.query.impl.engine.eval.PointerObject; -import static fr.inria.corese.core.next.query.impl.engine.model.PointerType.TRIPLE; - -public class EdgeInv extends PointerObject implements Edge { +public class EdgeInv implements Edge { private final Edge edge; private final Edge ent; @@ -81,7 +78,6 @@ public Node getProperty() { return edge.getProperty(); } - @Override public Node getNode() { return null; } @@ -95,14 +91,4 @@ public Object getProvenance() { public void setProvenance(Object obj) { ent.setProvenance(obj); } - - @Override - public Iterable getLoop() { - return ent.getLoop(); - } - - @Override - public PointerType pointerType() { - return TRIPLE; - } } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Loopable.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Loopable.java deleted file mode 100644 index 432fa013c..000000000 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Loopable.java +++ /dev/null @@ -1,12 +0,0 @@ -package fr.inria.corese.core.next.query.impl.engine.model; - -/** - * Interface for (var in exp) {} - * - * @author Olivier Corby 2015 - */ -public interface Loopable { - - Iterable getLoop(); - -} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Node.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Node.java index bdfa085dd..8fbda7e3b 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Node.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Node.java @@ -10,7 +10,7 @@ * * @author Olivier Corby, Edelweiss, INRIA 2010 */ -public interface Node extends Pointerable, Comparable { +public interface Node extends Comparable { String INITKEY = ""; int REGEX = 2; @@ -75,7 +75,6 @@ default void setDatatypeValue(DatatypeValue dt) { Node getGraph(); - @Override Node getNode(); Object getNodeObject(); diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/NodeImpl.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/NodeImpl.java index 10faefed2..b53491f0c 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/NodeImpl.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/NodeImpl.java @@ -193,7 +193,6 @@ public Object getNodeObject() { return payload; } - @Override public Edge getEdge() { return payload instanceof Edge edge ? edge : null; } @@ -218,11 +217,6 @@ public void setKey(String str) { key = Objects.requireNonNull(str, "str"); } - @Override - public TripleStore getTripleStore() { - return null; - } - @Override public int hashCode() { return isVariable() ? variableName.hashCode() : value.hashCode(); diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/PointerType.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/PointerType.java deleted file mode 100644 index 46730f3a7..000000000 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/PointerType.java +++ /dev/null @@ -1,36 +0,0 @@ -package fr.inria.corese.core.next.query.impl.engine.model; - -/** - * Pointer type for object that can be object of CoresePointer - * - * @author Olivier Corby, Wimmics INRIA I3S, 2019 - */ -public enum PointerType { - - UNDEF("pointer"), - MAPPINGS("mappings"), - MAPPING("mapping"), - GRAPH("graph"), - NODE("node"), - TRIPLE("triple"), - PATH("path"), - QUERY("query"), - EXPRESSION("expression"), - STATEMENT("statement"), - DATASET("dataset"), - PRODUCER("producer"), - METADATA("metadata"), - CONTEXT("context"), - NSMANAGER("nsmanager"), - VISITOR("visitor") - ; - - private static final String DATATYPE_NAMESPACE = "http://ns.inria.fr/corese/datatype/"; - - final String name; - - PointerType(String n) { - name = DATATYPE_NAMESPACE + n; - } - -} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Pointerable.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Pointerable.java deleted file mode 100644 index f4e000b56..000000000 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/Pointerable.java +++ /dev/null @@ -1,77 +0,0 @@ -package fr.inria.corese.core.next.query.impl.engine.model; - -import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; -import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; -import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; -import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; - -import java.util.ArrayList; - -/** - * Interface for objects that can be object of a CoresePointer - * - * @author Olivier Corby - Wimmics Inria I3S - 2015 - */ -public interface Pointerable extends Loopable { - - default PointerType pointerType() { - return PointerType.UNDEF; - } - - default Mappings getMappings() { - return null; - } - - - default Mapping getMapping() { - return null; - } - - default Edge getEdge() { - return null; - } - - default Node getNode() { - return null; - } - - default QueryPlan getQueryPlan() { - return getQuery(); - } - - default QueryPlan getQuery() { - return null; - } - - default Exp getStatement() { - return null; - } - - - default TripleStore getTripleStore() { - return null; - } - - default int size() { - return 0; - } - - default Object getValue(String varString, int n) { - return null; - } - - - @Override - default Iterable getLoop() { - return new ArrayList<>(0); - } - - default String getDatatypeLabel() { - return Integer.toString(hashCode()); - } - - default int compare(Pointerable obj) { - return Integer.compare(hashCode(), obj.hashCode()); - } - -} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/Path.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/Path.java index 244c26d0b..8452c64aa 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/Path.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/path/Path.java @@ -2,8 +2,6 @@ import fr.inria.corese.core.next.query.impl.engine.model.Edge; import fr.inria.corese.core.next.query.impl.engine.model.Node; -import fr.inria.corese.core.next.query.impl.engine.model.PointerType; -import fr.inria.corese.core.next.query.impl.engine.model.Pointerable; import fr.inria.corese.core.next.query.impl.engine.spi.Environment; import fr.inria.corese.core.next.query.impl.engine.spi.Producer; import fr.inria.corese.core.next.query.impl.engine.model.EdgeInv; @@ -12,15 +10,13 @@ import java.util.ArrayList; import java.util.List; -import static fr.inria.corese.core.next.query.impl.engine.model.PointerType.PATH; - /** * List of relations between two resources found by path Can be used as a * Producer to enumerate path edges/nodes * * @author Olivier Corby, Edelweiss, INRIA 2010 */ -public final class Path extends ProducerDefault implements Pointerable { +public final class Path extends ProducerDefault { boolean isShort = false; boolean isReverse = false; @@ -43,21 +39,6 @@ public Path(boolean b) { edges = new ArrayList<>(n); } - @Override - public String getDatatypeLabel() { - if (edges.size() == 1) { - return String.format("(1)[%s]", edges.getFirst()); - } - if (edges.size() > 1) { - return String.format("(%s)[%s ...]", edges.size(), edges.getFirst()); - } - return "(0)[]"; - } - - @Override - public PointerType pointerType() { - return PATH; - } void setIsShort(boolean b) { isShort = b; @@ -129,7 +110,6 @@ public int length() { return edges.size(); } - @Override public int size() { return edges.size(); } @@ -168,14 +148,4 @@ public String toString() { public Iterable getEdges(Node gNode, List from, Edge qEdge, Environment env) { return edges; } - - @Override - public Iterable getLoop() { - return edges; - } - - @Override - public Edge getValue(String variable, int n) { - return edges.get(n); - } } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/Exp.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/Exp.java index 25e6d92c0..d92e28d47 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/Exp.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/Exp.java @@ -1,5 +1,4 @@ package fr.inria.corese.core.next.query.impl.engine.pattern; -import fr.inria.corese.core.next.query.impl.engine.eval.PointerObject; import fr.inria.corese.core.next.query.impl.engine.eval.Stack; import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; @@ -11,7 +10,6 @@ import fr.inria.corese.core.next.query.impl.engine.model.ExprType; import fr.inria.corese.core.next.query.impl.engine.model.Filter; import fr.inria.corese.core.next.query.impl.engine.model.Node; -import fr.inria.corese.core.next.query.impl.engine.model.PointerType; import fr.inria.corese.core.next.query.impl.engine.path.PropertyPathEdge; import fr.inria.corese.core.next.query.impl.engine.spi.Producer; @@ -19,8 +17,6 @@ import java.util.Iterator; import java.util.List; -import static fr.inria.corese.core.next.query.impl.engine.model.PointerType.STATEMENT; - /** * Mutable operators for compiled SPARQL graph patterns and solution modifiers. * @@ -31,7 +27,7 @@ * * @author Olivier Corby, Edelweiss, INRIA 2010 */ -public class Exp extends PointerObject +public class Exp implements ExpType, ExpPattern, Iterable, PlanCopyable { public static final int SUBJECT = 0; @@ -219,11 +215,14 @@ public Exp duplicate() { return exp; } - @Override public int size() { return args.size(); } + public QueryPlan getQuery() { + return null; + } + public void add(Exp e) { args.add(e); } @@ -511,7 +510,6 @@ public Exp get(int n) { return args.get(n); } - @Override public Edge getEdge() { return edge; } @@ -520,7 +518,6 @@ public void setEdge(Edge e) { edge = e; } - @Override public Mappings getMappings() { return map; } @@ -612,7 +609,6 @@ public Node getGraphNode() { return node; } - @Override public Node getNode() { return node; } @@ -1166,28 +1162,6 @@ public void setPostpone(Exp postpone) { this.postpone = postpone; } - @Override - public PointerType pointerType() { - return STATEMENT; - } - - @Override - public String getDatatypeLabel() { - return "[Statement]"; - } - - @Override - public Exp getStatement() { - return this; - } - - @Override - public Object getValue(String variable, int n) { - if (n < size()) { - return get(n); - } - return null; - } public List getInscopeFilter() { return inscopeFilter; diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java index 606ac2af9..792dc31a1 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java @@ -961,7 +961,6 @@ public QueryPlan getQuery() { return this; } - @Override public QueryPlan getQueryPlan() { return this; } @@ -1446,21 +1445,12 @@ public void setNumber(int number) { this.number = number; } - @Override - public Iterable getLoop() { - return () -> getEdges().stream().map(Object.class::cast).iterator(); - } - public List getEdges(){ ArrayList list = new ArrayList<>(); getBody().getEdgeList(list); return list; } - @Override - public String getDatatypeLabel() { - return "[QueryPlan]"; - } public boolean isUseBind() { @@ -1480,7 +1470,6 @@ public void setReportEnabled(boolean reportEnabled) { - @Override public Mapping getMapping() { return mapping; } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mapping.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mapping.java index cc239f828..4d7721e00 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mapping.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mapping.java @@ -8,8 +8,6 @@ import fr.inria.corese.core.next.query.impl.engine.model.Expr; import fr.inria.corese.core.next.query.impl.engine.model.ExprType; import fr.inria.corese.core.next.query.impl.engine.model.Node; -import fr.inria.corese.core.next.query.impl.engine.model.PointerType; -import fr.inria.corese.core.next.query.impl.engine.model.Pointerable; import fr.inria.corese.core.next.query.impl.engine.model.TripleStore; import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; @@ -25,8 +23,6 @@ import java.util.Map; import java.util.Set; -import static fr.inria.corese.core.next.query.impl.engine.model.PointerType.MAPPING; - /** * An elementary result of a query or a subquery * Store query/target nodes and edges @@ -40,7 +36,7 @@ */ public final class Mapping extends EnvironmentImpl - implements Pointerable>, PlanCopyable { + implements PlanCopyable { private static final Logger LOGGER = LoggerFactory.getLogger(Mapping.class); @@ -276,10 +272,6 @@ public Path getPath(int n) { return getTargetNodes()[n].getPath(); } - @Override - public String getDatatypeLabel() { - return toString(" "); - } @Override public String toString() { @@ -501,7 +493,6 @@ Node getNodeBasic(Node node) { * let (((?var, ?val)) = ?m) * let ((?x, ?y) = ?m) */ - @Override public Object getValue(String varString, int n) { if (varString == null) { // let (((?var, ?val)) = ?m) -- ?m : Mapping @@ -523,14 +514,6 @@ List getBinding(int n) { return null; } - /** - * List of variable binding - * - */ - @Override - public Iterable> getLoop() { - return getList(); - } public List> getList() { ArrayList> list = new ArrayList<>(); @@ -881,20 +864,6 @@ public Node get(Expr varExpr) { return (Node) getBind().get(varExpr); } - @Override - public PointerType pointerType() { - return MAPPING; - } - - @Override - public Edge getEdge() { - throw new UnsupportedOperationException(NOT_SUPPORTED_YET); - } - - @Override - public TripleStore getTripleStore() { - throw new UnsupportedOperationException(NOT_SUPPORTED_YET); - } @Override public Node getGraphNode() { diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mappings.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mappings.java index c9c947fd2..21ac32e83 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mappings.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mappings.java @@ -4,11 +4,9 @@ import fr.inria.corese.core.next.query.impl.engine.pattern.PlanCopyable; import fr.inria.corese.core.next.query.api.exception.QueryTypeErrorException; import fr.inria.corese.core.next.query.impl.engine.eval.Eval; -import fr.inria.corese.core.next.query.impl.engine.eval.PointerObject; import fr.inria.corese.core.next.query.impl.engine.model.Edge; import fr.inria.corese.core.next.query.impl.engine.model.Filter; import fr.inria.corese.core.next.query.impl.engine.model.Node; -import fr.inria.corese.core.next.query.impl.engine.model.PointerType; import fr.inria.corese.core.next.query.impl.engine.model.TripleStore; import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; import fr.inria.corese.core.next.query.impl.engine.pattern.Group; @@ -25,8 +23,6 @@ import java.util.List; import java.util.Map; -import static fr.inria.corese.core.next.query.impl.engine.model.PointerType.MAPPINGS; - /** * Manage list of Mapping, result of a query * @@ -35,7 +31,7 @@ * * @author Olivier Corby, Edelweiss, INRIA 2009 */ -public final class Mappings extends PointerObject +public final class Mappings implements Comparator, Iterable, PlanCopyable { @@ -119,16 +115,6 @@ public static Mappings create(QueryPlan q, boolean subEval) { return lMap; } - @Override - @SuppressWarnings("unchecked") // Every Mapping is an Object; preserve the historical self view. - public Iterable getLoop() { - return (Iterable) (Iterable) this; - } - - @Override - public String getDatatypeLabel() { - return String.format("[Mappings: size=%s]", size()); - } void init(QueryPlan q, boolean subEval) { initiate(q, !subEval && q.isDistinct() && !q.isDistinctRedundantWithGroupBy()); @@ -220,7 +206,6 @@ public Iterator iterator() { return getMappingList().iterator(); } - @Override public int size() { return getMappingList().size(); } @@ -245,7 +230,6 @@ public void clear() { getMappingList().clear(); } - @Override public QueryPlan getQuery() { return query; } @@ -300,8 +284,7 @@ void print(Mapping map, Node qNode, StringBuilder sb, boolean ptr) { if (node != null) { sb.append(qNode).append(" = ").append(node); Object obj = node.getNodeObject(); - if (ptr && obj != this - && obj instanceof PointerObject) { + if (ptr && obj != this) { sb.append(" : \n"); sb.append(obj); } @@ -338,14 +321,6 @@ public DatatypeValue getValue(String varString) { return node.getDatatypeValue(); } - @Override - // PRAGMA: Do **not** take var into account - public Object getValue(String varString, int n) { - if (n >= size()) { - return null; - } - return get(n); - } @@ -1196,10 +1171,6 @@ public Mappings limit(int n) { return this; } - @Override - public TripleStore getTripleStore() { - return null; - } public List getInsert() { return insert; @@ -1219,12 +1190,6 @@ public void setDelete(List lDelete) { - @Override - public PointerType pointerType() { - return MAPPINGS; - } - - @Override public Mappings getMappings() { return this; } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Memory.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Memory.java index 101e48876..abfefe48f 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Memory.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Memory.java @@ -3,7 +3,6 @@ import fr.inria.corese.core.next.query.api.exception.QueryTypeErrorException; import fr.inria.corese.core.next.query.impl.engine.eval.Eval; -import fr.inria.corese.core.next.query.impl.engine.eval.PointerObject; import fr.inria.corese.core.next.query.impl.engine.eval.Stack; import fr.inria.corese.core.next.query.impl.engine.event.ProcessVisitor; import fr.inria.corese.core.next.query.impl.engine.model.BindingContext; @@ -37,7 +36,7 @@ * * @author Olivier Corby, Edelweiss, INRIA 2009 */ -public class Memory extends PointerObject implements Environment { +public class Memory implements Environment { // number of times nodes are bound by Stack // decrease with backtrack @@ -945,15 +944,6 @@ public boolean hasBind() { return bindingContext != null && bindingContext.hasBind(); } - /** - * List of variable binding - * - */ - @Override - public Iterable getLoop() { - return new ArrayList<>(0); - } - List> getList() { ArrayList> list = new ArrayList<>(); int i = 0; @@ -974,7 +964,6 @@ List> getList() { * let (((?var, ?val)) = ?m) * let ((?x, ?y) = ?m) */ - @Override public Object getValue(String varString, int n) { if (varString == null) { // let (((?var, ?val)) = ?m) -- ?m : Mapping diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalVisitorDispatcherTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalVisitorDispatcherTest.java index ff0a16b82..9f08e2a8f 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalVisitorDispatcherTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalVisitorDispatcherTest.java @@ -6,7 +6,6 @@ import fr.inria.corese.core.next.query.impl.engine.event.ResultListener; import fr.inria.corese.core.next.query.impl.engine.model.Edge; import fr.inria.corese.core.next.query.impl.engine.model.Expr; -import fr.inria.corese.core.next.query.impl.engine.model.Loopable; import fr.inria.corese.core.next.query.impl.engine.model.Node; import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; import org.junit.jupiter.api.BeforeEach; @@ -35,7 +34,6 @@ void testDefaults() { assertNotNull(dispatcher.getVisitor()); assertTrue(dispatcher.getVisitor() instanceof ProcessVisitorDefault); assertFalse(dispatcher.hasStatement()); - assertFalse(dispatcher.hasProduce()); assertFalse(dispatcher.hasCandidate()); assertFalse(dispatcher.hasFilter()); assertFalse(dispatcher.hasListener()); @@ -47,7 +45,6 @@ void testDefaults() { void testCustomVisitorFlags() { ProcessVisitor mockVisitor = mock(ProcessVisitor.class); when(mockVisitor.statement()).thenReturn(true); - when(mockVisitor.produce()).thenReturn(true); when(mockVisitor.candidate()).thenReturn(false); when(mockVisitor.filter()).thenReturn(true); @@ -55,7 +52,6 @@ void testCustomVisitorFlags() { assertSame(mockVisitor, dispatcher.getVisitor()); assertTrue(dispatcher.hasStatement()); - assertTrue(dispatcher.hasProduce()); assertFalse(dispatcher.hasCandidate()); assertTrue(dispatcher.hasFilter()); } @@ -67,7 +63,6 @@ void testNullVisitorFallback() { assertNotNull(dispatcher.getVisitor()); assertTrue(dispatcher.getVisitor() instanceof ProcessVisitorDefault); assertFalse(dispatcher.hasStatement()); - assertFalse(dispatcher.hasProduce()); assertFalse(dispatcher.hasCandidate()); assertFalse(dispatcher.hasFilter()); } @@ -109,22 +104,4 @@ void testListenerBypassWhenNull() { Edge entity = mock(Edge.class); assertTrue(dispatcher.listen(exp, qEdge, entity)); } - - @Test - @DisplayName("Should dispatch produce to visitor and wrap loopable results") - void testProduceDispatching() { - ProcessVisitor mockVisitor = mock(ProcessVisitor.class); - Eval mockEval = mock(Eval.class); - Node mockNode = mock(Node.class); - Edge mockEdge = mock(Edge.class); - - DatatypeValue mockDt = mock(DatatypeValue.class, withSettings().extraInterfaces(Loopable.class)); - List elements = Collections.emptyList(); - doReturn(elements).when((Loopable) mockDt).getLoop(); - when(mockVisitor.produce(mockEval, mockNode, mockEdge)).thenReturn(mockDt); - - dispatcher.setVisitor(mockVisitor); - Iterable produced = dispatcher.produce(mockEval, mockNode, mockEdge); - assertNotNull(produced); - } } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/ExpTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/ExpTest.java index b650ad3fe..273a2087f 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/ExpTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/ExpTest.java @@ -636,13 +636,6 @@ void testToStringNotNull() { String str = exp.toString(); assertNotNull(str, "toString should not return null"); } - - @Test - @DisplayName("Should get datatype label") - void testGetDatatypeLabel() { - String label = exp.getDatatypeLabel(); - assertNotNull(label, "Datatype label should not be null"); - } } @Nested @@ -656,13 +649,6 @@ void testIsStatement() { assertDoesNotThrow(() -> exp.isStatement(), "isStatement should not throw"); } - - @Test - @DisplayName("Should get statement") - void testGetStatement() { - assertDoesNotThrow(() -> exp.getStatement(), - "getStatement should not throw"); - } } @Nested diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlanTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlanTest.java index 805d6a9e8..b155e4a55 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlanTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlanTest.java @@ -658,13 +658,6 @@ void testToString() { String str = query.toString(); assertNotNull(str, "toString should not return null"); } - - @Test - @DisplayName("Should get datatype label") - void testGetDatatypeLabel() { - String label = query.getDatatypeLabel(); - assertEquals("[QueryPlan]", label, "Datatype label should be [QueryPlan]"); - } } @Nested diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingTest.java index e05bcf2ce..b863c83ef 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingTest.java @@ -175,15 +175,6 @@ void testGetQueryNodes() { @DisplayName("Comparison Tests") class ComparisonTests { - @Test - @DisplayName("Should compare mappings") - void testCompareMappings() { - Mapping other = new Mapping(); - - assertDoesNotThrow(() -> mapping.compare(other), - "compare should not throw exception"); - } - @Test @DisplayName("Mapping should equal itself") void testMappingEqualsItself() { diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsTest.java index d2716a039..0b5aa0252 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsTest.java @@ -736,21 +736,6 @@ void testSetGetLength() { } } - @Nested - @DisplayName("Datatype Label Tests") - class DatatypeLabelTests { - - @Test - @DisplayName("Should return datatype label with size") - void testGetDatatypeLabel() { - mappings.add(mockMapping); - String label = mappings.getDatatypeLabel(); - - assertNotNull(label, "Label should not be null"); - assertTrue(label.contains("Mappings"), "Label should contain 'Mappings'"); - assertTrue(label.contains("size=1"), "Label should contain size"); - } - } @Nested @@ -770,17 +755,4 @@ void testCompare() { assertEquals(0, result, "Empty mappings should be equal"); } } - - @Nested - @DisplayName("Loop Interface Tests") - class LoopInterfaceTests { - - @Test - @DisplayName("Should return iterable for loop") - void testGetLoop() { - Iterable loop = mappings.getLoop(); - assertNotNull(loop, "Loop should not be null"); - assertEquals(mappings, loop, "Loop should return self"); - } - } } From ea3375ec121c09c33e74ac73b12c943e8c4dde74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?= Date: Mon, 26 Oct 2026 17:38:07 +0100 Subject: [PATCH 3/7] refactor(solution): decompose Mappings god class into specialized sorter, distinct filter, aggregator, and relational operations --- .../query/impl/engine/solution/Mappings.java | 967 +++--------------- .../engine/solution/MappingsAggregator.java | 304 ++++++ .../solution/MappingsDistinctFilter.java | 138 +++ .../MappingsRelationalOperations.java | 196 ++++ .../impl/engine/solution/MappingsSorter.java | 253 +++++ .../solution/MappingsAggregatorTest.java | 96 ++ .../solution/MappingsDistinctFilterTest.java | 70 ++ .../MappingsRelationalOperationsTest.java | 75 ++ .../engine/solution/MappingsSorterTest.java | 89 ++ 9 files changed, 1341 insertions(+), 847 deletions(-) create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsAggregator.java create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsDistinctFilter.java create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsRelationalOperations.java create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsSorter.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsAggregatorTest.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsDistinctFilterTest.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsRelationalOperationsTest.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsSorterTest.java diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mappings.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mappings.java index 21ac32e83..3c5cdfe46 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mappings.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mappings.java @@ -1,50 +1,39 @@ package fr.inria.corese.core.next.query.impl.engine.solution; -import fr.inria.corese.core.next.query.impl.engine.pattern.PlanCopy; -import fr.inria.corese.core.next.query.impl.engine.pattern.PlanCopyable; -import fr.inria.corese.core.next.query.api.exception.QueryTypeErrorException; +import fr.inria.corese.core.next.data.api.model.DatatypeValue; import fr.inria.corese.core.next.query.impl.engine.eval.Eval; import fr.inria.corese.core.next.query.impl.engine.model.Edge; -import fr.inria.corese.core.next.query.impl.engine.model.Filter; import fr.inria.corese.core.next.query.impl.engine.model.Node; -import fr.inria.corese.core.next.query.impl.engine.model.TripleStore; import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; import fr.inria.corese.core.next.query.impl.engine.pattern.Group; +import fr.inria.corese.core.next.query.impl.engine.pattern.PlanCopy; +import fr.inria.corese.core.next.query.impl.engine.pattern.PlanCopyable; import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; - -import fr.inria.corese.core.next.query.impl.engine.spi.Environment; import fr.inria.corese.core.next.query.impl.engine.spi.Evaluator; import fr.inria.corese.core.next.query.impl.engine.spi.Producer; -import fr.inria.corese.core.next.data.api.model.DatatypeValue; + import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; /** - * Manage list of Mapping, result of a query - * - * process select distinct - * process group by, order by, limit offset, aggregates, having(?count>50) + * Manage list of Mapping, result of a query. + * Delegates sorting, distinct filtering, relational set operations, and aggregation + * to specialized single-responsibility components. * * @author Olivier Corby, Edelweiss, INRIA 2009 */ public final class Mappings implements Comparator, Iterable, PlanCopyable { - private static final String NL = "\n"; - private static final int SELECT = -1; - private static final int HAVING = -2; - // SPARQL: -1 (unbound first) - // Corese order: 1 (unbound last) - private static final int UNBOUND_ORDER = -1; - @SuppressWarnings("java:S1845") // select field holds projection nodes, distinct from SELECT constant + + @SuppressWarnings("java:S1845") // select field holds projection nodes List select; boolean isDistinct = false; - // statisfy having(test) + // satisfy having(test) boolean isValid = true; // if true, store all Mapping of the group boolean isListGroup = false; @@ -53,7 +42,6 @@ public final class Mappings private QueryPlan query; private final List list; // Original join Mappings - // use case: union manage its own way private Mappings joinMappings; // Update result private List insert; @@ -68,7 +56,6 @@ public final class Mappings // parse error in service result private boolean error = false; // return Binding stack as part of result to share it - // Federate Service manage provenance private Object provenance; // Linked Result URL List private final List link; @@ -76,14 +63,18 @@ public final class Mappings private int length = 0; // limit number of results to be displayed private int display = Integer.MAX_VALUE; - private static volatile GroupAggregator groupAggregator; + + private final MappingsSorter sorter; + private final MappingsDistinctFilter distinctFilter; + private final MappingsRelationalOperations relationalOps; + private final MappingsAggregator aggregator; public static void setGroupAggregator(GroupAggregator aggregator) { - groupAggregator = aggregator; + MappingsAggregator.setGroupAggregator(aggregator); } public static GroupAggregator getGroupAggregator() { - return groupAggregator; + return MappingsAggregator.getGroupAggregator(); } /** Copies a static VALUES table into independent row containers. */ @@ -91,13 +82,19 @@ public static GroupAggregator getGroupAggregator() { public Mappings copyPlan(PlanCopy copies) { Mappings target = new Mappings(); copies.remember(this, target); - for (Mapping row : list) target.add(copies.copy(row)); + for (Mapping row : list) { + target.add(copies.copy(row)); + } return target; } public Mappings() { list = new ArrayList<>(); link = new ArrayList<>(); + sorter = new MappingsSorter(this); + distinctFilter = new MappingsDistinctFilter(this); + relationalOps = new MappingsRelationalOperations(this); + aggregator = new MappingsAggregator(this); } Mappings(QueryPlan q) { @@ -111,11 +108,12 @@ public static Mappings create(QueryPlan q) { public static Mappings create(QueryPlan q, boolean subEval) { Mappings lMap = new Mappings(q); - lMap.init(q, subEval); + if (q != null) { + lMap.init(q, subEval); + } return lMap; } - void init(QueryPlan q, boolean subEval) { initiate(q, !subEval && q.isDistinct() && !q.isDistinctRedundantWithGroupBy()); } @@ -125,58 +123,23 @@ void initiate(QueryPlan q, boolean b) { } void initiate(QueryPlan q, boolean b, boolean all) { - this.setQuery(q); - isDistinct = b; - isListGroup = q.isListGroup(); - setSelect(q.getSelect()); - - if (isDistinct) { - if (all) { - setDistinct(group(getAllExpList(q))); - } else { - setDistinct(group(q.getSelectFun())); - } - getDistinct().setDistinct(true); - getDistinct().setDuplicate(q.isDistribute()); - } + distinctFilter.initiate(q, b, all); } - // use case: service require distinct mappings for bindings - // for variables in body of q, not for select (exp as var) List getAllExpList(QueryPlan q) { - // variables in body of q - List nodesFromPattern = q.selectNodesFromPattern(); - if (nodesFromPattern.isEmpty()) { - return q.getSelectFun(); - } else { - return q.toExp(nodesFromPattern); - } + return distinctFilter.getAllExpList(q); } public Mappings distinct() { - Mappings res = new Mappings(); - res.initiate(getQuery(), true, true); - for (Mapping m : this) { - res.submit(m); - } - return res; + return distinctFilter.distinct(); } public Mappings distinct(List list) { - return distinct(getQuery().getSelect(), list); + return distinctFilter.distinct(list); } public Mappings distinct(List selectList, List distinctList) { - Mappings map = new Mappings(getQuery()); - map.setSelect(selectList); - Group distinctListGroup = Group.create(distinctList); - distinctListGroup.setDistinct(true); - for (Mapping m : this) { - if (distinctListGroup.isDistinct(m)) { - map.add(m); - } - } - return map; + return distinctFilter.distinct(selectList, distinctList); } public boolean isDistinct() { @@ -193,41 +156,42 @@ public Mappings add(Mapping m) { } public List getList() { - return getMappingList(); + return list; } - public void add(Mappings lm) { - getMappingList().addAll(lm.getMappingList()); + for (Mapping m : lm) { + add(m); + } } @Override public Iterator iterator() { - return getMappingList().iterator(); + return list.iterator(); } public int size() { - return getMappingList().size(); + return list.size(); } public boolean isEmpty() { - return size() == 0; + return list.isEmpty(); } public Mapping get(int n) { - return getMappingList().get(n); + return list.get(n); } public Mapping set(int n, Mapping m) { - return getMappingList().set(n, m); + return list.set(n, m); } public void remove(int n) { - getMappingList().remove(n); + list.remove(n); } public void clear() { - getMappingList().clear(); + list.clear(); } public QueryPlan getQuery() { @@ -241,133 +205,78 @@ public Mappings setQuery(QueryPlan q) { @Override public String toString() { - return toString(false); + return toString(true); } public String toString(boolean all) { - return toString(all, false, size()); + return toString(all, false, getDisplay()); } public String toString(boolean all, boolean ptr, int max) { StringBuilder sb = new StringBuilder(); - int i = 1; - boolean isSelect = select != null && !all; - + if (all && getSelect() != null) { + for (Node qNode : getSelect()) { + sb.append(qNode.getLabel()).append(" "); + } + sb.append(NL); + } + int i = 0; for (Mapping map : this) { - if (i > max) { - sb.append(String.format("# size = %s, stop after: %s", size(), (i - 1))); - sb.append(NL); + if (i++ >= max) { break; } - String str = ((i < 10) ? "0" : "") + i + " "; - sb.append(str); - - if (isSelect) { - for (Node qNode : select) { - print(map, qNode, sb, ptr); - - } - } else { - for (Node qNode : map.getQueryNodes()) { - print(map, qNode, sb, ptr); - } - } - - i++; + sb.append(map.toString()); sb.append(NL); } return sb.toString(); } - void print(Mapping map, Node qNode, StringBuilder sb, boolean ptr) { - Node node = map.getNode(qNode); - if (node != null) { - sb.append(qNode).append(" = ").append(node); - Object obj = node.getNodeObject(); - if (ptr && obj != this) { - sb.append(" : \n"); - sb.append(obj); - } - sb.append("; "); - } - } - public List getSelect() { return select; } - void setSelect(List nodes) { - select = nodes; + void setSelect(List lNode) { + select = lNode; } public void setSelect(Node node) { - select = new ArrayList<>(1); + select = new ArrayList<>(); select.add(node); } public Node getNode(String varString) { - if (size() == 0) { + if (isEmpty()) { return null; } - Mapping map = get(0); - return map.getNode(varString); + return get(0).getNode(varString); } public DatatypeValue getValue(String varString) { Node node = getNode(varString); - if (node == null) { - return null; + if (node != null) { + return node.getDatatypeValue(); } - return node.getDatatypeValue(); + return null; } - - - - /** - * select distinct in case of aggregates, accept Mapping now, distinct will - * be computed below - */ public void submit(Mapping a) { - if (a == null) { - return; - } - if (acceptable(a)) { - add(a); - } + distinctFilter.submit(a); } public boolean acceptable(Mapping m) { - return getQuery().isAggregate() || accept(m); + return distinctFilter.acceptable(m); } - /** - * Used for distinct on aggregates - */ void submit2(Mapping a) { - if (getQuery().isAggregate()) { - if (accept(a)) { - add(a); - } - } else { - add(a); - } + distinctFilter.submit2(a); } - public boolean accept(Node node) { - return getDistinct() == null || getDistinct().accept(node); + return distinctFilter.accept(node); } - // Note: check select == null public boolean accept(Mapping r) { - if (select == null || select.isEmpty()) { - return true; - } - if (isDistinct) { - return getDistinct().isDistinct(r); - } - return true; + return distinctFilter.accept(r); } boolean isValid() { @@ -378,490 +287,75 @@ void setValid(boolean b) { isValid = b; } - - /* - * - * MappingSet sort - * - ********************************/ - public void sort(List varList) { - Collections.sort(getMappingList(), new VariableSorter(varList)); + sorter.sort(varList); } - - /** - * Is there a Mapping compatible with m - */ int find(Mapping m, List list) { - return find(m, getVariableSorter(list), 0, size() - 1); - } - - - VariableSorter getVariableSorter(List varList) { - return new VariableSorter(varList); - } - - int find(Mapping m, VariableSorter vs, int first, int last) { - if (first >= last) { - return first; - } else { - int mid = (first + last) / 2; - Mapping mm = get(mid); - int res = vs.compare(mm, m); - if (res >= 0) { - return find(m, vs, first, mid); - } else { - return find(m, vs, mid + 1, last); - } - } + return sorter.find(m, list); } boolean minusCompatible(Mapping m, List list) { - int n = find(m, getVariableSorter(list), 0, size() - 1); - if (n >= 0 && n < size()) { - Mapping mm = get(n); - return m.minusCompatible(mm, list); - } - return false; + return sorter.minusCompatible(m, list); } void sort(Eval eval) { - this.setEval(eval); - sort(); - this.setEval(null); + sorter.sort(eval); } - - /* - * - * Standard sort - * order by - * join() - * - *****************************************/ - void sort() { - getMappingList().sort(this); + sorter.sort(); } - /** - * Sort according to node - * use case: join(exp, exp) - */ public void sort(Eval eval, Node node) { - prepare(node); - sort(eval); + sorter.sort(eval, node); } public void sort(Node node) { - prepare(node); - sort(); + sorter.sort(node); } - void prepare(Node node) { - sortWithDesc = false; - for (Mapping m : this) { - m.setOrderBy(m.getNode(node)); - } - } - - // find index of node where qnode=node - // with standard sort public int find(Node node, Node qnode) { - return find(node, qnode, 0, size() - 1); - } - - /** - * comparator of Node for standard sort - * use DatatypeValue compareTo() - * compare with sameTerm semantics: order 1 and 01 in deterministic way - * authorize overload of comparator for specific datatypes using a Visitor - */ - int comparator(Node n1, Node n2) { - if (getEval() != null) { - return getEval().getVisitor().compare(getEval(), n1.compare(n2), n1.getDatatypeValue(), n2.getDatatypeValue()); - } - return n1.compare(n2); + return sorter.find(node, qnode); } - // comparator of Mapping for standard sort @Override public int compare(Mapping m1, Mapping m2) { - Node[] order1 = m1.getOrderBy(); - Node[] order2 = m2.getOrderBy(); - List orderBy = getQuery().getOrderBy(); - int res = 0; - - for (int i = 0; i < order1.length && i < order2.length && res == 0; i++) { - if (order1[i] != null && order2[i] != null) { // sort ?x - res = comparator(order1[i], order2[i]); - } // unbound - else if (order1[i] == null) { // unbound var - if (order2[i] == null) { - res = 0; - } else { - res = UNBOUND_ORDER; - } - } else if (order2[i] == null) { - res = -UNBOUND_ORDER; - } else { - res = 0; - } - if (sortWithDesc && !orderBy.isEmpty() && orderBy.get(i).status()) { - res = desc(res); - } - } - - return res; - } - - int desc(int i) { - if (i == 0) { - return 0; - } else if (i < 0) { - return +1; - } else { - return -1; - } - } - - // find index of node where qnode=node - int find(Node node, Node qnode, int first, int last) { - if (first >= last) { - return first; - } else { - int mid = (first + last) / 2; - Node n1 = getMappingList().get(mid).getNodeValue(qnode); - int res = compare(n1, node); - if (res >= 0) { - return find(node, qnode, first, mid); - } else { - return find(node, qnode, mid + 1, last); - } - } - } - - // standard node comparator where argument may be null - int compare(Node n1, Node n2) { - int res; - if (n1 != null && n2 != null) { // sort ?x - res = comparator(n1, n2); - } // unbound - else if (n1 == null) { // unbound var - if (n2 == null) { - res = 0; - } else { - res = UNBOUND_ORDER; - } - } else { - res = -UNBOUND_ORDER; - } - return res; - } - - int genCompare(Node n1, Node n2) { - return compare(n1, n2); + return sorter.compare(m1, m2); } - /* - * Alternative sorter - * use case: gui - * - **************************/ - - /** - * order by limit offset - */ public void complete(Eval eval) { - if (!getQuery().getOrderBy().isEmpty()) { - sort(eval); - } - limitOffset(); + sorter.complete(eval); } void limitOffset() { - if (getQuery().getOffset() > 0) { - // skip offset - // Note: optimize this - for (int i = 0; i < getQuery().getOffset() && size() > 0; i++) { - remove(0); - } - } - while (size() > getQuery().getLimit()) { - remove(size() - 1); - } + sorter.limitOffset(); } - - /* - * ********************************************************* - * - * Aggregates - * - * 1. select [distinct] var where 2. group by 3. count/min/max as var 4. - * order by 5. limit offset - * - * group by with aggregate return one Mapping per group where the mapping - * hold the result of the aggregate - * - */ - public void aggregate(Evaluator evaluator, Memory memory, Producer p) { - aggregate(getQuery(), evaluator, memory, p); + aggregator.aggregate(evaluator, memory, p); } public void aggregate(QueryPlan q, Evaluator evaluator, Memory memory, Producer p) { - if (size() == 0) { - if (q.isAggregate() && !q.hasGroupBy()) { - // SPARQL semantics requires that aggregate empty result set return one empty result - // and count() return 0 - add(Mapping.fake(q)); - setFake(true); - } else { - return; - } - } - - GroupAggregator aggregator = groupAggregator; - if (aggregator != null && aggregator.tryAggregate(q, this)) { - finish(q); - return; - } - - // select (count(?n) as ?count) - aggregateExpList(q, evaluator, memory, p, q.getSelectFun(), true); - - // order by with aggregate count(?n) - aggregateExpList(q, evaluator, memory, p, q.getOrderBy(), false); - - if (q.getHaving() != null) { - aggregateSwitch(q, evaluator, q.getHaving(), memory, p, HAVING); - } - - finish(q); - } - - /** - * list = list of select | order by - * select (aggregate() as ?c) - * order by aggregate() - */ - void aggregateExpList(QueryPlan q, Evaluator evaluator, Memory memory, Producer p, List list, boolean isSelect) { - int n = 0; - for (Exp exp : list) { - if (exp.isAggregate()) { - // perform group by and then aggregate - aggregateSwitch(q, evaluator, exp, memory, p, (isSelect) ? SELECT : n); - } - if (!isSelect) { - n++; - } - } + aggregator.aggregate(q, evaluator, memory, p); } - /** - * select count(?doc) as ?count group by ?person ?date order by ?count - */ - private void aggregateSwitch(QueryPlan q, Evaluator eval, Exp exp, Memory mem, Producer p, int n) { - if (exp.isExpGroupBy()) { - // min(?l, groupBy(?x, ?y)) as ?min - evalGroupByExp(eval, exp, mem, p, n); - } else if (q.hasGroupBy()) { - // perform group by and then aggregate - aggregateGroupMembers(getCreateGroup(), eval, exp, mem, p, n); - } else { - aggregate(eval, exp, mem, p, n); - } - } - - /** - * Compute select aggregate, order by aggregate and having on one group or on - * whole result (in both case: this Mappings) - */ - private void aggregate(Evaluator eval, Exp exp, Memory memory, Producer p, int n) { - // get first Mapping in current group - Mapping firstMap = get(0); - // bind the Mapping in memory to retrieve group by variables - memory.aggregate(firstMap); - - if (n == HAVING) { - aggregateHaving(eval, exp, memory, p); - } else { - aggregateSelectOrOrder(eval, exp, memory, p, n); - } - - memory.pop(firstMap); - } - - private void aggregateHaving(Evaluator eval, Exp exp, Memory memory, Producer p) { - boolean res = false; - try { - res = exp.getFilter().getExp().test(eval, memory.getBind(), memory, p); - } catch (QueryTypeErrorException ignored) { - res = false; - } - Eval ev = memory.getEval(); - if (ev != null) { - ev.getVisitor().having(ev, exp.getFilter().getExp(), res); - } - setValid(res); - } - - private void aggregateSelectOrOrder(Evaluator eval, Exp exp, Memory memory, Producer p, int n) { - Eval ev = memory.getEval(); - Node aggregateValue; - if (exp.getFilter() == null) { - // use case: order by var - aggregateValue = memory.getNode(exp.getNode()); - } else { - // Delegate the aggregate expression to the configured evaluator. - aggregateValue = eval(exp.getFilter(), eval, memory, p); - if (ev != null) { - ev.getVisitor().aggregate(ev, exp.getFilter().getExp(), - (aggregateValue == null) ? null : aggregateValue.getDatatypeValue()); - } - } - - for (Mapping map : this) { - if (n == SELECT) { - // select (count(?x) as ?c) - map.setNode(exp.getNode(), aggregateValue); - } else { - // order by count(?x) - map.setOrderBy(n, aggregateValue); - } - } - } - - Node eval(Filter f, Evaluator eval, Environment env, Producer p) { - try { - return p.getNode(f.getExp().evalWE(eval, env.getBind(), env, p)); - } catch (QueryTypeErrorException e) { - return null; - } - } - - /** - * Process aggregate for each group select, order by, having - */ - private void aggregateGroupMembers(Group group, Evaluator eval, Exp exp, Memory mem, Producer p, int n) { - int mappingCount = 0; - for (Mappings map : group.getValues()) { - map.setCount(mappingCount++); - mem.setGroup(map); - map.aggregate(eval, exp, mem, p, n); - mem.setGroup(null); - } - } - - void finish(QueryPlan qq) { - if (qq.hasGroupBy() && !qq.isConstruct()) { - // after group by (and aggregate), leave one Mapping for each group - // with result of the group - groupBy(); - } else if (qq.getHaving() != null) { - // clause 'having' with no group by - // e.g. select (max(?x) as ?max) having(?max > 100) - having(); - } else if (qq.isAggregate() && !qq.isConstruct()) { - clean(); - } - } - - void having() { - if (isValid()) { - clean(); - } else { - clear(); - } - } - - void clean() { - if (size() > 1) { - Mapping map = get(0); - clear(); - add(map); - } + void aggregate(Evaluator eval, Exp exp, Memory memory, Producer p, int n) { + aggregator.aggregate(eval, exp, memory, p, n); } public void prepareAggregate(Mapping map, QueryPlan q, Map bn, int n) { - setCount(n); - // in case there is a nested aggregate, map will be an Environment - // it must implement aggregate() and hence must know current Mappings group - map.setMappings(this); - map.setQuery(q); - // share same bnode table in all Mapping of current group solution - map.setMap(bn); - } - - // min(?l, groupBy(?x, ?y)) as ?min - void evalGroupByExp(Evaluator eval, Exp exp, Memory mem, Producer p, int n) { - Group g = createGroup(exp); - aggregateGroupMembers(g, eval, exp, mem, p, n); - if (exp.isHaving()) { - // min(?l, groupBy(?x, ?y), (?l = ?min)) as ?min - having(eval, exp, mem, p, g); - // remove global group if any - // may be recomputed with new Mapping list - setGroup(null); - } + aggregator.prepareAggregate(map, q, bn, n); } - /** - * exp : min(?l, groupBy(?x, ?y), (?l = ?min)) as ?min) test the filter, - * remove Mappping that fail - */ - void having(Evaluator eval, Exp exp, Memory mem, Producer p, Group g) { - Filter f = exp.getHavingFilter(); - clear(); - for (Mappings lm : g.getValues()) { - for (Mapping map : lm) { - mem.push(map, -1); - if (f.getExp().test(eval, mem.getBind(), mem, p)) { - add(map); - } - mem.pop(map); - } - } - } - - /** - * process group by leave one Mapping within each group - */ public void groupBy() { - // clear the current list - groupBy(getCreateGroup()); + aggregator.groupBy(); } - /** - * Generate the Mapping list according to the group PRAGMA: replace the - * original list by the group list - */ public void groupBy(Group group) { - clear(); - for (Mappings lMap : group.getValues()) { - if (lMap.isValid()) { - // clause 'having' may have tagged first mapping as not valid - Mapping map = lMap.get(0); - if (map != null) { - if (isListGroup) { - map.setMappings(lMap); - } else { - // it may have been set by aggregate (see process) - map.setMappings(null); - } - } - // add one element for current group - // check distinct if any - submit2(map); - } - } + aggregator.groupBy(group); } - /** - * Project on select variables of query Modify all the Mapping - */ public Mappings project() { for (Mapping map : this) { map.project(getQuery()); @@ -880,20 +374,6 @@ public Mappings project(Node q) { return map; } - /** - * for group by ?o1 .. ?on - */ - private Group createGroup() { - return createGroup(getQuery().getGroupBy()); - } - - private Group getCreateGroup() { - if (getGroup() == null) { - setGroup(createGroup()); - } - return getGroup(); - } - public Group getGroup() { return group; } @@ -902,245 +382,76 @@ public void setGroup(Group g) { group = g; } - /** - * group by - */ + public Group getCreateGroup() { + return aggregator.getCreateGroup(); + } + + Group createGroup() { + return aggregator.createGroup(); + } + Group createGroup(List list) { - return createGroup(list, false); + return aggregator.createGroup(list); } Group createGroup(Exp exp) { - return createGroup(exp.getExpGroupBy(), true); + return aggregator.createGroup(exp); } Group createGroup(List list, boolean extend) { - Group gp = Group.createFromExp(list); - gp.setDuplicate(getQuery().isDistribute()); - gp.setExtend(extend); - gp.setFake(isFake()); - - for (Mapping map : this) { - gp.add(map); - } - return gp; + return aggregator.createGroup(list, extend); } - /** - * for select distinct - */ Group group(List list) { return Group.createFromExp(list); } public Node max(Node qNode) { - return minmax(qNode, true); + return aggregator.max(qNode); } public Node min(Node qNode) { - return minmax(qNode, false); - } - - private Node minmax(Node qNode, boolean isMax) { - Node res = null; - for (Mapping map : this) { - Node node = map.getNode(qNode); - if (res == null) { - res = node; - } else if (node != null) { - if (isMax) { - if (node.compare(res) > 0) { - res = node; - } - } else if (node.compare(res) < 0) { - res = node; - } - } - - } - return res; + return aggregator.min(qNode); } - /* - * - * Pipeline Solutions implementation These operations use the select nodes - * if any and otherwise the query nodes - * - * - ******************************************************************** - */ public Node getCommonNode(Mappings map) { - if (isEmpty() || map.isEmpty()) { - return null; - } - return get(0).getCommonNode(map.get(0)); + return relationalOps.getCommonNode(map); } - public Mappings union(Mappings lm) { - Mappings res = (getQuery() == lm.getQuery()) ? Mappings.create(getQuery()) : new Mappings(); - for (Mapping m : this) { - res.add(m); - } - for (Mapping m : lm) { - res.add(m); - } - return res; + return relationalOps.union(lm); } public Mappings and(Mappings lm) { - return join(lm); + return relationalOps.join(lm); } public Mappings join(Mappings lm) { - Mappings res = Mappings.create(getQuery()); - for (Mapping m1 : this) { - for (Mapping m2 : lm) { - Mapping map = m1.join(m2); - if (map != null) { - res.add(map); - } - } - } - - return res; + return relationalOps.join(lm); } public Mappings joiner(Mappings lm) { - Mappings res = Mappings.create(getQuery()); - for (Mapping m1 : this) { - for (Mapping m2 : lm) { - Mapping map = m1.merge(m2); - if (map != null) { - res.add(map); - } - } - } - return res; + return relationalOps.joiner(lm); } - // join with cmn common variable, map2 is sorted on cmn, null value first public Mappings joiner(Mappings map2, Node cmn) { - Mappings map1 = this; - Mappings res = Mappings.create(map1.getQuery()); - - for (Mapping m1 : map1) { - Node val = m1.getNodeValue(cmn); - if (val == null) { - // common unbound in m1 - joinCommonUnbound(res, m1, map2); - } else { - joinCommonBound(res, m1, map2, cmn, val); - } - } - return res; - } - - private void joinCommonUnbound(Mappings res, Mapping m1, Mappings map2) { - for (Mapping m2 : map2) { - Mapping m = m1.merge(m2); - if (m != null) { - res.add(m); - } - } - } - - private void joinCommonBound(Mappings res, Mapping m1, Mappings map2, Node cmn, Node val) { - joinPrefixUnbound(res, m1, map2, cmn); - joinMatchingRange(res, m1, map2, cmn, val); - } - - private void joinPrefixUnbound(Mappings res, Mapping m1, Mappings map2, Node cmn) { - for (Mapping m2 : map2) { - Node val2 = m2.getNodeValue(cmn); - if (val2 == null) { - // common unbound in m2 - Mapping m = m1.merge(m2); - if (m != null) { - res.add(m); - } - } else { - break; - } - } - } - - private void joinMatchingRange(Mappings res, Mapping m1, Mappings map2, Node cmn, Node val) { - // index of common value in map2 - int index = map2.find(val, cmn); - if (index >= 0 && index < map2.size()) { - for (int i = index; i < map2.size(); i++) { - // get value of common in map2 - Mapping m2 = map2.get(i); - Node n2 = m2.getNodeValue(cmn); - if (n2 == null || !val.match(n2)) { - break; - } - Mapping m = m1.merge(m2); - if (m != null) { - res.add(m); - } - } - } + return relationalOps.joiner(map2, cmn); } public Mappings minus(Mappings lm) { - Mappings res = new Mappings(); - for (Mapping m1 : this) { - boolean ok = true; - for (Mapping m2 : lm) { - if (m1.compatible(m2)) { - ok = false; - break; - } - } - if (ok) { - res.add(m1); - } - } - return res; + return relationalOps.minus(lm); } public Mappings optional(Mappings lm) { - return option(lm); + return relationalOps.option(lm); } public Mappings option(Mappings lm) { - Mappings res = new Mappings(); - for (Mapping m1 : this) { - boolean ok = false; - for (Mapping m2 : lm) { - Mapping map = m1.join(m2); - if (map != null) { - ok = true; - res.add(map); - } - } - if (!ok) { - res.add(m1); - } - } - - return res; + return relationalOps.option(lm); } - /** - * Join (var = val) to each Mapping, remove those where var = something else - * Use case: service ?s { BGP } - */ public void join(Node varNode, Node val) { - if (!getSelect().contains(varNode)) { - getSelect().add(varNode); - } - Iterator it = getMappingList().iterator(); - while (it.hasNext()) { - Mapping m = it.next(); - Node node = m.getNodeValue(varNode); - if (node == null) { - m.addNode(varNode, val); - } else if (!node.equals(val)) { - it.remove(); - } - } + relationalOps.join(varNode, val); } public boolean inScope(Node varNode) { @@ -1154,30 +465,19 @@ public boolean inScope(Node varNode) { } public List aggregate(Node varNode) { - List aggregateNodeList = new ArrayList<>(); - for (Mapping m : this) { - Node val = m.getNodeValue(varNode); - if (val != null && !aggregateNodeList.contains(val)) { - aggregateNodeList.add(val); - } - } - return aggregateNodeList; + return aggregator.aggregate(varNode); } - public Mappings limit(int n) { - while (size() > n) { - remove(size() - 1); - } - return this; + public Mappings limit(int n) { + return sorter.limit(n); } - public List getInsert() { return insert; } public void setInsert(List lInsert) { - this.insert = lInsert; + insert = lInsert; } public List getDelete() { @@ -1185,11 +485,9 @@ public List getDelete() { } public void setDelete(List lDelete) { - this.delete = lDelete; + delete = lDelete; } - - public Mappings getMappings() { return this; } @@ -1212,21 +510,17 @@ public Mappings setFake(boolean isFake) { } public boolean isNodeList() { - return size() != 0 && getNodeList() != null && !getNodeList().isEmpty(); + return nodeList != null; } public List getNodeList() { return nodeList; } - /** - * @param nodeList the nodeList to set - */ public void setNodeList(List nodeList) { this.nodeList = nodeList; } - public boolean isError() { return error; } @@ -1244,20 +538,20 @@ public void setProvenance(Object provenance) { } public void setNamedGraph(Node node) { - for (Mapping m : this) { - m.setNamedGraph(node); + for (Mapping map : this) { + map.setNamedGraph(node); } } - public String getLink() { - if (getLinkList().isEmpty()) { + public String getLink() { + if (link.isEmpty()) { return null; } - return getLinkList().getFirst(); + return link.get(0); } public void addLink(String link) { - getLinkList().add(link); + this.link.add(link); } public List getLinkList() { @@ -1299,25 +593,4 @@ public void setDistinct(Group distinct) { public List getMappingList() { return list; } - - class VariableSorter implements Comparator { - - List varList; - - VariableSorter(List list) { - this.varList = list; - } - - @Override - public int compare(Mapping m1, Mapping m2) { - int res = 0; - for (int i = 0; i < varList.size() && res == 0; i++) { - Node n1 = m1.getNodeValue(varList.get(i)); - Node n2 = m2.getNodeValue(varList.get(i)); - res = genCompare(n1, n2); - } - return res; - } - } - } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsAggregator.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsAggregator.java new file mode 100644 index 000000000..f57c5c202 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsAggregator.java @@ -0,0 +1,304 @@ +package fr.inria.corese.core.next.query.impl.engine.solution; + +import fr.inria.corese.core.next.data.api.model.DatatypeValue; +import fr.inria.corese.core.next.query.api.exception.QueryTypeErrorException; +import fr.inria.corese.core.next.query.impl.engine.eval.Eval; +import fr.inria.corese.core.next.query.impl.engine.model.Filter; +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.pattern.Group; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; +import fr.inria.corese.core.next.query.impl.engine.spi.Environment; +import fr.inria.corese.core.next.query.impl.engine.spi.Evaluator; +import fr.inria.corese.core.next.query.impl.engine.spi.Producer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Handles SPARQL GROUP BY, aggregates (COUNT, MIN, MAX, SUM, AVG, SAMPLE, GROUP_CONCAT), + * and HAVING filtering for {@link Mappings}. + */ +public final class MappingsAggregator { + + private static final int SELECT = -1; + private static final int HAVING = -2; + + private static volatile GroupAggregator groupAggregator; + + private final Mappings mappings; + + public MappingsAggregator(Mappings mappings) { + this.mappings = mappings; + } + + public static void setGroupAggregator(GroupAggregator aggregator) { + groupAggregator = aggregator; + } + + public static GroupAggregator getGroupAggregator() { + return groupAggregator; + } + + public void aggregate(Evaluator evaluator, Memory memory, Producer p) { + aggregate(mappings.getQuery(), evaluator, memory, p); + } + + public void aggregate(QueryPlan q, Evaluator evaluator, Memory memory, Producer p) { + if (mappings.size() == 0) { + if (q != null && q.isAggregate() && !q.hasGroupBy()) { + mappings.add(Mapping.fake(q)); + mappings.setFake(true); + } else { + return; + } + } + + GroupAggregator aggregator = groupAggregator; + if (aggregator != null && aggregator.tryAggregate(q, mappings)) { + finish(q); + return; + } + + if (q != null) { + aggregateExpList(q, evaluator, memory, p, q.getSelectFun(), true); + aggregateExpList(q, evaluator, memory, p, q.getOrderBy(), false); + if (q.getHaving() != null) { + aggregateSwitch(q, evaluator, q.getHaving(), memory, p, HAVING); + } + finish(q); + } + } + + void aggregateExpList(QueryPlan q, Evaluator evaluator, Memory memory, Producer p, List list, boolean isSelect) { + int n = 0; + for (Exp exp : list) { + if (exp.isAggregate()) { + aggregateSwitch(q, evaluator, exp, memory, p, isSelect ? SELECT : n); + } + if (!isSelect) { + n++; + } + } + } + + private void aggregateSwitch(QueryPlan q, Evaluator eval, Exp exp, Memory mem, Producer p, int n) { + if (exp.isExpGroupBy()) { + evalGroupByExp(eval, exp, mem, p, n); + } else if (q.hasGroupBy()) { + aggregateGroupMembers(getCreateGroup(), eval, exp, mem, p, n); + } else { + aggregate(eval, exp, mem, p, n); + } + } + + void aggregate(Evaluator eval, Exp exp, Memory memory, Producer p, int n) { + Mapping firstMap = mappings.get(0); + memory.aggregate(firstMap); + + if (n == HAVING) { + aggregateHaving(eval, exp, memory, p); + } else { + aggregateSelectOrOrder(eval, exp, memory, p, n); + } + + memory.pop(firstMap); + } + + private void aggregateHaving(Evaluator eval, Exp exp, Memory memory, Producer p) { + boolean res = false; + try { + res = exp.getFilter().getExp().test(eval, memory.getBind(), memory, p); + } catch (QueryTypeErrorException ignored) { + res = false; + } + Eval ev = memory.getEval(); + if (ev != null) { + ev.getVisitor().having(ev, exp.getFilter().getExp(), res); + } + mappings.setValid(res); + } + + private void aggregateSelectOrOrder(Evaluator eval, Exp exp, Memory memory, Producer p, int n) { + Eval ev = memory.getEval(); + Node aggregateValue; + if (exp.getFilter() == null) { + aggregateValue = memory.getNode(exp.getNode()); + } else { + aggregateValue = eval(exp.getFilter(), eval, memory, p); + if (ev != null) { + ev.getVisitor().aggregate(ev, exp.getFilter().getExp(), + (aggregateValue == null) ? null : aggregateValue.getDatatypeValue()); + } + } + + for (Mapping map : mappings) { + if (n == SELECT) { + map.setNode(exp.getNode(), aggregateValue); + } else { + map.setOrderBy(n, aggregateValue); + } + } + } + + Node eval(Filter f, Evaluator eval, Environment env, Producer p) { + try { + return p.getNode(f.getExp().evalWE(eval, env.getBind(), env, p)); + } catch (QueryTypeErrorException e) { + return null; + } + } + + private void aggregateGroupMembers(Group group, Evaluator eval, Exp exp, Memory mem, Producer p, int n) { + int mappingCount = 0; + for (Mappings map : group.getValues()) { + map.setCount(mappingCount++); + mem.setGroup(map); + map.aggregate(eval, exp, mem, p, n); + mem.setGroup(null); + } + } + + public void finish(QueryPlan qq) { + if (qq == null) { + return; + } + if (qq.hasGroupBy() && !qq.isConstruct()) { + groupBy(); + } else if (qq.getHaving() != null) { + having(); + } else if (qq.isAggregate() && !qq.isConstruct()) { + clean(); + } + } + + public void having() { + if (mappings.isValid()) { + clean(); + } else { + mappings.clear(); + } + } + + public void clean() { + if (mappings.size() > 1) { + Mapping map = mappings.get(0); + mappings.clear(); + mappings.add(map); + } + } + + public void prepareAggregate(Mapping map, QueryPlan q, Map bn, int n) { + mappings.setCount(n); + map.setMappings(mappings); + map.setQuery(q); + map.setMap(bn); + } + + void evalGroupByExp(Evaluator eval, Exp exp, Memory mem, Producer p, int n) { + Group g = createGroup(exp); + aggregateGroupMembers(g, eval, exp, mem, p, n); + if (exp.isHaving()) { + having(eval, exp, mem, p, g); + mappings.setGroup(null); + } + } + + void having(Evaluator eval, Exp exp, Memory mem, Producer p, Group g) { + Filter f = exp.getHavingFilter(); + mappings.clear(); + for (Mappings lm : g.getValues()) { + for (Mapping map : lm) { + mem.push(map, -1); + if (f.getExp().test(eval, mem.getBind(), mem, p)) { + mappings.add(map); + } + mem.pop(map); + } + } + } + + public void groupBy() { + groupBy(getCreateGroup()); + } + + public void groupBy(Group group) { + mappings.clear(); + for (Mappings lMap : group.getValues()) { + if (lMap.isValid()) { + Mapping map = lMap.get(0); + if (map != null) { + map.setMappings(mappings.isListGroup ? lMap : null); + } + mappings.submit2(map); + } + } + } + + public Group getCreateGroup() { + if (mappings.getGroup() == null) { + mappings.setGroup(createGroup()); + } + return mappings.getGroup(); + } + + public Group createGroup() { + QueryPlan query = mappings.getQuery(); + return createGroup((query != null) ? query.getGroupBy() : Collections.emptyList()); + } + + public Group createGroup(List list) { + return createGroup(list, false); + } + + public Group createGroup(Exp exp) { + return createGroup(exp.getExpGroupBy(), true); + } + + public Group createGroup(List list, boolean extend) { + Group gp = Group.createFromExp(list); + QueryPlan query = mappings.getQuery(); + if (query != null) { + gp.setDuplicate(query.isDistribute()); + } + gp.setExtend(extend); + gp.setFake(mappings.isFake()); + + for (Mapping map : mappings) { + gp.add(map); + } + return gp; + } + + public Node max(Node qNode) { + return minmax(qNode, true); + } + + public Node min(Node qNode) { + return minmax(qNode, false); + } + + private Node minmax(Node qNode, boolean isMax) { + Node res = null; + for (Mapping map : mappings) { + Node node = map.getNode(qNode); + if (node != null && (res == null || (isMax && node.compare(res) > 0) || (!isMax && node.compare(res) < 0))) { + res = node; + } + } + return res; + } + + public List aggregate(Node varNode) { + List aggregateNodeList = new ArrayList<>(); + for (Mapping m : mappings) { + Node val = m.getNodeValue(varNode); + if (val != null && !aggregateNodeList.contains(val)) { + aggregateNodeList.add(val); + } + } + return aggregateNodeList; + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsDistinctFilter.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsDistinctFilter.java new file mode 100644 index 000000000..266696547 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsDistinctFilter.java @@ -0,0 +1,138 @@ +package fr.inria.corese.core.next.query.impl.engine.solution; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.pattern.Group; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; + +import java.util.List; + +/** + * Handles DISTINCT filtering, deduplication, and solution acceptance for {@link Mappings}. + */ +public final class MappingsDistinctFilter { + + private final Mappings mappings; + + public MappingsDistinctFilter(Mappings mappings) { + this.mappings = mappings; + } + + /** + * Initializes distinct tracking based on query plan and execution options. + */ + public void initiate(QueryPlan q, boolean distinct, boolean all) { + mappings.setQuery(q); + mappings.isDistinct = distinct; + if (q != null) { + mappings.isListGroup = q.isListGroup(); + mappings.setSelect(q.getSelect()); + if (distinct) { + List expList = all ? getAllExpList(q) : q.getSelectFun(); + Group distinctGroup = Group.createFromExp(expList); + distinctGroup.setDistinct(true); + distinctGroup.setDuplicate(q.isDistribute()); + mappings.setDistinct(distinctGroup); + } + } + } + + /** + * Computes expression list representing all variables in pattern body or select functions. + */ + public List getAllExpList(QueryPlan q) { + List nodesFromPattern = q.selectNodesFromPattern(); + if (nodesFromPattern.isEmpty()) { + return q.getSelectFun(); + } + return q.toExp(nodesFromPattern); + } + + /** + * Returns a distinct copy of mappings across all variables in the query. + */ + public Mappings distinct() { + Mappings res = new Mappings(); + res.initiate(mappings.getQuery(), true, true); + for (Mapping m : mappings) { + res.submit(m); + } + return res; + } + + /** + * Returns a distinct copy of mappings based on a given node list. + */ + public Mappings distinct(List list) { + QueryPlan query = mappings.getQuery(); + List selectList = (query != null) ? query.getSelect() : null; + return distinct(selectList, list); + } + + /** + * Returns a distinct copy of mappings projected on selectList and deduplicated on distinctList. + */ + public Mappings distinct(List selectList, List distinctList) { + Mappings map = new Mappings(mappings.getQuery()); + map.setSelect(selectList); + Group distinctListGroup = Group.create(distinctList); + distinctListGroup.setDistinct(true); + for (Mapping m : mappings) { + if (distinctListGroup.isDistinct(m)) { + map.add(m); + } + } + return map; + } + + /** + * Submits a mapping to the result set if it passes the distinct acceptance check. + */ + public void submit(Mapping a) { + if (a != null && acceptable(a)) { + mappings.add(a); + } + } + + /** + * Submits a mapping during aggregation. + */ + public void submit2(Mapping a) { + QueryPlan query = mappings.getQuery(); + if (query != null && query.isAggregate()) { + if (accept(a)) { + mappings.add(a); + } + } else { + mappings.add(a); + } + } + + /** + * Checks if a mapping is acceptable given the query configuration. + */ + public boolean acceptable(Mapping m) { + QueryPlan query = mappings.getQuery(); + return (query != null && query.isAggregate()) || accept(m); + } + + /** + * Checks if a node is accepted by distinct group. + */ + public boolean accept(Node node) { + return mappings.getDistinct() == null || mappings.getDistinct().accept(node); + } + + /** + * Checks if a mapping is distinct according to current projection and distinct group. + */ + public boolean accept(Mapping r) { + if (mappings.select == null || mappings.select.isEmpty()) { + return true; + } + if (mappings.isDistinct && mappings.getDistinct() != null) { + return mappings.getDistinct().isDistinct(r); + } + return true; + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsRelationalOperations.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsRelationalOperations.java new file mode 100644 index 000000000..b95719a3d --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsRelationalOperations.java @@ -0,0 +1,196 @@ +package fr.inria.corese.core.next.query.impl.engine.solution; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; + +import java.util.Iterator; + +/** + * Handles relational set operations (join, union, minus, optional) on {@link Mappings}. + */ +public final class MappingsRelationalOperations { + + private final Mappings mappings; + + public MappingsRelationalOperations(Mappings mappings) { + this.mappings = mappings; + } + + /** + * Returns the common node between the first solutions of two mappings, or null. + */ + public Node getCommonNode(Mappings other) { + if (mappings.isEmpty() || other.isEmpty()) { + return null; + } + return mappings.get(0).getCommonNode(other.get(0)); + } + + /** + * Computes the union of two Mappings sets. + */ + public Mappings union(Mappings lm) { + Mappings res = (mappings.getQuery() != null && mappings.getQuery() == lm.getQuery()) + ? Mappings.create(mappings.getQuery()) + : new Mappings(); + for (Mapping m : mappings) { + res.add(m); + } + for (Mapping m : lm) { + res.add(m); + } + return res; + } + + /** + * Computes the inner join of two Mappings sets. + */ + public Mappings join(Mappings lm) { + Mappings res = Mappings.create(mappings.getQuery()); + for (Mapping m1 : mappings) { + for (Mapping m2 : lm) { + Mapping map = m1.join(m2); + if (map != null) { + res.add(map); + } + } + } + return res; + } + + /** + * Computes the merge joiner of two Mappings sets. + */ + public Mappings joiner(Mappings lm) { + Mappings res = Mappings.create(mappings.getQuery()); + for (Mapping m1 : mappings) { + for (Mapping m2 : lm) { + Mapping map = m1.merge(m2); + if (map != null) { + res.add(map); + } + } + } + return res; + } + + /** + * Optimized joiner when map2 is sorted on common variable cmn. + */ + public Mappings joiner(Mappings map2, Node cmn) { + Mappings res = Mappings.create(mappings.getQuery()); + for (Mapping m1 : mappings) { + Node val = m1.getNodeValue(cmn); + if (val == null) { + joinCommonUnbound(res, m1, map2); + } else { + joinCommonBound(res, m1, map2, cmn, val); + } + } + return res; + } + + private void joinCommonUnbound(Mappings res, Mapping m1, Mappings map2) { + for (Mapping m2 : map2) { + Mapping m = m1.merge(m2); + if (m != null) { + res.add(m); + } + } + } + + private void joinCommonBound(Mappings res, Mapping m1, Mappings map2, Node cmn, Node val) { + joinPrefixUnbound(res, m1, map2, cmn); + joinMatchingRange(res, m1, map2, cmn, val); + } + + private void joinPrefixUnbound(Mappings res, Mapping m1, Mappings map2, Node cmn) { + for (Mapping m2 : map2) { + Node val2 = m2.getNodeValue(cmn); + if (val2 == null) { + Mapping m = m1.merge(m2); + if (m != null) { + res.add(m); + } + } else { + break; + } + } + } + + private void joinMatchingRange(Mappings res, Mapping m1, Mappings map2, Node cmn, Node val) { + int index = map2.find(val, cmn); + if (index >= 0 && index < map2.size()) { + for (int i = index; i < map2.size(); i++) { + Mapping m2 = map2.get(i); + Node n2 = m2.getNodeValue(cmn); + if (n2 == null || !val.match(n2)) { + break; + } + Mapping m = m1.merge(m2); + if (m != null) { + res.add(m); + } + } + } + } + + /** + * Computes the SPARQL MINUS between this and lm. + */ + public Mappings minus(Mappings lm) { + Mappings res = new Mappings(); + for (Mapping m1 : mappings) { + boolean ok = true; + for (Mapping m2 : lm) { + if (m1.compatible(m2)) { + ok = false; + break; + } + } + if (ok) { + res.add(m1); + } + } + return res; + } + + /** + * Computes the SPARQL OPTIONAL between this and lm. + */ + public Mappings option(Mappings lm) { + Mappings res = new Mappings(); + for (Mapping m1 : mappings) { + boolean ok = false; + for (Mapping m2 : lm) { + Mapping map = m1.join(m2); + if (map != null) { + ok = true; + res.add(map); + } + } + if (!ok) { + res.add(m1); + } + } + return res; + } + + /** + * In-place join of a variable with a bound value. + */ + public void join(Node varNode, Node val) { + if (mappings.getSelect() != null && !mappings.getSelect().contains(varNode)) { + mappings.getSelect().add(varNode); + } + Iterator it = mappings.getMappingList().iterator(); + while (it.hasNext()) { + Mapping m = it.next(); + Node node = m.getNodeValue(varNode); + if (node == null) { + m.addNode(varNode, val); + } else if (!node.equals(val)) { + it.remove(); + } + } + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsSorter.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsSorter.java new file mode 100644 index 000000000..b94b856a2 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsSorter.java @@ -0,0 +1,253 @@ +package fr.inria.corese.core.next.query.impl.engine.solution; + +import fr.inria.corese.core.next.query.impl.engine.eval.Eval; +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Handles sorting, binary searching, and limit/offset operations for {@link Mappings}. + */ +public final class MappingsSorter { + + private static final int UNBOUND_ORDER = -1; + + private final Mappings mappings; + + public MappingsSorter(Mappings mappings) { + this.mappings = mappings; + } + + /** + * Sorts mappings by variable names. + * + * @param varList list of variable names + */ + public void sort(List varList) { + Collections.sort(mappings.getMappingList(), new VariableSorter(varList)); + } + + /** + * Finds mapping index compatible with m for given variables. + */ + public int find(Mapping m, List list) { + return find(m, getVariableSorter(list), 0, mappings.size() - 1); + } + + VariableSorter getVariableSorter(List varList) { + return new VariableSorter(varList); + } + + int find(Mapping m, VariableSorter vs, int first, int last) { + if (first >= last) { + return first; + } + int mid = (first + last) / 2; + Mapping mm = mappings.get(mid); + int res = vs.compare(mm, m); + if (res >= 0) { + return find(m, vs, first, mid); + } + return find(m, vs, mid + 1, last); + } + + /** + * Checks if mapping m is compatible with any mapping for minus operation. + */ + public boolean minusCompatible(Mapping m, List list) { + int n = find(m, getVariableSorter(list), 0, mappings.size() - 1); + if (n >= 0 && n < mappings.size()) { + Mapping mm = mappings.get(n); + return m.minusCompatible(mm, list); + } + return false; + } + + /** + * Sorts with evaluation context. + */ + public void sort(Eval eval) { + mappings.setEval(eval); + sort(); + mappings.setEval(null); + } + + /** + * Standard sort according to query ORDER BY clauses. + */ + public void sort() { + mappings.getMappingList().sort(this::compare); + } + + /** + * Sorts according to a specific node using an evaluator. + */ + public void sort(Eval eval, Node node) { + prepare(node); + sort(eval); + } + + /** + * Sorts according to a specific node without evaluator. + */ + public void sort(Node node) { + prepare(node); + sort(); + } + + void prepare(Node node) { + mappings.sortWithDesc = false; + for (Mapping m : mappings) { + m.setOrderBy(m.getNode(node)); + } + } + + /** + * Finds index of node where qnode equals node with standard sort. + */ + public int find(Node node, Node qnode) { + return find(node, qnode, 0, mappings.size() - 1); + } + + int find(Node node, Node qnode, int first, int last) { + if (first >= last) { + return first; + } + int mid = (first + last) / 2; + Node n1 = mappings.getMappingList().get(mid).getNodeValue(qnode); + int res = compare(n1, node); + if (res >= 0) { + return find(node, qnode, first, mid); + } + return find(node, qnode, mid + 1, last); + } + + /** + * Node comparator delegating to visitor if evaluator is present. + */ + int comparator(Node n1, Node n2) { + if (mappings.getEval() != null) { + return mappings.getEval().getVisitor().compare( + mappings.getEval(), + n1.compare(n2), + n1.getDatatypeValue(), + n2.getDatatypeValue()); + } + return n1.compare(n2); + } + + /** + * Compares two mappings according to ORDER BY expressions. + */ + public int compare(Mapping m1, Mapping m2) { + Node[] order1 = m1.getOrderBy(); + Node[] order2 = m2.getOrderBy(); + QueryPlan query = mappings.getQuery(); + List orderBy = (query != null) ? query.getOrderBy() : Collections.emptyList(); + + for (int i = 0; i < order1.length && i < order2.length; i++) { + boolean desc = mappings.sortWithDesc && i < orderBy.size() && orderBy.get(i).status(); + int res = compareOrdersAtIndex(order1[i], order2[i], desc); + if (res != 0) { + return res; + } + } + return 0; + } + + private int compareOrdersAtIndex(Node n1, Node n2, boolean desc) { + int res; + if (n1 != null && n2 != null) { + res = comparator(n1, n2); + } else if (n1 == null && n2 == null) { + res = 0; + } else if (n1 == null) { + res = UNBOUND_ORDER; + } else { + res = -UNBOUND_ORDER; + } + return desc ? desc(res) : res; + } + + int desc(int i) { + if (i == 0) { + return 0; + } + return (i < 0) ? 1 : -1; + } + + int compare(Node n1, Node n2) { + if (n1 != null && n2 != null) { + return comparator(n1, n2); + } + if (n1 == null && n2 == null) { + return 0; + } + return (n1 == null) ? UNBOUND_ORDER : -UNBOUND_ORDER; + } + + /** + * Applies ORDER BY sorting and LIMIT/OFFSET slice. + */ + public void complete(Eval eval) { + QueryPlan query = mappings.getQuery(); + if (query != null && !query.getOrderBy().isEmpty()) { + sort(eval); + } + limitOffset(); + } + + /** + * Truncates mappings based on query OFFSET and LIMIT. + */ + public void limitOffset() { + QueryPlan query = mappings.getQuery(); + if (query == null) { + return; + } + if (query.getOffset() > 0) { + for (int i = 0; i < query.getOffset() && mappings.size() > 0; i++) { + mappings.remove(0); + } + } + while (mappings.size() > query.getLimit()) { + mappings.remove(mappings.size() - 1); + } + } + + /** + * Limits the number of results to n. + */ + public Mappings limit(int n) { + while (mappings.size() > n) { + mappings.remove(mappings.size() - 1); + } + return mappings; + } + + final class VariableSorter implements Comparator { + + private final List varList; + + VariableSorter(List list) { + this.varList = list; + } + + @Override + public int compare(Mapping m1, Mapping m2) { + for (String varName : varList) { + Node n1 = m1.getNodeValue(varName); + Node n2 = m2.getNodeValue(varName); + int res = MappingsSorter.this.compare(n1, n2); + if (res != 0) { + return res; + } + } + return 0; + } + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsAggregatorTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsAggregatorTest.java new file mode 100644 index 000000000..3632a380d --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsAggregatorTest.java @@ -0,0 +1,96 @@ +package fr.inria.corese.core.next.query.impl.engine.solution; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayName("MappingsAggregator Tests") +class MappingsAggregatorTest { + + @Test + @DisplayName("min and max compute correct boundary values") + void testMinMax() { + Mappings mappings = new Mappings(); + MappingsAggregator aggregator = new MappingsAggregator(mappings); + + Node x = NodeImpl.forVariable("?x"); + Node v1 = NodeImpl.forLiteral("10", null, null); + Node v2 = NodeImpl.forLiteral("20", null, null); + + Mapping m1 = Mapping.create(); + m1.addNode(x, v1); + Mapping m2 = Mapping.create(); + m2.addNode(x, v2); + + mappings.add(m1); + mappings.add(m2); + + assertEquals(v1, aggregator.min(x)); + assertEquals(v2, aggregator.max(x)); + } + + @Test + @DisplayName("aggregate collects distinct node values for a variable") + void testAggregateVarNode() { + Mappings mappings = new Mappings(); + MappingsAggregator aggregator = new MappingsAggregator(mappings); + + Node x = NodeImpl.forVariable("?x"); + Node v1 = NodeImpl.forLiteral("a", null, null); + + Mapping m1 = Mapping.create(); + m1.addNode(x, v1); + Mapping m2 = Mapping.create(); + m2.addNode(x, v1); + + mappings.add(m1); + mappings.add(m2); + + List collected = aggregator.aggregate(x); + assertEquals(1, collected.size()); + assertEquals(v1, collected.get(0)); + } + + @Test + @DisplayName("clean reduces mappings with size > 1 to first row") + void testClean() { + Mappings mappings = new Mappings(); + mappings.add(Mapping.create()); + mappings.add(Mapping.create()); + + MappingsAggregator aggregator = new MappingsAggregator(mappings); + aggregator.clean(); + + assertEquals(1, mappings.size()); + } + + @Test + @DisplayName("finish without group by or having cleans aggregate") + void testFinish() { + QueryPlan query = mock(QueryPlan.class); + when(query.hasGroupBy()).thenReturn(false); + when(query.getHaving()).thenReturn(null); + when(query.isAggregate()).thenReturn(true); + when(query.isConstruct()).thenReturn(false); + + Mappings mappings = Mappings.create(query); + mappings.add(Mapping.create()); + mappings.add(Mapping.create()); + + MappingsAggregator aggregator = new MappingsAggregator(mappings); + aggregator.finish(query); + + assertEquals(1, mappings.size()); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsDistinctFilterTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsDistinctFilterTest.java new file mode 100644 index 000000000..baaf45a97 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsDistinctFilterTest.java @@ -0,0 +1,70 @@ +package fr.inria.corese.core.next.query.impl.engine.solution; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayName("MappingsDistinctFilter Tests") +class MappingsDistinctFilterTest { + + @Test + @DisplayName("distinct removes duplicate solutions") + void testDistinctRemovesDuplicates() { + Node x = NodeImpl.forVariable("?x"); + Node v1 = NodeImpl.forLiteral("a", null, null); + + QueryPlan query = mock(QueryPlan.class); + when(query.getSelect()).thenReturn(List.of(x)); + when(query.selectNodesFromPattern()).thenReturn(List.of(x)); + + Mappings mappings = Mappings.create(query); + MappingsDistinctFilter filter = new MappingsDistinctFilter(mappings); + + Mapping m1 = Mapping.create(); + m1.addNode(x, v1); + Mapping m2 = Mapping.create(); + m2.addNode(x, v1); + + mappings.add(m1); + mappings.add(m2); + + Mappings distinct = filter.distinct(List.of(x)); + assertEquals(1, distinct.size()); + } + + @Test + @DisplayName("submit filters non-acceptable mappings") + void testSubmitFiltersNonAcceptable() { + QueryPlan query = mock(QueryPlan.class); + when(query.isAggregate()).thenReturn(false); + + Mappings mappings = Mappings.create(query); + MappingsDistinctFilter filter = new MappingsDistinctFilter(mappings); + + filter.submit(null); + assertEquals(0, mappings.size()); + + Mapping m = Mapping.create(); + filter.submit(m); + assertEquals(1, mappings.size()); + } + + @Test + @DisplayName("accept returns true when no select projection") + void testAcceptWithoutSelect() { + Mappings mappings = new Mappings(); + MappingsDistinctFilter filter = new MappingsDistinctFilter(mappings); + + assertTrue(filter.accept(Mapping.create())); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsRelationalOperationsTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsRelationalOperationsTest.java new file mode 100644 index 000000000..e8ca17bdf --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsRelationalOperationsTest.java @@ -0,0 +1,75 @@ +package fr.inria.corese.core.next.query.impl.engine.solution; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +@DisplayName("MappingsRelationalOperations Tests") +class MappingsRelationalOperationsTest { + + @Test + @DisplayName("union combines both solution tables") + void testUnion() { + Mappings m1 = new Mappings(); + m1.add(Mapping.create()); + + Mappings m2 = new Mappings(); + m2.add(Mapping.create()); + m2.add(Mapping.create()); + + MappingsRelationalOperations ops = new MappingsRelationalOperations(m1); + Mappings res = ops.union(m2); + + assertEquals(3, res.size()); + } + + @Test + @DisplayName("getCommonNode returns common node or null") + void testGetCommonNode() { + Mappings m1 = new Mappings(); + Mappings m2 = new Mappings(); + MappingsRelationalOperations ops = new MappingsRelationalOperations(m1); + + assertNull(ops.getCommonNode(m2)); + + Node x = NodeImpl.forVariable("?x"); + Node v1 = NodeImpl.forLiteral("1", null, null); + + Mapping row1 = Mapping.create(); + row1.addNode(x, v1); + m1.add(row1); + + Mapping row2 = Mapping.create(); + row2.addNode(x, v1); + m2.add(row2); + + assertNotNull(ops.getCommonNode(m2)); + } + + @Test + @DisplayName("minus filters compatible solutions") + void testMinus() { + Node x = NodeImpl.forVariable("?x"); + Node v1 = NodeImpl.forLiteral("val", null, null); + + Mappings m1 = new Mappings(); + Mapping row1 = Mapping.create(); + row1.addNode(x, v1); + m1.add(row1); + + Mappings m2 = new Mappings(); + Mapping row2 = Mapping.create(); + row2.addNode(x, v1); + m2.add(row2); + + MappingsRelationalOperations ops = new MappingsRelationalOperations(m1); + Mappings res = ops.minus(m2); + + assertEquals(0, res.size()); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsSorterTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsSorterTest.java new file mode 100644 index 000000000..4eabfbaa1 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsSorterTest.java @@ -0,0 +1,89 @@ +package fr.inria.corese.core.next.query.impl.engine.solution; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayName("MappingsSorter Tests") +class MappingsSorterTest { + + @Test + @DisplayName("sort by variable names sorts rows correctly") + void testSortByVariableNames() { + Mappings mappings = new Mappings(); + MappingsSorter sorter = new MappingsSorter(mappings); + + Node x = NodeImpl.forVariable("?x"); + Node v1 = NodeImpl.forLiteral("alpha", null, null); + Node v2 = NodeImpl.forLiteral("beta", null, null); + + Mapping m1 = Mapping.create(); + m1.addNode(x, v2); + + Mapping m2 = Mapping.create(); + m2.addNode(x, v1); + + mappings.add(m1); + mappings.add(m2); + + sorter.sort(List.of("?x")); + + assertEquals(v1, mappings.get(0).getNodeValue(x)); + assertEquals(v2, mappings.get(1).getNodeValue(x)); + } + + @Test + @DisplayName("limitOffset applies offset and limit correctly") + void testLimitOffset() { + QueryPlan query = mock(QueryPlan.class); + when(query.getOffset()).thenReturn(1); + when(query.getLimit()).thenReturn(2); + when(query.getOrderBy()).thenReturn(Collections.emptyList()); + + Mappings mappings = Mappings.create(query); + MappingsSorter sorter = new MappingsSorter(mappings); + + mappings.add(Mapping.create()); + mappings.add(Mapping.create()); + mappings.add(Mapping.create()); + mappings.add(Mapping.create()); + + sorter.limitOffset(); + + assertEquals(2, mappings.size()); + } + + @Test + @DisplayName("minusCompatible checks compatibility") + void testMinusCompatible() { + Mappings mappings = new Mappings(); + MappingsSorter sorter = new MappingsSorter(mappings); + + Node x = NodeImpl.forVariable("?x"); + Node v1 = NodeImpl.forLiteral("val", null, null); + + Mapping m1 = Mapping.create(); + m1.addNode(x, v1); + mappings.add(m1); + + Mapping target = Mapping.create(); + target.addNode(x, v1); + + assertTrue(sorter.minusCompatible(target, List.of("?x"))); + + Mapping nonCompat = Mapping.create(); + nonCompat.addNode(x, NodeImpl.forLiteral("other", null, null)); + assertFalse(sorter.minusCompatible(nonCompat, List.of("?x"))); + } +} From 0911ac32b5ef175d5e9f614aacdede949cad047b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?= Date: Mon, 26 Oct 2026 17:52:32 +0100 Subject: [PATCH 4/7] refactor(pattern): decompose QueryPlan god class into QueryClauses and QueryExpIndexer --- .../impl/engine/pattern/QueryClauses.java | 344 ++++++++++++ .../impl/engine/pattern/QueryExpIndexer.java | 253 +++++++++ .../query/impl/engine/pattern/QueryPlan.java | 502 +++--------------- .../impl/engine/pattern/QueryClausesTest.java | 82 +++ .../engine/pattern/QueryExpIndexerTest.java | 66 +++ 5 files changed, 816 insertions(+), 431 deletions(-) create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryClauses.java create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryExpIndexer.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryClausesTest.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryExpIndexerTest.java diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryClauses.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryClauses.java new file mode 100644 index 000000000..14421f00c --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryClauses.java @@ -0,0 +1,344 @@ +package fr.inria.corese.core.next.query.impl.engine.pattern; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Encapsulates SPARQL query clauses, projection, grouping, ordering, dataset specification, + * slice limits, and template definitions for {@link QueryPlan}. + */ +public final class QueryClauses { + + private int limit = Integer.MAX_VALUE; + private int offset = 0; + private int slice = 20; + + private boolean distinctResults = false; + private boolean aggregateQuery = false; + private boolean constructQuery = false; + private boolean insertQuery = false; + private boolean deleteQuery = false; + private boolean updateQuery = false; + private boolean askQuery = false; + private boolean listGroup = false; + private boolean datasetSpecified = false; + + private List from; + private List named; + private List selectNodes; + private List selectExp; + private List orderBy; + private List groupBy; + private Exp having; + private Exp construct; + private Exp delete; + + public QueryClauses() { + from = new ArrayList<>(); + named = new ArrayList<>(); + selectNodes = new ArrayList<>(); + selectExp = new ArrayList<>(); + orderBy = new ArrayList<>(); + groupBy = new ArrayList<>(); + } + + public QueryClauses copyPlan(PlanCopy copies) { + QueryClauses target = new QueryClauses(); + target.limit = this.limit; + target.offset = this.offset; + target.slice = this.slice; + target.distinctResults = this.distinctResults; + target.aggregateQuery = this.aggregateQuery; + target.constructQuery = this.constructQuery; + target.insertQuery = this.insertQuery; + target.deleteQuery = this.deleteQuery; + target.updateQuery = this.updateQuery; + target.askQuery = this.askQuery; + target.listGroup = this.listGroup; + target.datasetSpecified = this.datasetSpecified; + target.from = copies.copy(this.from); + target.named = copies.copy(this.named); + target.selectNodes = copies.copy(this.selectNodes); + target.selectExp = copies.copy(this.selectExp); + target.orderBy = copies.copy(this.orderBy); + target.groupBy = copies.copy(this.groupBy); + target.having = copies.copy(this.having); + target.construct = copies.copy(this.construct); + target.delete = copies.copy(this.delete); + return target; + } + + public List getSelect() { + return selectNodes; + } + + public void setSelect(List list) { + selectNodes = list; + } + + public void addSelect(Node node) { + if (selectExp == null) { + selectExp = new ArrayList<>(); + } + selectExp.add(Exp.create(Exp.Type.NODE, node)); + } + + public List getSelectFun() { + return selectExp; + } + + public void setSelectFun(List s) { + selectExp = s; + } + + public Exp getSelectExp(String label) { + for (Exp exp : selectExp) { + if (exp.getNode() != null && exp.getNode().getLabel().equals(label)) { + return exp; + } + } + return null; + } + + public Node getSelectNode(String label) { + for (Node node : selectNodes) { + if (node.getLabel().equals(label)) { + return node; + } + } + return null; + } + + public List getOrderBy() { + return orderBy; + } + + public void setOrderBy(List s) { + orderBy = s; + } + + public void addOrderBy(Node node) { + orderBy.add(Exp.create(Exp.Type.NODE, node)); + } + + public boolean isOrderBy() { + return !orderBy.isEmpty(); + } + + public List getGroupBy() { + return groupBy; + } + + public void setGroupBy(List s) { + groupBy = s; + } + + public void addGroupBy(Node node) { + groupBy.add(Exp.create(Exp.Type.NODE, node)); + } + + public boolean isGroupBy() { + return !groupBy.isEmpty(); + } + + public boolean hasGroupBy() { + return !groupBy.isEmpty(); + } + + public boolean isListGroup() { + return listGroup; + } + + public void setListGroup(boolean listGroup) { + this.listGroup = listGroup; + } + + public Exp getHaving() { + return having; + } + + public void setHaving(Exp f) { + having = f; + } + + public List getFrom() { + return from; + } + + public void setFrom(List l) { + from = l; + } + + public List getNamed() { + return named; + } + + public void setNamed(List l) { + named = l; + } + + public boolean isDatasetSpecified() { + return datasetSpecified; + } + + public void setDatasetSpecified(boolean datasetSpecified) { + this.datasetSpecified = datasetSpecified; + } + + public List getFrom(Node gNode) { + return (gNode == null) ? from : named; + } + + public int getLimit() { + return limit; + } + + public void setLimit(int n) { + limit = n; + } + + public int getOffset() { + return offset; + } + + public void setOffset(int n) { + offset = n; + } + + public int getLimitOffset() { + if (!isConstruct() && (isOrderBy() || hasGroupBy() || isAggregate())) { + return Integer.MAX_VALUE; + } + if (limit < Integer.MAX_VALUE - offset) { + return limit + offset; + } + return limit; + } + + public int getSlice() { + return slice; + } + + public void setSlice(int n) { + slice = n; + } + + public boolean isDistinct() { + return distinctResults; + } + + public void setDistinct(boolean b) { + distinctResults = b; + } + + public boolean isDistinctRedundantWithGroupBy() { + if (!isDistinct() || !hasGroupBy() || isListGroup()) { + return false; + } + List groupList = getGroupBy(); + List currentSelect = getSelect(); + if (groupList == null || groupList.isEmpty() || currentSelect == null || currentSelect.isEmpty()) { + return false; + } + Set selectVarNames = extractSelectVariableNames(currentSelect); + return areAllGroupKeysProjected(groupList, selectVarNames); + } + + private static Set extractSelectVariableNames(List selectNodes) { + return selectNodes.stream() + .filter(n -> n != null && n.isVariable()) + .map(Node::getLabel) + .collect(Collectors.toSet()); + } + + private static boolean areAllGroupKeysProjected(List groupList, Set selectVarNames) { + for (Exp groupExp : groupList) { + if (groupExp == null || groupExp.getNode() == null || groupExp.getFilter() != null) { + return false; + } + Node groupNode = groupExp.getNode(); + if (!groupNode.isVariable() || !selectVarNames.contains(groupNode.getLabel())) { + return false; + } + } + return true; + } + + public boolean isAggregate() { + return aggregateQuery; + } + + public void setAggregate(boolean b) { + aggregateQuery = b; + } + + public boolean isConstruct() { + return constructQuery; + } + + public void setConstruct(boolean b) { + constructQuery = b; + } + + public Exp getConstruct() { + return construct; + } + + public void setConstruct(Exp c) { + construct = c; + if (c != null) { + setConstruct(true); + } + } + + public Exp getInsert() { + return (construct != null) ? construct : Exp.create(Exp.Type.BGP); + } + + public boolean isInsert() { + return insertQuery; + } + + public void setInsert(boolean b) { + insertQuery = b; + } + + public boolean isDelete() { + return deleteQuery; + } + + public void setDelete(boolean b) { + deleteQuery = b; + } + + public Exp getDelete() { + return delete; + } + + public void setDelete(Exp c) { + delete = c; + if (c != null) { + setDelete(true); + } + } + + public boolean isUpdate() { + return updateQuery; + } + + public void setUpdate(boolean b) { + updateQuery = b; + } + + public boolean isAsk() { + return askQuery; + } + + public void setAsk(boolean b) { + askQuery = b; + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryExpIndexer.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryExpIndexer.java new file mode 100644 index 000000000..4054238d6 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryExpIndexer.java @@ -0,0 +1,253 @@ +package fr.inria.corese.core.next.query.impl.engine.pattern; + +import fr.inria.corese.core.next.query.impl.engine.eval.Message; +import fr.inria.corese.core.next.query.impl.engine.model.Edge; +import fr.inria.corese.core.next.query.impl.engine.model.Expr; +import fr.inria.corese.core.next.query.impl.engine.model.ExprType; +import fr.inria.corese.core.next.query.impl.engine.model.Filter; +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * Handles AST traversal, variable collection, and slot index allocation for {@link QueryPlan}. + */ +public final class QueryExpIndexer { + + private static final Logger logger = LoggerFactory.getLogger(QueryExpIndexer.class); + + private final QueryPlan queryPlan; + private final VariableLayout layout; + + public QueryExpIndexer(QueryPlan queryPlan, VariableLayout layout) { + this.queryPlan = queryPlan; + this.layout = layout; + } + + /** + * Traverses query patterns and filters to register visible variables. + */ + public void collect() { + for (Exp ee : queryPlan) { + collect(ee, false); + } + + for (Node node : layout.getBindingNodes()) { + layout.store(node, false, false); + } + + for (Node node : layout.getArgList()) { + layout.store(node, false, false); + } + + for (Filter f : queryPlan.getFunList()) { + collectExist(f.getExp()); + } + } + + void collect(Exp exp, boolean exist) { + switch (exp.type()) { + case FILTER -> collectExist(exp.getFilter().getExp()); + case NODE -> layout.store(exp.getNode(), exist, false); + case VALUES -> { + for (Node node : exp.getNodeList()) { + layout.store(node, exist, false); + } + } + case EDGE -> collectEdgeNodes(exp.getEdge(), exist); + case XPATH, EVAL -> { + for (int i = 0; i < exp.nbNode(); i++) { + layout.store(exp.getNode(i), exist, false); + } + } + case MINUS -> collectMinusNodes(exp, exist); + case QUERY -> { + for (Exp ee : exp.getQuery().getSelectFun()) { + layout.store(ee.getNode(), exist, true); + } + } + case BIND -> collectBindNodes(exp, exist); + default -> { + for (Exp ee : exp) { + collect(ee, exist); + } + } + } + } + + private void collectEdgeNodes(Edge edge, boolean insideExistencePattern) { + layout.store(edge.getNode(0), insideExistencePattern, false); + if (edge.getEdgeVariable() != null) { + layout.store(edge.getEdgeVariable(), insideExistencePattern, false); + } + layout.store(edge.getNode(1), insideExistencePattern, false); + for (int index = 2; index < edge.nbNode(); index++) { + layout.store(edge.getNode(index), insideExistencePattern, false); + } + } + + private void collectMinusNodes(Exp expression, boolean insideExistencePattern) { + if (expression.first() != null) { + collect(expression.first(), insideExistencePattern); + } + if (expression.rest() != null) { + collect(expression.rest(), true); + } + } + + private void collectBindNodes(Exp expression, boolean insideExistencePattern) { + collectExist(expression.getFilter().getExp()); + layout.store(expression.getNode(), insideExistencePattern, false); + if (expression.getNodeList() != null) { + for (Node node : expression.getNodeList()) { + layout.store(node, insideExistencePattern, false); + } + } + } + + void collectExist(Expr exp) { + if (exp.oper() == ExprType.EXIST) { + Exp pat = exp.getPattern(); + collect(pat, true); + } else { + for (Expr ee : exp.getExpList()) { + collectExist(ee); + } + } + } + + /** + * Recursively indexes expression nodes and edges. + */ + public int index(QueryPlan query, Exp exp, boolean isExist, int start) { + int min = switch (exp.type()) { + case EDGE, XPATH, EVAL -> indexEdgeExp(exp); + case VALUES -> indexValuesExp(exp); + case NODE -> queryPlan.index(exp.getNode()); + case BIND -> indexBindExp(query, exp, isExist); + case FILTER -> indexExpFilter(query, exp, isExist); + case QUERY -> indexExpQuery(query, exp, isExist); + case OPT_BIND, ACCEPT -> Integer.MAX_VALUE; + default -> indexDefaultExp(query, exp, isExist, start); + }; + + if (exp.getGraphNode() != null) { + queryPlan.index(exp.getGraphNode()); + } + + return min; + } + + private int indexEdgeExp(Exp exp) { + Edge edge = exp.getEdge(); + edge.setEdgeIndex(layout.allocateEdgeIndex()); + return indexExpEdge(exp); + } + + private int indexValuesExp(Exp exp) { + int min = Integer.MAX_VALUE; + for (Node node : exp.getNodeList()) { + int n = queryPlan.index(node); + min = Math.min(min, n); + } + return min; + } + + private int indexBindExp(QueryPlan query, Exp exp, boolean isExist) { + Node qn = exp.getNode(); + int min = queryPlan.index(qn); + if (exp.getNodeList() != null) { + for (Node bn : exp.getNodeList()) { + int ii = queryPlan.index(bn); + min = Math.min(min, ii); + } + } + return indexExpFilter(query, exp, isExist); + } + + private int indexDefaultExp(QueryPlan query, Exp exp, boolean isExist, int start) { + int min = Integer.MAX_VALUE; + int startIndex = (start >= 0) ? start : queryPlan.globalNodeIndex(); + int ind = exp.isUnion() ? startIndex : -1; + for (Exp e : exp) { + int n = index(query, e, isExist, ind); + min = Math.min(min, n); + } + return min; + } + + int indexExpFilter(QueryPlan query, Exp exp, boolean isExist) { + int min = Integer.MAX_VALUE; + boolean hasExist = index(query, exp.getFilter()); + List lVar = exp.getFilter().getVariables(true); + + for (String variable : lVar) { + Node qNode = query.getProperAndSubSelectNode(variable); + if (qNode == null) { + logger.warn(Message.Prefix.UNDEF_VAR.getString(), variable); + queryPlan.addError(Message.Prefix.UNDEF_VAR.getString(), variable); + } else if (!isExist && !hasExist) { + int n = queryPlan.index(qNode); + min = Math.min(min, n); + } + } + if (hasExist) { + min = -1; + } + return min; + } + + int indexExpQuery(QueryPlan query, Exp exp, boolean isExist) { + int min = Integer.MAX_VALUE; + QueryPlan qq = exp.getQuery(); + qq.setCompiled(); + qq.setGlobalQuery(queryPlan); + qq.setOuterQuery(query); + qq.setAggregate(); + + for (Exp e : exp) { + index(qq, e, isExist, -1); + } + + for (Exp ee : qq.getSelectFun()) { + Node sNode = ee.getNode(); + int n = queryPlan.index(sNode); + min = Math.min(min, n); + + if (ee.getFilter() != null) { + index(qq, ee.getFilter()); + } + } + + qq.complete2(); + return min; + } + + private int indexExpEdge(Exp exp) { + int min = Integer.MAX_VALUE; + for (int i = 0; i < exp.nbNode(); i++) { + int n = queryPlan.index(exp.getNode(i)); + min = Math.min(min, n); + } + return min; + } + + public boolean index(QueryPlan query, Filter f) { + return index(query, f.getExp()); + } + + public boolean index(QueryPlan query, Expr exp) { + boolean b = false; + if (exp.oper() == ExprType.EXIST) { + index(query, exp.getPattern(), true, -1); + b = true; + } else { + for (Expr ee : exp.getExpList()) { + b = index(query, ee) || b; + } + } + return b; + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java index 792dc31a1..e97e1efc6 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java @@ -9,15 +9,10 @@ import fr.inria.corese.core.next.query.impl.engine.model.Filter; import fr.inria.corese.core.next.query.impl.engine.spi.Producer; import fr.inria.corese.core.next.query.impl.engine.filter.FilterOptimizer; -import fr.inria.corese.core.next.query.impl.engine.eval.Message; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; /** * Compiled query operators, projection, dataset and variable indexes. @@ -38,31 +33,17 @@ public final class QueryPlan extends Exp { public static final int QP_HEURISTICS_BASED = 2;//Heuristics based QP public static final int QP_BGP = 3;//BGP based QP - private static final Logger logger = LoggerFactory.getLogger(QueryPlan.class); - - int limit = Integer.MAX_VALUE; - int offset = 0; - // if slice > 0 : service gets mappings from previous pattern by slices - int slice = 20; - private int number = 0; - private boolean distinctResults; private final VariableLayout layout; - List from; - List named; - List selectNode; + private final QueryClauses clauses; + private final QueryExpIndexer indexer; + List relaxEdges; - List selectExp; - List orderBy; - List groupBy; List failure; List funList; List errors; List info; - Exp having; - Exp construct; - Exp delete; // gNode is a local graph node when subquery has no ?g in its select // use case: graph ?g (select where ...) Node gNode; @@ -86,28 +67,19 @@ public final class QueryPlan extends Exp { // Extended queries for additional group by List queries; private boolean isCompiled = false; - private boolean datasetSpecified; private boolean reportEnabled; private boolean isUseBind = true; - private boolean aggregateQuery; boolean isRelax = false; boolean isDistribute = false; boolean isOptimize = false; boolean isTest = false; boolean isNew = true; - boolean isConstruct = false; - private boolean isInsert = false; - boolean isDelete = false; - boolean isUpdate = false; - boolean isAsk = false; - boolean isListGroup = false; private boolean validate = false; private boolean federate = false; - boolean - isCorrect = true; + boolean isCorrect = true; boolean isMap = true; boolean isRule = false; boolean isDetail = true; @@ -116,7 +88,6 @@ public final class QueryPlan extends Exp { private boolean isService = false; - private Mapping mapping; private String service; @@ -125,11 +96,8 @@ public final class QueryPlan extends Exp { public QueryPlan() { super(Type.QUERY); layout = new VariableLayout(); - from = new ArrayList<>(); - named = new ArrayList<>(); - selectExp = new ArrayList<>(); - orderBy = new ArrayList<>(); - groupBy = new ArrayList<>(); + clauses = new QueryClauses(); + indexer = new QueryExpIndexer(this, layout); failure = new ArrayList<>(); funList = new ArrayList<>(); compiler = new FilterOptimizer(this); @@ -139,7 +107,6 @@ public QueryPlan() { ptable = new HashMap<>(); etable = new HashMap<>(); queries = new ArrayList<>(); - selectNode = new ArrayList<>(); relaxEdges = new ArrayList<>(); querySorter = new QueryPlanner(this); } @@ -154,27 +121,16 @@ private QueryPlan(QueryPlan source, PlanCopy copies) { copies.remember(source, this); source.copyPlanFields(this, copies); layout = source.layout.copyPlan(copies); + clauses = source.clauses.copyPlan(copies); + indexer = new QueryExpIndexer(this, layout); querySorter = null; compiler = null; - this.limit = source.limit; - this.offset = source.offset; - this.slice = source.slice; this.number = source.number; - this.distinctResults = source.distinctResults; - this.from = copies.copy(source.from); - this.named = copies.copy(source.named); - this.selectNode = copies.copy(source.selectNode); this.relaxEdges = copies.copy(source.relaxEdges); - this.selectExp = copies.copy(source.selectExp); - this.orderBy = copies.copy(source.orderBy); - this.groupBy = copies.copy(source.groupBy); this.failure = copies.copy(source.failure); this.funList = copies.copy(source.funList); this.errors = copies.copy(source.errors); this.info = copies.copy(source.info); - this.having = copies.copy(source.having); - this.construct = copies.copy(source.construct); - this.delete = copies.copy(source.delete); this.gNode = copies.copy(source.gNode); this.globalQuery = copies.copy(source.globalQuery); this.outerQuery = copies.copy(source.outerQuery); @@ -186,21 +142,13 @@ private QueryPlan(QueryPlan source, PlanCopy copies) { this.table = copies.copy(source.table); this.queries = copies.copy(source.queries); this.isCompiled = source.isCompiled; - this.datasetSpecified = source.datasetSpecified; this.reportEnabled = source.reportEnabled; this.isUseBind = source.isUseBind; - this.aggregateQuery = source.aggregateQuery; this.isRelax = source.isRelax; this.isDistribute = source.isDistribute; this.isOptimize = source.isOptimize; this.isTest = source.isTest; this.isNew = source.isNew; - this.isConstruct = source.isConstruct; - this.isInsert = source.isInsert; - this.isDelete = source.isDelete; - this.isUpdate = source.isUpdate; - this.isAsk = source.isAsk; - this.isListGroup = source.isListGroup; this.validate = source.validate; this.federate = source.federate; this.isCorrect = source.isCorrect; @@ -425,27 +373,23 @@ public List getFunList(){ public List getFrom(Node gNode) { - if (gNode == null) { - return from; - } else { - return named; - } + return clauses.getFrom(gNode); } public List getFrom() { - return from; + return clauses.getFrom(); } public List getNamed() { - return named; + return clauses.getNamed(); } public void setFrom(List l) { - from = l; + clauses.setFrom(l); } public void setNamed(List l) { - named = l; + clauses.setNamed(l); } /** @@ -453,12 +397,12 @@ public void setNamed(List l) { * of its graph sets is empty. */ public void setDatasetSpecified(boolean datasetSpecified) { - this.datasetSpecified = datasetSpecified; + clauses.setDatasetSpecified(datasetSpecified); } /** Returns whether the query has an explicit active dataset. */ public boolean isDatasetSpecified() { - return datasetSpecified; + return clauses.isDatasetSpecified(); } VariableLayout getLayout() { @@ -510,11 +454,11 @@ public List selectNodesFromPattern() { * computed by compiler transformer */ public List getSelect() { - return selectNode; + return clauses.getSelect(); } public void setSelect(List list) { - selectNode = list; + clauses.setSelect(list); } /** @@ -522,7 +466,7 @@ public void setSelect(List list) { * computed by compiler transformer */ public List getSelectFun() { - return selectExp; + return clauses.getSelectFun(); } public Node getPatternNode(String name) { @@ -542,11 +486,11 @@ public Node getQuerySelectNode(String name) { } public void setSlice(int n) { - slice = n; + clauses.setSlice(n); } public int getSlice() { - return slice; + return clauses.getSlice(); } public void setMap(boolean b) { @@ -558,99 +502,45 @@ public boolean isMap() { } public void setLimit(int n) { - limit = n; + clauses.setLimit(n); } public int getLimit() { - return limit; + return clauses.getLimit(); } public int getLimitOffset() { - // when order by/group by/count(), return all results, group sort agg, and then apply offset/limit - if (!isConstruct() - && (isOrderBy() || hasGroupBy() || isAggregate())) { - return Integer.MAX_VALUE; - } - if (limit < Integer.MAX_VALUE - offset) { - return limit + offset; - } else { - return limit; - } + return clauses.getLimitOffset(); } public void setOffset(int n) { - offset = n; + clauses.setOffset(n); } public int getOffset() { - return offset; + return clauses.getOffset(); } public boolean isDistinct() { - return distinctResults; + return clauses.isDistinct(); } public void setDistinct(boolean b) { - distinctResults = b; + clauses.setDistinct(b); } - /** - * Determines whether DISTINCT is strictly redundant with GROUP BY. - * - *

In SPARQL 1.1, if a query uses GROUP BY and all grouping expressions are simple - * variables directly projected in the SELECT list, then every solution row emitted - * by the GROUP BY stage already possesses a unique combination of group key values. - * Consequently, each projected row is guaranteed to be pairwise distinct, making an - * additional DISTINCT deduplication pass redundant.

- * - *

A counter-example where DISTINCT is NOT redundant: - * {@code SELECT DISTINCT (COUNT(*) AS ?n) WHERE { ... } GROUP BY ?x} - * Here, two distinct groups (:a and :b) may yield the same count ?n = 1. If ?x is not - * projected, removing DISTINCT would erroneously return two rows instead of one.

- * - * @return true if DISTINCT is guaranteed to be redundant with GROUP BY - */ public boolean isDistinctRedundantWithGroupBy() { - if (!isDistinct() || !hasGroupBy() || isListGroup()) { - return false; - } - List groupList = getGroupBy(); - List selectNodes = getSelect(); - if (groupList == null || groupList.isEmpty() || selectNodes == null || selectNodes.isEmpty()) { - return false; - } - Set selectVarNames = extractSelectVariableNames(selectNodes); - return areAllGroupKeysProjected(groupList, selectVarNames); - } - - private static Set extractSelectVariableNames(List selectNodes) { - return selectNodes.stream() - .filter(n -> n != null && n.isVariable()) - .map(Node::getLabel) - .collect(Collectors.toSet()); - } - - private static boolean areAllGroupKeysProjected(List groupList, Set selectVarNames) { - for (Exp groupExp : groupList) { - if (groupExp == null || groupExp.getNode() == null || groupExp.getFilter() != null) { - return false; - } - Node groupNode = groupExp.getNode(); - if (!groupNode.isVariable() || !selectVarNames.contains(groupNode.getLabel())) { - return false; - } - } - return true; + return clauses.isDistinctRedundantWithGroupBy(); } @Override public boolean isAggregate() { - return aggregateQuery; + return clauses.isAggregate(); } @Override public void setAggregate(boolean b) { - aggregateQuery = b; + clauses.setAggregate(b); } public boolean isRelax() { @@ -669,41 +559,40 @@ public boolean isDistribute() { } } - - public boolean isSelect(){ - return ! (isConstruct() || isUpdate() || isInsert() || isDelete() || isAsk()); + public boolean isSelect() { + return !(isConstruct() || isUpdate() || isInsert() || isDelete() || isAsk()); } public boolean isConstruct() { - return isConstruct; + return clauses.isConstruct(); } public void setConstruct(boolean b) { - isConstruct = b; + clauses.setConstruct(b); } public boolean isDelete() { - return isDelete; + return clauses.isDelete(); } public void setDelete(boolean b) { - isDelete = b; + clauses.setDelete(b); } public boolean isUpdate() { - return isUpdate; + return clauses.isUpdate(); } public void setUpdate(boolean b) { - isUpdate = b; + clauses.setUpdate(b); } public boolean isAsk() { - return isAsk; + return clauses.isAsk(); } public void setAsk(boolean b) { - isAsk = b; + clauses.setAsk(b); } public boolean isTest() { @@ -743,53 +632,56 @@ public void setAggregate() { } } - public void setSelectFun(List s) { - selectExp = s; + clauses.setSelectFun(s); } public void addSelect(Node node) { - selectExp.add(Exp.create(Type.NODE, node)); + clauses.addSelect(node); } public void addOrderBy(Node node) { - orderBy.add(Exp.create(Type.NODE, node)); + clauses.addOrderBy(node); } public void addGroupBy(Node node) { - groupBy.add(Exp.create(Type.NODE, node)); + clauses.addGroupBy(node); } public void setOrderBy(List s) { - orderBy = s; + clauses.setOrderBy(s); } public boolean isOrderBy() { - return !orderBy.isEmpty(); + return clauses.isOrderBy(); } public List getOrderBy() { - return orderBy; + return clauses.getOrderBy(); } public void setGroupBy(List s) { - groupBy = s; + clauses.setGroupBy(s); } public List getGroupBy() { - return groupBy; + return clauses.getGroupBy(); } public boolean isGroupBy() { - return !groupBy.isEmpty(); + return clauses.isGroupBy(); } public boolean hasGroupBy() { - return isGroupBy(); + return clauses.hasGroupBy(); } public boolean isListGroup() { - return isListGroup; + return clauses.isListGroup(); + } + + public void setListGroup(boolean listGroup) { + clauses.setListGroup(listGroup); } public boolean isCorrect() { @@ -801,23 +693,23 @@ public void setCorrect(boolean b) { } public void setHaving(Exp f) { - having = f; + clauses.setHaving(f); } public Exp getHaving() { - return having; + return clauses.getHaving(); } public void setConstruct(Exp c) { - construct = c; + clauses.setConstruct(c); } public Exp getConstruct() { - return construct; + return clauses.getConstruct(); } public Exp getInsert() { - return construct; + return clauses.getInsert(); } public List getConstructNodes() { @@ -829,11 +721,11 @@ public void setConstructNodes(List constructNodes) { } public void setDelete(Exp c) { - delete = c; + clauses.setDelete(c); } public Exp getDelete() { - return delete; + return clauses.getDelete(); } @Override @@ -851,21 +743,11 @@ boolean member(String variable, List lExp) { } public Exp getSelectExp(String label) { - for (Exp exp : getSelectFun()) { - Node node = exp.getNode(); - if (node.getLabel().equals(label)) { - return exp; - } - } - return null; + return clauses.getSelectExp(label); } public Node getSelectNode(String label) { - Exp exp = getSelectExp(label); - if (exp != null) { - return exp.getNode(); - } - return null; + return clauses.getSelectNode(label); } public int nbNodes() { @@ -1012,243 +894,11 @@ void store(Node node, boolean exist, boolean select) { * called by compiler transformer */ public void collect() { - for (Exp ee : this) { - collect(ee, false); - } - - for (Node node : getBindingNodes()) { - store(node, false, false); - } - - for (Node node : getArgList()) { - store(node, false, false); - } - - for (Filter f : getFunList()){ - collectExist(f.getExp()); - } - } - - // exist: inside exists ( exp ) - // or inside A minus ( exp ) - void collect(Exp exp, boolean exist) { - switch (exp.type()) { - - case FILTER: - collectExist(exp.getFilter().getExp()); - break; - - case NODE: - store(exp.getNode(), exist, false); - break; - - case VALUES: - for (Node node : exp.getNodeList()) { - store(node, exist, false); - } - break; - - case EDGE: - collectEdgeNodes(exp.getEdge(), exist); - break; - - case XPATH, EVAL: - for (int i = 0; i < exp.nbNode(); i++) { - Node node = exp.getNode(i); - store(node, exist, false); - } - break; - - case MINUS: - collectMinusNodes(exp, exist); - break; - - case QUERY: - for (Exp ee : exp.getQuery().getSelectFun()) { - store(ee.getNode(), exist, true); - } - break; - - case BIND: - collectBindNodes(exp, exist); - break; - - default: - for (Exp ee : exp) { - collect(ee, exist); - } - } - + indexer.collect(); } - private void collectEdgeNodes(Edge edge, boolean insideExistencePattern) { - store(edge.getNode(0), insideExistencePattern, false); - if (edge.getEdgeVariable() != null) { - store(edge.getEdgeVariable(), insideExistencePattern, false); - } - store(edge.getNode(1), insideExistencePattern, false); - for (int index = 2; index < edge.nbNode(); index++) { - store(edge.getNode(index), insideExistencePattern, false); - } - } - - private void collectMinusNodes(Exp expression, boolean insideExistencePattern) { - if (expression.first() != null) { - collect(expression.first(), insideExistencePattern); - } - // The right operand is indexed but does not introduce solution bindings. - if (expression.rest() != null) { - collect(expression.rest(), true); - } - } - - private void collectBindNodes(Exp expression, boolean insideExistencePattern) { - collectExist(expression.getFilter().getExp()); - store(expression.getNode(), insideExistencePattern, false); - if (expression.getNodeList() != null) { - for (Node node : expression.getNodeList()) { - store(node, insideExistencePattern, false); - } - } - } - - void collectExist(Expr exp) { - if (exp.oper() == ExprType.EXIST) { - Exp pat = getPattern(exp); - collect(pat, true); - } else { - for (Expr ee : exp.getExpList()) { - collectExist(ee); - } - } - } - - /** - * set Index for EDGE & NODE - * check if subquery is aggregate - * in case of UNION, start is the start index for both branches - * return the min of index of exp Nodes - * query is (sub)query this is global query - */ int index(QueryPlan query, Exp exp, boolean isExist, int start) { - int min = switch (exp.type()) { - case EDGE, XPATH, EVAL -> indexEdgeExp(exp); - case VALUES -> indexValuesExp(exp); - case NODE -> index(exp.getNode()); - case BIND -> indexBindExp(query, exp, isExist); - case FILTER -> indexExpFilter(query, exp, isExist); - case QUERY -> indexExpQuery(query, exp, isExist); - case OPT_BIND, ACCEPT -> Integer.MAX_VALUE; - default -> indexDefaultExp(query, exp, isExist, start); - }; - - if (exp.getGraphNode() != null) { - index(exp.getGraphNode()); - } - - return min; - } - - private int indexEdgeExp(Exp exp) { - Edge edge = exp.getEdge(); - edge.setEdgeIndex(layout.allocateEdgeIndex()); - return indexExpEdge(exp); - } - - private int indexValuesExp(Exp exp) { - int min = Integer.MAX_VALUE; - for (Node node : exp.getNodeList()) { - int n = index(node); - min = Math.min(min, n); - } - return min; - } - - private int indexBindExp(QueryPlan query, Exp exp, boolean isExist) { - Node qn = exp.getNode(); - int min = index(qn); - if (exp.getNodeList() != null) { - for (Node bn : exp.getNodeList()) { - int ii = index(bn); - min = Math.min(min, ii); - } - } - return indexExpFilter(query, exp, isExist); - } - - private int indexDefaultExp(QueryPlan query, Exp exp, boolean isExist, int start) { - int min = Integer.MAX_VALUE; - int startIndex = (start >= 0) ? start : globalNodeIndex(); - int ind = exp.isUnion() ? startIndex : -1; - for (Exp e : exp) { - int n = index(query, e, isExist, ind); - min = Math.min(min, n); - } - return min; - } - - // use case: index filter exists (?x ?p ?y) - int indexExpFilter(QueryPlan query, Exp exp, boolean isExist) { - int min = Integer.MAX_VALUE; - boolean hasExist = index(query, exp.getFilter()); - List lVar = exp.getFilter().getVariables(true); - - for (String variable : lVar) { - Node qNode = query.getProperAndSubSelectNode(variable); - if (qNode == null) { - // Note: does not work with filter in exists pattern - // because getProperAndSubSelectNode does not go into exists pattern - logger.warn(Message.Prefix.UNDEF_VAR.getString(), variable); - addError(Message.Prefix.UNDEF_VAR.getString(), variable); - } else if (!isExist && !hasExist) { - int n = index(qNode); - min = Math.min(min, n); - } - } - if (hasExist) { - // use case: - // exists (?x p ?y filter(?x != ?z)) - min = -1; - } - return min; - } - - int indexExpQuery(QueryPlan query, Exp exp, boolean isExist) { - int min = Integer.MAX_VALUE; - QueryPlan qq = exp.getQuery(); - qq.setCompiled(); - qq.setGlobalQuery(this); - qq.setOuterQuery(query); - qq.setAggregate(); - - for (Exp e : exp) { - // for subquery, do not consider index here - index(qq, e, isExist, -1); - } - - for (Exp ee : qq.getSelectFun()) { - // use case: query node created to hold fun result - // see Transformer compiler.compileSelectFun() - Node sNode = ee.getNode(); - int n = index(sNode); - min = Math.min(min, n); - - if (ee.getFilter() != null) { - index(qq, ee.getFilter()); - } - } - - qq.complete2(); - return min; - } - - private int indexExpEdge(Exp exp) { - int min = Integer.MAX_VALUE; - for (int i = 0; i < exp.nbNode(); i++) { - int n = index(exp.getNode(i)); - min = Math.min(min, n); - } - return min; + return indexer.index(query, exp, isExist, start); } @@ -1329,26 +979,16 @@ public List getNodes(Filter f) { /** * Dependency with filter exp for tracking filter(exists {PAT}) - * */ boolean index(QueryPlan query, Filter f) { - return index(query, f.getExp()); + return indexer.index(query, f); } /** * Looking for filter(exist {}) */ boolean index(QueryPlan query, Expr exp) { - boolean b = false; - if (exp.oper() == ExprType.EXIST) { - index(query, getPattern(exp), true, -1); - b = true; - } else { - for (Expr ee : exp.getExpList()) { - b = index(query, ee) || b; - } - } - return b; + return indexer.index(query, exp); } public QueryPlan orderBy(Node node) { @@ -1476,11 +1116,11 @@ public Mapping getMapping() { public void setInsert(boolean isInsert) { - this.isInsert = isInsert; + clauses.setInsert(isInsert); } public boolean isInsert() { - return isInsert; + return clauses.isInsert(); } public boolean isFederate() { diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryClausesTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryClausesTest.java new file mode 100644 index 000000000..50084fb82 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryClausesTest.java @@ -0,0 +1,82 @@ +package fr.inria.corese.core.next.query.impl.engine.pattern; + +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@DisplayName("QueryClauses Tests") +class QueryClausesTest { + + @Test + @DisplayName("manage select nodes and functions") + void testSelectManagement() { + QueryClauses clauses = new QueryClauses(); + Node x = NodeImpl.forVariable("?x"); + clauses.setSelect(List.of(x)); + + assertEquals(1, clauses.getSelect().size()); + assertEquals(x, clauses.getSelectNode("?x")); + assertNull(clauses.getSelectNode("?y")); + + clauses.addSelect(x); + assertEquals(1, clauses.getSelectFun().size()); + assertNotNull(clauses.getSelectExp("?x")); + } + + @Test + @DisplayName("manage order by and group by") + void testOrderAndGroupBy() { + QueryClauses clauses = new QueryClauses(); + assertFalse(clauses.isOrderBy()); + assertFalse(clauses.hasGroupBy()); + + Node x = NodeImpl.forVariable("?x"); + clauses.addOrderBy(x); + clauses.addGroupBy(x); + + assertTrue(clauses.isOrderBy()); + assertTrue(clauses.hasGroupBy()); + assertEquals(1, clauses.getOrderBy().size()); + assertEquals(1, clauses.getGroupBy().size()); + } + + @Test + @DisplayName("limit and offset calculation") + void testLimitOffset() { + QueryClauses clauses = new QueryClauses(); + clauses.setLimit(10); + clauses.setOffset(5); + + assertEquals(10, clauses.getLimit()); + assertEquals(5, clauses.getOffset()); + assertEquals(15, clauses.getLimitOffset()); + + clauses.setOrderBy(List.of(Exp.create(Exp.Type.NODE, NodeImpl.forVariable("?x")))); + assertEquals(Integer.MAX_VALUE, clauses.getLimitOffset()); + } + + @Test + @DisplayName("redundant distinct with group by detection") + void testDistinctRedundantWithGroupBy() { + QueryClauses clauses = new QueryClauses(); + Node x = NodeImpl.forVariable("?x"); + + clauses.setDistinct(true); + clauses.setSelect(List.of(x)); + clauses.addGroupBy(x); + + assertTrue(clauses.isDistinctRedundantWithGroupBy()); + + clauses.setListGroup(true); + assertFalse(clauses.isDistinctRedundantWithGroupBy()); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryExpIndexerTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryExpIndexerTest.java new file mode 100644 index 000000000..29d8eb0d1 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryExpIndexerTest.java @@ -0,0 +1,66 @@ +package fr.inria.corese.core.next.query.impl.engine.pattern; + +import fr.inria.corese.core.next.query.impl.engine.model.Edge; +import fr.inria.corese.core.next.query.impl.engine.model.Node; +import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayName("QueryExpIndexer Tests") +class QueryExpIndexerTest { + + @Test + @DisplayName("collect variables from patterns into layout") + void testCollect() { + QueryPlan query = QueryPlan.create(); + VariableLayout layout = query.getLayout(); + QueryExpIndexer indexer = new QueryExpIndexer(query, layout); + + Node s = NodeImpl.forVariable("?s"); + Node p = NodeImpl.forVariable("?p"); + Node o = NodeImpl.forVariable("?o"); + + Edge edge = mock(Edge.class); + when(edge.nbNode()).thenReturn(2); + when(edge.getNode(0)).thenReturn(s); + when(edge.getEdgeVariable()).thenReturn(p); + when(edge.getNode(1)).thenReturn(o); + + query.add(Exp.create(Exp.Type.EDGE, edge)); + + indexer.collect(); + + assertNotNull(layout.getPatternNode("?s")); + assertNotNull(layout.getPatternNode("?p")); + assertNotNull(layout.getPatternNode("?o")); + } + + @Test + @DisplayName("index allocates sequential indexes to edges and nodes") + void testIndex() { + QueryPlan query = QueryPlan.create(); + VariableLayout layout = query.getLayout(); + QueryExpIndexer indexer = new QueryExpIndexer(query, layout); + + Node s = NodeImpl.forVariable("?s"); + Node o = NodeImpl.forVariable("?o"); + + Edge edge = mock(Edge.class); + when(edge.nbNode()).thenReturn(2); + when(edge.getNode(0)).thenReturn(s); + when(edge.getNode(1)).thenReturn(o); + + Exp edgeExp = Exp.create(Exp.Type.EDGE, edge); + + int min = indexer.index(query, edgeExp, false, -1); + + assertTrue(min >= 0); + assertEquals(min, s.getIndex()); + } +} From 75bc21ec4d42c2c8b975b54a39c3e33d2154f4b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?= Date: Mon, 26 Oct 2026 18:04:27 +0100 Subject: [PATCH 5/7] refactor(engine): eliminate legacy LDScript relics and unused constants in core.next --- .../impl/engine/eval/EvaluationState.java | 8 ---- .../next/query/impl/engine/eval/Message.java | 12 +---- .../query/impl/engine/filter/Extension.java | 39 --------------- .../next/query/impl/engine/model/ExpType.java | 10 ---- .../query/impl/engine/model/ExprType.java | 41 ---------------- .../impl/engine/pattern/QueryExpIndexer.java | 2 +- .../query/impl/engine/pattern/QueryPlan.java | 24 ---------- .../query/impl/engine/solution/Mapping.java | 48 +------------------ .../query/impl/engine/solution/Memory.java | 45 ----------------- .../impl/engine/solution/MappingTest.java | 35 ++++++++++++++ 10 files changed, 38 insertions(+), 226 deletions(-) delete mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Extension.java diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvaluationState.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvaluationState.java index e2f55a4c6..a0b2ec7ab 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvaluationState.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvaluationState.java @@ -36,18 +36,10 @@ public int getNbEdge() { return nbEdge; } - public void setNbEdge(int nbEdge) { - this.nbEdge = nbEdge; - } - public int getNbCall() { return nbCall; } - public void setNbCall(int nbCall) { - this.nbCall = nbCall; - } - public int getNbResult() { return nbResult; } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Message.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Message.java index 3cbc949ec..b246233e3 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Message.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/Message.java @@ -8,17 +8,7 @@ private Message() { } public enum Prefix { - UNDEF_VAR("Undefined variable: "), - UNDEF_FUN("Undefined function: "), - FAIL("Corese fail at compile time"), - FAIL_AT("Corese fail at: "), - EVAL("Eval: "), - FREE("Pattern is Free: "), - CHECK("Check: "), - REWRITE("Compiler rewrite error: "), - PRAGMA("Pragma: "), - LOOP("Loop: "), - AGG("Aggregate limited to (defined) variable: "); + UNDEF_VAR("Undefined variable: "); private final String text; Prefix(String text) { diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Extension.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Extension.java deleted file mode 100644 index b2ddcb76e..000000000 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/filter/Extension.java +++ /dev/null @@ -1,39 +0,0 @@ -package fr.inria.corese.core.next.query.impl.engine.filter; - -import fr.inria.corese.core.next.query.impl.engine.model.Expr; -import fr.inria.corese.core.next.data.api.model.DatatypeValue; - -/** - * Manage extension functions - * Expr exp must have executed exp.local() to tag - * local variables - * - * @author Olivier Corby, Wimmics INRIA I3S, 2015 - * - */ -public interface Extension { - - void define(Expr exp); - - Expr get(String name); - - Expr get(Expr exp); - - Expr get(Expr exp, String name); - - Expr get(String label, int n); - - Expr getMetadata(String metadata, int n); - - Expr getMethod(String label, DatatypeValue type, DatatypeValue[] param); - - - void removeNamespace(String name); - - boolean isDefined(Expr exp); - - boolean isMethod(); - - void setDebug(boolean b); - -} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/ExpType.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/ExpType.java index c65262042..bb4145050 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/ExpType.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/ExpType.java @@ -7,16 +7,6 @@ */ public interface ExpType { - String KGRAM = "http://ns.inria.fr/corese.core.kgram/"; - String SPARQL = "http://ns.inria.fr/sparql-function/"; - String STL = "http://ns.inria.fr/sparql-template/"; - String EXT = "http://ns.inria.fr/sparql-extension/"; - String DOM = "http://ns.inria.fr/sparql-extension/dom/"; - String BNODE = EXT + "bnode"; - String DT = "http://ns.inria.fr/sparql-datatype/"; - - - enum Type { EMPTY("EMPTY"), AND("AND"), diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/ExprType.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/ExprType.java index c6192f8e9..2a0148f67 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/ExprType.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/model/ExprType.java @@ -41,12 +41,9 @@ public interface ExprType { int REGEX = 29; int DATATYPE = 30; - int SELF = 32; int DEBUG = 33; int KGRAM = 36; - int SQL = 37; int XPATH = 38; - int SKIP = 39; int UNNEST = 41; int EXIST = 42; @@ -66,22 +63,18 @@ public interface ExprType { int MINUTES = 64; int SECONDS = 65; - int URI = 74; int TZ = 75; - int TRIPLE = 88; int SUBJECT = 89; int PREDICATE = 90; int OBJECT = 91; - int EQNE = 109; int EQ = 110; int NE = 111; - int NEQ = 111; int GL = 112; int LE = 113; @@ -93,59 +86,25 @@ public interface ExprType { int MINUS = 118; int MULT = 119; - int START = 122; // ^ int IN = 123; int STAR = 125; - - int DISPLAY = 200; - int NODE = 209; int LOAD = 212; - int INDEX = 221; int ID = 223; int TEST = 224; - int TURTLE = 227; - int QNAME = 238; - - int AGGREGATE = 276; - int STL_FORMAT = 277; - - int FORMAT = 283; int QUERY = 302; int EXTENSION = 303; int LIST = 309; - int MAP = 310; - int MAPLIST = 311; - int APPLY = 312; - int LET = 313; int ERROR = 315; - int MAPEVERY = 316; - int MAPANY = 317; - int MAPFIND = 318; - int MAPFINDLIST = 319; - int MAPMERGE = 320; - int SET = 322; - int SEQUENCE = 323; - int RETURN = 324; int EVAL = 325; - int FUNCALL = 326; int FOR = 327; - int REDUCE = 329; - - - int STATIC = 336; - int THROW = 338; - - int SLICE = 500; - - } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryExpIndexer.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryExpIndexer.java index 4054238d6..5fd8f2ed0 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryExpIndexer.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryExpIndexer.java @@ -186,7 +186,7 @@ int indexExpFilter(QueryPlan query, Exp exp, boolean isExist) { for (String variable : lVar) { Node qNode = query.getProperAndSubSelectNode(variable); if (qNode == null) { - logger.warn(Message.Prefix.UNDEF_VAR.getString(), variable); + logger.warn("{}{}", Message.Prefix.UNDEF_VAR.getString(), variable); queryPlan.addError(Message.Prefix.UNDEF_VAR.getString(), variable); } else if (!isExist && !hasExist) { int n = queryPlan.index(qNode); diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java index e97e1efc6..697cb952b 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java @@ -43,7 +43,6 @@ public final class QueryPlan extends Exp { List funList; List errors; - List info; // gNode is a local graph node when subquery has no ?g in its select // use case: graph ?g (select where ...) Node gNode; @@ -130,7 +129,6 @@ private QueryPlan(QueryPlan source, PlanCopy copies) { this.failure = copies.copy(source.failure); this.funList = copies.copy(source.funList); this.errors = copies.copy(source.errors); - this.info = copies.copy(source.info); this.gNode = copies.copy(source.gNode); this.globalQuery = copies.copy(source.globalQuery); this.outerQuery = copies.copy(source.outerQuery); @@ -284,19 +282,6 @@ void setError(String mes, Object obj, boolean duplicate) { } } - - public void addInfo(String mes, Object obj) { - if (info == null) { - info = new ArrayList<>(); - } - if (obj != null) { - mes += obj; - } - if (!info.contains(mes)) { - info.add(mes); - } - } - public Exp getBody() { return first(); } @@ -843,11 +828,6 @@ public QueryPlan getQuery() { return this; } - public QueryPlan getQueryPlan() { - return this; - } - - public Node getOuterNode(Node subNode) { return getExtNode(subNode.getLabel()); } @@ -1055,10 +1035,6 @@ public Filter getFilter(String name) { return ftable.get(name); } - public Filter getGlobalFilter(String name) { - return getGlobalQuery().getFilter(name); - } - public void setRule(boolean rule) { isRule = rule; } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mapping.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mapping.java index 4d7721e00..a68721676 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mapping.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mapping.java @@ -199,6 +199,7 @@ public void project(QueryPlan q) { ltNodes.add(tNode); } } + this.values = null; init(lqNodes, ltNodes); } @@ -336,10 +337,6 @@ public Node[] getGroupNodes() { return getGroupAlter(); } - public Node getDistinctNode(int n) { - return getDistinctNodes()[n]; - } - public Node getGroupBy(Node qNode, int n) { if (getGroupByNodes().length == 0) { return getNode(qNode); @@ -488,49 +485,6 @@ Node getNodeBasic(Node node) { return null; } - /** - * Use case: - * let (((?var, ?val)) = ?m) - * let ((?x, ?y) = ?m) - */ - public Object getValue(String varString, int n) { - if (varString == null) { - // let (((?var, ?val)) = ?m) -- ?m : Mapping - // compiled as: let (?vv = xt:get(?m, 0), (?var, ?val) = ?vv) - // xt:get(?m, 0) evaluated as xt:gget(?m, null, 0) - // hence var == null - return getBinding(n); - } - // let ((?x, ?y) = ?m) -- ?m : Mapping - return getValue(varString); - } - - @SuppressWarnings("java:S1168") // Null signals an unbound index in internal evaluation - List getBinding(int n) { - List> l = getList(); - if (n < l.size()) { - return l.get(n); - } - return null; - } - - - public List> getList() { - ArrayList> list = new ArrayList<>(); - int i = 0; - for (Node n : getQueryNodes()) { - Node val = getNode(i++); - if (val != null) { - ArrayList l = new ArrayList<>(2); - l.add(n.getDatatypeValue()); - l.add(val.getDatatypeValue()); - list.add(l); - } - } - return list; - } - - @Override public Node[] getQueryNodes() { return queryNodes; diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Memory.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Memory.java index abfefe48f..d89ea9ba0 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Memory.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Memory.java @@ -944,51 +944,6 @@ public boolean hasBind() { return bindingContext != null && bindingContext.hasBind(); } - List> getList() { - ArrayList> list = new ArrayList<>(); - int i = 0; - for (Node n : getQueryNodes()) { - Node val = getNode(i++); - if (n != null && val != null) { - ArrayList l = new ArrayList<>(2); - l.add(n.getDatatypeValue()); - l.add(val.getDatatypeValue()); - list.add(l); - } - } - return list; - } - - /** - * Use case: - * let (((?var, ?val)) = ?m) - * let ((?x, ?y) = ?m) - */ - public Object getValue(String varString, int n) { - if (varString == null) { - // let (((?var, ?val)) = ?m) -- ?m : Mapping - // compiled as: let (?vv = xt:get(?m, 0), (?var, ?val) = ?vv) - // xt:get(?m, 0) evaluated as xt:gget(?m, null, 0) - // hence var == null - return getBinding(n); - } - // let ((?x, ?y) = ?m) -- ?m : Mapping - Node node = getNode(varString); - if (node == null) { - return null; - } - return node.getDatatypeValue(); - } - - @SuppressWarnings("java:S1168") // Null signals an unbound index in internal evaluation - List getBinding(int n) { - List> l = getList(); - if (n < l.size()) { - return l.get(n); - } - return null; - } - public DatatypeValue getReport() { return detail; } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingTest.java index b863c83ef..963a7f1d9 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingTest.java @@ -368,4 +368,39 @@ void testIsNotBound() { assertFalse(mapping.isBound(mockQueryNode), "Unbound node should return false"); } } + + @Nested + @DisplayName("Project Tests") + class ProjectTests { + + @Test + @DisplayName("Projecting mapping should eliminate unselected variables from node values and variable names") + void testProjectRemovesUnselectedVariables() { + Node qx = mock(Node.class); + when(qx.isVariable()).thenReturn(true); + when(qx.getLabel()).thenReturn("?x"); + Node qy = mock(Node.class); + when(qy.isVariable()).thenReturn(true); + when(qy.getLabel()).thenReturn("?y"); + + Node tx = mock(Node.class); + Node ty = mock(Node.class); + + Mapping m = Mapping.create(List.of(qx, qy), List.of(tx, ty)); + assertEquals(tx, m.getNode("?x")); + assertEquals(ty, m.getNode("?y")); + assertTrue(m.getVariableNames().contains("?x")); + assertTrue(m.getVariableNames().contains("?y")); + + fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan q = mock(fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan.class); + when(q.getSelect()).thenReturn(List.of(qx)); + + m.project(q); + + assertEquals(tx, m.getNode("?x")); + assertNull(m.getNode("?y"), "Unprojected variable ?y should be null"); + assertTrue(m.getVariableNames().contains("?x")); + assertFalse(m.getVariableNames().contains("?y"), "Unprojected variable ?y should be removed from variable names"); + } + } } \ No newline at end of file From e3a3d0ea9bba9099aa3a2181e745e7406f641922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?= Date: Mon, 26 Oct 2026 18:09:24 +0100 Subject: [PATCH 6/7] refactor(solution): relocate Group to solution package and remove dead indexing branches --- .../engine/{pattern => solution}/Group.java | 21 ++----------------- .../query/impl/engine/solution/Mappings.java | 1 - .../engine/solution/MappingsAggregator.java | 5 ----- .../solution/MappingsDistinctFilter.java | 2 -- .../{pattern => solution}/GroupTest.java | 4 +--- 5 files changed, 3 insertions(+), 30 deletions(-) rename src/main/java/fr/inria/corese/core/next/query/impl/engine/{pattern => solution}/Group.java (86%) rename src/test/java/fr/inria/corese/core/next/query/impl/engine/{pattern => solution}/GroupTest.java (99%) diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/Group.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Group.java similarity index 86% rename from src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/Group.java rename to src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Group.java index 0aa7696f4..4c8d41acb 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/Group.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Group.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.next.query.impl.engine.pattern; +package fr.inria.corese.core.next.query.impl.engine.solution; import java.util.ArrayList; import java.util.Arrays; @@ -8,8 +8,7 @@ import java.util.Map; import fr.inria.corese.core.next.query.impl.engine.model.Node; -import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; -import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; +import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; /** * Groups mappings and tracks distinct solutions using immutable tuple keys. @@ -19,7 +18,6 @@ */ public class Group implements Comparator { - private static final boolean COMPARE_INDEX = false; private static final Mappings DISTINCT_MARKER = new Mappings(); private final Map table = new LinkedHashMap<>(); @@ -37,10 +35,6 @@ public Group(List list) { this.nodes = list; } - public static boolean isCompareIndex() { - return COMPARE_INDEX; - } - public static Group create(List nodeList) { return new Group(nodeList); } @@ -78,11 +72,6 @@ public void setExtend(boolean extend) { isExtend = extend; } - @SuppressWarnings("java:S1172") // Retained for API compatibility. - public void setDuplicate(boolean duplicate) { - // Duplicates are managed by the mappings collection. - } - @SuppressWarnings("java:S1172") // Retained as an extension point. public boolean accept(Node node) { return true; @@ -179,9 +168,6 @@ private static boolean sameNode(Node left, Node right) { if (left == null || right == null) { return false; } - if (isCompareIndex() && left.getIndex() != -1 && right.getIndex() != -1) { - return left.getIndex() == right.getIndex(); - } // Preserve the original grouping comparator, including datatype ordering. return left.compare(right) == 0; } @@ -190,9 +176,6 @@ private static int nodeHash(Node node) { if (node == null) { return 0; } - if (isCompareIndex() && node.getIndex() != -1) { - return Integer.hashCode(node.getIndex()); - } return node.hashCode(); } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mappings.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mappings.java index 3c5cdfe46..027631e49 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mappings.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/Mappings.java @@ -5,7 +5,6 @@ import fr.inria.corese.core.next.query.impl.engine.model.Edge; import fr.inria.corese.core.next.query.impl.engine.model.Node; import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; -import fr.inria.corese.core.next.query.impl.engine.pattern.Group; import fr.inria.corese.core.next.query.impl.engine.pattern.PlanCopy; import fr.inria.corese.core.next.query.impl.engine.pattern.PlanCopyable; import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsAggregator.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsAggregator.java index f57c5c202..bdab89fb7 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsAggregator.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsAggregator.java @@ -6,7 +6,6 @@ import fr.inria.corese.core.next.query.impl.engine.model.Filter; import fr.inria.corese.core.next.query.impl.engine.model.Node; import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; -import fr.inria.corese.core.next.query.impl.engine.pattern.Group; import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; import fr.inria.corese.core.next.query.impl.engine.spi.Environment; import fr.inria.corese.core.next.query.impl.engine.spi.Evaluator; @@ -259,10 +258,6 @@ public Group createGroup(Exp exp) { public Group createGroup(List list, boolean extend) { Group gp = Group.createFromExp(list); - QueryPlan query = mappings.getQuery(); - if (query != null) { - gp.setDuplicate(query.isDistribute()); - } gp.setExtend(extend); gp.setFake(mappings.isFake()); diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsDistinctFilter.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsDistinctFilter.java index 266696547..e46482495 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsDistinctFilter.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/solution/MappingsDistinctFilter.java @@ -2,7 +2,6 @@ import fr.inria.corese.core.next.query.impl.engine.model.Node; import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; -import fr.inria.corese.core.next.query.impl.engine.pattern.Group; import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; import java.util.List; @@ -31,7 +30,6 @@ public void initiate(QueryPlan q, boolean distinct, boolean all) { List expList = all ? getAllExpList(q) : q.getSelectFun(); Group distinctGroup = Group.createFromExp(expList); distinctGroup.setDistinct(true); - distinctGroup.setDuplicate(q.isDistribute()); mappings.setDistinct(distinctGroup); } } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/GroupTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/GroupTest.java similarity index 99% rename from src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/GroupTest.java rename to src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/GroupTest.java index 76af11ffc..594901218 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/engine/pattern/GroupTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/engine/solution/GroupTest.java @@ -1,9 +1,7 @@ -package fr.inria.corese.core.next.query.impl.engine.pattern; +package fr.inria.corese.core.next.query.impl.engine.solution; import fr.inria.corese.core.next.query.impl.engine.model.Node; import fr.inria.corese.core.next.query.impl.engine.model.NodeImpl; -import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; -import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; import java.lang.reflect.Constructor; import java.util.ArrayList; From 11dcb8f1c0d3e8e2da1345e148e594cf27c8b44e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?= Date: Mon, 26 Oct 2026 18:14:11 +0100 Subject: [PATCH 7/7] refactor(event): streamline ProcessVisitor notification contracts and prune dead hooks --- docs/next-engine-ast-boundary.md | 4 +- .../engine/eval/EvalVisitorDispatcher.java | 2 +- .../impl/engine/event/ProcessVisitor.java | 125 +++++++----------- .../engine/event/ProcessVisitorDefault.java | 45 ++----- .../query/impl/engine/pattern/QueryPlan.java | 2 +- 5 files changed, 67 insertions(+), 111 deletions(-) diff --git a/docs/next-engine-ast-boundary.md b/docs/next-engine-ast-boundary.md index eba71ebbd..1efb2d4a4 100644 --- a/docs/next-engine-ast-boundary.md +++ b/docs/next-engine-ast-boundary.md @@ -13,8 +13,8 @@ Previously, three source files in `next/query/impl/engine` retained AST dependen `AstBackedFilter` retains AST-level access internally where needed. - `pattern/Exp.java`: exposed `getFilterExpression()` delegating to `Filter`. Removed as it had no engine callers. -- `pattern/Query.java`: retained `QueryAst ast` with `getAST()`, `getGlobalAST()`, and `setAST()`. - Removed from `Query`; query builder compilation produces pure engine operator trees. +- `pattern/QueryPlan.java` (formerly `Query.java`): retained `QueryAst ast` with `getAST()`, `getGlobalAST()`, and `setAST()`. + Removed from `QueryPlan`; query builder compilation produces pure engine operator trees. ## Guard behavior diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalVisitorDispatcher.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalVisitorDispatcher.java index 84021d387..d5a740670 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalVisitorDispatcher.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/eval/EvalVisitorDispatcher.java @@ -15,7 +15,7 @@ * Coordinates and dispatches execution lifecycle visitors and result listeners * for SPARQL evaluation in {@link Eval}. * - *

Encapsulates the active {@link ProcessVisitor} (for LDScript and federation hooks) + *

Encapsulates the active {@link ProcessVisitor} (for query observation and reporting) * and the optional {@link ResultListener} (for inference rule execution).

*/ public final class EvalVisitorDispatcher { diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ProcessVisitor.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ProcessVisitor.java index 43db1ff78..73f08a133 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ProcessVisitor.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ProcessVisitor.java @@ -1,55 +1,51 @@ package fr.inria.corese.core.next.query.impl.engine.event; +import fr.inria.corese.core.next.data.api.model.DatatypeValue; import fr.inria.corese.core.next.query.impl.engine.eval.Eval; +import fr.inria.corese.core.next.query.impl.engine.model.Edge; +import fr.inria.corese.core.next.query.impl.engine.model.Expr; +import fr.inria.corese.core.next.query.impl.engine.model.Node; import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; import fr.inria.corese.core.next.query.impl.engine.pattern.QueryPlan; import fr.inria.corese.core.next.query.impl.engine.solution.Mapping; import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; -import fr.inria.corese.core.next.query.impl.engine.model.Edge; -import fr.inria.corese.core.next.query.impl.engine.model.Expr; -import fr.inria.corese.core.next.query.impl.engine.model.Node; -import fr.inria.corese.core.next.query.impl.engine.path.Path; -import fr.inria.corese.core.next.data.api.model.DatatypeValue; - - /** + * Execution observer and lifecycle listener interface for query processing in {@link Eval}. + * + *

Supports life-cycle notifications (init, before, after, start, finish), + * solution filtering (distinct, limit, result), term comparison, and AST operator events.

+ * * @author Olivier Corby, Wimmics INRIA I3S, 2018 */ public interface ProcessVisitor { - int SLICE_DEFAULT = 20; - default boolean isShareable() { return false; } - default DatatypeValue defaultValue() { - return null; + default void init(QueryPlan q) { + // Notification callback } - default DatatypeValue init(QueryPlan q) { - return defaultValue(); + default void before(QueryPlan q) { + // Notification callback } - default DatatypeValue before(QueryPlan q) { - return defaultValue(); + default void after(Mappings map) { + // Notification callback } - default DatatypeValue after(Mappings map) { - return defaultValue(); + default void start(QueryPlan q) { + // Notification callback } - default DatatypeValue start(QueryPlan q) { - return defaultValue(); + default void finish(Mappings map) { + // Notification callback } - default DatatypeValue finish(Mappings map) { - return defaultValue(); - } - - default DatatypeValue orderby(Mappings map) { - return defaultValue(); + default void orderby(Mappings map) { + // Notification callback } default boolean distinct(Eval eval, QueryPlan q, Mapping map) { @@ -60,93 +56,72 @@ default boolean limit(Mappings map) { return true; } - default int slice() { - return SLICE_DEFAULT; - } - - default DatatypeValue produce(Eval eval, Node g, Edge edge) { - return defaultValue(); - } - default DatatypeValue candidate(Eval eval, Node g, Edge q, Edge e) { - return defaultValue(); - } - - default DatatypeValue path(Eval eval, Node g, Edge q, Path p, Node s, Node o) { - return defaultValue(); - } - - default boolean step(Eval eval, Node g, Edge q, Path p, Node s, Node o) { - return true; + return null; } default boolean result(Eval eval, Mappings map, Mapping m) { return true; } - default DatatypeValue statement(Eval eval, Node g, Exp e) { - return defaultValue(); + default void statement(Eval eval, Node g, Exp e) { + // Notification callback } - - default DatatypeValue bgp(Eval eval, Node g, Exp e, Mappings m) { - return defaultValue(); + default void bgp(Eval eval, Node g, Exp e, Mappings m) { + // Notification callback } - default DatatypeValue join(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { - return defaultValue(); + default void join(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { + // Notification callback } - default DatatypeValue optional(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { - return defaultValue(); + default void optional(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { + // Notification callback } - default DatatypeValue minus(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { - return defaultValue(); + default void minus(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { + // Notification callback } - default DatatypeValue union(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { - return defaultValue(); + default void union(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { + // Notification callback } - default DatatypeValue graph(Eval eval, Node g, Exp e, Mappings m) { - return defaultValue(); + default void graph(Eval eval, Node g, Exp e, Mappings m) { + // Notification callback } - default DatatypeValue query(Eval eval, Node g, Exp e, Mappings m) { - return defaultValue(); + default void query(Eval eval, Node g, Exp e, Mappings m) { + // Notification callback } - default DatatypeValue service(Eval eval, Node s, Exp e, Mappings m) { - return defaultValue(); + default void service(Eval eval, Node s, Exp e, Mappings m) { + // Notification callback } - default DatatypeValue values(Eval eval, Node g, Exp e, Mappings m) { - return defaultValue(); + default void values(Eval eval, Node g, Exp e, Mappings m) { + // Notification callback } default boolean filter(Eval eval, Node g, Expr e, boolean b) { return b; } - default boolean having(Eval eval, Expr e, boolean b) { - return b; + default void having(Eval eval, Expr e, boolean b) { + // Notification callback } - default DatatypeValue bind(Eval eval, Node g, Exp e, DatatypeValue val) { - return val; + default void bind(Eval eval, Node g, Exp e, DatatypeValue val) { + // Notification callback } - default DatatypeValue select(Eval eval, Expr e, DatatypeValue val) { - return val; + default void select(Eval eval, Expr e, DatatypeValue val) { + // Notification callback } - default DatatypeValue aggregate(Eval eval, Expr e, DatatypeValue val) { - return val; - } - - default boolean produce() { - return false; + default void aggregate(Eval eval, Expr e, DatatypeValue val) { + // Notification callback } default boolean statement() { diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ProcessVisitorDefault.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ProcessVisitorDefault.java index 52155e422..b792b1696 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ProcessVisitorDefault.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/event/ProcessVisitorDefault.java @@ -1,74 +1,55 @@ package fr.inria.corese.core.next.query.impl.engine.event; import fr.inria.corese.core.next.query.impl.engine.eval.Eval; +import fr.inria.corese.core.next.query.impl.engine.model.Node; import fr.inria.corese.core.next.query.impl.engine.pattern.Exp; import fr.inria.corese.core.next.query.impl.engine.solution.Mappings; -import fr.inria.corese.core.next.query.impl.engine.model.Node; -import fr.inria.corese.core.next.data.api.model.DatatypeValue; - -/** No-op process visitor that forwards reporting events to the query binding context. */ -@SuppressWarnings("java:S4144") // Distinct visitor callbacks intentionally share no-op behavior. +/** + * Default process visitor that forwards reporting events to the query binding context + * when query report is enabled. + */ public final class ProcessVisitorDefault implements ProcessVisitor { - @Override - public int slice() { - return ProcessVisitor.SLICE_DEFAULT; - } - - - @Override - public DatatypeValue defaultValue() { - return null; - } - - void visit(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { if (eval.getQuery().isReportEnabled()) { eval.getBind().visit(e, g, m1, m2); } } - @Override - public DatatypeValue graph(Eval eval, Node g, Exp e, Mappings m1) { + public void graph(Eval eval, Node g, Exp e, Mappings m1) { visit(eval, g, e, m1, null); - return defaultValue(); } @Override - public DatatypeValue query(Eval eval, Node g, Exp e, Mappings m1) { + public void query(Eval eval, Node g, Exp e, Mappings m1) { visit(eval, g, e, m1, null); - return defaultValue(); } @Override - public DatatypeValue service(Eval eval, Node g, Exp e, Mappings m1) { + public void service(Eval eval, Node g, Exp e, Mappings m1) { visit(eval, g, e, m1, null); - return defaultValue(); } @Override - public DatatypeValue optional(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { + public void optional(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { visit(eval, g, e, m1, m2); - return defaultValue(); } @Override - public DatatypeValue minus(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { + public void minus(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { visit(eval, g, e, m1, m2); - return defaultValue(); } @Override - public DatatypeValue union(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { + public void union(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { visit(eval, g, e, m1, m2); - return defaultValue(); } @Override - public DatatypeValue join(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { + public void join(Eval eval, Node g, Exp e, Mappings m1, Mappings m2) { visit(eval, g, e, m1, m2); - return defaultValue(); } + } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java index 697cb952b..fefe7d2a6 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/engine/pattern/QueryPlan.java @@ -177,7 +177,7 @@ public static QueryPlan create(Exp e) { return new QueryPlan(e); } - /** Creates an empty compiled query. */ + /** Creates an empty query plan. */ public static QueryPlan create() { return new QueryPlan(); }