Skip to content

Commit 58971f4

Browse files
committed
Mixedcase support for cassandra
1 parent 3c526ac commit 58971f4

File tree

12 files changed

+240
-25
lines changed

12 files changed

+240
-25
lines changed

presto-cassandra/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@
266266
<configuration>
267267
<excludes>
268268
<exclude>**/TestCassandraIntegrationSmokeTest.java</exclude>
269+
<exclude>**/TestCassandraIntergrationMixedCase.java</exclude>
269270
</excludes>
270271
</configuration>
271272
</plugin>
@@ -282,6 +283,7 @@
282283
<configuration>
283284
<includes>
284285
<include>**/TestCassandraIntegrationSmokeTest.java</include>
286+
<include>**/TestCassandraIntergrationMixedCase.java</include>
285287
</includes>
286288
</configuration>
287289
</plugin>

presto-cassandra/src/main/java/com/facebook/presto/cassandra/CassandraClientConfig.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public class CassandraClientConfig
7575
private String truststorePassword;
7676
private File keystorePath;
7777
private String keystorePassword;
78+
private boolean caseSensitiveNameMatchingEnabled;
7879

7980
@NotNull
8081
@Size(min = 1)
@@ -475,4 +476,16 @@ public CassandraClientConfig setTruststorePassword(String truststorePassword)
475476
this.truststorePassword = truststorePassword;
476477
return this;
477478
}
479+
480+
public boolean isCaseSensitiveNameMatchingEnabled()
481+
{
482+
return caseSensitiveNameMatchingEnabled;
483+
}
484+
485+
@Config("case-sensitive-name-matching")
486+
public CassandraClientConfig setCaseSensitiveNameMatchingEnabled(boolean caseSensitiveNameMatchingEnabled)
487+
{
488+
this.caseSensitiveNameMatchingEnabled = caseSensitiveNameMatchingEnabled;
489+
return this;
490+
}
478491
}

presto-cassandra/src/main/java/com/facebook/presto/cassandra/CassandraClientModule.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,6 @@ public static CassandraSession createCassandraSession(
155155
contactPoints.forEach(clusterBuilder::addContactPoint);
156156
return clusterBuilder.build();
157157
}),
158-
config.getNoHostAvailableRetryTimeout());
158+
config.getNoHostAvailableRetryTimeout(), config.isCaseSensitiveNameMatchingEnabled());
159159
}
160160
}

presto-cassandra/src/main/java/com/facebook/presto/cassandra/CassandraMetadata.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353

5454
import static com.facebook.presto.cassandra.CassandraType.toCassandraType;
5555
import static com.facebook.presto.cassandra.util.CassandraCqlUtils.cqlNameToSqlName;
56+
import static com.facebook.presto.cassandra.util.CassandraCqlUtils.validColumnName;
5657
import static com.facebook.presto.cassandra.util.CassandraCqlUtils.validSchemaName;
5758
import static com.facebook.presto.cassandra.util.CassandraCqlUtils.validTableName;
5859
import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED;
@@ -71,6 +72,7 @@ public class CassandraMetadata
7172
private final CassandraPartitionManager partitionManager;
7273
private final boolean allowDropTable;
7374
private final ProtocolVersion protocolVersion;
75+
private boolean caseSensitiveNameMatchingEnabled;
7476

7577
private final JsonCodec<List<ExtraColumnMetadata>> extraColumnMetadataCodec;
7678

@@ -88,13 +90,14 @@ public CassandraMetadata(
8890
this.allowDropTable = requireNonNull(config, "config is null").getAllowDropTable();
8991
this.extraColumnMetadataCodec = requireNonNull(extraColumnMetadataCodec, "extraColumnMetadataCodec is null");
9092
this.protocolVersion = requireNonNull(config, "config is null").getProtocolVersion();
93+
this.caseSensitiveNameMatchingEnabled = requireNonNull(config, "config is null").isCaseSensitiveNameMatchingEnabled();
9194
}
9295

9396
@Override
9497
public List<String> listSchemaNames(ConnectorSession session)
9598
{
9699
return cassandraSession.getCaseSensitiveSchemaNames().stream()
97-
.map(name -> name.toLowerCase(ENGLISH))
100+
.map(name -> normalizeIdentifier(session, name))
98101
.collect(toImmutableList());
99102
}
100103

@@ -139,7 +142,8 @@ public List<SchemaTableName> listTables(ConnectorSession session, String schemaN
139142
for (String schemaName : listSchemas(session, schemaNameOrNull)) {
140143
try {
141144
for (String tableName : cassandraSession.getCaseSensitiveTableNames(schemaName)) {
142-
tableNames.add(new SchemaTableName(schemaName, tableName.toLowerCase(ENGLISH)));
145+
String finalTableName = normalizeIdentifier(session, tableName);
146+
tableNames.add(new SchemaTableName(schemaName, finalTableName));
143147
}
144148
}
145149
catch (SchemaNotFoundException e) {
@@ -165,7 +169,9 @@ public Map<String, ColumnHandle> getColumnHandles(ConnectorSession session, Conn
165169
CassandraTable table = cassandraSession.getTable(getTableName(tableHandle));
166170
ImmutableMap.Builder<String, ColumnHandle> columnHandles = ImmutableMap.builder();
167171
for (CassandraColumnHandle columnHandle : table.getColumns()) {
168-
columnHandles.put(CassandraCqlUtils.cqlNameToSqlName(columnHandle.getName()).toLowerCase(ENGLISH), columnHandle);
172+
String columnName = CassandraCqlUtils.cqlNameToSqlName(columnHandle.getName());
173+
String columnNameKey = normalizeIdentifier(session, columnName);
174+
columnHandles.put(columnNameKey, columnHandle);
169175
}
170176
return columnHandles.build();
171177
}
@@ -244,7 +250,7 @@ public String toString()
244250
@Override
245251
public void createTable(ConnectorSession session, ConnectorTableMetadata tableMetadata, boolean ignoreExisting)
246252
{
247-
createTable(tableMetadata);
253+
createTable(session, tableMetadata);
248254
}
249255

250256
@Override
@@ -273,10 +279,10 @@ public void renameTable(ConnectorSession session, ConnectorTableHandle tableHand
273279
@Override
274280
public ConnectorOutputTableHandle beginCreateTable(ConnectorSession session, ConnectorTableMetadata tableMetadata, Optional<ConnectorNewTableLayout> layout)
275281
{
276-
return createTable(tableMetadata);
282+
return createTable(session, tableMetadata);
277283
}
278284

279-
private CassandraOutputTableHandle createTable(ConnectorTableMetadata tableMetadata)
285+
private CassandraOutputTableHandle createTable(ConnectorSession session, ConnectorTableMetadata tableMetadata)
280286
{
281287
ImmutableList.Builder<String> columnNames = ImmutableList.builder();
282288
ImmutableList.Builder<Type> columnTypes = ImmutableList.builder();
@@ -297,9 +303,10 @@ private CassandraOutputTableHandle createTable(ConnectorTableMetadata tableMetad
297303
StringBuilder queryBuilder = new StringBuilder(String.format("CREATE TABLE \"%s\".\"%s\"(id uuid primary key", schemaName, tableName));
298304
for (int i = 0; i < columns.size(); i++) {
299305
String name = columns.get(i);
306+
String columnName = validColumnName(normalizeIdentifier(session, name));
300307
Type type = types.get(i);
301308
queryBuilder.append(", ")
302-
.append(name)
309+
.append(columnName)
303310
.append(" ")
304311
.append(toCassandraType(type, protocolVersion).name().toLowerCase(ENGLISH));
305312
}
@@ -351,4 +358,10 @@ public Optional<ConnectorOutputMetadata> finishInsert(ConnectorSession session,
351358
{
352359
return Optional.empty();
353360
}
361+
362+
@Override
363+
public String normalizeIdentifier(ConnectorSession session, String identifier)
364+
{
365+
return caseSensitiveNameMatchingEnabled ? identifier : identifier.toLowerCase(ENGLISH);
366+
}
354367
}

presto-cassandra/src/main/java/com/facebook/presto/cassandra/CassandraPageSink.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939

4040
import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker;
4141
import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto;
42+
import static com.facebook.presto.cassandra.util.CassandraCqlUtils.validColumnName;
43+
import static com.facebook.presto.cassandra.util.CassandraCqlUtils.validSchemaName;
44+
import static com.facebook.presto.cassandra.util.CassandraCqlUtils.validTableName;
4245
import static com.facebook.presto.common.type.BigintType.BIGINT;
4346
import static com.facebook.presto.common.type.BooleanType.BOOLEAN;
4447
import static com.facebook.presto.common.type.DateType.DATE;
@@ -92,14 +95,14 @@ public CassandraPageSink(
9295
toCassandraDate = value -> LocalDate.fromDaysSinceEpoch(toIntExact(value));
9396
}
9497

95-
Insert insert = insertInto(schemaName, tableName);
98+
Insert insert = insertInto(validSchemaName(schemaName), validTableName(tableName));
9699
if (generateUUID) {
97100
insert.value("id", bindMarker());
98101
}
99102
for (int i = 0; i < columnNames.size(); i++) {
100103
String columnName = columnNames.get(i);
101104
checkArgument(columnName != null, "columnName is null at position: %d", i);
102-
insert.value(columnName, bindMarker());
105+
insert.value(validColumnName(columnName), bindMarker());
103106
}
104107
this.insert = cassandraSession.prepare(insert);
105108
}

presto-cassandra/src/main/java/com/facebook/presto/cassandra/NativeCassandraSession.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,16 @@ public KeyspaceMetadata load(String key)
118118
private final Cluster cluster;
119119
private final Supplier<Session> session;
120120
private final Duration noHostAvailableRetryTimeout;
121+
private static boolean caseSensitiveNameMatchingEnabled;
121122

122-
public NativeCassandraSession(String connectorId, JsonCodec<List<ExtraColumnMetadata>> extraColumnMetadataCodec, Cluster cluster, Duration noHostAvailableRetryTimeout)
123+
public NativeCassandraSession(String connectorId, JsonCodec<List<ExtraColumnMetadata>> extraColumnMetadataCodec, Cluster cluster, Duration noHostAvailableRetryTimeout, boolean caseSensitiveNameMatchingEnabled)
123124
{
124125
this.connectorId = requireNonNull(connectorId, "connectorId is null");
125126
this.extraColumnMetadataCodec = requireNonNull(extraColumnMetadataCodec, "extraColumnMetadataCodec is null");
126127
this.cluster = requireNonNull(cluster, "cluster is null");
127128
this.noHostAvailableRetryTimeout = requireNonNull(noHostAvailableRetryTimeout, "noHostAvailableRetryTimeout is null");
128129
this.session = memoize(cluster::connect);
130+
this.caseSensitiveNameMatchingEnabled = requireNonNull(caseSensitiveNameMatchingEnabled, "caseSensitiveNameMatchingEnabled is null");
129131
}
130132

131133
@Override
@@ -320,7 +322,7 @@ private static AbstractTableMetadata getTableMetadata(KeyspaceMetadata keyspace,
320322
List<AbstractTableMetadata> tables = Stream.concat(
321323
keyspace.getTables().stream(),
322324
keyspace.getMaterializedViews().stream())
323-
.filter(table -> table.getName().equalsIgnoreCase(caseInsensitiveTableName))
325+
.filter(table -> caseSensitiveNameMatchingEnabled ? table.getName().equals(caseInsensitiveTableName) : table.getName().equalsIgnoreCase(caseInsensitiveTableName))
324326
.collect(toImmutableList());
325327
if (tables.size() == 0) {
326328
throw new TableNotFoundException(new SchemaTableName(keyspace.getName(), caseInsensitiveTableName));
@@ -348,14 +350,14 @@ private static void checkColumnNames(List<ColumnMetadata> columns)
348350
{
349351
Map<String, ColumnMetadata> lowercaseNameToColumnMap = new HashMap<>();
350352
for (ColumnMetadata column : columns) {
351-
String lowercaseName = column.getName().toLowerCase(ENGLISH);
352-
if (lowercaseNameToColumnMap.containsKey(lowercaseName)) {
353+
String columnNameKey = caseSensitiveNameMatchingEnabled ? column.getName() : column.getName().toLowerCase(ENGLISH);
354+
if (lowercaseNameToColumnMap.containsKey(columnNameKey)) {
353355
throw new PrestoException(
354356
NOT_SUPPORTED,
355357
format("More than one column has been found for the case insensitive column name: %s -> (%s, %s)",
356-
lowercaseName, lowercaseNameToColumnMap.get(lowercaseName).getName(), column.getName()));
358+
columnNameKey, lowercaseNameToColumnMap.get(columnNameKey).getName(), column.getName()));
357359
}
358-
lowercaseNameToColumnMap.put(lowercaseName, column);
360+
lowercaseNameToColumnMap.put(columnNameKey, column);
359361
}
360362
}
361363

presto-cassandra/src/test/java/com/facebook/presto/cassandra/CassandraQueryRunner.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import com.google.common.collect.ImmutableMap;
2020
import io.airlift.tpch.TpchTable;
2121

22+
import java.util.HashMap;
2223
import java.util.List;
24+
import java.util.Map;
2325

2426
import static com.facebook.presto.cassandra.CassandraTestingUtils.createKeyspace;
2527
import static com.facebook.presto.testing.TestingSession.testSessionBuilder;
@@ -34,19 +36,21 @@ private CassandraQueryRunner()
3436

3537
private static boolean tpchLoaded;
3638

37-
public static DistributedQueryRunner createCassandraQueryRunner(CassandraServer server)
39+
public static DistributedQueryRunner createCassandraQueryRunner(CassandraServer server, Map<String, String> connectorProperties)
3840
throws Exception
3941
{
4042
DistributedQueryRunner queryRunner = new DistributedQueryRunner(createCassandraSession("tpch"), 4);
4143

4244
queryRunner.installPlugin(new TpchPlugin());
4345
queryRunner.createCatalog("tpch", "tpch");
4446

47+
connectorProperties = new HashMap<>(ImmutableMap.copyOf(connectorProperties));
48+
connectorProperties.putIfAbsent("cassandra.contact-points", server.getHost());
49+
connectorProperties.putIfAbsent("cassandra.native-protocol-port", Integer.toString(server.getPort()));
50+
connectorProperties.putIfAbsent("cassandra.allow-drop-table", "true");
51+
4552
queryRunner.installPlugin(new CassandraPlugin());
46-
queryRunner.createCatalog("cassandra", "cassandra", ImmutableMap.of(
47-
"cassandra.contact-points", server.getHost(),
48-
"cassandra.native-protocol-port", Integer.toString(server.getPort()),
49-
"cassandra.allow-drop-table", "true"));
53+
queryRunner.createCatalog("cassandra", "cassandra", connectorProperties);
5054

5155
createKeyspace(server.getSession(), "tpch");
5256
List<TpchTable<?>> tables = TpchTable.getTables();

presto-cassandra/src/test/java/com/facebook/presto/cassandra/CassandraServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public CassandraServer()
8383
"EmbeddedCassandra",
8484
JsonCodec.listJsonCodec(ExtraColumnMetadata.class),
8585
cluster,
86-
new Duration(1, MINUTES));
86+
new Duration(1, MINUTES), false);
8787
this.metadata = cluster.getMetadata();
8888

8989
try {

presto-cassandra/src/test/java/com/facebook/presto/cassandra/TestCassandraDistributed.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.facebook.presto.testing.MaterializedResult;
1717
import com.facebook.presto.testing.QueryRunner;
1818
import com.facebook.presto.tests.AbstractTestDistributedQueries;
19+
import com.google.common.collect.ImmutableMap;
1920
import org.testng.annotations.AfterClass;
2021
import org.testng.annotations.Optional;
2122

@@ -35,7 +36,7 @@ protected QueryRunner createQueryRunner()
3536
throws Exception
3637
{
3738
this.server = new CassandraServer();
38-
return CassandraQueryRunner.createCassandraQueryRunner(server);
39+
return CassandraQueryRunner.createCassandraQueryRunner(server, ImmutableMap.of());
3940
}
4041

4142
@AfterClass(alwaysRun = true)

presto-cassandra/src/test/java/com/facebook/presto/cassandra/TestCassandraIntegrationSmokeTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.facebook.presto.testing.QueryRunner;
2222
import com.facebook.presto.tests.AbstractTestIntegrationSmokeTest;
2323
import com.google.common.collect.ImmutableList;
24+
import com.google.common.collect.ImmutableMap;
2425
import org.testng.annotations.AfterClass;
2526
import org.testng.annotations.Test;
2627

@@ -97,7 +98,7 @@ protected QueryRunner createQueryRunner()
9798
this.server = server;
9899
this.session = server.getSession();
99100
createTestTables(session, server.getMetadata(), KEYSPACE, DATE_TIME_LOCAL);
100-
return createCassandraQueryRunner(server);
101+
return createCassandraQueryRunner(server, ImmutableMap.of());
101102
}
102103

103104
@Test
@@ -312,7 +313,7 @@ public void testUppercaseNameEscaped()
312313
.row("column_2", "bigint", "", "")
313314
.build());
314315

315-
execute("INSERT INTO \"KEYSPACE_2\".\"TABLE_2\" (\"COLUMN_2\") VALUES (1)");
316+
session.execute("INSERT INTO \"KEYSPACE_2\".\"TABLE_2\" (\"COLUMN_2\") VALUES (1)");
316317

317318
assertEquals(execute("SELECT column_2 FROM cassandra.keyspace_2.table_2").getRowCount(), 1);
318319
assertUpdate("DROP TABLE cassandra.keyspace_2.table_2");

0 commit comments

Comments
 (0)