private void executeAndVerifyCas(CassandraClient client, Map<String, Pair<byte[], byte[]>> casMap) { CqlQuery casQueryBuffer = CassandraTimestampUtils.constructCheckAndSetMultipleQuery(casMap); CqlResult casResult = executeQueryUnchecked(client, casQueryBuffer); CassandraTimestampUtils.verifyCompatible(casResult, casMap); }
private BoundData getCurrentBoundData(CassandraClient client) { checkTimestampTableExists(); CqlQuery selectQuery = CassandraTimestampUtils.constructSelectFromTimestampTableQuery(); CqlResult existingData = executeQueryUnchecked(client, selectQuery); Map<String, byte[]> columnarResults = CassandraTimestampUtils.getValuesFromSelectionResult(existingData); return ImmutableBoundData.builder() .bound(columnarResults.get(CassandraTimestampUtils.ROW_AND_COLUMN_NAME)) .backupBound(columnarResults.get(CassandraTimestampUtils.BACKUP_COLUMN_NAME)) .build(); }
private BoundReadability getReadability(BoundData boundData) { boolean boundReadable = boundData.bound() == null || CassandraTimestampUtils.isValidTimestampData(boundData.bound()); boolean backupBoundReadable = CassandraTimestampUtils.isValidTimestampData(boundData.backupBound()); if (boundReadable) { return backupBoundReadable ? BoundReadability.BOTH : BoundReadability.BOUND; } return backupBoundReadable ? BoundReadability.BACKUP : BoundReadability.NEITHER; }
private static String constructCheckAndSetQuery(String columnName, byte[] expected, byte[] target) { Preconditions.checkState(target != null, "Should not CAS to a null target!"); if (expected == null) { return constructInsertIfNotExistsQuery(columnName, target); } return constructUpdateIfEqualQuery(columnName, expected, target); }
private static String constructUpdateIfEqualQuery(String columnName, byte[] expected, byte[] target) { return String.format( "UPDATE %s SET value=%s WHERE key=%s AND column1=%s AND column2=%s IF value=%s;", wrapInQuotes(AtlasDbConstants.TIMESTAMP_TABLE.getQualifiedName()), encodeCassandraHexBytes(target), ROW_AND_COLUMN_NAME_HEX_STRING, encodeCassandraHexString(columnName), CASSANDRA_TIMESTAMP, encodeCassandraHexBytes(expected)); }
@Test public void unappliedResultIsIncompatibleIfWeHaveMissingRows() { CqlResult mockResult = createMockCqlResult( ImmutableList.of( createMockCqlRow(buildUnappliedColumnList(COLUMN_BYTES_1, VALUE_1)))); assertThatThrownBy(() -> CassandraTimestampUtils.verifyCompatible(mockResult, CAS_MAP_TWO_COLUMNS)) .isInstanceOf(IllegalStateException.class); }
@Test public void checkAndSetThrowsIfTryingToSetToNull() { assertThatThrownBy(() -> CassandraTimestampUtils.constructCheckAndSetMultipleQuery( ImmutableMap.of(COLUMN_NAME_1, Pair.create(VALUE_1, null)))) .isInstanceOf(IllegalStateException.class); }
private static Set<Incongruency> getIncongruencies(CqlResult casResult, Map<String, Pair<byte[], byte[]>> casMap) { Map<String, byte[]> relevantCassandraState = getRelevantCassandraState(casResult, casMap); return casMap.entrySet().stream() .filter(entry -> !Arrays.equals(relevantCassandraState.get(entry.getKey()), entry.getValue().getRhSide())) .map(entry -> createIncongruency(relevantCassandraState, entry.getKey(), entry.getValue().getRhSide())) .collect(Collectors.toSet()); }
@Test public void canGetSelectQuery() { CqlQuery query = CassandraTimestampUtils.constructSelectFromTimestampTableQuery(); assertThat(query.toString()).isEqualTo("SELECT column1, value FROM \"_timestamp\" WHERE key=0x7473;"); }
@Test public void canGetValuesFromSelectionResult() { List<Column> columnList1 = buildKeyValueColumnList(KEY_1, COLUMN_BYTES_1, VALUE_1); List<Column> columnList2 = buildKeyValueColumnList(KEY_2, COLUMN_BYTES_2, VALUE_2); CqlResult mockResult = createMockCqlResult( ImmutableList.of( createMockCqlRow(columnList1), createMockCqlRow(columnList2))); assertThat(CassandraTimestampUtils.getValuesFromSelectionResult(mockResult)) .isEqualTo(ImmutableMap.of(COLUMN_NAME_1, VALUE_1, COLUMN_NAME_2, VALUE_2)); }
private static String encodeCassandraHexString(String string) { return encodeCassandraHexBytes(PtBytes.toBytes(string)); }
public static CqlQuery constructCheckAndSetMultipleQuery(Map<String, Pair<byte[], byte[]>> checkAndSetRequest) { StringBuilder builder = new StringBuilder(); builder.append("BEGIN UNLOGGED BATCH\n"); // Safe, because all updates are on the same partition key // Safe, because ordering does not apply in batches checkAndSetRequest.forEach((columnName, value) -> { byte[] expected = value.getLhSide(); byte[] target = value.getRhSide(); builder.append(constructCheckAndSetQuery(columnName, expected, target)); }); builder.append("APPLY BATCH;"); // This looks awkward. However, we know that all expressions in this String pertain to timestamps and known // table references, hence this is actually safe. Doing this quickly owing to priority. // TODO (jkong): Build up a query by passing around legitimate formats and args. return CqlQuery.builder() .safeQueryFormat(builder.toString()) .build(); }
private static String constructInsertIfNotExistsQuery(String columnName, byte[] target) { return String.format( "INSERT INTO %s (key, column1, column2, value) VALUES (%s, %s, %s, %s) IF NOT EXISTS;", wrapInQuotes(AtlasDbConstants.TIMESTAMP_TABLE.getQualifiedName()), ROW_AND_COLUMN_NAME_HEX_STRING, encodeCassandraHexString(columnName), CASSANDRA_TIMESTAMP, encodeCassandraHexBytes(target)); }
@Test public void unappliedResultIsIncompatibleIfTheStateOfTheWorldMatchesExpecteds() { CqlResult mockResult = createMockCqlResult( ImmutableList.of( createMockCqlRow(buildUnappliedColumnList(COLUMN_BYTES_1, EMPTY_BYTE_ARRAY)), createMockCqlRow(buildUnappliedColumnList(COLUMN_BYTES_2, EMPTY_BYTE_ARRAY)))); assertThatThrownBy(() -> CassandraTimestampUtils.verifyCompatible(mockResult, CAS_MAP_TWO_COLUMNS)) .isInstanceOf(IllegalStateException.class); }
@Test public void checkAndSetIsInsertIfNotExistsIfExpectedIsNull() { CqlQuery query = CassandraTimestampUtils.constructCheckAndSetMultipleQuery( ImmutableMap.of(COLUMN_NAME_1, Pair.create(null, VALUE_1))); assertThat(query.toString()).contains("INSERT").contains("IF NOT EXISTS;"); }
private static String constructCheckAndSetQuery(String columnName, byte[] expected, byte[] target) { Preconditions.checkState(target != null, "Should not CAS to a null target!"); if (expected == null) { return constructInsertIfNotExistsQuery(columnName, target); } return constructUpdateIfEqualQuery(columnName, expected, target); }
private static Set<Incongruency> getIncongruencies(CqlResult casResult, Map<String, Pair<byte[], byte[]>> casMap) { Map<String, byte[]> relevantCassandraState = getRelevantCassandraState(casResult, casMap); return casMap.entrySet().stream() .filter(entry -> !Arrays.equals(relevantCassandraState.get(entry.getKey()), entry.getValue().getRhSide())) .map(entry -> createIncongruency(relevantCassandraState, entry.getKey(), entry.getValue().getRhSide())) .collect(Collectors.toSet()); }
private static String encodeCassandraHexString(String string) { return encodeCassandraHexBytes(PtBytes.toBytes(string)); }
public static CqlQuery constructCheckAndSetMultipleQuery(Map<String, Pair<byte[], byte[]>> checkAndSetRequest) { StringBuilder builder = new StringBuilder(); builder.append("BEGIN UNLOGGED BATCH\n"); // Safe, because all updates are on the same partition key // Safe, because ordering does not apply in batches checkAndSetRequest.forEach((columnName, value) -> { byte[] expected = value.getLhSide(); byte[] target = value.getRhSide(); builder.append(constructCheckAndSetQuery(columnName, expected, target)); }); builder.append("APPLY BATCH;"); // This looks awkward. However, we know that all expressions in this String pertain to timestamps and known // table references, hence this is actually safe. Doing this quickly owing to priority. // TODO (jkong): Build up a query by passing around legitimate formats and args. return CqlQuery.builder() .safeQueryFormat(builder.toString()) .build(); }
private static String constructUpdateIfEqualQuery(String columnName, byte[] expected, byte[] target) { return String.format( "UPDATE %s SET value=%s WHERE key=%s AND column1=%s AND column2=%s IF value=%s;", wrapInQuotes(AtlasDbConstants.TIMESTAMP_TABLE.getQualifiedName()), encodeCassandraHexBytes(target), ROW_AND_COLUMN_NAME_HEX_STRING, encodeCassandraHexString(columnName), CASSANDRA_TIMESTAMP, encodeCassandraHexBytes(expected)); }