package org.basex.query.up;
import static org.basex.query.up.primitives.PrimitiveType.*;
import static org.basex.query.util.Err.*;
import java.io.IOException;
import org.basex.core.Prop;
import org.basex.core.cmd.Export;
import org.basex.data.Data;
import org.basex.query.QueryException;
import org.basex.query.item.NodeType;
import org.basex.query.item.QNm;
import org.basex.query.up.primitives.NodeCopy;
import org.basex.query.up.primitives.UpdatePrimitive;
import org.basex.util.hash.IntMap;
import org.basex.util.list.IntList;
/**
* This class holds all updates for a specific database. Before applied,
* updates are sorted in a descending manner regarding the pre value of their
* target nodes. As a result, update operations are applied from bottom to
* top and we can stick to pre values as primitive identifier as pre value
* shifts won't have any effect on updates that have not yet been applied.
*
* @author BaseX Team 2005-12, BSD License
* @author Lukas Kircher
*/
final class DatabaseUpdates {
/** Data reference. */
private final Data data;
/** Pre values of target nodes. */
private IntList nodes = new IntList(0);
/** Mapping between pre values of the target nodes and all update primitives
* which operate on this target. */
private final IntMap<NodeUpdates> updatePrimitives =
new IntMap<NodeUpdates>();
/**
* Constructor.
* @param d data reference
*/
DatabaseUpdates(final Data d) {
data = d;
}
/**
* Adds an update primitive to the list.
* @param p update primitive
* @throws QueryException query exception
*/
void add(final UpdatePrimitive p) throws QueryException {
final int pre = p.pre;
NodeUpdates pc = updatePrimitives.get(pre);
if(pc == null) {
pc = new NodeUpdates();
updatePrimitives.add(pre, pc);
}
pc.add(p);
}
/**
* Checks updates for violations. If a violation is found the complete update
* process is aborted.
* @throws QueryException query exception
*/
void check() throws QueryException {
// get and sort keys (pre/id values)
final int s = updatePrimitives.size();
nodes = new IntList(s);
for(int i = 1; i <= updatePrimitives.size(); i++)
nodes.add(updatePrimitives.key(i));
nodes.sort();
for(int i = 0; i < s; ++i) {
final NodeUpdates ups = updatePrimitives.get(nodes.get(i));
for(final UpdatePrimitive p : ups.prim) {
if(p instanceof NodeCopy) ((NodeCopy) p).prepare();
/* check if the identity of all target nodes of fn:put operations is
still available after the execution of updates. that includes parent
nodes as well */
if(p.type == PUT && ancestorDeleted(nodes.get(i))) {
UPFOTYPE.thrw(p.input, p);
}
}
}
// check attribute duplicates
int p = nodes.size() - 1;
int par = -1;
while(p >= 0) {
// parent of a previous attribute has already been checked
if(par == nodes.get(p) && --p < 0) break;
int pre = nodes.get(p);
// catching optimize statements which have PRE == -1 as a target
if(pre == -1) return;
final int k = data.kind(pre);
if(k == Data.ATTR) {
par = data.parent(pre, k);
final IntList il = new IntList();
while(p >= 0 && (pre = nodes.get(p)) > par) {
il.add(pre);
--p;
}
if(par != -1) il.add(par);
checkNames(il.toArray());
} else {
if(k == Data.ELEM) checkNames(pre);
--p;
}
}
}
/**
* Checks nodes for namespace conflicts and duplicate attributes.
* @param pres pre values of nodes to check (in descending order)
* @throws QueryException query exception
*/
private void checkNames(final int... pres) throws QueryException {
// check for namespace conflicts
final NamePool pool = new NamePool();
for(final int pre : pres) {
final NodeUpdates ups = updatePrimitives.get(pre);
if(ups != null) for(final UpdatePrimitive up : ups.prim) up.update(pool);
}
if(!pool.nsOK()) UPNSCONFL2.thrw(null);
// check for duplicate attributes
final IntList il = new IntList();
for(final int pre : pres) {
// pre values consist exclusively of element and attribute nodes
if(data.kind(pre) == Data.ATTR) {
il.add(pre);
} else {
final int ps = pre + data.attSize(pre, Data.ELEM);
for(int p = pre + 1; p < ps; ++p) {
final byte[] nm = data.name(p, Data.ATTR);
if(!il.contains(p)) {
final QNm name = new QNm(nm);
final byte[] uri = data.nspaces.uri(data.nspaces.uri(nm, p));
if(uri != null) name.uri(uri);
pool.add(name, NodeType.ATT);
}
}
}
}
final QNm dup = pool.duplicate();
if(dup != null) UPATTDUPL.thrw(null, dup);
}
/**
* Identifies unnecessary update operations and removes them from the pending
* update list.
*/
private void treeAwareUpdates() {
/* Tree Aware Updates: Unnecessary updates on the descendant axis of a
* deleted or replaced node or of a node which is target of a replace
* element content expression are identified and removed from the pending
* update list. */
final int l = nodes.size();
int ni = 0;
int c = 0;
while(ni < l - 1) {
final int pre = nodes.get(ni++);
// If a node is deleted or replaced or affected by a replace element
// content expression ...
final int[] destroyed = updatePrimitives.get(pre).
destroyedNodeIdentities().toArray();
for(final int pd : destroyed) {
final int followingAxisPre = pd + data.size(pd, data.kind(pd));
// mark obsolete target nodes on the descendant axis.
while(ni < l && nodes.get(ni) < followingAxisPre) {
nodes.set(ni++, -1);
c++;
}
}
}
// return if nothing changed on the pending update list
if(c == 0) return;
// Create a new list that contains necessary targets only
final IntList newNodes = new IntList(nodes.size() - c);
for(int i = 0; i < nodes.size(); i++) {
final int pre = nodes.get(i);
if(pre != -1) newNodes.add(pre);
}
nodes = newNodes;
}
/**
* Applies all updates for this specific database.
* @throws QueryException query exception
*/
void apply() throws QueryException {
treeAwareUpdates();
// mark disk database instances as updating
if(!data.updating(true)) LOCK.thrw(null, data.meta.name);
/*
* For each target node, the update primitives in the corresponding
* container are applied. Certain operations may lead to text node
* adjacency. As the updates in a container, including eventual text node
* merges, may not affect the preceding sibling axis (as there could be
* other update primitives with a target on this axis), we have to make
* sure that updates on the preceding sibling axis have been carried out.
*
* To achieve this we keep track of the most recently applied container
* and resolve text adjacency issues after the next container on the
* preceding axis has been executed.
*/
try {
NodeUpdates recent = null;
// apply updates from the highest to the lowest pre value
for(int i = nodes.size() - 1; i >= 0; i--) {
final NodeUpdates current = updatePrimitives.get(nodes.get(i));
// first run, no recent container
if(recent == null)
current.makePrimitivesEffective();
else
recent.resolveExternalTextNodeAdjacency(
current.makePrimitivesEffective());
recent = current;
}
// resolve text adjacency issues of the last container
recent.resolveExternalTextNodeAdjacency(0);
} finally {
data.flush();
// mark disk database instances as updating
if(!data.updating(false)) UNLOCK.thrw(null, data.meta.name);
}
if(data.meta.prop.is(Prop.WRITEBACK) && !data.meta.original.isEmpty()) {
try {
Export.export(data, data.meta.original);
} catch(final IOException ex) {
UPPUTERR.thrw(null, data.meta.original);
}
}
}
/**
* Determines recursively whether an ancestor of a given node is deleted.
* @param n pre value
* @return true if ancestor deleted
*/
boolean ancestorDeleted(final int n) {
final NodeUpdates up = updatePrimitives.get(n);
if(up != null && up.updatesDestroyIdentity(n)) return true;
final int p = data.parent(n, data.kind(n));
return p != -1 && ancestorDeleted(p);
}
/**
* Returns the number of performed updates.
* @return number of updates
*/
int size() {
int s = 0;
for(int i = nodes.size() - 1; i >= 0; i--) {
for(final UpdatePrimitive up : updatePrimitives.get(nodes.get(i)).prim) {
s += up.size();
}
}
return s;
}
}