Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -344,17 +344,17 @@
<limit>
<counter>COMPLEXITY</counter>
<value>COVEREDRATIO</value>
<minimum>0.42</minimum>
<minimum>0.43</minimum>
</limit>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>53%</minimum>
<minimum>53.5%</minimum>
</limit>
<limit>
<counter>LINE</counter>
<value>MISSEDCOUNT</value>
<maximum>1490</maximum>
<maximum>1480</maximum>
</limit>
</limits>
</rule>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

import com.datastax.cdm.cql.CommonMocks;
import com.datastax.cdm.properties.KnownProperties;
import com.datastax.oss.driver.api.core.cql.*;
import com.datastax.oss.driver.api.core.type.DataTypes;

Expand Down Expand Up @@ -343,4 +345,160 @@ public void bind_withNonEmptyMap_shouldCallSet() {
verify(boundStatement, atLeastOnce()).set(anyInt(), any(), any(Class.class));
}

// ---- bind(): input validation and bind-index ordering ----

/**
* When TTL columns are enabled but no TTL value is supplied, bind() rejects the input with a RuntimeException
* naming the missing TTL property.
*/
@Test
public void bind_checkBindInputsThrowsWhenTtlMissing() {
when(writetimeTTLFeature.isEnabled()).thenReturn(true);
when(writetimeTTLFeature.hasTTLColumns()).thenReturn(true);
targetInsertStatement = new TargetInsertStatement(propertyHelper, targetSession);

RuntimeException ex = assertThrows(RuntimeException.class,
() -> targetInsertStatement.bind(originRow, targetRow, null, null, null, null));
assertEquals(KnownProperties.ORIGIN_TTL_NAMES + " specified, but no TTL value was provided", ex.getMessage());
}

/**
* When writetime columns are enabled but no writetime value is supplied, bind() rejects the input with a
* RuntimeException naming the missing writetime property.
*/
@Test
public void bind_checkBindInputsThrowsWhenWriteTimeMissing() {
when(writetimeTTLFeature.isEnabled()).thenReturn(true);
when(writetimeTTLFeature.hasWritetimeColumns()).thenReturn(true);
targetInsertStatement = new TargetInsertStatement(propertyHelper, targetSession);

RuntimeException ex = assertThrows(RuntimeException.class,
() -> targetInsertStatement.bind(originRow, targetRow, null, null, null, null));
assertEquals(KnownProperties.ORIGIN_WRITETIME_NAMES + " specified, but no WriteTime value was provided",
ex.getMessage());
}

/**
* bind() binds every target column exactly once at sequential indices 0, 1, 2, ...
*/
@Test
public void bind_bindsAllColumnsAtSequentialIndices() {
targetInsertStatement = new TargetInsertStatement(propertyHelper, targetSession);

BoundStatement result = targetInsertStatement.bind(originRow, targetRow, null, null, null, null);
assertNotNull(result);

ArgumentCaptor<Integer> indexCaptor = ArgumentCaptor.forClass(Integer.class);
verify(boundStatement, times(targetColumnNames.size())).set(indexCaptor.capture(), any(), any(Class.class));

List<Integer> capturedIndices = indexCaptor.getAllValues();
for (int i = 0; i < capturedIndices.size(); i++) {
assertEquals(i, capturedIndices.get(i).intValue());
}
}

/**
* Constant columns are not bound: bind() sets only the non-constant columns, at sequential indices starting from
* zero.
*/
@Test
public void bind_skipsConstantColumns() {
commonSetup(false, true, false);
targetInsertStatement = new TargetInsertStatement(propertyHelper, targetSession);

ArgumentCaptor<Integer> indexCaptor = ArgumentCaptor.forClass(Integer.class);

targetInsertStatement.bind(originRow, targetRow, null, null, null, null);

// Constant columns are skipped; only the remaining columns are bound.
int expectedBindCount = targetColumnNames.size() - constantColumns.size();
verify(boundStatement, times(expectedBindCount)).set(indexCaptor.capture(), any(), any(Class.class));

List<Integer> capturedIndices = indexCaptor.getAllValues();
for (int i = 0; i < capturedIndices.size(); i++) {
assertEquals(i, capturedIndices.get(i).intValue());
}
}

/**
* With an explode-map column present, bind() sets every target column at sequential indices starting from zero.
*/
@Test
public void bind_bindsExplodeMapColumnsAtSequentialIndices() {
commonSetup(true, false, false);
targetInsertStatement = new TargetInsertStatement(propertyHelper, targetSession);

ArgumentCaptor<Integer> indexCaptor = ArgumentCaptor.forClass(Integer.class);

targetInsertStatement.bind(originRow, targetRow, null, null, getSampleData(explodeMapKeyType),
getSampleData(explodeMapValueType));

verify(boundStatement, times(targetColumnNames.size())).set(indexCaptor.capture(), any(), any(Class.class));
List<Integer> capturedIndices = indexCaptor.getAllValues();
for (int i = 0; i < capturedIndices.size(); i++) {
assertEquals(i, capturedIndices.get(i).intValue());
}
}

/**
* With both TTL and writetime enabled, bind() binds each target column at sequential indices and then appends the
* TTL value (as an Integer) and the writetime value (as a Long) at the two trailing indices, in that order.
*/
@Test
public void bind_appendsTtlAndWritetimeAtTrailingIndicesWithTypes() {
when(writetimeTTLFeature.isEnabled()).thenReturn(true);
when(writetimeTTLFeature.hasTTLColumns()).thenReturn(true);
when(writetimeTTLFeature.hasWritetimeColumns()).thenReturn(true);
targetInsertStatement = new TargetInsertStatement(propertyHelper, targetSession);

ArgumentCaptor<Integer> indexCaptor = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Object> valueCaptor = ArgumentCaptor.forClass(Object.class);
ArgumentCaptor<Class> classCaptor = ArgumentCaptor.forClass(Class.class);

Integer ttl = 7200;
Long writeTime = 42L;

BoundStatement result = targetInsertStatement.bind(originRow, targetRow, ttl, writeTime, null, null);
assertNotNull(result);

verify(boundStatement, times(targetColumnNames.size() + 2)).set(indexCaptor.capture(), valueCaptor.capture(),
classCaptor.capture());

List<Integer> allIndices = indexCaptor.getAllValues();
List<Object> allValues = valueCaptor.getAllValues();
List<Class> allClasses = classCaptor.getAllValues();

// Columns are bound at sequential indices.
for (int i = 0; i < targetColumnNames.size(); i++) {
assertEquals(i, allIndices.get(i).intValue());
}

// TTL is bound immediately after the columns, with its value and Integer type.
int ttlIdx = targetColumnNames.size();
assertEquals(ttlIdx, allIndices.get(ttlIdx).intValue());
assertEquals(ttl, allValues.get(ttlIdx));
assertEquals(Integer.class, allClasses.get(ttlIdx));

// WriteTime is bound at the next index, with its value and Long type.
int wtIdx = targetColumnNames.size() + 1;
assertEquals(wtIdx, allIndices.get(wtIdx).intValue());
assertEquals(writeTime, allValues.get(wtIdx));
assertEquals(Long.class, allClasses.get(wtIdx));
}

/**
* When converting an origin value throws, bind() wraps the failure in a RuntimeException whose message identifies
* the value being bound.
*/
@Test
public void bind_exceptionWithNonNullOriginValue() {
when(targetTable.getCorrespondingIndex(0)).thenReturn(0);
when(originTable.getAndConvertData(0, originRow)).thenThrow(new RuntimeException("binding error"));

RuntimeException ex = assertThrows(RuntimeException.class, () -> targetInsertStatement.bind(originRow,
targetRow, 3600, 123456789L, getSampleData(explodeMapKeyType), getSampleData(explodeMapValueType)));

assertTrue(ex.getMessage().contains("Error trying to bind value"));
}

}