Skip to content

Commit 15f56bf

Browse files
committed
Fix leak + interpolation
1 parent 59e524b commit 15f56bf

3 files changed

Lines changed: 87 additions & 19 deletions

File tree

src/main/java/com/databricks/jdbc/api/impl/DatabricksPreparedStatement.java

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.getDatabricksTypeFromSQLType;
55
import static com.databricks.jdbc.common.util.DatabricksTypeUtil.inferDatabricksType;
66
import static com.databricks.jdbc.common.util.SQLInterpolator.interpolateSQL;
7+
import static com.databricks.jdbc.common.util.SQLInterpolator.surroundPlaceholdersWithQuotes;
78
import static com.databricks.jdbc.common.util.ValidationUtil.throwErrorIfNull;
89

910
import com.databricks.jdbc.common.StatementType;
1011
import com.databricks.jdbc.common.util.DatabricksTypeUtil;
11-
import com.databricks.jdbc.dbclient.impl.common.StatementId;
1212
import com.databricks.jdbc.exception.*;
1313
import com.databricks.jdbc.log.JdbcLogger;
1414
import com.databricks.jdbc.log.JdbcLoggerFactory;
@@ -45,6 +45,18 @@ public DatabricksPreparedStatement(DatabricksConnection connection, String sql)
4545
this.databricksBatchParameterMetaData = new ArrayList<>();
4646
}
4747

48+
DatabricksPreparedStatement(
49+
DatabricksConnection connection,
50+
String sql,
51+
boolean interpolateParameters,
52+
DatabricksParameterMetaData databricksParameterMetaData) {
53+
super(connection);
54+
this.sql = sql;
55+
this.interpolateParameters = interpolateParameters;
56+
this.databricksParameterMetaData = databricksParameterMetaData;
57+
this.databricksBatchParameterMetaData = new ArrayList<>();
58+
}
59+
4860
@Override
4961
public ResultSet executeQuery() throws SQLException {
5062
LOGGER.debug("public ResultSet executeQuery()");
@@ -834,18 +846,20 @@ private DatabricksResultSet interpolateIfRequiredAndExecute(StatementType statem
834846
* @throws DatabricksSQLException if there is an error executing the DESCRIBE QUERY command
835847
*/
836848
private ResultSetMetaData getMetaDataFromDescribeQuery() throws DatabricksSQLException {
837-
838-
try (DatabricksResultSet metadataResultSet = executeDescribeQueryCommand()) {
849+
String describeQuerySQL = "DESCRIBE QUERY " + surroundPlaceholdersWithQuotes(sql);
850+
try (DatabricksPreparedStatement preparedStatement =
851+
new DatabricksPreparedStatement(
852+
connection, describeQuerySQL, interpolateParameters, databricksParameterMetaData);
853+
ResultSet metadataResultSet = preparedStatement.executeQuery(); ) {
839854
ArrayList<String> columnNames = new ArrayList<>();
840855
ArrayList<String> columnDataTypes = new ArrayList<>();
841856

842857
while (metadataResultSet.next()) {
843858
columnNames.add(metadataResultSet.getString(1));
844859
columnDataTypes.add(metadataResultSet.getString(2));
845860
}
846-
847861
return new DatabricksResultSetMetaData(
848-
StatementId.deserialize(metadataResultSet.getStatementId()),
862+
preparedStatement.getStatementId(),
849863
columnNames,
850864
columnDataTypes,
851865
this.connection.getConnectionContext());
@@ -856,18 +870,4 @@ private ResultSetMetaData getMetaDataFromDescribeQuery() throws DatabricksSQLExc
856870
errorMessage, e, DatabricksDriverErrorCode.EXECUTE_STATEMENT_FAILED);
857871
}
858872
}
859-
860-
private DatabricksResultSet executeDescribeQueryCommand() throws SQLException {
861-
String interpolatedSql =
862-
this.interpolateParameters
863-
? interpolateSQL(sql, this.databricksParameterMetaData.getParameterBindings())
864-
: sql;
865-
866-
interpolatedSql = "DESCRIBE QUERY " + interpolatedSql;
867-
Map<Integer, ImmutableSqlParameter> paramMap =
868-
this.interpolateParameters
869-
? new HashMap<>()
870-
: this.databricksParameterMetaData.getParameterBindings();
871-
return executeInternal(interpolatedSql, paramMap, StatementType.QUERY);
872-
}
873873
}

src/main/java/com/databricks/jdbc/common/util/SQLInterpolator.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,20 @@ public static String interpolateSQL(String sql, Map<Integer, ImmutableSqlParamet
6767
}
6868
return sb.toString();
6969
}
70+
71+
/**
72+
* Surrounds unquoted placeholders (?) with single quotes, preserving already quoted ones. This is
73+
* crucial for DESCRIBE QUERY commands as unquoted placeholders will cause a parse_syntax_error.
74+
*/
75+
public static String surroundPlaceholdersWithQuotes(String sql) {
76+
if (sql == null || sql.isEmpty()) {
77+
return sql;
78+
}
79+
// First replace '?' with a temporary marker
80+
String temp = sql.replace("'?'", "##QUOTED##");
81+
// Then replace remaining ? with '?'
82+
String result = temp.replace("?", "'?'");
83+
// Finally restore the original quoted placeholders
84+
return result.replace("##QUOTED##", "'?'");
85+
}
7086
}

src/test/java/com/databricks/jdbc/common/util/SQLInterpolatorTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
import com.databricks.jdbc.exception.DatabricksValidationException;
1010
import java.util.HashMap;
1111
import java.util.Map;
12+
import java.util.stream.Stream;
1213
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.params.ParameterizedTest;
15+
import org.junit.jupiter.params.provider.Arguments;
16+
import org.junit.jupiter.params.provider.MethodSource;
1317

1418
public class SQLInterpolatorTest {
1519

@@ -87,4 +91,52 @@ public void testBinaryType() throws DatabricksValidationException {
8791
String expected = "INSERT INTO sales (id, data) VALUES (101, X'0102030405')";
8892
assertEquals(expected, SQLInterpolator.interpolateSQL(sql, params));
8993
}
94+
95+
private static Stream<Arguments> providePlaceholderQuotingTestCases() {
96+
return Stream.of(
97+
// Basic placeholder quoting
98+
Arguments.of(
99+
"SELECT * FROM table WHERE id = ?",
100+
"SELECT * FROM table WHERE id = '?'",
101+
"Basic placeholder quoting"),
102+
103+
// Multiple placeholders
104+
Arguments.of(
105+
"SELECT * FROM table WHERE id = ? AND name = ?",
106+
"SELECT * FROM table WHERE id = '?' AND name = '?'",
107+
"Multiple placeholders"),
108+
109+
// Already quoted placeholders
110+
Arguments.of(
111+
"SELECT * FROM table WHERE id = '?' AND name = ?",
112+
"SELECT * FROM table WHERE id = '?' AND name = '?'",
113+
"Already quoted placeholders"),
114+
115+
// Mixed quoted and unquoted placeholders
116+
Arguments.of(
117+
"SELECT * FROM table WHERE id = '?' AND name = ? AND age = '?'",
118+
"SELECT * FROM table WHERE id = '?' AND name = '?' AND age = '?'",
119+
"Mixed quoted and unquoted placeholders"),
120+
121+
// Null input
122+
Arguments.of(null, null, "Null input"),
123+
124+
// Empty input
125+
Arguments.of("", "", "Empty input"),
126+
127+
// No placeholders
128+
Arguments.of("SELECT * FROM table", "SELECT * FROM table", "No placeholders"),
129+
130+
// Complex query with multiple conditions
131+
Arguments.of(
132+
"SELECT * FROM table WHERE id = ? AND (name = ? OR age = ?) AND status = ?",
133+
"SELECT * FROM table WHERE id = '?' AND (name = '?' OR age = '?') AND status = '?'",
134+
"Complex query with multiple conditions"));
135+
}
136+
137+
@ParameterizedTest(name = "{2}")
138+
@MethodSource("providePlaceholderQuotingTestCases")
139+
public void testSurroundPlaceholdersWithQuotes(String input, String expected, String testName) {
140+
assertEquals(expected, SQLInterpolator.surroundPlaceholdersWithQuotes(input), testName);
141+
}
90142
}

0 commit comments

Comments
 (0)