Skip to content

Commit

Permalink
Quote column names for ADD, RENAME and DROP COLUMN in JDBC connectors
Browse files Browse the repository at this point in the history
  • Loading branch information
ebyhr committed Aug 31, 2022
1 parent f6e621d commit 3ebd9c2
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -795,18 +795,24 @@ protected String renameColumnSql(JdbcTableHandle handle, JdbcColumnHandle jdbcCo
return format(
"ALTER TABLE %s RENAME COLUMN %s TO %s",
quoted(handle.asPlainTable().getRemoteTableName()),
jdbcColumn.getColumnName(),
newRemoteColumnName);
quoted(jdbcColumn.getColumnName()),
quoted(newRemoteColumnName));
}

@Override
public void dropColumn(ConnectorSession session, JdbcTableHandle handle, JdbcColumnHandle column)
{
String sql = format(
"ALTER TABLE %s DROP COLUMN %s",
quoted(handle.asPlainTable().getRemoteTableName()),
column.getColumnName());
execute(session, sql);
try (Connection connection = connectionFactory.openConnection(session)) {
String remoteColumnName = identifierMapping.toRemoteColumnName(connection, column.getColumnName());
String sql = format(
"ALTER TABLE %s DROP COLUMN %s",
quoted(handle.asPlainTable().getRemoteTableName()),
quoted(remoteColumnName));
execute(connection, sql);
}
catch (SQLException e) {
throw new TrinoException(JDBC_ERROR, e);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,39 @@ public void testRenameColumn()
throw new SkipException("TODO: test not implemented yet");
}

@Override
public void testAddAndDropColumnName(String columnName)
{
// TODO: Enable this test
assertThatThrownBy(() -> super.testAddAndDropColumnName(columnName))
.hasMessageContaining("is not supported by storage Log");
throw new SkipException("TODO");
}

@Override
public void testRenameColumnName(String columnName)
{
// TODO: Enable this test
if (columnName.equals("a.dot")) {
assertThatThrownBy(() -> super.testRenameColumnName(columnName))
.hasMessageContaining("Cannot rename column from nested struct to normal column");
throw new SkipException("TODO");
}
assertThatThrownBy(() -> super.testRenameColumnName(columnName))
.hasMessageContaining("is not supported by storage Log");
throw new SkipException("TODO");
}

@Override
protected Optional<String> filterColumnNameTestData(String columnName)
{
// TODO: Investigate why a\backslash allows creating a table, but it throws an exception when selecting
if (columnName.equals("a\\backslash`")) {
return Optional.empty();
}
return Optional.of(columnName);
}

@Override
public void testDropColumn()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,17 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior)
}
}

@Override
public void testAddAndDropColumnName(String columnName)
{
if (columnName.equals("a.dot")) {
assertThatThrownBy(() -> super.testAddAndDropColumnName(columnName))
.hasMessageContaining("Cannot add column with ambiguous name");
return;
}
super.testAddAndDropColumnName(columnName);
}

@Override
protected void verifyVersionedQueryFailurePermissible(Exception e)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,24 @@ public void testRenameTableToUnqualifiedPreservesSchema()
.hasMessage("Creating schema in Kudu connector not allowed if schema emulation is disabled.");
}

@Override
public void testAddAndDropColumnName(String columnName)
{
// TODO: Enable this test
assertThatThrownBy(() -> super.testAddAndDropColumnName(columnName))
.hasMessage("Table partitioning must be specified using setRangePartitionColumns or addHashPartitions");
throw new SkipException("TODO");
}

@Override
public void testRenameColumnName(String columnName)
{
// TODO: Enable this test
assertThatThrownBy(() -> super.testRenameColumnName(columnName))
.hasMessage("Table partitioning must be specified using setRangePartitionColumns or addHashPartitions");
throw new SkipException("TODO");
}

@Test
@Override
public void testDescribeTable()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ public void testRenameColumn()
.hasMessageContaining("Rename column not supported for the MariaDB server version");
}

@Override
public void testRenameColumnName(String columnName)
{
assertThatThrownBy(() -> super.testRenameColumnName(columnName))
.hasMessageContaining("Rename column not supported for the MariaDB server version");
}

@Override
public void testAlterTableRenameColumnToLongName()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ public void testRenameColumn()
.hasStackTraceContaining("RENAME COLUMN x TO before_y");
}

@Override
public void testRenameColumnName(String columnName)
{
assertThatThrownBy(() -> super.testRenameColumnName(columnName))
.hasMessageContaining("You have an error in your SQL syntax")
.hasStackTraceContaining("RENAME COLUMN");
}

@Override
public void testAlterTableRenameColumnToLongName()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ public void addColumn(ConnectorSession session, ConnectorTableHandle tableHandle
phoenixClient.execute(session, format(
"ALTER TABLE %s ADD %s %s",
getEscapedTableName(handle.getSchemaName(), handle.getTableName()),
column.getName(),
phoenixClient.quoted(column.getName()),
phoenixClient.toWriteMapping(session, column.getType()).getDataType()));
}

Expand All @@ -246,7 +246,7 @@ public void dropColumn(ConnectorSession session, ConnectorTableHandle tableHandl
phoenixClient.execute(session, format(
"ALTER TABLE %s DROP COLUMN %s",
getEscapedTableName(handle.getSchemaName(), handle.getTableName()),
columnHandle.getColumnName()));
phoenixClient.quoted(columnHandle.getColumnName())));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,37 @@ public void testAlterTableRenameColumnToLongName()
throw new SkipException("Rename column is not yet supported by Phoenix connector");
}

@Override
public void testRenameColumnName(String columnName)
{
// The column name is rejected when creating a table
if (columnName.equals("a\"quote")) {
super.testRenameColumnName(columnName);
return;
}
assertThatThrownBy(() -> super.testRenameColumnName(columnName))
// TODO (https://github.com/trinodb/trino/issues/7205) support column rename in Phoenix
.hasMessageContaining("Syntax error. Encountered \"RENAME\"");
throw new SkipException("Rename column is not yet supported by Phoenix connector");
}

@Override
public void testAddAndDropColumnName(String columnName)
{
// TODO: Investigate why these two case fail
if (columnName.equals("an'apostrophe")) {
assertThatThrownBy(() -> super.testAddAndDropColumnName(columnName))
.hasMessageContaining("Syntax error. Mismatched input");
throw new SkipException("TODO");
}
if (columnName.equals("a\\backslash`")) {
assertThatThrownBy(() -> super.testAddAndDropColumnName(columnName))
.hasMessageContaining("Undefined column");
throw new SkipException("TODO");
}
super.testAddAndDropColumnName(columnName);
}

@Override
public void testInsert()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,13 @@ protected String renameColumnSql(JdbcTableHandle handle, JdbcColumnHandle jdbcCo
{
return format(
"sp_rename %s, %s, 'COLUMN'",
singleQuote(handle.getCatalogName(), handle.getSchemaName(), handle.getTableName(), jdbcColumn.getColumnName()),
singleQuote(newRemoteColumnName));
singleQuote(handle.getCatalogName(), handle.getSchemaName(), handle.getTableName(), "[" + escape(jdbcColumn.getColumnName()) + "]"),
"[" + newRemoteColumnName + "]");

This comment has been minimized.

Copy link
@hashhar

hashhar Sep 9, 2022

Member

is an escape missing here? i.e. escape(newRemoteColumnName)?

This comment has been minimized.

Copy link
@ebyhr

ebyhr Sep 9, 2022

Author Member

[] doesn't need inside escape as far as I confirmed.

> CREATE TABLE database_4be45e56c25940ce9296cbe2386c6de6.dbo.test (c1 int, c2 int);
> sp_rename 'database_4be45e56c25940ce9296cbe2386c6de6.dbo.test.[c1]', [ab'c], 'COLUMN'
> SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'test' ORDER BY ORDINAL_POSITION
ab'c
c2

This comment has been minimized.

Copy link
@hashhar

hashhar Sep 9, 2022

Member

Thanks, makes sense. i read the documentation after asking here. It seems [] is used as the quoting character (" is also mentioned as valid replacement).

This comment has been minimized.

Copy link
@findepi

findepi Sep 9, 2022

Member

what if column name (existing or newRemoteColumnName) contains [ or ]?

This comment has been minimized.

Copy link
@hashhar

hashhar Sep 10, 2022

Member

This comment has been minimized.

Copy link
@findepi

findepi Sep 12, 2022

Member

Thanks @hashhar for the link.

I wish a relevant summary, or a link to such knowledge, be contained in the code comment, since it's an important reference.

BTW i still don't think it's correct. Do we know that newRemoteColumnName contains no ], eg. ]; DROP ...?

This comment has been minimized.

Copy link
@hashhar

hashhar Sep 12, 2022

Member

Yes, current code isn't correct for all possible column names. I'm experimenting to see how sp_rename behaves. That linked comment is just notes from my experiments so far. I plan to open a draft PR with some ideas and we can continue there.

}

private static String escape(String name)
{
return name.replace("'", "''");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3831,6 +3831,79 @@ protected void testColumnName(String columnName, boolean delimited)
}
}

@Test(dataProvider = "testColumnNameDataProvider")
public void testAddAndDropColumnName(String columnName)
{
skipTestUnless(hasBehavior(SUPPORTS_ADD_COLUMN) && hasBehavior(SUPPORTS_DROP_COLUMN));

if (!requiresDelimiting(columnName)) {
testAddAndDropColumnName(columnName, false);
}
testAddAndDropColumnName(columnName, true);
}

protected void testAddAndDropColumnName(String columnName, boolean delimited)
{
String nameInSql = toColumnNameInSql(columnName, delimited);
String tableName = "tcn_" + nameInSql.toLowerCase(ENGLISH).replaceAll("[^a-z0-9]", "") + randomTableSuffix();

try {
assertUpdate("CREATE TABLE " + tableName + "(" + nameInSql + " varchar(50), value varchar(50))");
}
catch (RuntimeException e) {
if (isColumnNameRejected(e, columnName, delimited)) {
// It is OK if give column name is not allowed and is clearly rejected by the connector.
return;
}
throw e;
}
assertTableColumnNames(tableName, columnName.toLowerCase(ENGLISH), "value");

assertUpdate("ALTER TABLE " + tableName + " DROP COLUMN " + nameInSql);
assertTableColumnNames(tableName, "value");

assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + nameInSql + " varchar(50)");
assertTableColumnNames(tableName, "value", columnName.toLowerCase(ENGLISH));

assertUpdate("DROP TABLE " + tableName);
}

@Test(dataProvider = "testColumnNameDataProvider")
public void testRenameColumnName(String columnName)
{
skipTestUnless(hasBehavior(SUPPORTS_RENAME_COLUMN));

if (!requiresDelimiting(columnName)) {
testRenameColumnName(columnName, false);
}
testRenameColumnName(columnName, true);
}

protected void testRenameColumnName(String columnName, boolean delimited)
{
String nameInSql = toColumnNameInSql(columnName, delimited);
String tableName = "tcn_" + nameInSql.replaceAll("[^a-z0-9]", "") + randomTableSuffix();
// Use complex identifier to test a source column name when renaming columns
String sourceColumnName = "a;b$c";

try {
assertUpdate("CREATE TABLE " + tableName + "(\"" + sourceColumnName + "\" varchar(50))");
assertTableColumnNames(tableName, sourceColumnName);

assertUpdate("ALTER TABLE " + tableName + " RENAME COLUMN \"" + sourceColumnName + "\" TO " + nameInSql);
assertTableColumnNames(tableName, columnName.toLowerCase(ENGLISH));
}
catch (RuntimeException e) {
if (isColumnNameRejected(e, columnName, delimited)) {
// It is OK if give column name is not allowed and is clearly rejected by the connector.
return;
}
throw e;
}

assertUpdate("DROP TABLE " + tableName);
}

private static String toColumnNameInSql(String columnName, boolean delimited)
{
String nameInSql = columnName;
Expand Down

0 comments on commit 3ebd9c2

Please sign in to comment.