Skip to content

Commit 22701a2

Browse files
RecordType updates (#2590)
* prevent "map instance of record" from success without field checking * implement intersection of map and record * remove "extensible" flag for records, drop excess fields in coercion
1 parent 30e094e commit 22701a2

File tree

17 files changed

+202
-280
lines changed

17 files changed

+202
-280
lines changed

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

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import org.basex.query.util.parse.*;
3838
import org.basex.query.value.array.*;
3939
import org.basex.query.value.item.*;
40-
import org.basex.query.value.map.*;
4140
import org.basex.query.value.seq.*;
4241
import org.basex.query.value.type.*;
4342
import org.basex.query.var.*;
@@ -993,15 +992,10 @@ private void namedRecordTypeDecl(final AnnList anns) throws QueryException {
993992
if(NSGlobal.reserved(qn.uri())) throw error(TYPERESERVED_X, qn.string());
994993
wsCheck("(");
995994
final TokenObjectMap<RecordField> fields = new TokenObjectMap<>();
996-
boolean extensible = false;
997995
if(!wsConsume(")")) {
998996
boolean exprRequired = false;
999997
do {
1000998
skipWs();
1001-
if(!fields.isEmpty() && consume("*")) {
1002-
extensible = true;
1003-
break;
1004-
}
1005999
final byte[] name = ncName(NONCNAME_X, false);
10061000
final boolean optional = wsConsume("?");
10071001
final SeqType seqType = wsConsume(AS) ? sequenceType() : null;
@@ -1019,7 +1013,7 @@ private void namedRecordTypeDecl(final AnnList anns) throws QueryException {
10191013
} while(wsConsume(","));
10201014
wsCheck(")");
10211015
}
1022-
final RecordType rt = new RecordType(fields, extensible, qn, anns);
1016+
final RecordType rt = new RecordType(fields, qn, anns);
10231017
declaredTypes.put(qn, rt.seqType());
10241018
namedRecordTypes.put(qn, rt);
10251019
if(!anns.contains(Annotation.PRIVATE)) {
@@ -1057,15 +1051,6 @@ private void declareRecordConstructor(final RecordType rt, final InputInfo ii)
10571051
final Expr init = initExpr == null && optional ? Empty.VALUE : initExpr;
10581052
params.add(new QNm(key), pst, init, null);
10591053
}
1060-
if(rt.isExtensible()) {
1061-
byte[] key;
1062-
int i = -1;
1063-
do {
1064-
final String paramName = ++i == 0 ? "options" : "options" + i;
1065-
key = Token.token(paramName);
1066-
} while(fields.contains(key));
1067-
params.add(new QNm(key), Types.MAP_O, XQMap.empty(), null);
1068-
}
10691054
params.seqType(rt.seqType()).finish(qc, localVars);
10701055

10711056
final Var[] pv = params.vars();
@@ -3593,29 +3578,30 @@ private SeqType itemType() throws QueryException {
35933578
* @throws QueryException query exception
35943579
*/
35953580
private Type functionTest(final AnnList anns, final Type type) throws QueryException {
3596-
// wildcard
3597-
if(wsConsume("*")) {
3581+
if(type == Types.RECORD) {
3582+
// empty record
3583+
if(wsConsume(")")) return type;
3584+
} else if(wsConsume("*")) {
3585+
// wildcard
35983586
wsCheck(")");
35993587
return type;
36003588
}
36013589

36023590
// record
36033591
if(type instanceof RecordType) {
36043592
final TokenObjectMap<RecordField> fields = new TokenObjectMap<>();
3605-
boolean extensible = !consume(')');
3606-
if(extensible) {
3593+
if(!consume(')')) {
36073594
do {
3608-
extensible = wsConsume("*");
3609-
if(extensible) break;
3595+
skipWs();
36103596
final byte[] name = quote(current()) ? stringLiteral() : ncName(NOSTRNCN_X, false);
36113597
final boolean optional = wsConsume("?");
36123598
final SeqType seqType = wsConsume(AS) ? sequenceType() : null;
36133599
if(fields.contains(name)) throw error(DUPFIELD_X, name);
36143600
fields.put(name, new RecordField(seqType, optional));
36153601
} while(wsConsume(","));
3616-
wsCheck(")");
3602+
check(')');
36173603
}
3618-
return qc.shared.record(new RecordType(fields, extensible));
3604+
return qc.shared.record(new RecordType(fields));
36193605
}
36203606
// map
36213607
if(type instanceof MapType) {

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,7 @@ private boolean flatten(final Expr expr, final ExprList list) throws QueryExcept
127127
} else if(expr instanceof CRecord) {
128128
// { 'a': <a/> }
129129
final RecordType rt = (RecordType) expr.seqType().type;
130-
final boolean extensible = rt.isExtensible();
131-
if(extensible || rt.hasOptional()) return false;
130+
if(rt.hasOptional()) return false;
132131
final TokenObjectMap<RecordField> fields = rt.fields();
133132
final int fs = fields.size();
134133
for(int f = 1; f <= fs; f++) list.add(Str.get(fields.key(f))).add(expr.arg(f - 1));

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

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ public Expr optimize(final CompileContext cc) throws QueryException {
3838
@Override
3939
public XQMap item(final QueryContext qc, final InputInfo ii) throws QueryException {
4040
final RecordType rt = (RecordType) seqType().type;
41-
final boolean extensible = rt.isExtensible();
42-
if(extensible || rt.hasOptional()) {
41+
if(rt.hasOptional()) {
4342
final MapBuilder mb = new MapBuilder(exprs.length);
4443
final TokenObjectMap<RecordField> fields = rt.fields();
4544
final int fs = fields.size();
@@ -52,11 +51,6 @@ public XQMap item(final QueryContext qc, final InputInfo ii) throws QueryExcepti
5251
}
5352
if(add) mb.put(fields.key(f), value);
5453
}
55-
if(extensible) {
56-
toMap(arg(fs), qc).forEach((k, v) -> {
57-
if(!mb.contains(k)) mb.put(k, v);
58-
});
59-
}
6054
return mb.map();
6155
}
6256

@@ -70,7 +64,7 @@ public XQMap item(final QueryContext qc, final InputInfo ii) throws QueryExcepti
7064
@Override
7165
public long structSize() {
7266
final RecordType rt = (RecordType) seqType().type;
73-
return rt.isExtensible() || rt.hasOptional() ? -1 : exprs.length;
67+
return rt.hasOptional() ? -1 : exprs.length;
7468
}
7569

7670
@Override

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

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,36 @@
1616
*/
1717
public enum Records {
1818
/** Record definition. */
19-
DIVIDED_DECIMALS("divided-decimals", false,
19+
DIVIDED_DECIMALS("divided-decimals",
2020
field("quotient", Types.DECIMAL_O),
2121
field("remainder", Types.DECIMAL_O)
2222
),
2323
/** Record definition. */
24-
INFER_ENCODING("infer-encoding", false,
24+
INFER_ENCODING("infer-encoding",
2525
field("encoding", Types.STRING_O, false),
2626
field("offset", Types.INTEGER_O, false)
2727
),
2828
/** Record definition. */
29-
LOAD_XQUERY_MODULE("load-xquery-module", false,
29+
LOAD_XQUERY_MODULE("load-xquery-module",
3030
field("variables", MapType.get(BasicType.QNAME, Types.ITEM_ZO).seqType()),
3131
field("functions", MapType.get(BasicType.QNAME,
3232
MapType.get(BasicType.INTEGER, Types.FUNCTION_O).seqType()).seqType())),
3333
/** Record definition. */
34-
MEMBER("member", false,
34+
MEMBER("member",
3535
field("value", Types.ITEM_ZM)),
3636
/** Record definition. */
37-
PARSED_CSV_STRUCTURE("parsed-csv-structure", false,
37+
PARSED_CSV_STRUCTURE("parsed-csv-structure",
3838
field("columns", Types.STRING_ZM),
3939
field("column-index", MapType.get(BasicType.STRING, Types.INTEGER_O).seqType(Occ.ZERO_OR_ONE)),
4040
field("rows", ArrayType.get(Types.STRING_O).seqType(Occ.ZERO_OR_MORE)),
4141
field("get", FuncType.get(Types.STRING_O, Types.POSITIVE_INTEGER_O,
4242
ChoiceItemType.get(Types.POSITIVE_INTEGER_O, Types.STRING_O).seqType()).seqType())),
4343
/** Record definition. */
44-
RANDOM_NUMBER_GENERATOR("random-number-generator", true),
44+
RANDOM_NUMBER_GENERATOR("random-number-generator"),
4545
/** Record definition. */
46-
SCHEMA_TYPE("schema-type", true),
46+
SCHEMA_TYPE("schema-type"),
4747
/** Record definition. */
48-
URI_STRUCTURE("uri-structure", true,
48+
URI_STRUCTURE("uri-structure",
4949
field("uri", Types.STRING_ZO, true),
5050
field("scheme", Types.STRING_ZO, true),
5151
field("absolute", Types.BOOLEAN_ZO, true),
@@ -111,16 +111,15 @@ private record NamedRecordField(byte[] name, RecordField field) { }
111111
/**
112112
* Constructor.
113113
* @param name name of record
114-
* @param extensible extensible flag
115114
* @param fields field declarations
116115
*/
117-
Records(final String name, final boolean extensible, final NamedRecordField... fields) {
116+
Records(final String name, final NamedRecordField... fields) {
118117
final TokenObjectMap<RecordField> map = new TokenObjectMap<>(fields.length);
119118
for(final NamedRecordField field : fields) {
120119
map.put(field.name, field.field);
121120
}
122121
final QNm qnm = new QNm(name + "-record", QueryText.FN_URI);
123-
type = new RecordType(map, extensible, qnm, AnnList.EMPTY);
122+
type = new RecordType(map, qnm, AnnList.EMPTY);
124123
}
125124

126125
/**

basex-core/src/main/java/org/basex/query/func/map/MapContains.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ protected Expr opt(final CompileContext cc) throws QueryException {
3737
if(mti.field != null) {
3838
if(!mti.field.isOptional()) return Bln.TRUE;
3939
} else if(mti.validKey) {
40-
if(!mti.record.isExtensible()) return Bln.FALSE;
40+
return Bln.FALSE;
4141
}
4242
if(mti.mapType != null) {
4343
// map:contains({ 1: 1 }, 'string') → false()

basex-core/src/main/java/org/basex/query/func/map/MapFn.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ final long mapSize(final Expr expr) {
2323
@Override
2424
public long structSize() {
2525
return seqType().type instanceof final RecordType rt &&
26-
!rt.isExtensible() && !rt.hasOptional() ? rt.fields().size() : -1;
26+
!rt.hasOptional() ? rt.fields().size() : -1;
2727
}
2828
}

basex-core/src/main/java/org/basex/query/func/map/MapGet.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ protected Expr opt(final CompileContext cc) throws QueryException {
4242
st = mti.field.seqType();
4343
} else if(mti.validKey) {
4444
// map:get({ 'a': 1 }, 'b') → ()
45-
if(!mti.record.isExtensible()) notFound = true;
45+
notFound = true;
4646
}
4747

4848
if(mti.mapType != null) {

basex-core/src/main/java/org/basex/query/func/map/MapPut.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,7 @@ protected Expr opt(final CompileContext cc) throws QueryException {
4646
tp = mti.record.copy(null, mti.key, vt.union(ft), cc);
4747
}
4848
} else if(mti.validKey) {
49-
if(mti.record.isExtensible()) {
50-
// structure does not change: propagate record type
51-
tp = mti.record;
52-
} else if(mti.key != null && mti.record.fields().size() < RecordType.MAX_GENERATED_SIZE) {
49+
if(mti.key != null && mti.record.fields().size() < RecordType.MAX_GENERATED_SIZE) {
5350
// otherwise, derive new record type
5451
tp = mti.record.copy(null, mti.key, value.seqType(), cc);
5552
}

basex-core/src/main/java/org/basex/query/func/map/MapRemove.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,12 @@ protected Expr opt(final CompileContext cc) throws QueryException {
4141
// otherwise, derive new record type
4242
final RecordType rt = mti.record.copy(mti.key, null, null, cc);
4343
// return empty map if it will contain no entries
44-
if(rt != null && rt.fields().isEmpty() && !rt.isExtensible()) return XQMap.empty();
44+
if(rt != null && rt.fields().isEmpty()) return XQMap.empty();
4545
tp = rt;
4646
}
4747
} else if(mti.validKey) {
4848
// return input map if nothing changes: map:remove({ 'a': 1 }, 'b') → { 'a': 1 }
49-
if(!mti.record.isExtensible()) return map;
50-
// structure does not change: propagate record type
51-
tp = mti.record;
49+
return map;
5250
}
5351

5452
if(tp == null && mti.mapType != null) {

basex-core/src/main/java/org/basex/query/value/map/XQMap.java

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -205,19 +205,17 @@ public final boolean instanceOf(final Type tp, final boolean coerce) {
205205
!Token.eq(fields.key(f), key.string(null)) ||
206206
st != Types.ITEM_ZM && !st.instance(getOrNull(key))) return false;
207207
}
208-
return keys.next() == null || rt.isExtensible();
208+
return keys.next() == null;
209209
}
210210

211211
for(int f = 1; f <= fs; f++) {
212212
final RecordField rf = fields.value(f);
213213
final Value value = getOrNull(Str.get(fields.key(f)));
214214
if(value != null ? !rf.seqType().instance(value) : !rf.isOptional()) return false;
215215
}
216-
if(!rt.isExtensible()) {
217-
for(final Item key : keys()) {
218-
if(!key.type.instanceOf(BasicType.STRING) || !fields.contains(key.string(null)))
219-
return false;
220-
}
216+
for(final Item key : keys()) {
217+
if(!key.type.instanceOf(BasicType.STRING) || !fields.contains(key.string(null)))
218+
return false;
221219
}
222220
return true;
223221
}
@@ -332,16 +330,6 @@ public final XQMap coerceTo(final RecordType rt, final QueryContext qc, final Co
332330
throw typeError(this, rt, ii);
333331
}
334332
}
335-
// add remaining values
336-
if(mb.size() < ms) {
337-
if(!rt.isExtensible()) throw typeError(this, rt, ii);
338-
forEach((key, value) -> {
339-
if(!mb.contains(key)) {
340-
qc.checkStop();
341-
mb.put(key, value);
342-
}
343-
});
344-
}
345333

346334
// assign record type to speed up future type checks
347335
final XQMap map = mb.map();

0 commit comments

Comments
 (0)