Skip to content

Commit 3ece5cc

Browse files
[MOD] XQuery, record constructor: equality checks, string representation
1 parent 55ea2b9 commit 3ece5cc

5 files changed

Lines changed: 103 additions & 66 deletions

File tree

basex-core/src/main/java/org/basex/query/expr/constr/CMap.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public Expr optimize(final CompileContext cc) throws QueryException {
6767
}
6868

6969
// not too large, only strings as keys? replace with record constructor
70-
boolean record = el <= RecordType.MAX_GENERATED_SIZE;
70+
boolean record = el / 2 <= RecordType.MAX_GENERATED_SIZE;
7171
for(int e = 0; e < el && record; e += 2) {
7272
if(nested(e) || !(exprs[e] instanceof AStr && exprs[e].seqType().eq(Types.STRING_O))) {
7373
record = false;

basex-core/src/main/java/org/basex/query/func/RecordConstructor.java

Lines changed: 47 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,10 @@
2020
public class RecordConstructor extends StandardFunc {
2121
/** Record type. */
2222
private final RecordType recordType;
23-
/** The size of a structure resulting from this constructor. */
24-
private final long structSize;
25-
/** The map builder function. */
26-
private final QueryBiFunction<QueryContext, InputInfo, XQMap> builder;
2723
/** Field names. */
2824
private final QNm[] names;
25+
/** Created compact record map. */
26+
private final boolean compact;
2927

3028
/**
3129
* Constructor.
@@ -36,18 +34,12 @@ public RecordConstructor(final RecordType recordType) {
3634
final TokenObjectMap<RecordField> fields = recordType.fields();
3735
final int fs = fields.size();
3836
names = new QNm[fs];
39-
boolean complete = true;
40-
for(int i = 1; i <= fs; ++i) {
41-
names[i - 1] = new QNm(fields.key(i));
42-
complete = complete && fields.value(i).alwaysAdded();
43-
}
44-
if(complete) {
45-
builder = this::recordMap;
46-
structSize = fs;
47-
} else {
48-
builder = this::map;
49-
structSize = -1;
37+
boolean c = true;
38+
for(int f = 1; f <= fs; ++f) {
39+
names[f - 1] = new QNm(fields.key(f));
40+
c = c && fields.value(f).alwaysAdded();
5041
}
42+
compact = c;
5143
}
5244

5345
/**
@@ -58,69 +50,62 @@ public RecordConstructor(final RecordType recordType) {
5850
* @return constructor function
5951
*/
6052
public static RecordConstructor get(final InputInfo ii, final RecordType rt, final Expr[] args) {
61-
RecordConstructor constructor = new RecordConstructor(rt);
62-
constructor.init(ii, definition(rt), args);
63-
return constructor;
64-
}
65-
66-
@Override
67-
public Expr opt(final CompileContext cc) throws QueryException {
68-
return values(true, cc) ? cc.preEval(this) : this;
53+
final RecordConstructor rc = new RecordConstructor(rt);
54+
rc.init(ii, definition(rt), args);
55+
return rc;
6956
}
7057

7158
@Override
7259
public XQMap item(final QueryContext qc, final InputInfo ii) throws QueryException {
73-
return builder.apply(qc, ii);
74-
}
75-
76-
@Override
77-
public long structSize() {
78-
return structSize;
79-
}
80-
81-
/**
82-
* Creates a compact record map. This is more efficient than the regular map implementation, but
83-
* only possible if all fields of the type are present.
84-
* @param qc query context
85-
* @param ii input info
86-
* @return record map
87-
* @throws QueryException query exception
88-
*/
89-
private XQRecordMap recordMap(final QueryContext qc, final InputInfo ii) throws QueryException {
9060
final TokenObjectMap<RecordField> fields = recordType.fields();
91-
final int fs = fields.size();
61+
final int fs = fields.size(), el = exprs.length;
9262
final Value[] values = new Value[fs];
9363
for(int f = 0; f < fs; ++f) {
9464
final RecordField rf = fields.value(f + 1);
95-
final Value value = f < exprs.length ? exprs[f].value(qc) : rf.init().value(qc);
96-
values[f] = rf.seqType().coerce(value, qc, ii, names[f], null);
65+
final Value value = f < el ? exprs[f].value(qc) : rf.init().value(qc);
66+
if(!value.isEmpty() || rf.alwaysAdded()) {
67+
values[f] = rf.seqType().coerce(value, qc, ii, names[f], null);
68+
}
9769
}
98-
return new XQRecordMap(recordType, values);
99-
}
70+
// create compact record map if all fields of the type are present
71+
if(compact) return new XQRecordMap(recordType, values);
10072

101-
/**
102-
* Creates a regular map. This is used if some fields of the record type may be absent.
103-
* @param qc query context
104-
* @param ii input info
105-
* @return map
106-
* @throws QueryException query exception
107-
*/
108-
private XQMap map(final QueryContext qc, final InputInfo ii) throws QueryException {
109-
final TokenObjectMap<RecordField> fields = recordType.fields();
110-
final int fs = fields.size();
73+
// create regular map otherwise
11174
final MapBuilder mb = new MapBuilder(fs);
11275
for(int f = 0; f < fs; ++f) {
113-
final RecordField rf = fields.value(f + 1);
114-
final Value value = f < exprs.length ? exprs[f].value(qc) : rf.init().value(qc);
115-
if(!value.isEmpty() || rf.alwaysAdded()) {
116-
mb.put(fields.key(f + 1), rf.seqType().coerce(value, qc, ii, names[f], null));
117-
}
76+
if(values[f] != null) mb.put(fields.key(f + 1), values[f]);
11877
}
11978
final XQMap map = mb.map();
12079
map.type = recordType;
12180
return map;
12281
}
12382

83+
@Override
84+
public long structSize() {
85+
return compact ? recordType.fields().size() : -1;
86+
}
87+
88+
@Override
89+
public boolean equals(final Object obj) {
90+
return this == obj || obj instanceof final RecordConstructor rc && recordType.eq(rc.recordType);
91+
}
92+
93+
@Override
94+
public void toString(final QueryString qs) {
95+
if(recordType.name() != null) {
96+
super.toString(qs);
97+
} else {
98+
qs.token("{ ");
99+
final TokenObjectMap<RecordField> fields = recordType.fields();
100+
int f = 0;
101+
for(final Expr expr : exprs) {
102+
if(++f > 1) qs.token(',');
103+
qs.quoted(fields.key(f)).token(':').token(expr);
104+
}
105+
qs.token(" }");
106+
}
107+
}
108+
124109
/**
125110
* Returns a constructor function definition for this record type.
126111
* @param rt record type
@@ -147,8 +132,8 @@ public static FuncDefinition definition(final RecordType rt) {
147132
params[i] = f.isOptional() ? st.union(Occ.ZERO) : st;
148133
}
149134

150-
Supplier<RecordConstructor> supplier = () -> new RecordConstructor(rt);
135+
final Supplier<RecordConstructor> supplier = () -> new RecordConstructor(rt);
151136
return new FuncDefinition(supplier, description, params, rt.seqType(),
152137
EnumSet.noneOf(Flag.class), rt.name() == null ? Token.EMPTY : rt.name().uri(), Perm.NONE);
153138
}
154-
}
139+
}

basex-core/src/main/java/org/basex/query/func/StandardFunc.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,7 @@ protected final QueryException error(final QueryException ex, final QueryError e
885885
}
886886

887887
@Override
888-
public final boolean equals(final Object obj) {
888+
public boolean equals(final Object obj) {
889889
return this == obj || obj instanceof final StandardFunc sf && definition == sf.definition &&
890890
super.equals(obj);
891891
}
@@ -917,7 +917,7 @@ public final void toXml(final QueryPlan plan) {
917917
}
918918

919919
@Override
920-
public final void toString(final QueryString qs) {
920+
public void toString(final QueryString qs) {
921921
final byte[] name = definition.name.prefixId(FN_URI);
922922
final int undefined = undefined();
923923
if(undefined == 0) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* @author Gunther Rademacher
2323
*/
2424
public final class RecordType extends MapType {
25-
/** Maximum size for generated record definitions. */
25+
/** Maximum number of entries in generated records. */
2626
public static final int MAX_GENERATED_SIZE = 32;
2727

2828
/** Record fields. */

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.basex.query.func.*;
88
import org.basex.query.value.item.*;
99
import org.basex.query.value.map.*;
10+
import org.basex.query.value.seq.*;
1011
import org.junit.jupiter.api.*;
1112

1213
/**
@@ -321,4 +322,55 @@ declare record cx:complex(r as xs:double, i? as xs:double := ());
321322
error("let $m as record(a, b?) := {} return $m", INVTYPE_X);
322323
error("let $m as record(a, b?) := { 'c': 3 } return $m", INVTYPE_X);
323324
}
325+
326+
/** Equality of map/record constructors. */
327+
@Test public void equal() {
328+
// empty record
329+
String constr = "{}";
330+
check("(" + constr + ", " + constr + ")", "{}\n{}", exists(SingletonSeq.class));
331+
332+
// single-entry record (compile-time evaluation)
333+
constr = "{ 'AA': 0 }";
334+
check("(" + constr + ", " + constr + ")?AA", "0\n0", exists(SingletonSeq.class));
335+
// small record (compile-time evaluation)
336+
constr = "{ 'AA': 0, 'AB': 1 }";
337+
check("(" + constr + ", " + constr + ")?AA", "0\n0", exists(SingletonSeq.class));
338+
// big record (compile-time evaluation)
339+
constr =
340+
"{ 'AA': 0, 'AB': 0, 'AC': 0, 'AD': 0, 'AE': 0, 'AF': 0, 'AG': 0, 'AH': 0, 'AI': 0, "
341+
+ "'AJ': 0, 'AK': 0, 'AL': 0, 'AM': 0, 'AN': 0, 'AO': 0, 'AP': 0, 'AQ': 0, 'AR': 0, "
342+
+ "'AS': 0, 'AT': 0, 'AU': 0, 'AV': 0, 'AW': 0, 'AX': 0, 'AY': 0, 'AZ': 0 }";
343+
check("(" + constr + ", " + constr + ")?AA", "0\n0", exists(SingletonSeq.class));
344+
// map (compile-time evaluation)
345+
constr =
346+
"{ 'AA': 0, 'AB': 0, 'AC': 0, 'AD': 0, 'AE': 0, 'AF': 0, 'AG': 0, 'AH': 0, 'AI': 0, "
347+
+ "'AJ': 0, 'AK': 0, 'AL': 0, 'AM': 0, 'AN': 0, 'AO': 0, 'AP': 0, 'AQ': 0, 'AR': 0, "
348+
+ "'AS': 0, 'AT': 0, 'AU': 0, 'AV': 0, 'AW': 0, 'AX': 0, 'AY': 0, 'AZ': 0, "
349+
+ "'BA': 0, 'BB': 0, 'BC': 0, 'BD': 0, 'BE': 0, 'BF': 0, 'BG': 0 }";
350+
check("(" + constr + ", " + constr + ")?AA", "0\n0", exists(SingletonSeq.class));
351+
352+
// single-entry record (runtime evaluation)
353+
constr = "{ 'AA': <a/> }";
354+
check("(" + constr + ", " + constr + ")?AA", "<a/>\n<a/>", exists(REPLICATE));
355+
// small record (runtime evaluation)
356+
constr = "{ 'AA': 0, 'AB': <a/> }";
357+
check("(" + constr + ", " + constr + ")?AA", "0\n0", exists(REPLICATE));
358+
// big record (runtime evaluation)
359+
constr =
360+
"{ 'AA': 0, 'AB': 0, 'AC': 0, 'AD': 0, 'AE': 0, 'AF': 0, 'AG': 0, 'AH': 0, 'AI': 0, "
361+
+ "'AJ': 0, 'AK': 0, 'AL': 0, 'AM': 0, 'AN': 0, 'AO': 0, 'AP': 0, 'AQ': 0, 'AR': 0, "
362+
+ "'AS': 0, 'AT': 0, 'AU': 0, 'AV': 0, 'AW': 0, 'AX': 0, 'AY': 0, 'AZ': <a/> }";
363+
check("(" + constr + ", " + constr + ")?AA", "0\n0", exists(REPLICATE));
364+
// map (runtime evaluation)
365+
constr =
366+
"{ 'AA': 0, 'AB': 0, 'AC': 0, 'AD': 0, 'AE': 0, 'AF': 0, 'AG': 0, 'AH': 0, 'AI': 0, "
367+
+ "'AJ': 0, 'AK': 0, 'AL': 0, 'AM': 0, 'AN': 0, 'AO': 0, 'AP': 0, 'AQ': 0, 'AR': 0, "
368+
+ "'AS': 0, 'AT': 0, 'AU': 0, 'AV': 0, 'AW': 0, 'AX': 0, 'AY': 0, 'AZ': 0, "
369+
+ "'BA': 0, 'BB': 0, 'BC': 0, 'BD': 0, 'BE': 0, 'BF': 0, 'BG': <a/> }";
370+
check("(" + constr + ", " + constr + ")?AA", "0\n0", exists(REPLICATE));
371+
372+
// named record (runtime evaluation)
373+
check("declare record local:r(AA, AB); (local:r(0, <a/>), local:r(0, <a/>))?AA",
374+
"0\n0", exists(REPLICATE));
375+
}
324376
}

0 commit comments

Comments
 (0)