Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package org.basex.query;
import static org.basex.core.Text.*;
import static org.basex.query.QueryError.*;
import static org.basex.util.Token.*;
import java.io.*;
import java.util.*;
import java.util.Map.*;
import java.util.concurrent.atomic.*;
import org.basex.build.json.*;
import org.basex.build.json.JsonOptions.*;
import org.basex.core.*;
import org.basex.core.MainOptions.MainParser;
import org.basex.core.cmd.*;
import org.basex.core.jobs.*;
import org.basex.core.locks.*;
import org.basex.data.*;
import org.basex.io.parse.json.*;
import org.basex.io.serial.*;
import org.basex.query.ann.*;
import org.basex.query.expr.*;
import org.basex.query.func.*;
import org.basex.query.func.java.*;
import org.basex.query.iter.*;
import org.basex.query.scope.*;
import org.basex.query.up.*;
import org.basex.query.util.collation.*;
import org.basex.query.util.ft.*;
import org.basex.query.util.hash.*;
import org.basex.query.util.list.*;
import org.basex.query.value.*;
import org.basex.query.value.item.*;
import org.basex.query.value.node.*;
import org.basex.query.value.seq.*;
import org.basex.query.value.type.*;
import org.basex.query.var.*;
import org.basex.util.*;
import org.basex.util.ft.*;
import org.basex.util.hash.*;
import org.basex.util.list.*;
/**
* This class organizes both static and dynamic properties that are specific to a
* single query.
*
* @author BaseX Team 2005-22, BSD License
* @author Christian Gruen
*/
public final class QueryContext extends Job implements Closeable {
/** The evaluation stack. */
public final QueryStack stack = new QueryStack();
/** Static variables. */
public final Variables vars = new Variables();
/** Functions. */
public final StaticFuncs functions = new StaticFuncs();
/** Query resources. */
public final QueryResources resources;
/** Parent query context. */
public final QueryContext parent;
/** Database context. */
public final Context context;
/** Query info. */
public final QueryInfo info;
/** Global database options (will be reassigned after query execution). */
final QueryOptions options = new QueryOptions(this);
/** Query threads. */
public final QueryThreads threads = new QueryThreads();
/** User-defined locks. */
public final LockList locks = new LockList();
/** Current query focus. */
public QueryFocus focus = new QueryFocus();
/** Date/time values. */
private QueryDateTime dateTime;
/** Update container; will be created if the first update is evaluated. */
public Updates updates;
/** Full-text position data (needed for highlighting full-text results). */
public FTPosData ftPosData = Prop.gui ? new FTPosData() : null;
/** Current full-text lexer. */
public FTLexer ftLexer;
/** Current full-text options. */
private FTOpt ftOpt;
/** Full-text token positions (needed for highlighting full-text results). */
public int ftPos;
/** Scoring flag. */
public boolean scoring;
/** Available collations. */
public TokenObjMap collations;
/** Number of successive tail calls. */
public int tailCalls;
/** Maximum number of successive tail calls (will be set before compilation). */
public int maxCalls;
/** Function for the next tail call. */
private XQFunction tcFunc;
/** Arguments for the next tail call. */
private Value[] tcArgs;
/** Counter for variable IDs. */
public int varIDs;
/** Parsed modules, containing the file path and module uri. */
public final TokenMap modParsed = new TokenMap();
/** Pre-declared modules, containing module uri and their file paths (required for test APIs). */
final TokenMap modDeclared = new TokenMap();
/** Stack of module files that are currently parsed. */
final TokenList modStack = new TokenList();
/** Main module (root expression). */
public MainModule main;
/** Context scope. */
public ContextScope contextScope;
/** Indicates if context scope exists and is final. */
public boolean finalContext;
/** External variables and context to be bound at compile time. */
private final QNmMap bindings = new QNmMap<>();
/** Serialization options. */
private SerializerOptions sopts;
/** Indicates if the default serialization parameters are used. */
private boolean defaultOutput;
/** Indicates if the query has been compiled. */
private boolean compiled;
/** Indicates if the query has been optimized. */
private boolean optimized;
/** Indicates if the query context has been closed. */
private boolean closed;
/**
* Constructor.
* @param parent parent context
*/
public QueryContext(final QueryContext parent) {
this(parent.context, parent, parent.resources, parent.info);
parent.pushJob(this);
updates = parent.updates;
}
/**
* Constructor.
* @param context database context
*/
public QueryContext(final Context context) {
this(context, null, null, null);
}
/**
* Constructor.
* @param context database context
* @param parent parent context (can be {@code null})
* @param info query info (can be {@code null})
* @param resources resources (can be {@code null})
*/
public QueryContext(final Context context, final QueryContext parent,
final QueryResources resources, final QueryInfo info) {
this.context = context;
this.parent = parent;
this.info = info != null ? info : new QueryInfo(context);
this.resources = resources != null ? resources : new QueryResources(this);
}
/**
* Parses the specified query.
* @param query query string
* @param uri base URI (can be {@code null})
* @return module
* @throws QueryException query exception
*/
public AModule parse(final String query, final String uri) throws QueryException {
return QueryParser.isLibrary(query) ? parseLibrary(query, uri) : parseMain(query, uri);
}
/**
* Collects suggestions for completing a path expression.
* @param query query string
* @param data data reference
* @return list of suggestions, followed by valid input string
*/
public StringList suggest(final String query, final Data data) {
final QuerySuggest qs = new QuerySuggest(query, this, data);
try {
qs.parseMain();
return qs.complete(qs.mark);
} catch(final QueryException ex) {
return qs.complete(ex.column() - 1);
}
}
/**
* Parses the specified query.
* @param query query string
* @param uri base URI (can be {@code null})
* @return main module
* @throws QueryException query exception
*/
public MainModule parseMain(final String query, final String uri) throws QueryException {
return parseMain(query, uri, null);
}
/**
* Parses the specified query and assigns the root expression.
* @param query query string
* @param uri base URI (can be {@code null})
* @param sc static context (can be {@code null})
* @return main module
* @throws QueryException query exception
*/
public MainModule parseMain(final String query, final String uri, final StaticContext sc)
throws QueryException {
return run(info.parsing, () -> {
info.query = query;
main = new QueryParser(query, uri, this, sc).parseMain();
return main;
});
}
/**
* Parses the specified module.
* @param query query string
* @param uri base URI (can be {@code null})
* @return library module
* @throws QueryException query exception
*/
public LibraryModule parseLibrary(final String query, final String uri) throws QueryException {
return run(info.parsing, () -> {
info.query = query;
return new QueryParser(query, uri, this, null).parseLibrary(true);
});
}
/**
* Creates a function call for the specified function and assigns it as root expression.
* @param func user-defined function
* @param args arguments
* @throws QueryException query exception
*/
public void assign(final StaticFunc func, final Expr... args) throws QueryException {
for(final StaticFunc sf : functions.funcs()) {
if(func.info.equals(sf.info)) {
// inline arguments of called function
sf.anns.addUnique(new Ann(sf.info, Annotation._BASEX_INLINE, Empty.VALUE));
// create and assign function call
final StaticFuncCall call = new StaticFuncCall(sf.name, args, sf.sc, sf.info).init(sf);
main = new MainModule(call, new VarScope(sf.sc));
updating = sf.updating();
return;
}
}
throw BASEX_WHICH_X.get(null, func);
}
/**
* Compiles the expression. Performs static optimizations.
* @throws QueryException query exception
*/
public void compile() throws QueryException {
if(compiled) return;
compiled = true;
run(info.compiling, () -> {
// assign tail call option after compiling options
options.compile();
maxCalls = context.options.get(MainOptions.TAILCALLS);
// bind external variables
if(parent == null) {
final Map map = context.options.toMap(MainOptions.BINDINGS);
for(final Entry entry : map.entrySet()) {
bind(entry.getKey(), Atm.get(entry.getValue()), null, main.sc);
}
}
vars.bindExternal(this, bindings);
return compile(false);
});
}
/**
* Optimizes the expression. Performs dynamic optimizations.
* @throws QueryException query exception
*/
public void optimize() throws QueryException {
compile();
if(optimized) return;
optimized = true;
run(info.optimizing, () -> {
// bind context
final StaticContext sc = main.sc;
if(parent == null && !bindings.contains(QNm.EMPTY)) {
final DBNodes nodes = context.current();
if(nodes != null) bind(null, resources.compile(nodes), null, sc);
}
final Value value = bindings.get(QNm.EMPTY);
if(value != null) contextScope = new ContextScope(value, sc.contextType, new VarScope(sc));
if(contextScope != null) finalContext = true;
return compile(true);
});
}
/**
* Compiles the expression.
* @param dynamic dynamic compilation
* @return {@code null}
* @throws QueryException query exception
*/
private Void compile(final boolean dynamic) throws QueryException {
checkStop();
info.runtime = false;
try {
final CompileContext cc = new CompileContext(this, dynamic);
if(contextScope != null && dynamic) {
try {
contextScope.compile(cc);
focus.value = contextScope.value(this);
} catch(final QueryException ex) {
Util.debug(ex);
throw ex.error() == NOCTX_X ? CIRCCTX.get(ex.info()) : ex;
}
}
if(main != null) {
new QueryCompiler().compile(cc);
} else {
// required for XQueryParse
functions.compileAll(cc);
}
} catch(final StackOverflowError ex) {
Util.debug(ex);
throw BASEX_OVERFLOW.get(null, ex);
} finally {
info.runtime = true;
}
return null;
}
/**
* Returns a result iterator.
* @return result iterator
* @throws QueryException query exception
*/
public Iter iter() throws QueryException {
optimize();
return run(info.evaluating, () -> updating ? update().iter() : main.iter(this));
}
/**
* Returns the result.
* @return result iterator
* @throws QueryException query exception
*/
public Value value() throws QueryException {
optimize();
return run(info.evaluating, () -> updating ? update() : main.value(this));
}
/**
* Checks if evaluation has been stopped and returns the next item of an iterator.
* @param iter iterator
* @return item or {@code null}
* @throws QueryException query exception
*/
public Item next(final Iter iter) throws QueryException {
checkStop();
return iter.next();
}
/**
* Returns the updates container.
* @return updates container
*/
public synchronized Updates updates() {
if(updates == null) updates = new Updates(false);
return updates;
}
@Override
public void addLocks() {
// choose read or write locks
final Locks l = jc().locks;
final LockList list = updating ? l.writes : l.reads;
// locks in main module (can be null if parsing failed)
boolean local = main == null || main.databases(new LockVisitor(list, contextScope == null));
// locks in context expression
if(local && contextScope != null) {
// check if scope may still be overwritten by dynamic context
if(!finalContext) list.add(Locking.CONTEXT);
local &= contextScope.databases(new LockVisitor(list, true));
}
if(local) {
list.add(locks);
} else {
// global locking, referenced databases cannot be determined statically
list.addGlobal();
}
}
/**
* Binds a value to a global variable or the context value.
* The arguments will be ignored if a value has already been assigned.
* The specified type is interpreted as follows:
*
*
If {@code "json"} is specified, the value is converted according to the rules
* specified in {@link JsonXQueryConverter}.
*
Otherwise, the type is cast to the specified XDM type.
*
* @param name name of variable; context value if empty string or {@code null}
* @param value value to be bound (object or XQuery {@link Value})
* @param type type (may be {@code null})
* @param sc static context
* @throws QueryException query exception
*/
public void bind(final String name, final Object value, final String type, final StaticContext sc)
throws QueryException {
final QNm qnm = name == null || name.isEmpty() ? QNm.EMPTY : qname(name, sc);
if(!bindings.contains(qnm)) bindings.put(qnm, cast(value, type));
}
/**
* Adds some evaluation info.
* @param string evaluation info
*/
public void evalInfo(final String string) {
QueryContext qc = this;
while(qc.parent != null) qc = qc.parent;
qc.info.evalInfo(string);
}
/**
* Returns query-specific or default serialization parameters.
* @return serialization parameters
*/
public SerializerOptions parameters() {
if(sopts == null) {
sopts = new SerializerOptions(context.options.get(MainOptions.SERIALIZER));
defaultOutput = main != null;
}
return sopts;
}
/**
* Returns the current full-text options. Creates a new instance if called for the first time.
* @return full-text options
*/
public FTOpt ftOpt() {
if(ftOpt == null) ftOpt = new FTOpt();
return ftOpt;
}
/**
* Assigns full-text options.
* @param opt full-text options
*/
public void ftOpt(final FTOpt opt) {
ftOpt = opt;
}
/**
* Creates and returns an XML query plan (expression tree) for this query.
* @param full include comprehensive information
* @return query plan
*/
public FElem toXml(final boolean full) {
// only show root node if functions or variables exist
final QueryPlan plan = new QueryPlan(compiled, closed, full);
if(main != null) {
for(final StaticScope ss : QueryCompiler.usedDecls(main)) ss.toXml(plan);
main.toXml(plan);
} else {
functions.toXml(plan);
vars.toXml(plan);
}
return plan.root();
}
/**
* Indicates that the query contains updating expressions.
*/
public void updating() {
updating = true;
}
@Override
public void close() {
if(closed) return;
closed = true;
if(parent == null) {
// topmost query: close resources (opened by compile step)
resources.close();
threads.close();
} else {
// otherwise, adopt update reference (may have been initialized by sub query)
parent.updates = updates;
parent.popJob();
}
options.close();
final Performance perf = jc().performance;
if(perf != null) info.serializing.addAndGet(perf.ns());
}
@Override
public String shortInfo() {
return SAVE;
}
/**
* Gets the value currently bound to the given variable.
* @param var variable
* @return bound value
*/
public Value get(final Var var) {
return stack.get(var);
}
/**
* Binds an expression to a local variable.
* @param var variable
* @param value expression to be bound
* @throws QueryException exception
*/
public void set(final Var var, final Value value) throws QueryException {
stack.set(var, value, this);
}
/**
* Registers a tail-called function and its arguments to this query context.
* @param fn function to call
* @param arg arguments to pass to {@code fn}
*/
public void registerTailCall(final XQFunction fn, final Value[] arg) {
tcFunc = fn;
tcArgs = arg;
}
/**
* Returns and clears the currently registered tail-call function.
* @return function to call if present, {@code null} otherwise
*/
public XQFunction pollTailCall() {
final XQFunction fn = tcFunc;
tcFunc = null;
return fn;
}
/**
* Returns and clears registered arguments of a tail-called function.
* @return argument values if a tail call was registered, {@code null} otherwise
*/
public Value[] pollTailArgs() {
final Value[] as = tcArgs;
tcArgs = null;
return as;
}
/**
* Initializes the static date and time context of a query if not done yet.
* @return self reference
* @throws QueryException query exception
*/
public QueryDateTime dateTime() throws QueryException {
if(dateTime == null) dateTime = new QueryDateTime();
return dateTime;
}
@Override
public String toString() {
return main != null ? QueryInfo.usedDecls(main) : info.query;
}
// CLASS METHODS ================================================================================
/**
* This function is called by the GUI.
* Caches the result of the specified query. If all nodes are of the same database
* instance, the returned value will be of type {@link DBNodes}.
* @param cmd query command
* @param max maximum number of items to cache (negative: return full result)
*/
void cache(final AQuery cmd, final int max) {
final ItemList items = new ItemList();
final IntList pres = new IntList();
final Data data = resources.globalData();
try {
optimize();
run(info.evaluating, () -> {
// evaluates the query
final int mx = max >= 0 ? max : Integer.MAX_VALUE;
final Iter iter = iter();
Item item;
do {
// collect pre values if a database is opened globally
if(defaultOutput && data != null) {
while((item = next(iter)) != null && item.data() == data && pres.size() < mx) {
pres.add(((DBNode) item).pre());
}
// skip if all results have been collected
if(item == null || pres.size() == mx) break;
// otherwise, add nodes to standard iterator
for(final int pre : pres.finish()) items.add(new DBNode(data, pre));
items.add(item);
}
// use standard iterator
while((item = next(iter)) != null && items.size() < mx) {
item.cache(false, null);
items.add(item);
}
} while(false);
return null;
});
} catch(final QueryException | JobException ex) {
cmd.exception = ex;
}
// collect results
cmd.result = !items.isEmpty() ? items.value() :
!pres.isEmpty() ? new DBNodes(data, pres.finish()).ftpos(ftPosData) : Empty.VALUE;
}
// PRIVATE METHODS ==============================================================================
/**
* Returns the result of an updating expression.
* @return result iterator
* @throws QueryException query exception
*/
private Value update() throws QueryException {
try {
// retrieve result
final Value value = main.value(this);
// only perform updates if no parent context exists
if(updates == null || parent != null) return value;
// create copies of results that will be modified by an update operation
final HashSet datas = updates.prepare(this);
final StringList dbs = updates.databases();
final ValueBuilder vb = new ValueBuilder(this);
final QueryConsumer materialize = val -> vb.add(val.materialize(d -> d != null &&
(datas.contains(d) || !d.inMemory() && dbs.contains(d.meta.name)), null, this));
materialize.accept(value);
materialize.accept(updates.output(true));
// invalidate current node set in context, apply updates
if(context.data() != null) context.invalidate();
updates.apply(this);
return vb.value(value);
} catch(final StackOverflowError ex) {
Util.debug(ex);
throw BASEX_OVERFLOW.get(null);
}
}
/**
* Casts a value to the specified type.
* @param value value to be cast
* @param type type (may be {@code null})
* @return cast value
* @throws QueryException query exception
*/
private Value cast(final Object value, final String type) throws QueryException {
// interpret string input
Object object = value;
if(object instanceof String) {
final String string = (String) object;
final StringList strings = new StringList(1);
// strings containing multiple items (value \1 ...)
if(string.indexOf('\1') == -1) {
strings.add(string);
} else {
object = strings.add(string.split("\1")).toArray();
}
// subtypes overriding the global value (value \2 type)
if(string.indexOf('\2') != -1) {
final ValueBuilder vb = new ValueBuilder(this);
for(final String str : strings) {
final int i = str.indexOf('\2');
final String val = i == -1 ? str : str.substring(0, i);
final String tp = i == -1 ? type : str.substring(i + 1);
vb.add(cast(val, tp));
}
return vb.value();
}
}
// no type specified: return original value or convert as Java object
if(type == null || type.isEmpty()) {
return object instanceof Value ? (Value) object : JavaCall.toValue(object, this, null);
}
// convert JSON input
if(type.equalsIgnoreCase(MainParser.JSON.name())) {
try {
final JsonParserOptions jp = new JsonParserOptions();
jp.set(JsonOptions.FORMAT, JsonFormat.XQUERY);
return JsonConverter.get(jp).convert(object.toString(), "");
} catch(final QueryIOException ex) {
throw ex.getCause();
}
}
// parse target type
final StaticContext sc = main != null ? main.sc : new StaticContext(this);
final SeqType st = new QueryParser(type, null, this, sc).parseSeqType();
if(st.eq(SeqType.EMPTY_SEQUENCE_Z)) return Empty.VALUE;
final Type tp = st.type;
// cast XDM values
if(object instanceof Value) {
final Value vl = (Value) object;
// cast single item
if(vl.isItem()) return tp.cast((Item) vl, this, sc, null);
// cast sequence
final ValueBuilder vb = new ValueBuilder(this);
for(final Item item : vl) vb.add(tp.cast(item, this, sc, null));
return vb.value(tp);
}
// cast sequences
if(object instanceof String[]) {
final ValueBuilder vb = new ValueBuilder(this);
for(final String string : (String[]) object) vb.add(tp.cast(string, this, null));
return vb.value(tp);
}
// cast any other object to XDM
return tp.cast(object, this, null);
}
/**
* Runs code and measures its runtime.
* @param code code to run
* @param runtime value storing the runtime
* @param type of result value
* @return result
* @throws QueryException query exception
*/
private T run(final AtomicLong runtime, final QuerySupplier code) throws QueryException {
Performance perf = jc().performance;
if(perf == null) {
perf = new Performance();
} else {
perf.ns();
}
try {
return code.get();
} finally {
runtime.addAndGet(perf.ns());
}
}
/**
* Converts the specified variable name to a QName.
* @param name name of variable
* @param sc static context
* @return QName
* @throws QueryException query context
*/
private static QNm qname(final String name, final StaticContext sc) throws QueryException {
return QNm.resolve(token(Strings.startsWith(name, '$') ? name.substring(1) : name), sc);
}
}