package org.basex.query.up; import static org.basex.query.util.Err.*; import static org.basex.util.Token.*; import org.basex.data.MemData; import org.basex.query.QueryContext; import org.basex.query.QueryException; import org.basex.query.item.ANode; import org.basex.query.item.DBNode; import org.basex.query.iter.AxisIter; import org.basex.query.up.primitives.PrimitiveType; import org.basex.query.up.primitives.Put; import org.basex.query.up.primitives.UpdatePrimitive; import org.basex.query.util.DataBuilder; import org.basex.util.hash.IntMap; import org.basex.util.hash.TokenSet; /** * ***** Implementation of the W3C XQUERY UPDATE FACILITY 1.0 ***** * * * Holds all update operations and primitives a snapshot contains, checks * constraints and finally executes the updates. * * Fragment updates are treated like database updates. An artificial data * instance is created for each fragment. Ultimately the updating process for * a fragment is the same as for a database node. * * The complete updating process is custom-tailored to the * sequential table encoding of BaseX. As a general rule, all updates are * collected and applied for each database from bottom to top, regarding the * PRE values of the corresponding target nodes. Updates on the highest pre * values are applied first. * * * ***** Updates work like the following: ***** * * 1. Each call of an updating expression creates an {@link UpdatePrimitive} * 2. All update primitives for a snapshot/query are collected here * 3. Primitives are kept separately for each database that is addressed. This * way we can operate on PRE values instead of node IDs, skip mapping * overhead and further optimize the update process. * {@link DatabaseUpdates} * 4. Primitives are further kept separately for each database node - each * individual target PRE value. There's a specific container for this: * {@link NodeUpdates} * 5. Transform expressions are executed in an 'isolated' updating environment, * see {@link TransformModifier}. All the other updates are executed by * a {@link DatabaseModifier}. * 6. Fn:put statements are treated exactly like an update primitive. They are * represented by an update primitive. * 7. After the query has been parsed and all update primitives have been added * to the list, constraints, which cannot be taken care of on the fly, are * checked. If no problems occur, updates are TO BE carried out. * * 8. Updates are carried out separately for each database, the following * way: * 1. Update primitives are sorted by the PRE value of their target node * in a descending manner. Applying updates from the highest to the * lowest PRE value ensures that updates, not-yet carried out, are not * affected by PRE value shifts. PRE value shifts are a result of * structural changes of the table - which occur each time a node is * inserted or deleted, for more see {@link PrimitiveType}. * 2. For each specific target node, updates which are collected in a * {@link NodeUpdates} container are executed in a specific * order, depending on their type {@link PrimitiveType}. This order * relates more or less to the XQUF specification, at least it leads to * the same result. * * 3. Neighboring text nodes have to be merged together (see XQuery Data * Model). Adjacent text nodes can only be a result of structural * updates. With our approach this takes some extra effort, but can be * carried out on-the-fly, applying the two following steps: * 1. An {@link NodeUpdates} container holds all update * primitives for a specific database node N. If all updates with * target N (all updates in the current container) are carried out, * text node adjacency can only occur on the child axis, or the * sibling axis of N. * * Regarding adjacency on the child axis, this can be taken care off * by first applying all updates on this axis and subsequently * checking the affected PRE positions for adjacent text nodes. * * The sibling axis of N can only be checked for adjacent text nodes * after an eventual left sibling target container has executed its * updates. Example: We have two siblings A(1) and B(2). A is a text * node, B is an element. The numbers in brackets are their PRE * values. If A is target of a delete primitive and B is target of * an insert before primitive (text 'foo' is inserted) the following * happens: insert before is executed on B, A is deleted, location * around B is checked for adjacent text nodes. No merges occur. We * repeat this for every target node and only apply text node merges * if updates of the next container have been carried out. * * If we would carry out text merges immediately, the following * (which leads to inconsistency) happens: insert before * is executed on B, 'foo' is adjacent to A and therefore the two * nodes are merged together, A is finally deleted. As a result we * loose the text node 'foo' during the process, as it is merged * with A and subsequently deleted together with A (consider the pre * value shifts!). * * * XQUF SPECIALTIES WITH BASEX * * Fn:put: the target node of an fn:put statement cannot be deleted, but it * can be among the deleted nodes as a result of a 'replace element content' * statement. * * @author BaseX Team 2005-12, BSD License * @author Lukas Kircher */ public final class Updates { /** Current context modifier. */ public ContextModifier mod; /** Mapping between fragment IDs and the temorary data instances created * to apply updates on the corresponding fragments. */ private final IntMap<MemData> fragmentIDs = new IntMap<MemData>(); /** Set which contains all URIs which are targeted during a snapshot. */ private final TokenSet putUris = new TokenSet(); /** * Adds an update primitive to the current context modifier. * @param up update primitive * @param ctx query context * @throws QueryException query exception */ public void add(final UpdatePrimitive up, final QueryContext ctx) throws QueryException { if(mod == null) mod = new DatabaseModifier(); // check for duplicate Put target URIs if(up instanceof Put) { final Put put = (Put) up; if(putUris.add(token(put.path(0))) < 0) UPURIDUP.thrw(put.input, put.path(0)); } mod.add(up, ctx); } /** * Determines the data reference and pre value for an update primitive * which has a fragment as a target node. If an ancestor of the given target * node has already been added to the pending update list, the corresponding * data reference and the pre value of the given target node within this * database table are calculated. Otherwise a new data instance is created. * * @param target target fragment * @param ctx query context * @return database node created from input fragment */ public DBNode determineDataRef(final ANode target, final QueryContext ctx) { if(target instanceof DBNode) return (DBNode) target; // determine highest ancestor node ANode anc = target; final AxisIter it = target.ancestor(); for(ANode p; (p = it.next()) != null;) anc = p; /* See if this ancestor has already been added to the pending update list. * In this case a database has already been created. */ final int ancID = anc.id; MemData data = fragmentIDs.get(ancID); // if data doesn't exist, create a new one if(data == null) { data = new MemData(ctx.context.prop); new DataBuilder(data).build(anc); // create a mapping between the fragment id and the data reference fragmentIDs.add(ancID, data); } // determine the pre value of the target node within its database final int trgID = target.id; final int pre = preSteps(anc, trgID); return new DBNode(data, pre); } /** * Recursively determines the pre value for a given fragment node within the * corresponding data reference. * @param node current * @param trgID ID of fragment for which we calculate the pre value * @return pre value */ private static int preSteps(final ANode node, final int trgID) { if(node.id == trgID) return 0; int s = 1; AxisIter it = node.attributes(); for(ANode n; (n = it.next()) != null;) { final int st = preSteps(n, trgID); if(st == 0) return s; s += st; } it = node.children(); for(ANode n; (n = it.next()) != null && n.id <= trgID;) { s += preSteps(n, trgID); } return s; } /** * Executes all updates. * @throws QueryException query exception */ public void applyUpdates() throws QueryException { if(mod != null) mod.applyUpdates(); } /** * Number of updates on the pending update list. * @return #updates */ public int size() { return mod == null ? 0 : mod.size(); } }