-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multi-join pushdown appends recursively _<number> to column names so that they exceed maximum alias length #18924
Changes from all commits
e36d9f5
8fce268
25ea98e
cf29b3c
356a829
4346680
2ee0b52
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.trino.plugin.jdbc; | ||
|
||
import static com.google.common.base.Splitter.fixedLength; | ||
import static com.google.common.base.Verify.verify; | ||
|
||
public class SyntheticColumnHandleBuilder | ||
{ | ||
public static final int DEFAULT_COLUMN_ALIAS_LENGTH = 30; | ||
|
||
public JdbcColumnHandle get(JdbcColumnHandle column, int nextSyntheticColumnId) | ||
{ | ||
verify(nextSyntheticColumnId >= 0, "nextSyntheticColumnId rolled over and is not monotonically increasing any more"); | ||
|
||
int sequentialNumberLength = String.valueOf(nextSyntheticColumnId).length(); | ||
int originalColumnNameLength = DEFAULT_COLUMN_ALIAS_LENGTH - sequentialNumberLength - "_".length(); | ||
|
||
String columnNameTruncated = fixedLength(originalColumnNameLength) | ||
.split(column.getColumnName()) | ||
.iterator() | ||
.next(); | ||
String columnName = columnNameTruncated + "_" + nextSyntheticColumnId; | ||
return JdbcColumnHandle.builderFrom(column) | ||
.setColumnName(columnName) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.trino.plugin.jdbc; | ||
|
||
import com.google.inject.AbstractModule; | ||
import com.google.inject.Singleton; | ||
|
||
public class SyntheticColumnHandleBuilderModule | ||
extends AbstractModule | ||
{ | ||
@Override | ||
public void configure() | ||
{ | ||
bind(SyntheticColumnHandleBuilder.class).in(Singleton.class); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.trino.plugin.jdbc; | ||
|
||
import com.google.common.base.VerifyException; | ||
import org.testng.annotations.DataProvider; | ||
import org.testng.annotations.Test; | ||
|
||
import static io.trino.plugin.jdbc.TestingJdbcTypeHandle.JDBC_VARCHAR; | ||
import static io.trino.spi.type.VarcharType.VARCHAR; | ||
import static java.lang.Integer.MAX_VALUE; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
||
public class TestSyntheticColumnHandleBuilder | ||
{ | ||
private final SyntheticColumnHandleBuilder syntheticColumnHandleBuilder = new SyntheticColumnHandleBuilder(); | ||
|
||
@DataProvider(name = "columns") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Helper method should be located after the caller. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you elaborate or provide an example. I don't think I understand your comment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean replacing the method order: @Test(dataProvider = "columns")
public void testColumnAliasTruncation(String columnName, int nextSynthenticId, String expectedSyntheticColumnName)
{
JdbcColumnHandle column = getDefaultColumnHandleBuilder()
.setColumnName(columnName)
.build();
JdbcColumnHandle result = syntheticColumnHandleBuilder.get(column, nextSynthenticId);
assertThat(result.getColumnName()).isEqualTo(expectedSyntheticColumnName);
}
@DataProvider(name = "columns")
public static Object[][] testData()
{
return new Object[][] {
{"column_0", 999, "column_0_999"},
{"column_with_over_twenty_characters", 100, "column_with_over_twenty_ch_100"},
{"column_with_over_twenty_characters", MAX_VALUE, "column_with_over_tw_2147483647"}
};
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The point is that we are reading from the top to bottom. That way you read more abstract things, then if you care you go deeper and understand details. This follows Clean Code book. |
||
public static Object[][] testData() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We usually use |
||
{ | ||
return new Object[][] { | ||
{"column_0", 999, "column_0_999"}, | ||
{"column_with_over_twenty_characters", 100, "column_with_over_twenty_ch_100"}, | ||
{"column_with_over_twenty_characters", MAX_VALUE, "column_with_over_tw_2147483647"} | ||
}; | ||
} | ||
|
||
@Test(dataProvider = "columns") | ||
public void testColumnAliasTruncation(String columnName, int nextSynthenticId, String expectedSyntheticColumnName) | ||
{ | ||
JdbcColumnHandle column = getDefaultColumnHandleBuilder() | ||
.setColumnName(columnName) | ||
.build(); | ||
|
||
JdbcColumnHandle result = syntheticColumnHandleBuilder.get(column, nextSynthenticId); | ||
|
||
assertThat(result.getColumnName()).isEqualTo(expectedSyntheticColumnName); | ||
} | ||
|
||
@Test | ||
public void testNegativeSyntheticId() | ||
{ | ||
JdbcColumnHandle column = getDefaultColumnHandleBuilder() | ||
.setColumnName("column_0") | ||
.build(); | ||
|
||
assertThatThrownBy(() -> syntheticColumnHandleBuilder.get(column, -2147483648)).isInstanceOf(VerifyException.class); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would better to verify the error message. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likewise here. Can you explain in more detail? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. assertThatThrownBy(() -> syntheticColumnHandleBuilder.get(column, -2147483648))
.isInstanceOf(VerifyException.class)
.hasMessage("nextSyntheticColumnId rolled over and is not monotonically increasing any more"); |
||
} | ||
|
||
private static JdbcColumnHandle.Builder getDefaultColumnHandleBuilder() | ||
{ | ||
return JdbcColumnHandle.builder() | ||
.setJdbcTypeHandle(JDBC_VARCHAR) | ||
.setColumnType(VARCHAR); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,19 +17,24 @@ | |
import io.airlift.testing.Closeables; | ||
import io.trino.testing.QueryRunner; | ||
import io.trino.testing.sql.SqlExecutor; | ||
import io.trino.testing.sql.TestTable; | ||
import org.testng.annotations.AfterClass; | ||
import org.testng.annotations.Test; | ||
|
||
import static io.trino.plugin.jdbc.SyntheticColumnHandleBuilder.DEFAULT_COLUMN_ALIAS_LENGTH; | ||
import static io.trino.plugin.oracle.TestingOracleServer.TEST_PASS; | ||
import static io.trino.plugin.oracle.TestingOracleServer.TEST_SCHEMA; | ||
import static io.trino.plugin.oracle.TestingOracleServer.TEST_USER; | ||
import static java.lang.String.format; | ||
import static java.util.stream.Collectors.joining; | ||
import static java.util.stream.IntStream.range; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
public class TestOracleConnectorTest | ||
extends BaseOracleConnectorTest | ||
{ | ||
private static final String MAXIMUM_LENGTH_COLUMN_IDENTIFIER = "z".repeat(DEFAULT_COLUMN_ALIAS_LENGTH); | ||
|
||
private TestingOracleServer oracleServer; | ||
|
||
@Override | ||
|
@@ -83,4 +88,16 @@ protected SqlExecutor onRemoteDatabase() | |
{ | ||
return oracleServer::execute; | ||
} | ||
|
||
@Test | ||
public void testPushdownJoinWithLongNameSucceeds() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we have a test with multiple super long column names used in the same Trino query that are almost the same ( Or is it an overkill? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, see the tests being added in #18984 fortunately the remote databases which silently truncate long identifiers (SQLServer, Postgres, Redshift) all prevent this bug from happening because they realise that the truncated alias is now ambiguous. |
||
{ | ||
try (TestTable table = new TestTable(getQueryRunner()::execute, "long_identifier", "(%s bigint)".formatted(MAXIMUM_LENGTH_COLUMN_IDENTIFIER))) { | ||
assertThat(query(joinPushdownEnabled(getSession()), """ | ||
SELECT r.name, t.%s, n.name | ||
FROM %s t JOIN region r ON r.regionkey = t.%s | ||
JOIN nation n ON r.regionkey = n.regionkey""".formatted(MAXIMUM_LENGTH_COLUMN_IDENTIFIER, table.getName(), MAXIMUM_LENGTH_COLUMN_IDENTIFIER))) | ||
.isFullyPushedDown(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We format arguments like below when putting arguments on separate lines:
Same for Ignite and Phoenix.