package org.basex.index.value; import static org.basex.data.DataText.*; import static org.basex.util.Token.*; import java.io.IOException; import org.basex.data.Data; import org.basex.index.IndexIterator; import org.basex.index.RangeToken; import org.basex.util.Num; import org.basex.util.hash.TokenObjMap; import org.basex.util.list.IntList; import org.basex.util.list.TokenList; /** * This class provides access to attribute values and text contents stored on * disk. The data structure is described in the {@link ValueBuilder} class. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class UpdatableDiskValues extends DiskValues { /** * Constructor, initializing the index structure. * @param d data reference * @param txt value type (texts/attributes) * @throws IOException I/O Exception */ public UpdatableDiskValues(final Data d, final boolean txt) throws IOException { this(d, txt, txt ? DATATXT : DATAATV); } /** * Constructor, initializing the index structure. * @param d data reference * @param txt value type (texts/attributes) * @param pref file prefix * @throws IOException I/O Exception */ private UpdatableDiskValues(final Data d, final boolean txt, final String pref) throws IOException { super(d, txt, pref); } @Override protected IndexIterator iter(final int s, final long ps) { final IntList pres = new IntList(s); long p = ps; for(int l = 0, v = 0; l < s; ++l) { v += idxl.readNum(p); p = idxl.cursor(); pres.add(data.pre(v)); } return iter(pres.sort()); } @Override protected IndexIterator idRange(final RangeToken tok) { final double min = tok.min; final double max = tok.max; // check if min and max are positive integers with the same number of digits final int len = max > 0 && (long) max == max ? token(max).length : 0; final boolean simple = len != 0 && min > 0 && (long) min == min && token(min).length == len; final IntList pres = new IntList(); for(int l = 0; l < size; ++l) { final int ds = idxl.readNum(idxr.read5(l * 5L)); int id = idxl.readNum(); final int pre = data.pre(id); final double v = data.textDbl(pre, text); if(v >= min && v <= max) { // value is in range for(int d = 0; d < ds; ++d) { pres.add(data.pre(id)); id += idxl.readNum(); } } else if(simple && v > max && data.textLen(pre, text) == len) { // if limits are integers, if min, max and current value have the same // string length, and if current value is larger than max, test can be // skipped, as all remaining values will be bigger break; } } return iter(pres.sort()); } @Override protected int firstpre(final long pos) { return data.pre(super.firstpre(pos)); } @Override public void flush() throws IOException { idxl.write4(0, size); super.flush(); } @Override public void index(final TokenObjMap<IntList> m) { final int last = size - 1; // create a sorted list of all keys: allows faster binary search final TokenList allkeys = new TokenList(m.keys()).sort(true); // create a sorted list of the new keys and update the old keys final TokenList nkeys = new TokenList(m.size()); int p = 0; for(final byte[] key : allkeys) { p = get(key, p, last); if(p < 0) { p = -(p + 1); nkeys.add(key); } else { appendIds(p, key, diffs(m.get(key))); } } // insert new keys, starting from the biggest one for(int j = nkeys.size() - 1, i = last, pos = size + j; j >= 0; --j) { final byte[] key = nkeys.get(j); final int ins = -(1 + get(key, 0, i)); if(ins < 0) throw new IllegalStateException("Key should not exist"); // shift all bigger keys to the right while(i >= ins) { idxr.write5(pos * 5L, idxr.read5(i * 5L)); ctext.add(pos--, ctext.get(i--)); } // add the new key and its ids idxr.write5(pos * 5L, idxl.appendNums(diffs(m.get(key)))); ctext.add(pos--, key); // [DP] should the entry be added to the cache? } size += nkeys.size(); } /** * Add record ids to an index entry. * @param ix index of the key * @param key key * @param nids sorted list of record ids to add: the first value is the * smallest id and all others are only difference to the previous one */ private void appendIds(final int ix, final byte[] key, final int[] nids) { final long oldpos = idxr.read5(ix * 5L); final int numold = idxl.readNum(oldpos); final int[] ids = new int[numold + nids.length]; // read the old ids for(int i = 0; i < numold; ++i) { final int v = idxl.readNum(); nids[0] -= v; // adjust the first new id ids[i] = v; } // append the new ids - they are bigger than the old ones System.arraycopy(nids, 0, ids, numold, nids.length); final long newpos = idxl.appendNums(ids); idxr.write5(ix * 5L, newpos); // check if key is cached and update the cache entry final int cacheid = cache.id(key); if(cacheid > 0) cache.update(cacheid, ids.length, newpos + Num.length(ids.length)); } @Override public void delete(final TokenObjMap<IntList> m) { // create a sorted list of all keys: allows faster binary search final TokenList allkeys = new TokenList(m.keys()).sort(true); // delete ids and create a list of the key positions which should be deleted final IntList empty = new IntList(m.size()); int p = 0; for(final byte[] key : allkeys) { p = get(key, p, size - 1); if(p < 0) p = -(p + 1); // should not occur, but anyway else if(deleteIds(p, key, m.get(key).sort().toArray()) == 0) empty.add(p); } // empty should contain sorted keys, since allkeys was sorted, too if(!empty.empty()) deleteKeys(empty.toArray()); } /** * Remove record ids from the index. * @param ix index of the key * @param key record key * @param ids list of record ids to delete * @return number of remaining records */ private int deleteIds(final int ix, final byte[] key, final int[] ids) { final long pos = idxr.read5(ix * 5L); final int numold = idxl.readNum(pos); if(numold == ids.length) { // all ids should be deteted: the key itself will be deleted, too cache.delete(key); return 0; } // read each id from the list and skip the ones which should be deleted // collect remaining values final int[] nids = new int[numold - ids.length]; for(int i = 0, j = 0, cid = 0, pid = 0; i < nids.length;) { cid += idxl.readNum(); if(j < ids.length && ids[j] == cid) ++j; else { nids[i++] = cid - pid; pid = cid; } } idxl.writeNums(pos, nids); // check if key is cached and update the cache entry final int cacheid = cache.id(key); if(cacheid > 0) cache.update(cacheid, nids.length, pos + Num.length(nids.length)); return nids.length; } /** * Delete keys from the index. * @param keys list of key positions to delete */ private void deleteKeys(final int[] keys) { // shift all keys to the left, skipping the ones which have to be deleted int j = 0; for(int pos = keys[j++], i = pos + 1; i < size; ++i) { if(j < keys.length && i == keys[j]) ++j; else { idxr.write5(pos * 5L, idxr.read5(i * 5L)); ctext.add(pos++, ctext.get(i)); } } // reduce the size of the index size -= j; } @Override public void replace(final byte[] o, final byte[] n, final int id) { // delete the id from the old key final int p = get(o); if(p >= 0) { final int[] tmp = new int[] { id}; if(deleteIds(p, o, tmp) == 0) { // the old key remains empty: delete it cache.delete(o); tmp[0] = p; deleteKeys(tmp); } } // add the id to the new key insertId(n, id); } /** * Add a text entry to the index. * @param key text to index * @param id id value */ private void insertId(final byte[] key, final int id) { int ix = get(key); if(ix < 0) { ix = -(ix + 1); // shift all entries with bigger keys to the right for(int i = size; i > ix; --i) idxr.write5(i * 5L, idxr.read5((i - 1) * 5L)); // add the key and the id idxr.write5(ix * 5L, idxl.appendNums(new int[] { id})); ctext.add(ix, key); // [DP] should the entry be added to the cache? ++size; } else { // add id to the list of ids in the index node final long pos = idxr.read5(ix * 5L); final int num = idxl.readNum(pos); final int[] ids = new int[num + 1]; boolean notadded = true; int cid = 0; for(int i = 0, j = -1; i < num; ++i) { int v = idxl.readNum(); if(notadded && id < cid + v) { // add the new id ids[++j] = id - cid; notadded = false; // decrement the difference to the next id v -= id - cid; cid = id; } ids[++j] = v; cid += v; } if(notadded) ids[ids.length - 1] = id - cid; final long newpos = idxl.appendNums(ids); idxr.write5(ix * 5L, newpos); // check if key is cached and update the cache entry final int cacheid = cache.id(key); if(cacheid > 0) cache.update(cacheid, ids.length, newpos + Num.length(ids.length)); } } /** * Sort and calculate the differences between a list of ids. * @param ids id list * @return differences */ private static int[] diffs(final IntList ids) { final int[] a = ids.sort().toArray(); for(int l = a.length - 1; l > 0; --l) a[l] -= a[l - 1]; return a; } }