@SuppressWarnings("deprecation") @Override public DResult get(Get get, long startId) throws IOException { if (get.hasFamilies()) get.addFamily(DominoConst.INNER_FAMILY); get.setTimeRange(0, startId + 1); // [x, y) get.setMaxVersions(); Result preRead = region.get(get); List<KeyValue> status = preRead.getColumn(DominoConst.INNER_FAMILY, DominoConst.STATUS_COL); if (status == null || status.size() == 0) { Result ret = MVCC.handleResult(this, getTrxMetaTable(), preRead, startId, null); return new DResult(ret, null); } Integer lockId = region.getLock(null, get.getRow(), true); try { Result r = MVCC.handleResult(this, getTrxMetaTable(), region.get(get, lockId), startId, lockId); return new DResult(r, null); } catch (TransactionOutOfDateException oode) { return new DResult(null, oode.getMessage()); } catch (InvalidRowStatusException e) { return new DResult(null, e.getMessage()); } finally { region.releaseRowLock(lockId); } }
@Test public void testRegionObserverFlushTimeStacking() throws Exception { byte[] ROW = Bytes.toBytes("testRow"); byte[] TABLE = Bytes.toBytes(getClass().getName()); byte[] A = Bytes.toBytes("A"); byte[][] FAMILIES = new byte[][] {A}; Configuration conf = HBaseConfiguration.create(); HRegion region = initHRegion(TABLE, getClass().getName(), conf, FAMILIES); RegionCoprocessorHost h = region.getCoprocessorHost(); h.load(NoDataFromFlush.class, Coprocessor.PRIORITY_HIGHEST, conf); h.load(EmptyRegionObsever.class, Coprocessor.PRIORITY_USER, conf); // put a row and flush it to disk Put put = new Put(ROW); put.add(A, A, A); region.put(put); region.flushcache(); Get get = new Get(ROW); Result r = region.get(get); assertNull( "Got an unexpected number of rows - no data should be returned with the NoDataFromScan coprocessor. Found: " + r, r.listCells()); }
private void doGets(HRegion region) throws IOException { for (int i = 0; i < NUM_ROWS; ++i) { final byte[] rowKey = LoadTestKVGenerator.md5PrefixedKey(i).getBytes(); for (int j = 0; j < NUM_COLS_PER_ROW; ++j) { final String qualStr = String.valueOf(j); if (VERBOSE) { System.err.println( "Reading row " + i + ", column " + j + " " + Bytes.toString(rowKey) + "/" + qualStr); } final byte[] qualBytes = Bytes.toBytes(qualStr); Get get = new Get(rowKey); get.addColumn(CF_BYTES, qualBytes); Result result = region.get(get); assertEquals(1, result.size()); byte[] value = result.getValue(CF_BYTES, qualBytes); assertTrue(LoadTestKVGenerator.verify(value, rowKey, qualBytes)); } } }
@SuppressWarnings("deprecation") @Override public void rollbackRow(byte[] row, long startId, Integer lockId) throws IOException { byte[] family = DominoConst.INNER_FAMILY; Get get = new Get(row); get.setTimeStamp(startId); get.addFamily(family); Result r = region.get(get, lockId); if (r == null || r.isEmpty()) return; byte[] colBytes = r.getValue(family, DominoConst.COLUMNS_COL); if (colBytes == null || colBytes.length == 0) return; Delete del = new Delete(row); Columns cols = new Columns(colBytes); for (Column col : cols.cols) { del.deleteColumn(col.family, col.qualifier, startId); } del.deleteColumn(family, DominoConst.COLUMNS_COL, startId); del.deleteColumn(family, DominoConst.STATUS_COL, startId); mutateRow(del, lockId); }
@SuppressWarnings("deprecation") @Override public void commitRow(byte[] row, long startId, long commitId, boolean isDelete, Integer lockId) throws IOException { Get get = new Get(row); get.setMaxVersions(); get.addFamily(DominoConst.INNER_FAMILY); Result r = region.get(get, lockId); if (!containsStatus(r, startId)) { // Other transaction may have committed this row of this version LOG.info( "Commit: No status found, returning: {}.{}", new String(this.getName()), new String(row)); return; } List<KeyValue> versions = r.getColumn(DominoConst.INNER_FAMILY, DominoConst.VERSION_COL); Put commit = new Put(row); commit.setWriteToWAL(true); boolean isFresh = true; if (versions.size() >= DominoConst.MAX_VERSION) { // We need to clean the earliest version. LOG.info( "Commit: rolling version window: {}.{}", new String(this.getName()), new String(row)); isFresh = addClearColumns(commit, versions, r, row, isDelete, commitId, startId, lockId); } KeyValue clearStatusKV = new KeyValue( row, DominoConst.INNER_FAMILY, DominoConst.STATUS_COL, startId, KeyValue.Type.Delete); commit.add(clearStatusKV); byte[] value = DominoConst.versionValue(startId, isDelete); if (isFresh) { KeyValue commitKV = new KeyValue(row, DominoConst.INNER_FAMILY, DominoConst.VERSION_COL, commitId, value); commit.add(commitKV); } // commitNumericModifications(row, startId, lockId, commit); mutateRow(commit, lockId); }
/** * Clear the data out of version window & write them to the second lowest version. * * @param commit * @param versions * @param r * @param row * @param isDelete * @param commitId * @param startId * @param lockId * @return * @throws IOException */ @SuppressWarnings("deprecation") private boolean addClearColumns( Put commit, List<KeyValue> versions, Result r, byte[] row, boolean isDelete, long commitId, long startId, Integer lockId) throws IOException { KeyValue commitKV = new KeyValue( row, DominoConst.INNER_FAMILY, DominoConst.VERSION_COL, commitId, DominoConst.versionValue(startId, isDelete)); NavigableSet<KeyValue> orderedVersions = new TreeSet<KeyValue>(MVCC.VERSION_KV_COMPARATOR); orderedVersions.add(versions.get(versions.size() - 1)); if (versions.size() >= 2) { orderedVersions.add(versions.get(versions.size() - 2)); } orderedVersions.add(commitKV); Iterator<KeyValue> it = orderedVersions.descendingIterator(); KeyValue remove = it.next(); KeyValue prev = it.next(); byte[] removeValue = remove.getValue(); byte[] prevValue = prev.getValue(); long removeCommitId = remove.getTimestamp(); long removeStartId = DominoConst.getVersion(removeValue); long prevStartId = DominoConst.getVersion(prevValue); boolean isFresh = (removeCommitId != commitId); Columns removeCols = new Columns(DominoConst.getColumnsAt(r, removeStartId)); if (!DominoConst.isDeleteVersion(removeValue) && !DominoConst.isDeleteVersion(prevValue)) { Columns prevCols = new Columns(DominoConst.getColumnsAt(r, prevStartId)); Get get = new Get(row); get.setTimeStamp(removeStartId); Result res = region.get(get, lockId); for (Column col : removeCols.cols) { if (prevCols.contains(col.family, col.qualifier)) { continue; // a newer value } // merge it byte[] value = res.getValue(col.family, col.qualifier); prevCols.add(col.family, col.qualifier); commit.add(col.family, col.qualifier, prevStartId, value); } commit.add( DominoConst.INNER_FAMILY, DominoConst.COLUMNS_COL, prevStartId, prevCols.toByteArray()); } if (isFresh) { commit.add( new KeyValue( row, DominoConst.INNER_FAMILY, DominoConst.VERSION_COL, removeCommitId, KeyValue.Type.Delete)); } commit.add( new KeyValue( row, DominoConst.INNER_FAMILY, DominoConst.COLUMNS_COL, removeStartId, KeyValue.Type.Delete)); for (Column col : removeCols.cols) { commit.add(new KeyValue(row, col.family, col.qualifier, removeStartId, KeyValue.Type.Delete)); } return isFresh; }
@SuppressWarnings("deprecation") @Override public Result get(Get get, Integer lockId) throws IOException { return region.get(get, lockId); }
/** * Use PreIncrement hook of BaseRegionObserver to overcome deficiencies in Increment * implementation (HBASE-10254): 1) Lack of recognition and identification of when the key value * to increment doesn't exist 2) Lack of the ability to set the timestamp of the updated key * value. Works the same as existing region.increment(), except assumes there is a single column * to increment and uses Phoenix LONG encoding. * * @author jtaylor * @since 3.0.0 */ @Override public Result preIncrement( final ObserverContext<RegionCoprocessorEnvironment> e, final Increment increment) throws IOException { RegionCoprocessorEnvironment env = e.getEnvironment(); // We need to set this to prevent region.increment from being called e.bypass(); e.complete(); HRegion region = env.getRegion(); byte[] row = increment.getRow(); TimeRange tr = increment.getTimeRange(); region.startRegionOperation(); try { Integer lid = region.getLock(null, row, true); try { long maxTimestamp = tr.getMax(); if (maxTimestamp == HConstants.LATEST_TIMESTAMP) { maxTimestamp = EnvironmentEdgeManager.currentTimeMillis(); tr = new TimeRange(tr.getMin(), maxTimestamp); } Get get = new Get(row); get.setTimeRange(tr.getMin(), tr.getMax()); for (Map.Entry<byte[], NavigableMap<byte[], Long>> entry : increment.getFamilyMap().entrySet()) { byte[] cf = entry.getKey(); for (byte[] cq : entry.getValue().keySet()) { get.addColumn(cf, cq); } } Result result = region.get(get); if (result.isEmpty()) { return getErrorResult( row, maxTimestamp, SQLExceptionCode.SEQUENCE_UNDEFINED.getErrorCode()); } KeyValue currentValueKV = Sequence.getCurrentValueKV(result); KeyValue incrementByKV = Sequence.getIncrementByKV(result); KeyValue cacheSizeKV = Sequence.getCacheSizeKV(result); long value = PDataType.LONG .getCodec() .decodeLong(currentValueKV.getBuffer(), currentValueKV.getValueOffset(), null); long incrementBy = PDataType.LONG .getCodec() .decodeLong(incrementByKV.getBuffer(), incrementByKV.getValueOffset(), null); int cacheSize = PDataType.INTEGER .getCodec() .decodeInt(cacheSizeKV.getBuffer(), cacheSizeKV.getValueOffset(), null); value += incrementBy * cacheSize; byte[] valueBuffer = new byte[PDataType.LONG.getByteSize()]; PDataType.LONG.getCodec().encodeLong(value, valueBuffer, 0); Put put = new Put(row, currentValueKV.getTimestamp()); // Hold timestamp constant for sequences, so that clients always only see the latest value // regardless of when they connect. KeyValue newCurrentValueKV = KeyValueUtil.newKeyValue( row, currentValueKV.getFamily(), currentValueKV.getQualifier(), currentValueKV.getTimestamp(), valueBuffer); put.add(newCurrentValueKV); @SuppressWarnings("unchecked") Pair<Mutation, Integer>[] mutations = new Pair[1]; mutations[0] = new Pair<Mutation, Integer>(put, lid); region.batchMutate(mutations); return Sequence.replaceCurrentValueKV(result, newCurrentValueKV); } finally { region.releaseRowLock(lid); } } catch (Throwable t) { ServerUtil.throwIOException("Increment of sequence " + Bytes.toStringBinary(row), t); return null; // Impossible } finally { region.closeRegionOperation(); } }
/** * Override the preAppend for checkAndPut and checkAndDelete, as we need the ability to a) set the * TimeRange for the Get being done and b) return something back to the client to indicate * success/failure */ @SuppressWarnings("deprecation") @Override public Result preAppend( final ObserverContext<RegionCoprocessorEnvironment> e, final Append append) throws IOException { byte[] opBuf = append.getAttribute(OPERATION_ATTRIB); if (opBuf == null) { return null; } Op op = Op.values()[opBuf[0]]; long clientTimestamp = HConstants.LATEST_TIMESTAMP; byte[] clientTimestampBuf = append.getAttribute(MAX_TIMERANGE_ATTRIB); if (clientTimestampBuf != null) { clientTimestamp = Bytes.toLong(clientTimestampBuf); } boolean hadClientTimestamp = (clientTimestamp != HConstants.LATEST_TIMESTAMP); if (hadClientTimestamp) { // Prevent race condition of creating two sequences at the same timestamp // by looking for a sequence at or after the timestamp at which it'll be // created. if (op == Op.CREATE_SEQUENCE) { clientTimestamp++; } } else { clientTimestamp = EnvironmentEdgeManager.currentTimeMillis(); clientTimestampBuf = Bytes.toBytes(clientTimestamp); } RegionCoprocessorEnvironment env = e.getEnvironment(); // We need to set this to prevent region.append from being called e.bypass(); e.complete(); HRegion region = env.getRegion(); byte[] row = append.getRow(); region.startRegionOperation(); try { Integer lid = region.getLock(null, row, true); try { KeyValue keyValue = append.getFamilyMap().values().iterator().next().iterator().next(); byte[] family = keyValue.getFamily(); byte[] qualifier = keyValue.getQualifier(); Get get = new Get(row); get.setTimeRange(MetaDataProtocol.MIN_TABLE_TIMESTAMP, clientTimestamp); get.addColumn(family, qualifier); Result result = region.get(get); if (result.isEmpty()) { if (op == Op.DROP_SEQUENCE || op == Op.RESET_SEQUENCE) { return getErrorResult( row, clientTimestamp, SQLExceptionCode.SEQUENCE_UNDEFINED.getErrorCode()); } } else { if (op == Op.CREATE_SEQUENCE) { return getErrorResult( row, clientTimestamp, SQLExceptionCode.SEQUENCE_ALREADY_EXIST.getErrorCode()); } } Mutation m = null; switch (op) { case RESET_SEQUENCE: KeyValue currentValueKV = result.raw()[0]; long expectedValue = PDataType.LONG .getCodec() .decodeLong(append.getAttribute(CURRENT_VALUE_ATTRIB), 0, null); long value = PDataType.LONG .getCodec() .decodeLong(currentValueKV.getBuffer(), currentValueKV.getValueOffset(), null); // Timestamp should match exactly, or we may have the wrong sequence if (expectedValue != value || currentValueKV.getTimestamp() != clientTimestamp) { return new Result( Collections.singletonList( KeyValueUtil.newKeyValue( row, PhoenixDatabaseMetaData.SEQUENCE_FAMILY_BYTES, QueryConstants.EMPTY_COLUMN_BYTES, currentValueKV.getTimestamp(), ByteUtil.EMPTY_BYTE_ARRAY))); } m = new Put(row, currentValueKV.getTimestamp()); m.getFamilyMap().putAll(append.getFamilyMap()); break; case DROP_SEQUENCE: m = new Delete(row, clientTimestamp, null); break; case CREATE_SEQUENCE: m = new Put(row, clientTimestamp); m.getFamilyMap().putAll(append.getFamilyMap()); break; } if (!hadClientTimestamp) { for (List<KeyValue> kvs : m.getFamilyMap().values()) { for (KeyValue kv : kvs) { kv.updateLatestStamp(clientTimestampBuf); } } } @SuppressWarnings("unchecked") Pair<Mutation, Integer>[] mutations = new Pair[1]; mutations[0] = new Pair<Mutation, Integer>(m, lid); region.batchMutate(mutations); long serverTimestamp = MetaDataUtil.getClientTimeStamp(m); // Return result with single KeyValue. The only piece of information // the client cares about is the timestamp, which is the timestamp of // when the mutation was actually performed (useful in the case of . return new Result( Collections.singletonList( KeyValueUtil.newKeyValue( row, PhoenixDatabaseMetaData.SEQUENCE_FAMILY_BYTES, QueryConstants.EMPTY_COLUMN_BYTES, serverTimestamp, SUCCESS_VALUE))); } finally { region.releaseRowLock(lid); } } catch (Throwable t) { ServerUtil.throwIOException("Increment of sequence " + Bytes.toStringBinary(row), t); return null; // Impossible } finally { region.closeRegionOperation(); } }