Skip to content

Commit edf7b90

Browse files
[MOD] XQuery, fn:path: LRU step cache
1 parent 422e0d5 commit edf7b90

1 file changed

Lines changed: 84 additions & 52 deletions

File tree

  • basex-core/src/main/java/org/basex/query/func/fn

basex-core/src/main/java/org/basex/query/func/fn/FnPath.java

Lines changed: 84 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import static org.basex.query.QueryError.*;
44

5+
import java.util.*;
6+
57
import org.basex.query.*;
68
import org.basex.query.expr.*;
79
import org.basex.query.func.*;
@@ -34,79 +36,92 @@ public static class PathOptions extends Options {
3436
/** Option. */
3537
public static final BooleanOption INDEXES = new BooleanOption("indexes", true);
3638
}
39+
/** LRU step cache. */
40+
private final Map<Long, byte[]> cachedSteps = Collections.synchronizedMap(new StepCache());
3741

3842
@Override
3943
public Item item(final QueryContext qc, final InputInfo ii) throws QueryException {
4044
GNode node = toGNodeOrNull(context(qc), qc);
41-
final PathOptions options = toOptions(arg(1), new PathOptions(), qc);
45+
final XQMap map = toEmptyMap(arg(1), qc);
46+
final PathOptions options = toOptions(map, new PathOptions(), qc);
4247
if(node == null) return Empty.VALUE;
4348

44-
final TokenBuilder tb = new TokenBuilder();
45-
final TokenList steps = new TokenList();
4649
final boolean indexes = options.get(PathOptions.INDEXES);
4750
final Value ns = options.get(PathOptions.NAMESPACES);
4851
final XQMap namespaces = ns.isEmpty() ? XQMap.empty() : toMap(ns, qc);
4952
final boolean lexical = options.get(PathOptions.LEXICAL);
5053
final Value origin = options.get(PathOptions.ORIGIN);
54+
final boolean cache = map.structSize() == 0;
5155

56+
final TokenList steps = new TokenList();
57+
final TokenBuilder tb = new TokenBuilder();
5258
boolean relative = false;
5359
while(true) {
60+
// check if string representation of step was cached
61+
final Long cacheKey = cache && node instanceof final DBNode dbnode ?
62+
(long) dbnode.data().dbid << 32L | dbnode.pre() : null;
63+
byte[] step = cacheKey != null ? cachedSteps.get(cacheKey) : null;
64+
5465
final GNode parent = node.parent();
55-
final Type type = node.type;
56-
final Kind kind = type instanceof final NodeType nt ? nt.kind() : null;
57-
if(parent == null) {
58-
if(!kind.oneOf(Kind.DOCUMENT, Kind.JNODE)) {
59-
tb.add(name(Function.ROOT.definition().name, false, lexical, namespaces, qc)).add("()");
66+
if(step == null) {
67+
final Type type = node.type;
68+
final Kind kind = type instanceof final NodeType nt ? nt.kind() : null;
69+
if(parent == null) {
70+
if(!kind.oneOf(Kind.DOCUMENT, Kind.JNODE)) {
71+
tb.add(name(Function.ROOT.definition().name, false, lexical, namespaces, qc)).add("()");
72+
}
73+
break;
6074
}
61-
break;
62-
}
63-
// step: name/type
64-
final QNm qname = node.qname();
65-
if(kind == Kind.ATTRIBUTE) {
66-
tb.add('@').add(name(qname, true, lexical, namespaces, qc));
67-
} else if(kind == Kind.ELEMENT) {
68-
tb.add(name(qname, false, lexical, namespaces, qc));
69-
} else if(kind == Kind.PROCESSING_INSTRUCTION) {
70-
tb.add(kind.toString(Token.string(qname.local())));
71-
} else if(kind.oneOf(Kind.COMMENT, Kind.TEXT)) {
72-
tb.add(type.toString());
73-
} else if(parent instanceof final JNode jparent) {
74-
final JNode jnode = (JNode) node;
75-
final Item key = jnode.key;
76-
byte[] get = null;
77-
final byte[] string = key.string(info);
78-
if(jparent.value instanceof XQArray) {
79-
tb.add("*[").add(key).add("]");
80-
} else if(key instanceof AStr || key instanceof Atm || key instanceof Uri) {
81-
if(XMLToken.isNCName(string)) {
82-
tb.add(string);
75+
// step: name/type
76+
final QNm qname = node.qname();
77+
if(kind == Kind.ATTRIBUTE) {
78+
tb.add('@').add(name(qname, true, lexical, namespaces, qc));
79+
} else if(kind == Kind.ELEMENT) {
80+
tb.add(name(qname, false, lexical, namespaces, qc));
81+
} else if(kind == Kind.PROCESSING_INSTRUCTION) {
82+
tb.add(kind.toString(Token.string(qname.local())));
83+
} else if(kind.oneOf(Kind.COMMENT, Kind.TEXT)) {
84+
tb.add(type.toString());
85+
} else if(parent instanceof final JNode jparent) {
86+
final JNode jnode = (JNode) node;
87+
final Item key = jnode.key;
88+
byte[] get = null;
89+
final byte[] string = key.string(info);
90+
if(jparent.value instanceof XQArray) {
91+
tb.add("*[").add(key).add("]");
92+
} else if(key instanceof AStr || key instanceof Atm || key instanceof Uri) {
93+
if(XMLToken.isNCName(string)) {
94+
tb.add(string);
95+
} else {
96+
get = QueryString.toQuoted(string);
97+
}
98+
} else if(key instanceof ANum) {
99+
get = string;
100+
} else if(key instanceof final QNm qnm) {
101+
tb.add(qnm.eqName());
102+
} else if(key instanceof Bln) {
103+
get = Token.concat(string, "()");
83104
} else {
84-
get = QueryString.toQuoted(string);
105+
get = Token.concat(key.type.toString(), '(', QueryString.toQuoted(string), ')');
85106
}
86-
} else if(key instanceof ANum) {
87-
get = string;
88-
} else if(key instanceof final QNm qnm) {
89-
tb.add(qnm.eqName());
90-
} else if(key instanceof Bln) {
91-
get = Token.concat(string, "()");
92-
} else {
93-
get = Token.concat(key.type.toString(), '(', QueryString.toQuoted(string), ')');
107+
if(get != null) tb.add("get(").add(get).add(')');
94108
}
95-
if(get != null) tb.add("get(").add(get).add(')');
96-
}
97-
// optional index
98-
if(indexes && !kind.oneOf(Kind.ATTRIBUTE, Kind.JNODE)) {
99-
int p = 1;
100-
for(final GNode nd : node.precedingSiblingIter(false)) {
101-
qc.checkStop();
102-
final QNm qnm = nd.qname();
103-
if(nd.kind() == kind && (qnm == null || nd.qname().eq(qname))) {
104-
p++;
109+
// optional index
110+
if(indexes && !kind.oneOf(Kind.ATTRIBUTE, Kind.JNODE)) {
111+
int index = 1;
112+
for(final GNode nd : node.precedingSiblingIter(false)) {
113+
qc.checkStop();
114+
final QNm qnm = nd.qname();
115+
if(nd.kind() == kind && (qnm == null || nd.qname().eq(qname))) {
116+
index++;
117+
}
105118
}
119+
tb.add('[').addInt(index).add(']');
106120
}
107-
tb.add('[').addInt(p).add(']');
121+
step = tb.next();
122+
if(cacheKey != null) cachedSteps.put(cacheKey, step);
108123
}
109-
steps.add(tb.next());
124+
steps.add(step);
110125
node = parent;
111126

112127
// root node: finalize traversal
@@ -117,7 +132,7 @@ public Item item(final QueryContext qc, final InputInfo ii) throws QueryExceptio
117132
}
118133
if(origin instanceof GNode && !relative) throw PATH_X.get(info, origin);
119134

120-
// add all steps in reverse order; cache element paths
135+
// add all steps in reverse order
121136
for(int s = steps.size() - 1; s >= 0; --s) {
122137
if(!(tb.isEmpty() && origin instanceof GNode)) tb.add('/');
123138
tb.add(steps.get(s));
@@ -151,4 +166,21 @@ private byte[] name(final QNm qnm, final boolean attr, final boolean lexical,
151166
protected Expr opt(final CompileContext cc) {
152167
return optFirst(true, false, cc.qc.focus.value);
153168
}
169+
170+
/**
171+
* Step cache.
172+
* @author BaseX Team, BSD License
173+
* @author Christian Gruen
174+
*/
175+
private static final class StepCache extends LinkedHashMap<Long, byte[]> {
176+
/** Constructor. */
177+
private StepCache() {
178+
super(16, 0.75f, true);
179+
}
180+
181+
@Override
182+
protected boolean removeEldestEntry(final Map.Entry<Long, byte[]> eldest) {
183+
return size() > 1000;
184+
}
185+
}
154186
}

0 commit comments

Comments
 (0)