package org.exist.indexing.sort; import org.exist.EXistException; import org.exist.collections.Collection; import org.exist.dom.persistent.*; import org.exist.indexing.IndexController; import org.exist.indexing.IndexWorker; import org.exist.indexing.MatchListener; import org.exist.indexing.StreamListener; import org.exist.indexing.StreamListener.ReindexMode; import org.exist.storage.DBBroker; import org.exist.storage.NodePath; import org.exist.storage.btree.BTreeCallback; import org.exist.storage.btree.BTreeException; import org.exist.storage.btree.IndexQuery; import org.exist.storage.btree.Value; import org.exist.storage.lock.Lock; import org.exist.storage.lock.Lock.LockMode; import org.exist.util.*; import org.exist.xquery.QueryRewriter; import org.exist.xquery.TerminatedException; import org.exist.xquery.XQueryContext; import org.w3c.dom.NodeList; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; public class SortIndexWorker implements IndexWorker { private ReindexMode mode = ReindexMode.STORE; private DocumentImpl document = null; private SortIndex index; public SortIndexWorker(final SortIndex index) { this.index = index; } public void setDocument(final DocumentImpl doc, final ReindexMode mode) { this.document = doc; this.mode = mode; } public String getIndexId() { return SortIndex.ID; } public String getIndexName() { return index.getIndexName(); } @Override public QueryRewriter getQueryRewriter(final XQueryContext context) { return null; } @Override public void flush() { switch (mode) { case REMOVE_ALL_NODES: remove(document); break; } } /** * Create a new sort index identified by a name. The method iterates through all items in * the items list and adds the nodes to the index. It assumes that the list is already ordered. * * @param name the name by which the index will be identified * @param items ordered list of items to store * @throws EXistException * @throws LockException */ public void createIndex(final String name, final List<SortItem> items) throws EXistException, LockException { // get an id for the new index final short id = getOrRegisterId(name); final Lock lock = index.btree.getLock(); try { lock.acquire(LockMode.WRITE_LOCK); long idx = 0; for (final SortItem item : items) { final byte[] key = computeKey(id, item.getNode()); index.btree.addValue(new Value(key), idx++); } } catch (final LockException | IOException | BTreeException e) { throw new EXistException("Exception caught while creating sort index: " + e.getMessage(), e); } finally { lock.release(LockMode.WRITE_LOCK); } } public boolean hasIndex(final String name) throws EXistException, LockException { return getId(name) > 0; } /** * Looks up the given node in the specified index and returns its original position * in the ordered set as a long integer. * * @param name the name of the index * @param proxy the node * @return the original position of the node in the ordered set * @throws EXistException * @throws LockException */ public long getIndex(final String name, final NodeProxy proxy) throws EXistException, LockException { final short id = getId(name); final Lock lock = index.btree.getLock(); try { lock.acquire(LockMode.READ_LOCK); final byte[] key = computeKey(id, proxy); return index.btree.findValue(new Value(key)); } catch (final LockException | IOException | BTreeException e) { throw new EXistException("Exception caught while reading sort index: " + e.getMessage(), e); } finally { lock.release(LockMode.READ_LOCK); } } /** * Completely remove the index identified by its name. * * @param name the name of the index * @throws EXistException * @throws LockException */ public void remove(final String name) throws EXistException, LockException { final short id = getId(name); final Lock lock = index.btree.getLock(); try { lock.acquire(LockMode.READ_LOCK); final byte[] fromKey = computeKey(id); final byte[] toKey = computeKey((short) (id + 1)); final IndexQuery query = new IndexQuery(IndexQuery.RANGE, new Value(fromKey), new Value(toKey)); index.btree.remove(query, null); removeId(name); } catch (final BTreeException | TerminatedException | IOException e) { throw new EXistException("Exception caught while deleting sort index: " + e.getMessage(), e); } finally { lock.release(LockMode.READ_LOCK); } } public void remove(final String name, final DocumentImpl doc) throws EXistException, LockException { final short id = getId(name); remove(doc, id); } private void remove(final DocumentImpl doc, final short id) throws LockException, EXistException { final Lock lock = index.btree.getLock(); try { lock.acquire(LockMode.READ_LOCK); final byte[] fromKey = computeKey(id, doc.getDocId()); final byte[] toKey = computeKey(id, doc.getDocId() + 1); final IndexQuery query = new IndexQuery(IndexQuery.RANGE, new Value(fromKey), new Value(toKey)); index.btree.remove(query, null); } catch (final BTreeException | TerminatedException | IOException e) { throw new EXistException("Exception caught while deleting sort index: " + e.getMessage(), e); } finally { lock.release(LockMode.READ_LOCK); } } public void remove(final DocumentImpl doc) { if (index.btree == null) return; final byte[] fromKey = new byte[]{1}; final byte[] endKey = new byte[]{2}; final Lock lock = index.btree.getLock(); try { lock.acquire(LockMode.READ_LOCK); final IndexQuery query = new IndexQuery(IndexQuery.RANGE, new Value(fromKey), new Value(endKey)); final FindIdCallback callback = new FindIdCallback(true); index.btree.query(query, callback); for (final long id : callback.allIds) { remove(doc, (short) id); } } catch (final BTreeException | EXistException | LockException | TerminatedException | IOException e) { SortIndex.LOG.debug("Exception caught while reading sort index: " + e.getMessage(), e); } finally { lock.release(LockMode.READ_LOCK); } } /** * Register the given index name and return a short id for it. * * @param name the name of the index * @return a unique id to be used for the index entries * @throws EXistException * @throws LockException */ private short getOrRegisterId(final String name) throws EXistException, LockException { short id = getId(name); if (id < 0) { final byte[] fromKey = {1}; final byte[] endKey = {2}; final IndexQuery query = new IndexQuery(IndexQuery.RANGE, new Value(fromKey), new Value(endKey)); final Lock lock = index.btree.getLock(); try { lock.acquire(LockMode.READ_LOCK); final FindIdCallback callback = new FindIdCallback(false); index.btree.query(query, callback); id = (short) (callback.max + 1); registerId(id, name); } catch (final IOException | TerminatedException | BTreeException e) { throw new EXistException("Exception caught while reading sort index: " + e.getMessage(), e); } finally { lock.release(LockMode.READ_LOCK); } } return id; } private void registerId(final short id, final String name) throws EXistException { final byte[] key = new byte[1 + UTF8.encoded(name)]; key[0] = 1; UTF8.encode(name, key, 1); final Lock lock = index.btree.getLock(); try { lock.acquire(LockMode.READ_LOCK); index.btree.addValue(new Value(key), id); } catch (final LockException | IOException | BTreeException e) { throw new EXistException("Exception caught while reading sort index: " + e.getMessage(), e); } finally { lock.release(LockMode.READ_LOCK); } } private void removeId(final String name) throws EXistException { final byte[] key = new byte[1 + UTF8.encoded(name)]; key[0] = 1; UTF8.encode(name, key, 1); final Lock lock = index.btree.getLock(); try { lock.acquire(LockMode.READ_LOCK); index.btree.removeValue(new Value(key)); } catch (final LockException | IOException | BTreeException e) { throw new EXistException("Exception caught while reading sort index: " + e.getMessage(), e); } finally { lock.release(LockMode.READ_LOCK); } } private short getId(final String name) throws EXistException, LockException { final byte[] key = new byte[1 + UTF8.encoded(name)]; key[0] = 1; UTF8.encode(name, key, 1); final Lock lock = index.btree.getLock(); try { lock.acquire(LockMode.READ_LOCK); return (short) index.btree.findValue(new Value(key)); } catch (final BTreeException | IOException e) { throw new EXistException("Exception caught while reading sort index: " + e.getMessage(), e); } finally { lock.release(LockMode.READ_LOCK); } } private byte[] computeKey(final short id, final NodeProxy proxy) { final byte[] data = new byte[7 + proxy.getNodeId().size()]; data[0] = 0; ByteConversion.shortToByteH(id, data, 1); ByteConversion.intToByteH(proxy.getOwnerDocument().getDocId(), data, 3); proxy.getNodeId().serialize(data, 7); return data; } private byte[] computeKey(final short id, final int docId) { final byte[] data = new byte[7]; data[0] = 0; ByteConversion.shortToByteH(id, data, 1); ByteConversion.intToByteH(docId, data, 3); return data; } private byte[] computeKey(final short id) { final byte[] data = new byte[3]; data[0] = 0; ByteConversion.shortToByteH(id, data, 1); return data; } public Object configure(final IndexController controller, final NodeList configNodes, final Map<String, String> namespaces) throws DatabaseConfigurationException { return null; } public DocumentImpl getDocument() { return document; } public void setDocument(final DocumentImpl doc) { this.document = doc; } @Override public ReindexMode getMode() { return mode; } public void setMode(final ReindexMode mode) { this.mode = mode; } public IStoredNode getReindexRoot(final IStoredNode node, final NodePath path, final boolean insert, final boolean includeSelf) { return insert ? null : node; } public StreamListener getListener() { return null; } public MatchListener getMatchListener(final DBBroker broker, final NodeProxy proxy) { return null; } public void removeCollection(final Collection collection, final DBBroker broker, final boolean reindex) { } public boolean checkIndex(final DBBroker broker) { return false; } public Occurrences[] scanIndex(final XQueryContext context, final DocumentSet docs, final NodeSet contextSet, final Map hints) { return new Occurrences[0]; } private final static class FindIdCallback implements BTreeCallback { long max = 0; List<Long> allIds = null; private FindIdCallback(final boolean findIds) { if (findIds) allIds = new ArrayList<>(10); } public boolean indexInfo(final Value value, final long pointer) throws TerminatedException { max = Math.max(max, pointer); if (allIds != null) { allIds.add(pointer); } return true; } } }