Skip to content

Commit 81bbd43

Browse files
[MOD] XQuery, destructuring records. qtspecs#2764
1 parent 31c66f5 commit 81bbd43

5 files changed

Lines changed: 38 additions & 10 deletions

File tree

basex-core/src/main/java/org/basex/query/QueryParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1573,7 +1573,7 @@ private void letStructBinding(final InputInfo ii, final LinkedList<Clause> claus
15731573
for(final Var var : vrs) {
15741574
final Expr expr = switch(binding.endCp) {
15751575
case ']' -> Function._ARRAY_GET.get(var.info, seqRef, Itr.get(++i));
1576-
case '}' -> Function._MAP_GET.get(var.info, seqRef, Str.get(var.name.local()));
1576+
case '}' -> new Lookup(var.info, seqRef, Str.get(var.name.local()));
15771577
default -> (++i < vrs.size() ? Function.ITEMS_AT : Function.SUBSEQUENCE).
15781578
get(var.info, seqRef, Itr.get(i));
15791579
};

basex-core/src/main/java/org/basex/query/expr/Lookup.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public Lookup(final InputInfo info, final Expr... expr) {
3939
public Expr optimize(final CompileContext cc) throws QueryException {
4040
exprs[1] = exprs[1].simplifyFor(Simplify.DATA, cc);
4141

42-
final Expr inputs = exprs[0];
42+
final Expr inputs = exprs[0], keys = exprs[1];
4343
final long is = inputs.size();
4444
if(is == 0) return cc.replaceWith(this, inputs);
4545

@@ -48,11 +48,13 @@ public Expr optimize(final CompileContext cc) throws QueryException {
4848
final boolean map = tp instanceof MapType, array = tp instanceof ArrayType;
4949
if(!(map || array)) return this;
5050

51+
// pre-evaluate a single-key lookup
52+
if(is == 1 && inputs instanceof Value && keys instanceof Item) return cc.preEval(this);
53+
5154
final Expr expr = opt(cc);
5255
if(expr != this) return cc.replaceWith(this, expr);
5356

5457
// derive type from input expression
55-
final Expr keys = exprs[1];
5658
final SeqType kt = keys.seqType();
5759
final SeqType st = map ? ((MapType) tp).valueType() : ((ArrayType) tp).valueType();
5860
Occ occ = st.occ;
@@ -80,12 +82,14 @@ private Expr opt(final CompileContext cc) throws QueryException {
8082
if(ks == 0) return keys;
8183

8284
final Type it = input.seqType().type;
83-
if(it instanceof final RecordType rt && rt.strict() && keys != WILDCARD &&
84-
!(keys instanceof final AStr str && ks == 1 && rt.fields().contains(str.string(info)))) {
85-
return this;
86-
}
8785
final boolean map = it instanceof MapType, array = it instanceof ArrayType;
8886
if(map || array) {
87+
// keep the lookup if a runtime value could be a strict record that lacks a requested key
88+
if(keys != WILDCARD && it instanceof final MapType mt && (mt instanceof final RecordType rt
89+
? rt.strict() && !(ks == 1 && keys instanceof final AStr str &&
90+
rt.fields().contains(str.string(info)))
91+
: mt.keyType().intersect(BasicType.STRING) != null)) return this;
92+
8993
/* REWRITE LOOKUP:
9094
* MAP?* → map:items(MAP)
9195
* ARRAY?* → array:items(MAP)

basex-core/src/main/java/org/basex/query/value/type/RecordType.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ public final class RecordType extends MapType {
2323
/** Maximum number of entries in generated records. */
2424
public static final int MAX_GENERATED_SIZE = 32;
2525

26-
/** Sealed flag: a sealed record type carries a runtime type annotation and constrains
27-
* field access (lookup of an undeclared field raises a type error). Inferred record types
28-
* (e.g. from map constructors) are not sealed and behave like ordinary maps. */
26+
/** Sealed flag (runtime type annotation, constrains field access). */
2927
private final boolean sealed;
3028
/** Record fields. */
3129
private final TokenObjectMap<RecordField> fields;

basex-core/src/test/java/org/basex/query/expr/LookupTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public final class LookupTest extends SandboxTest {
1616
/** Test. */
1717
@Test public void map() {
1818
query("{ 'a': 'b' } ? a", "b");
19+
query("{ 'a': 'b' } ? c", "");
1920
query("({ 'a': 'b' }, { 'c': 'd' }) ? a", "b");
2021
query("{ 'a': 'b', 'c': 'd' } ? ('a', 'c')", "b\nd");
2122
query("({ 'a': 'b' }, { 'c': 'd' }) ? ('a', 'c')", "b\nd");
@@ -84,8 +85,26 @@ public final class LookupTest extends SandboxTest {
8485
query("({ 'k': 7 }, { 'k': 8 }) ! self::jnode()?k", "7\n8");
8586
}
8687

88+
/** Rewrite lookups to map:get/array:get unless this could suppress a strict-record error. */
89+
@Test public void rewrite() {
90+
check("({ 'a': <x/> }, { 'b': <y/> })?a", "<x/>", exists(Lookup.class), empty(_MAP_GET));
91+
check("declare record local:r(a, b); local:r(<x/>, <y/>)?('a', 'b')", "<x/>\n<y/>",
92+
exists(Lookup.class));
93+
check("[ <x/>, <y/> ]?(1, 2)", "<x/>\n<y/>", exists(Lookup.class));
94+
95+
check("({ 'a': <x/> })?a", "<x/>", exists(RecordGet.class), empty(Lookup.class));
96+
check("({ 'a': <x/> })?b", "", empty(Lookup.class));
97+
check("({ 1: <x/> })?1", "<x/>", exists(_MAP_GET), empty(Lookup.class));
98+
check("({ 'a': <x/> })?*", "<x/>", exists(_MAP_ITEMS), empty(Lookup.class));
99+
check("[ <x/> ]?1", "<x/>", exists(_ARRAY_GET), empty(Lookup.class));
100+
check("[ <x/> ]?*", "<x/>", exists(_ARRAY_ITEMS), empty(Lookup.class));
101+
}
102+
87103
/** Test. */
88104
@Test public void error() {
89105
error("1?a", LOOKUP_X);
106+
error("declare record local:r(a, b); local:r(1, 2)?c", RECORDFIELD_X_X);
107+
error("declare record local:r(a, b); (local:r(1, 2), { 'c': 3 })?c", RECORDFIELD_X_X);
108+
error("declare record local:r(a, b); local:r(<x/>, <y/>)?1", RECORDFIELD_X_X);
90109
}
91110
}

basex-core/src/test/java/org/basex/query/expr/XQuery4Test.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,13 @@ public final class XQuery4Test extends SandboxTest {
876876
query("let ${$a, $b, $c} := " + value + " return [ $a, $b, $c ]", "[1,2,3]");
877877
query("let ${$a, $b, $c, $d} := " + value + " return [ $a, $b, $c, $d ]", "[1,2,3,()]");
878878

879+
query("declare record local:r(a as xs:integer, b as xs:integer); "
880+
+ "let ${ $a, $b } := local:r(1, 2) return $a + $b", 3);
881+
error("declare record local:r(a as xs:integer, b as xs:integer); "
882+
+ "let ${ $c } := local:r(1, 2) return $c", RECORDFIELD_X_X);
883+
query("let $m as map(xs:string, xs:integer) := map:merge(('a', 'b') ! map:entry(., 1)) "
884+
+ "let ${ $z } := $m return empty($z)", true);
885+
879886
// GH-2452
880887
query("""
881888
let $($a, $b) := (1 to 6) ! string()

0 commit comments

Comments
 (0)