package org.basex.query; import static org.basex.core.Text.*; import static org.basex.query.QueryText.*; import static org.basex.query.util.Err.*; import static org.basex.util.Token.*; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.basex.core.Context; import org.basex.core.Progress; import org.basex.core.Prop; import org.basex.core.cmd.Set; import org.basex.data.Data; import org.basex.data.FTPosData; import org.basex.data.Nodes; import org.basex.data.Result; import org.basex.io.serial.Serializer; import org.basex.io.serial.SerializerException; import org.basex.io.serial.SerializerProp; import org.basex.query.expr.Expr; import org.basex.query.expr.ParseExpr; import org.basex.query.func.JavaMapping; import org.basex.query.func.UserFuncs; import org.basex.query.item.DBNode; import org.basex.query.item.Dat; import org.basex.query.item.Dtm; import org.basex.query.item.Item; import org.basex.query.item.QNm; import org.basex.query.item.SeqType; import org.basex.query.item.Tim; import org.basex.query.item.Type; import org.basex.query.item.Types; import org.basex.query.item.Value; import org.basex.query.iter.ItemCache; import org.basex.query.iter.Iter; import org.basex.query.up.Updates; import org.basex.query.util.JDBCConnections; import org.basex.query.util.Var; import org.basex.query.util.VarContext; import org.basex.query.util.json.JsonMapConverter; import org.basex.util.InputInfo; import org.basex.util.JarLoader; import org.basex.util.TokenBuilder; import org.basex.util.Util; import org.basex.util.XMLToken; import org.basex.util.ft.FTLexer; import org.basex.util.ft.FTOpt; import org.basex.util.hash.TokenMap; import org.basex.util.list.IntList; /** * This class provides query-specific methods and properties. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class QueryContext extends Progress { /** URL pattern (matching Clark and EQName notation). */ private static final Pattern BIND = Pattern.compile("^((\"|')(.*?)\\2:|(\\{(.*?)\\}))(.+)$"); /** Static context of an expression. */ public StaticContext sc = new StaticContext(); /** Variables. */ public final VarContext vars = new VarContext(); /** Functions. */ public final UserFuncs funcs = new UserFuncs(); /** Query resources. */ public final QueryResources resource = new QueryResources(this); /** Database context. */ public final Context context; /** XQuery version flag. */ public boolean xquery3; /** Cached stop word files. */ public HashMap<String, String> stop; /** Cached thesaurus files. */ public HashMap<String, String> thes; /** Query options (are valid during query execution). */ public final HashMap<String, String> dbOptions = new HashMap<String, String>(); /** Global options (will be set after query execution). */ public final HashMap<String, Object> globalOpt = new HashMap<String, Object>(); /** Root expression of the query. */ public Expr root; /** Current context value. */ public Value value; /** Current context position. */ public long pos = 1; /** Current context size. */ public long size = 1; /** Optional initial context set. */ Nodes nodes; /** Current full-text options. */ public FTOpt ftopt = new FTOpt(); /** Current full-text token. */ public FTLexer fttoken; /** Current Date. */ public Dat date; /** Current DateTime. */ public Dtm dtm; /** Current Time. */ public Tim time; /** Full-text position data (needed for highlighting of full-text results). */ public FTPosData ftpos; /** Full-text token counter (needed for highlighting of full-text results). */ public byte ftoknum; /** Pending updates. */ public final Updates updates = new Updates(); /** Indicates if this query might perform updates. */ public boolean updating; /** Compilation flag: current node has leaves. */ public boolean leaf; /** Compilation flag: GFLWOR clause performs grouping. */ public boolean grouping; /** Number of successive tail calls. */ public int tailCalls; /** Maximum number of successive tail calls. */ public final int maxCalls; /** Counter for variable IDs. */ public int varIDs; /** Pre-declared modules, containing module uri and their file paths. */ final TokenMap modDeclared = new TokenMap(); /** Parsed modules, containing the file path and module uri. */ final TokenMap modParsed = new TokenMap(); /** Serializer options. */ SerializerProp serProp; /** Initial context value. */ public Expr ctxItem; /** Java modules. */ public final HashMap<QueryModule, ArrayList<Method>> javaModules = new HashMap<QueryModule, ArrayList<Method>>(); /** JAR modules. */ public JarLoader jars; /** Opened connections to relational databases. */ JDBCConnections jdbc; /** String container for query background information. */ private final TokenBuilder info = new TokenBuilder(); /** Info flag. */ private final boolean inf; /** Optimization flag. */ private boolean firstOpt = true; /** Evaluation flag. */ private boolean firstEval = true; /** * Constructor. * @param ctx database context */ public QueryContext(final Context ctx) { context = ctx; nodes = ctx.current(); xquery3 = ctx.prop.is(Prop.XQUERY3); inf = ctx.prop.is(Prop.QUERYINFO) || Util.debug; final String path = ctx.prop.get(Prop.QUERYPATH); if(!path.isEmpty()) sc.baseURI(path); maxCalls = ctx.prop.num(Prop.TAILCALLS); } /** * Parses the specified query. * @param qu input query * @throws QueryException query exception */ public void parse(final String qu) throws QueryException { root = new QueryParser(qu, this).parse(sc.baseIO(), null); } /** * Parses the specified module. * @param qu input query * @throws QueryException query exception */ public void module(final String qu) throws QueryException { new QueryParser(qu, this).parse(sc.baseIO(), EMPTY); } /** * Compiles and optimizes the expression. * @throws QueryException query exception */ public void compile() throws QueryException { // dump compilation info if(inf) compInfo(NL + COMPILING_C); // temporarily set database values if(dbOptions != null) { for(final Entry<String, String> e : dbOptions.entrySet()) { Set.set(e.getKey(), e.getValue(), context.prop); } } if(ctxItem != null) { // evaluate initial expression try { value = ctxItem.value(this); } catch(final QueryException ex) { if(ex.err() != XPNOCTX) throw ex; // only {@link ParseExpr} instances may cause this error CTXINIT.thrw(((ParseExpr) ctxItem).input, ex.getMessage()); } } else if(nodes != null) { // add full-text container reference if(nodes.ftpos != null) ftpos = new FTPosData(); // cache the initial context nodes resource.compile(nodes); } // if specified, convert context item to specified type if(value != null && sc.initType != null) { value = sc.initType.promote(value, this, null); } try { // compile global functions. // variables will be compiled if called for the first time funcs.comp(this); // compile the expression root = root.comp(this); } catch(final StackOverflowError ex) { Util.debug(ex); XPSTACK.thrw(null); } // dump resulting query if(inf) info.add(NL + RESULT_C + funcs + root + NL); } /** * Returns a result iterator. * @return result iterator * @throws QueryException query exception */ public Iter iter() throws QueryException { try { // evaluate lazily if no updates are possible return updating ? value().iter() : iter(root); } catch(final StackOverflowError ex) { Util.debug(ex); throw XPSTACK.thrw(null); } } /** * Returns the result value. * @return result value * @throws QueryException query exception */ public Value value() throws QueryException { try { final Value v = value(root); if(updating) { updates.applyUpdates(); if(context.data() != null) context.update(); } return v; } catch(final StackOverflowError ex) { Util.debug(ex); throw XPSTACK.thrw(null); } } /** * Evaluates the expression with the specified context set. * @return resulting value * @throws QueryException query exception */ Result execute() throws QueryException { // evaluates the query final Iter ir = iter(); final ItemCache ic = new ItemCache(); Item it; // check if all results belong to the database of the input context if(serProp == null && nodes != null) { final IntList pre = new IntList(); while((it = ir.next()) != null) { checkStop(); if(!(it instanceof DBNode)) break; if(it.data() != nodes.data) break; pre.add(((DBNode) it).pre); } // completed... return standard nodeset with full-text positions final int ps = pre.size(); if(it == null) return ps == 0 ? ic : new Nodes(pre.toArray(), nodes.data, ftpos).checkRoot(); // otherwise, add nodes to standard iterator for(int p = 0; p < ps; ++p) ic.add(new DBNode(nodes.data, pre.get(p))); ic.add(it); } // use standard iterator while((it = ir.next()) != null) { checkStop(); ic.add(it); } return ic; } /** * Binds an object to a global variable. If the object is an {@link Expr} * instance, it is directly assigned. Otherwise, it is first cast to the * appropriate XQuery type. If {@code "json"} is specified as data type, * the value is interpreted according to the rules specified in * {@link JsonMapConverter}. * @param name name of variable * @param val object to be bound * @param type data type * @throws QueryException query exception */ public void bind(final String name, final Object val, final String type) throws QueryException { Object obj = val; if(type != null && !type.isEmpty()) { if(type.equals(JSONSTR)) { obj = JsonMapConverter.parse(token(val.toString()), null); } else { final QNm nm = new QNm(token(type), this); if(!nm.hasURI() && nm.hasPrefix()) NOURI.thrw(null, nm); final Type typ = Types.find(nm, true); if(typ == null) NOTYPE.thrw(null, nm); obj = typ.cast(obj, null); } } bind(name, obj); } /** * Binds an value to a global variable. If the value is an {@link Expr} * instance, it is directly assigned. Otherwise, it is first cast to the * appropriate XQuery type. * @param name name of variable * @param val value to be bound * @throws QueryException query exception */ public void bind(final String name, final Object val) throws QueryException { final Expr ex = val instanceof Expr ? (Expr) val : JavaMapping.toValue(val); // remove optional $ prefix String nm = name.indexOf('$') == 0 ? name.substring(1) : name; byte[] uri = EMPTY; // check for namespace declaration final Matcher m = BIND.matcher(nm); if(m.find()) { String u = m.group(3); if(u == null) u = m.group(5); uri = token(u); nm = m.group(6); } final byte[] ln = token(nm); if(nm.isEmpty() || !XMLToken.isNCName(ln)) return; // bind variable final QNm qnm = uri.length == 0 ? new QNm(ln, this) : new QNm(ln, uri); final Var gl = vars.globals().get(qnm); if(gl == null) { // assign new variable vars.updateGlobal(Var.create(this, null, qnm).bind(ex, this)); } else { // reset declaration state and bind new expression gl.declared = false; gl.bind(gl.type != null ? gl.type.type.cast(ex.item(this, null), this, null) : ex, this); } } /** * Recursively serializes the query plan. * @param ser serializer * @throws IOException I/O exception */ void plan(final Serializer ser) throws IOException { // only show root node if functions or variables exist final boolean r = funcs.size() != 0 || vars.globals().size != 0; if(r) ser.openElement(PLAN); funcs.plan(ser); vars.plan(ser); root.plan(ser); if(r) ser.closeElement(); } /** * Evaluates the specified expression and returns an iterator. * @param e expression to be evaluated * @return iterator * @throws QueryException query exception */ public Iter iter(final Expr e) throws QueryException { checkStop(); return e.iter(this); } /** * Evaluates the specified expression and returns an iterator. * @param expr expression to be evaluated * @return iterator * @throws QueryException query exception */ public Value value(final Expr expr) throws QueryException { checkStop(); return expr.value(this); } /** * Returns the current data reference of the context value, or {@code null}. * @return data reference */ public Data data() { return value != null ? value.data() : null; } /** * Creates a variable with a unique, non-clashing variable name. * @param ii input info * @param type type * @return variable */ public Var uniqueVar(final InputInfo ii, final SeqType type) { return Var.create(this, ii, new QNm(token(varIDs)), type); } /** * Adds some optimization info. * @param string evaluation info * @param ext text text extensions */ public void compInfo(final String string, final Object... ext) { if(!inf) return; if(!firstOpt) info.add(QUERYSEP); firstOpt = false; info.addExt(string, ext).add(NL); } /** * Adds some evaluation info. * @param string evaluation info */ public void evalInfo(final byte[] string) { if(!inf) return; if(firstEval) info.add(NL).add(EVALUATING_C).add(NL); info.add(QUERYSEP).add(string).add(NL); firstEval = false; } /** * Returns info on query compilation and evaluation. * @return query info */ public String info() { return info.toString(); } /** * Returns JDBC connections. * @return jdbc connections */ public JDBCConnections jdbc() { if(jdbc == null) jdbc = new JDBCConnections(); return jdbc; } /** * Returns the serialization properties. * @param opt return {@code null} reference if no properties are specified * @return serialization properties * @throws SerializerException serializer exception */ public SerializerProp serProp(final boolean opt) throws SerializerException { // if available, use local query properties if(serProp != null) return serProp; final String serial = context.prop.get(Prop.SERIALIZER); if(opt && serial.isEmpty()) return null; // otherwise, apply global serialization option return new SerializerProp(serial); } @Override public String tit() { return EVALUATING_C; } @Override public String det() { return EVALUATING_C; } @Override public double prog() { return 0; } }