Skip to content

Commit 2744580

Browse files
committed
Add tests for COPY TO command
and fix tests of COPY FROM command
1 parent e53fb83 commit 2744580

9 files changed

+215
-17
lines changed

Diff for: src/main/java/com/ing/data/cassandra/jdbc/commands/CopyToCommandExecutor.java

+27-8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.ing.data.cassandra.jdbc.commands;
1717

1818
import com.datastax.oss.driver.api.core.cql.ResultSet;
19+
import com.datastax.oss.driver.api.core.data.TupleValue;
1920
import com.ing.data.cassandra.jdbc.CassandraConnection;
2021
import com.ing.data.cassandra.jdbc.CassandraStatement;
2122
import com.opencsv.CSVWriterBuilder;
@@ -167,6 +168,7 @@ public ResultSet execute(final CassandraStatement statement, final String cql) t
167168

168169
final Path targetPath = Paths.get(translateFilename(this.target));
169170
ICSVWriter csvWriter = null;
171+
long exportedRows = 0;
170172
try {
171173
final CSVWriterBuilder builder = new CSVWriterBuilder(
172174
new OutputStreamWriter(
@@ -183,6 +185,9 @@ public ResultSet execute(final CassandraStatement statement, final String cql) t
183185
.build();
184186

185187
final boolean includeHeaders = Boolean.parseBoolean(this.options.getProperty(OPTION_HEADER));
188+
if (includeHeaders) {
189+
exportedRows -= 1;
190+
}
186191
csvWriter.writeAll(rs, includeHeaders);
187192
} catch (final IOException e) {
188193
throw new SQLException(String.format(CANNOT_WRITE_CSV_FILE, this.target, e.getMessage()), e);
@@ -192,9 +197,8 @@ public ResultSet execute(final CassandraStatement statement, final String cql) t
192197
IOUtils.closeQuietly(csvWriter);
193198
}
194199

195-
long exportedRows = -1;
196200
try (Stream<String> csvLines = Files.lines(targetPath)) {
197-
exportedRows = csvLines.count();
201+
exportedRows += csvLines.count();
198202
} catch (final IOException e) {
199203
LOG.warn("Failed to read exported CSV file to count exportedRows.");
200204
}
@@ -260,9 +264,6 @@ private String getColumnValue(final java.sql.ResultSet rs, final int colType, fi
260264
case Types.CLOB:
261265
value = handleClob(rs, colIndex);
262266
break;
263-
case Types.BIGINT:
264-
value = applyFormatter(this.integerFormat, rs.getBigDecimal(colIndex));
265-
break;
266267
case Types.DECIMAL:
267268
case Types.REAL:
268269
case Types.NUMERIC:
@@ -274,10 +275,17 @@ private String getColumnValue(final java.sql.ResultSet rs, final int colType, fi
274275
case Types.FLOAT:
275276
value = applyFormatter(this.floatingPointFormat, rs.getFloat(colIndex));
276277
break;
278+
case Types.BIGINT:
279+
value = applyFormatter(this.integerFormat, rs.getLong(colIndex));
280+
break;
277281
case Types.INTEGER:
282+
value = applyFormatter(this.integerFormat, rs.getInt(colIndex));
283+
break;
278284
case Types.TINYINT:
285+
value = applyFormatter(this.integerFormat, rs.getByte(colIndex));
286+
break;
279287
case Types.SMALLINT:
280-
value = applyFormatter(this.integerFormat, rs.getInt(colIndex));
288+
value = applyFormatter(this.integerFormat, rs.getShort(colIndex));
281289
break;
282290
case Types.DATE:
283291
value = handleDate(rs, colIndex, dateFormatString);
@@ -298,9 +306,20 @@ private String getColumnValue(final java.sql.ResultSet rs, final int colType, fi
298306
case Types.CHAR:
299307
value = handleVarChar(rs, colIndex, trim);
300308
break;
309+
case Types.BINARY:
310+
case Types.VARBINARY:
311+
case Types.LONGVARBINARY:
312+
value = new String(rs.getBytes(colIndex), StandardCharsets.UTF_8);
313+
break;
301314
default:
302-
// This takes care of Types.BIT, Types.JAVA_OBJECT, and anything unknown.
303-
value = Objects.toString(rs.getObject(colIndex), EMPTY);
315+
// This takes care of any types not previously handled.
316+
// For tuples, use the method "getFormattedContents()" to get the string representation of the
317+
// tuple.
318+
final Object objValue = rs.getObject(colIndex);
319+
if (TupleValue.class.isAssignableFrom(objValue.getClass())) {
320+
return ((TupleValue) objValue).getFormattedContents();
321+
}
322+
value = Objects.toString(objValue, EMPTY);
304323
}
305324

306325
if (rs.wasNull() || value == null) {

Diff for: src/test/java/com/ing/data/cassandra/jdbc/commands/CopyFromCommandTest.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
import java.sql.Statement;
4040
import java.sql.Time;
4141
import java.sql.Timestamp;
42-
import java.time.LocalDateTime;
42+
import java.time.OffsetDateTime;
43+
import java.time.ZoneId;
4344
import java.util.Arrays;
4445
import java.util.HashMap;
4546
import java.util.HashSet;
@@ -209,6 +210,7 @@ void givenTableWithAllTypesAndOriginFile_whenExecuteCopyFromCommand_executeExpec
209210
final ResultSet resultSet = executeCopyFromCommand(COPY_CMD_TEST_ALL_TYPES_TABLE_NAME,
210211
"test_all_types_import.csv", "WITH HEADER=true AND DELIMITER=;");
211212
assertCommandResultSet(resultSet, true, 1, 1, 0);
213+
212214
assertRowValues(sqlConnection, COPY_CMD_TEST_ALL_TYPES_TABLE,
213215
"key1", // text
214216
"abc123", // ascii
@@ -235,7 +237,9 @@ void givenTableWithAllTypesAndOriginFile_whenExecuteCopyFromCommand_executeExpec
235237
}},
236238
Time.valueOf("12:30:45"), // time
237239
Timestamp.valueOf( // timestamp
238-
LocalDateTime.parse("2023-03-25T13:30:45.789")), // fixme
240+
OffsetDateTime.parse("2023-03-25T12:30:45.789Z")
241+
.atZoneSameInstant(ZoneId.systemDefault())
242+
.toLocalDateTime()),
239243
UUID.fromString("1f4b6fe0-f12b-11ef-8b6b-fdf7025e383c"), // timeuuid
240244
(byte) 12, // tinyint
241245
new DefaultTupleValue( // tuple

Diff for: src/test/java/com/ing/data/cassandra/jdbc/commands/CopyToCommandTest.java

+168-7
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,191 @@
1515

1616
package com.ing.data.cassandra.jdbc.commands;
1717

18+
import com.datastax.oss.driver.api.core.data.CqlDuration;
19+
import com.datastax.oss.driver.api.core.data.CqlVector;
20+
import com.datastax.oss.driver.api.querybuilder.QueryBuilder;
1821
import com.ing.data.cassandra.jdbc.UsingCassandraContainerTest;
22+
import org.apache.commons.collections4.CollectionUtils;
23+
import org.approvaltests.Approvals;
24+
import org.approvaltests.core.Options;
1925
import org.junit.jupiter.api.BeforeAll;
2026
import org.junit.jupiter.api.Test;
27+
import org.junit.jupiter.params.ParameterizedTest;
28+
import org.junit.jupiter.params.provider.Arguments;
29+
import org.junit.jupiter.params.provider.MethodSource;
2130
import org.slf4j.Logger;
2231
import org.slf4j.LoggerFactory;
2332

33+
import java.io.File;
34+
import java.math.BigDecimal;
35+
import java.math.BigInteger;
36+
import java.sql.ResultSet;
2437
import java.sql.SQLException;
38+
import java.sql.Statement;
39+
import java.util.Arrays;
40+
import java.util.HashMap;
41+
import java.util.HashSet;
42+
import java.util.List;
43+
import java.util.Optional;
44+
import java.util.UUID;
45+
import java.util.stream.Stream;
2546

47+
import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.function;
48+
import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal;
49+
import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.tuple;
50+
import static com.ing.data.cassandra.jdbc.testing.CopyCommandsTestUtils.COPY_CMD_TEST_ALL_TYPES_TABLE_NAME;
51+
import static com.ing.data.cassandra.jdbc.testing.CopyCommandsTestUtils.COPY_CMD_TEST_KEYSPACE;
52+
import static com.ing.data.cassandra.jdbc.testing.CopyCommandsTestUtils.COPY_CMD_TEST_PARTIAL_TABLE_NAME;
53+
import static com.ing.data.cassandra.jdbc.testing.CopyCommandsTestUtils.COPY_CMD_TEST_TABLE_NAME;
54+
import static com.ing.data.cassandra.jdbc.testing.CopyCommandsTestUtils.assertCommandResultSet;
55+
import static org.apache.commons.lang3.StringUtils.EMPTY;
56+
import static org.hamcrest.MatcherAssert.assertThat;
57+
import static org.hamcrest.Matchers.containsString;
2658
import static org.junit.jupiter.api.Assertions.assertNotNull;
59+
import static org.junit.jupiter.api.Assertions.assertThrows;
2760

2861
public class CopyToCommandTest extends UsingCassandraContainerTest {
2962

30-
private static final Logger LOG = LoggerFactory.getLogger(CopyToCommandTest.class);
31-
private static final String KEYSPACE = "test_keyspace";
32-
3363
@BeforeAll
3464
static void finalizeSetUpTests() throws Exception {
35-
initConnection(KEYSPACE, "localdatacenter=datacenter1");
65+
initConnection(COPY_CMD_TEST_KEYSPACE, "localdatacenter=datacenter1");
66+
67+
// Populate tables with data to export.
68+
sqlConnection.createStatement().execute(QueryBuilder.insertInto(COPY_CMD_TEST_TABLE_NAME)
69+
.value("table_key", literal("key1"))
70+
.value("bool_val", literal(true))
71+
.value("decimal_val", literal(1.5))
72+
.asCql()
73+
);
74+
sqlConnection.createStatement().execute(QueryBuilder.insertInto(COPY_CMD_TEST_TABLE_NAME)
75+
.value("table_key", literal("key2"))
76+
.value("bool_val", literal(false))
77+
.value("decimal_val", literal(4200))
78+
.asCql()
79+
);
80+
sqlConnection.createStatement().execute(QueryBuilder.insertInto(COPY_CMD_TEST_TABLE_NAME)
81+
.value("table_key", literal("key3"))
82+
.value("bool_val", literal(false))
83+
.value("decimal_val", literal(null))
84+
.asCql()
85+
);
86+
87+
sqlConnection.createStatement().execute(QueryBuilder.insertInto(COPY_CMD_TEST_PARTIAL_TABLE_NAME)
88+
.value("table_key", literal("key1"))
89+
.value("int_val", literal(42))
90+
.value("str_val", literal("A string containing a character \" to escape"))
91+
.asCql()
92+
);
93+
94+
sqlConnection.createStatement().execute(QueryBuilder.insertInto(COPY_CMD_TEST_ALL_TYPES_TABLE_NAME)
95+
.value("table_key", literal("key1"))
96+
.value("ascii_val", literal("abc123"))
97+
.value("bigint_val", literal(12345678900000L))
98+
.value("blob_val", function("textAsBlob", literal("this is a blob")))
99+
.value("bool_val", literal(true))
100+
.value("date_val", literal("2023-03-25"))
101+
.value("decimal_val", literal(new BigDecimal("18.97")))
102+
.value("double_val", literal(2345.6d))
103+
.value("duration_val", literal(CqlDuration.from("3h15m")))
104+
.value("float_val", literal(21.3f))
105+
.value("inet_val", literal("127.0.0.1"))
106+
.value("int_val", literal(98))
107+
.value("list_val", literal(Arrays.asList(4, 6, 10)))
108+
.value("map_val", literal(new HashMap<Integer, String>(){{
109+
put(3, "three");
110+
put(8, "eight");
111+
}}))
112+
.value("smallint_val", literal(2))
113+
.value("set_val", literal(new HashSet<Integer>(){{
114+
add(2);
115+
add(3);
116+
add(5);
117+
}}))
118+
.value("time_val", literal("12:30:45.678"))
119+
.value("ts_val", literal("2023-03-25T12:30:45.789"))
120+
.value("timeuuid_val", literal(UUID.fromString("1f4b6fe0-f12b-11ef-8b6b-fdf7025e383c")))
121+
.value("tinyint_val", literal(12))
122+
.value("tuple_val", tuple(literal(5), literal("five")))
123+
.value("uuid_val", literal(UUID.fromString("f26f86e6-d8ef-4d30-9d9e-4027d21e02a9")))
124+
.value("varchar_val", literal("varchar text"))
125+
.value("varint_val", literal(new BigInteger("4321")))
126+
.value("vector_val", literal(CqlVector.newInstance(1, 3, 8, 6)))
127+
.asCql()
128+
);
36129
}
37130

38-
@Test
39-
void givenTableAndTargetFile_whenExecuteCopyToCommand_generateExpectedCsvFile() throws SQLException {
131+
private ResultSet executeCopyToCommand(final String sourceTable, final List<String> sourceColumns,
132+
final String targetCsvFile, final String options) throws SQLException {
40133
assertNotNull(sqlConnection);
41-
// TODO sqlConnection.createStatement().execute("COPY cf_test1(keyname, t1ivalue) TO 'test_result.csv'");
134+
final Statement copyCmdStmt = sqlConnection.createStatement();
135+
136+
String formattedSourceColumns = EMPTY;
137+
if (CollectionUtils.isNotEmpty(sourceColumns)) {
138+
formattedSourceColumns = String.format("(%s)", String.join(",", sourceColumns));
139+
}
140+
141+
copyCmdStmt.execute(String.format("COPY %s%s TO '%s' %s", sourceTable, formattedSourceColumns,
142+
targetCsvFile, Optional.ofNullable(options).orElse(EMPTY)));
143+
return copyCmdStmt.getResultSet();
144+
}
145+
146+
private static Options approvalTestsParameterName(final String csvFile) {
147+
return Approvals.NAMES.withParameters(csvFile.replace(".csv", EMPTY));
148+
}
149+
150+
static Stream<Arguments> buildCopyToCommandVariableParameters() {
151+
return Stream.of(
152+
Arguments.of("test_default_options.csv", EMPTY),
153+
Arguments.of("test_with_header.csv", "WITH HEADER=true"),
154+
Arguments.of("test_with_format_options.csv",
155+
"WITH DELIMITER=| AND QUOTE=` AND DECIMALSEP=, AND THOUSANDSSEP=. AND NULLVAL=N/A")
156+
);
157+
}
158+
159+
@ParameterizedTest
160+
@MethodSource("buildCopyToCommandVariableParameters")
161+
void givenTableAndTargetFile_whenExecuteCopyToCommand_generateExpectedCsvFile(final String csvFile,
162+
final String options)
163+
throws SQLException {
164+
final ResultSet resultSet = executeCopyToCommand(COPY_CMD_TEST_TABLE_NAME, null, csvFile, options);
165+
assertCommandResultSet(resultSet, false, 3, 1);
166+
Approvals.verify(new File(csvFile), approvalTestsParameterName(csvFile));
167+
}
168+
169+
@Test
170+
void givenTableAndTargetFile_whenExecuteCopyToCommandWithEscapedChars_generateExpectedCsvFile()
171+
throws SQLException {
172+
final String targetCsvFile = "test_with_escape.csv";
173+
final ResultSet resultSet = executeCopyToCommand(COPY_CMD_TEST_PARTIAL_TABLE_NAME, null, targetCsvFile,
174+
"WITH ESCAPE=\"");
175+
assertCommandResultSet(resultSet, false, 1, 1);
176+
Approvals.verify(new File(targetCsvFile), approvalTestsParameterName(targetCsvFile));
177+
}
178+
179+
@Test
180+
void givenTableWithColumnsAndTargetFile_whenExecuteCopyToCommand_generateExpectedCsvFile() throws SQLException {
181+
final String targetCsvFile = "test_partial_export.csv";
182+
final ResultSet resultSet = executeCopyToCommand(COPY_CMD_TEST_PARTIAL_TABLE_NAME,
183+
Arrays.asList("table_key", "int_val"), targetCsvFile, EMPTY);
184+
assertCommandResultSet(resultSet, false, 1, 1);
185+
Approvals.verify(new File(targetCsvFile), approvalTestsParameterName(targetCsvFile));
186+
}
187+
188+
@Test
189+
void givenTableAndTargetFile_whenExecuteCopyToCommandUnsupportedOptions_throwException() throws SQLException {
190+
final SQLException sqlException = assertThrows(SQLException.class, () ->
191+
executeCopyToCommand(COPY_CMD_TEST_TABLE_NAME, null, "test_exception.csv", "WITH BADOPTION=1"));
192+
assertThat(sqlException.getMessage(),
193+
containsString("Command COPY used with unknown or unsupported options: [BADOPTION]"));
194+
}
195+
196+
@Test
197+
void givenTableWithAllTypesAndTargetFile_whenExecuteCopyFromCommand_executeExpectedStatements() throws Exception {
198+
final String targetCsvFile = "test_all_types_export.csv";
199+
final ResultSet resultSet = executeCopyToCommand(COPY_CMD_TEST_ALL_TYPES_TABLE_NAME, null, targetCsvFile,
200+
"WITH HEADER=true AND DELIMITER=;");
201+
assertCommandResultSet(resultSet, false, 1, 1);
202+
Approvals.verify(new File(targetCsvFile), approvalTestsParameterName(targetCsvFile));
42203
}
43204

44205
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"key1","42","A string containing a character "" to escape"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"key1","true","1.5"
2+
"key3","false","null"
3+
"key2","false","4200"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
`key1`|`true`|`1,5`
2+
`key3`|`false`|`N/A`
3+
`key2`|`false`|`4.200`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"table_key","bool_val","decimal_val"
2+
"key1","true","1.5"
3+
"key3","false","null"
4+
"key2","false","4200"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"table_key";"ascii_val";"bigint_val";"blob_val";"bool_val";"date_val";"decimal_val";"double_val";"duration_val";"float_val";"inet_val";"int_val";"list_val";"map_val";"set_val";"smallint_val";"time_val";"timeuuid_val";"tinyint_val";"ts_val";"tuple_val";"uuid_val";"varchar_val";"varint_val";"vector_val"
2+
"key1";"abc123";"12345678900000";"this is a blob";"true";"2023-03-25";"18.97";"2345.6";"3h15m";"21.3";"/127.0.0.1";"98";"[4, 6, 10]";"{8=eight, 3=three}";"[2, 3, 5]";"2";"12:30:45";"1f4b6fe0-f12b-11ef-8b6b-fdf7025e383c";"12";"2023-03-25 13:30:45+0100";"(5,'five')";"f26f86e6-d8ef-4d30-9d9e-4027d21e02a9";"varchar text";"4321";"[1, 3, 8, 6]"

0 commit comments

Comments
 (0)