diff --git a/databend-jdbc/pom.xml b/databend-jdbc/pom.xml
index 9d291a6e..dc5699ed 100644
--- a/databend-jdbc/pom.xml
+++ b/databend-jdbc/pom.xml
@@ -31,6 +31,22 @@
okhttp
${dep.okhttp.version}
+
+
+ org.apache.commons
+ commons-lang3
+ 3.13.0
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+ provided
+
+
+
com.squareup.okio
diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/NonQueryRawStatement.java b/databend-jdbc/src/main/java/com/databend/jdbc/NonQueryRawStatement.java
new file mode 100644
index 00000000..41d77e64
--- /dev/null
+++ b/databend-jdbc/src/main/java/com/databend/jdbc/NonQueryRawStatement.java
@@ -0,0 +1,25 @@
+package com.databend.jdbc;
+
+import static com.databend.jdbc.StatementType.NON_QUERY;
+
+import java.util.List;
+
+
+import lombok.EqualsAndHashCode;
+
+/**
+ * A non query statement is a statement that does not return data (such as
+ * INSERT)
+ */
+@EqualsAndHashCode(callSuper = true)
+public class NonQueryRawStatement extends RawStatement {
+
+ public NonQueryRawStatement(String sql, String cleanSql, List paramPositions) {
+ super(sql, cleanSql, paramPositions);
+ }
+
+ @Override
+ public StatementType getStatementType() {
+ return NON_QUERY;
+ }
+}
diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/ParamMarker.java b/databend-jdbc/src/main/java/com/databend/jdbc/ParamMarker.java
new file mode 100644
index 00000000..b1266669
--- /dev/null
+++ b/databend-jdbc/src/main/java/com/databend/jdbc/ParamMarker.java
@@ -0,0 +1,10 @@
+package com.databend.jdbc;
+import lombok.AllArgsConstructor;
+import lombok.Value;
+
+@AllArgsConstructor
+@Value
+public class ParamMarker {
+ int id; // Id / index of the param marker in the SQL statement
+ int position; // Position in the SQL subStatement
+}
diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/QueryRawStatement.java b/databend-jdbc/src/main/java/com/databend/jdbc/QueryRawStatement.java
new file mode 100644
index 00000000..a6b22c4d
--- /dev/null
+++ b/databend-jdbc/src/main/java/com/databend/jdbc/QueryRawStatement.java
@@ -0,0 +1,39 @@
+package com.databend.jdbc;
+
+import static com.databend.jdbc.StatementType.QUERY;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+
+/**
+ * A query statement is a statement that returns data (Typically starts with
+ * SELECT, SHOW, etc)
+ */
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class QueryRawStatement extends RawStatement {
+
+ private final String database;
+
+ private final String table;
+
+ public QueryRawStatement(String sql, String cleanSql, List paramPositions) {
+ super(sql, cleanSql, paramPositions);
+ Pair, Optional> databaseAndTablePair = StatementUtil
+ .extractDbNameAndTableNamePairFromCleanQuery(this.getCleanSql());
+ this.database = databaseAndTablePair.getLeft().orElse(null);
+ this.table = databaseAndTablePair.getRight().orElse(null);
+ }
+
+ @Override
+ public StatementType getStatementType() {
+ return QUERY;
+ }
+
+}
diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/RawStatement.java b/databend-jdbc/src/main/java/com/databend/jdbc/RawStatement.java
new file mode 100644
index 00000000..68a68b88
--- /dev/null
+++ b/databend-jdbc/src/main/java/com/databend/jdbc/RawStatement.java
@@ -0,0 +1,57 @@
+package com.databend.jdbc;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+
+import com.databend.jdbc.ParamMarker;
+import com.databend.jdbc.StatementType;
+
+import lombok.Data;
+
+@Data
+public abstract class RawStatement {
+
+ private final String sql;
+ private final String cleanSql;
+ private final List paramMarkers;
+
+ protected RawStatement(String sql, String cleanSql, List paramPositions) {
+ this.sql = sql;
+ this.cleanSql = cleanSql;
+ this.paramMarkers = paramPositions;
+ }
+
+ public static RawStatement of(String sql, List paramPositions, String cleanSql) {
+ Optional> additionalProperties = StatementUtil.extractParamFromSetStatement(cleanSql, sql);
+ if (additionalProperties.isPresent()) {
+ return new SetParamRawStatement(sql, cleanSql, paramPositions, additionalProperties.get());
+ } else if (StatementUtil.isQuery(cleanSql)) {
+ return new QueryRawStatement(sql, cleanSql, paramPositions);
+ } else {
+ return new NonQueryRawStatement(sql, cleanSql, paramPositions);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "RawSqlStatement{" + "sql='" + sql + '\'' + ", cleanSql='" + cleanSql + '\'' + ", paramMarkers="
+ + StringUtils.join(paramMarkers, "|") + '}';
+ }
+
+ public List getParamMarkers() {
+ return paramMarkers;
+ }
+
+ public String getSql() {
+ return sql;
+ }
+
+ public String getCleanSql() {
+ return cleanSql;
+ }
+
+ public abstract StatementType getStatementType();
+}
diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/RawStatementWrapper.java b/databend-jdbc/src/main/java/com/databend/jdbc/RawStatementWrapper.java
new file mode 100644
index 00000000..fdca4c4a
--- /dev/null
+++ b/databend-jdbc/src/main/java/com/databend/jdbc/RawStatementWrapper.java
@@ -0,0 +1,29 @@
+package com.databend.jdbc;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+
+import lombok.CustomLog;
+import lombok.Value;
+
+@CustomLog
+@Value
+public class RawStatementWrapper {
+
+ List subStatements;
+
+ long totalParams;
+
+ public RawStatementWrapper(List subStatements) {
+ this.subStatements = subStatements;
+ this.totalParams = subStatements.stream().map(RawStatement::getParamMarkers).mapToLong(Collection::size).sum();
+ }
+
+ @Override
+ public String toString() {
+ return "SqlQueryWrapper{" + "subQueries=" + StringUtils.join(subStatements, "|") + ", totalParams="
+ + totalParams + '}';
+ }
+
+}
diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/SetParamRawStatement.java b/databend-jdbc/src/main/java/com/databend/jdbc/SetParamRawStatement.java
new file mode 100644
index 00000000..2e230319
--- /dev/null
+++ b/databend-jdbc/src/main/java/com/databend/jdbc/SetParamRawStatement.java
@@ -0,0 +1,32 @@
+package com.databend.jdbc;
+import static com.databend.jdbc.StatementType.PARAM_SETTING;
+
+import java.util.List;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+
+/**
+ * A Set param statement is a special statement that sets a parameter internally
+ * (this type of statement starts with SET)
+ */
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class SetParamRawStatement extends RawStatement {
+
+ private final Pair additionalProperty;
+
+ public SetParamRawStatement(String sql, String cleanSql, List paramPositions,
+ Pair additionalProperty) {
+ super(sql, cleanSql, paramPositions);
+ this.additionalProperty = additionalProperty;
+ }
+
+ @Override
+ public StatementType getStatementType() {
+ return PARAM_SETTING;
+ }
+
+}
diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/StatementInfoWrapper.java b/databend-jdbc/src/main/java/com/databend/jdbc/StatementInfoWrapper.java
new file mode 100644
index 00000000..282e3813
--- /dev/null
+++ b/databend-jdbc/src/main/java/com/databend/jdbc/StatementInfoWrapper.java
@@ -0,0 +1,52 @@
+package com.databend.jdbc;
+
+
+import static com.databend.jdbc.StatementType.PARAM_SETTING;
+
+import java.util.UUID;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NonNull;
+
+/**
+ * This represents a statement that is ready to be sent to Firebolt or executed
+ * internally to set a param
+ */
+@Data
+@AllArgsConstructor
+public class StatementInfoWrapper {
+ private String sql;
+ private String id;
+ private StatementType type;
+ private Pair param;
+ private RawStatement initialStatement;
+
+ /**
+ * Creates a StatementInfoWrapper from the {@link RawStatement}.
+ *
+ * @param rawStatement the raw statement
+ * @return the statement that will be sent to the server
+ */
+ public static StatementInfoWrapper of(@NonNull RawStatement rawStatement) {
+ return of(rawStatement, UUID.randomUUID().toString());
+ }
+
+ /**
+ * Creates a StatementInfoWrapper from the {@link RawStatement}.
+ *
+ * @param rawStatement the raw statement
+ * @param id the id of the statement to execute
+ * @return the statement that will be sent to the server
+ */
+ public static StatementInfoWrapper of(@NonNull RawStatement rawStatement, String id) {
+ Pair additionalProperties = rawStatement.getStatementType() == PARAM_SETTING
+ ? ((SetParamRawStatement) rawStatement).getAdditionalProperty()
+ : null;
+ return new StatementInfoWrapper(rawStatement.getSql(), id, rawStatement.getStatementType(),
+ additionalProperties, rawStatement);
+ }
+}
diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/StatementType.java b/databend-jdbc/src/main/java/com/databend/jdbc/StatementType.java
new file mode 100644
index 00000000..3eff1f3f
--- /dev/null
+++ b/databend-jdbc/src/main/java/com/databend/jdbc/StatementType.java
@@ -0,0 +1,6 @@
+package com.databend.jdbc;
+public enum StatementType {
+ PARAM_SETTING, // SET
+ QUERY, // eg: SELECT, SHOW
+ NON_QUERY // eg: INSERT
+}
diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/StatementUtil.java b/databend-jdbc/src/main/java/com/databend/jdbc/StatementUtil.java
new file mode 100644
index 00000000..d5c41e11
--- /dev/null
+++ b/databend-jdbc/src/main/java/com/databend/jdbc/StatementUtil.java
@@ -0,0 +1,305 @@
+package com.databend.jdbc;
+
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.RegExUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+
+import lombok.CustomLog;
+import lombok.NonNull;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+@CustomLog
+public class StatementUtil {
+
+ private static final String SET_PREFIX = "set";
+ private static final Pattern SET_WITH_SPACE_REGEX = Pattern.compile(SET_PREFIX + " ", Pattern.CASE_INSENSITIVE);
+ private static final String[] SELECT_KEYWORDS = new String[] { "show", "select", "describe", "exists", "explain",
+ "with", "call" };
+
+ /**
+ * Returns true if the statement is a query (eg: SELECT, SHOW).
+ *
+ * @param cleanSql the clean sql (sql statement without comments)
+ * @return true if the statement is a query (eg: SELECT, SHOW).
+ */
+ public static boolean isQuery(String cleanSql) {
+ if (StringUtils.isNotEmpty(cleanSql)) {
+ cleanSql = cleanSql.replace("(", "");
+ return StringUtils.startsWithAny(cleanSql.toLowerCase(), SELECT_KEYWORDS);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Extracts parameter from statement (eg: SET x=y)
+ *
+ * @param cleanSql the clean version of the sql (sql statement without comments)
+ * @param sql the sql statement
+ * @return an optional parameter represented with a pair of key/value
+ */
+ public Optional> extractParamFromSetStatement(@NonNull String cleanSql, String sql) {
+ if (StringUtils.startsWithIgnoreCase(cleanSql, SET_PREFIX)) {
+ return extractPropertyPair(cleanSql, sql);
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Parse the sql statement to a list of {@link StatementInfoWrapper}
+ *
+ * @param sql the sql statement
+ * @return a list of {@link StatementInfoWrapper}
+ */
+ public List parseToStatementInfoWrappers(String sql) {
+ return parseToRawStatementWrapper(sql).getSubStatements().stream().map(StatementInfoWrapper::of)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Parse sql statement to a {@link RawStatementWrapper}. The method construct
+ * the {@link RawStatementWrapper} by splitting it in a list of sub-statements
+ * (supports multistatements)
+ *
+ * @param sql the sql statement
+ * @return a list of {@link StatementInfoWrapper}
+ */
+ public RawStatementWrapper parseToRawStatementWrapper(String sql) {
+ List subStatements = new ArrayList<>();
+ List subStatementParamMarkersPositions = new ArrayList<>();
+ int subQueryStart = 0;
+ int currentIndex = 0;
+ char currentChar = sql.charAt(currentIndex);
+ StringBuilder cleanedSubQuery = isCommentStart(currentChar) ? new StringBuilder()
+ : new StringBuilder(String.valueOf(currentChar));
+ boolean isCurrentSubstringBetweenQuotes = currentChar == '\'';
+ boolean isCurrentSubstringBetweenDoubleQuotes = currentChar == '"';
+ boolean isInSingleLineComment = false;
+ boolean isInMultipleLinesComment = false;
+ boolean isInComment = false;
+ boolean foundSubqueryEndingSemicolon = false;
+ char previousChar;
+ int subQueryParamsCount = 0;
+ boolean isPreviousCharInComment;
+ while (currentIndex++ < sql.length() - 1) {
+ isPreviousCharInComment = isInComment;
+ previousChar = currentChar;
+ currentChar = sql.charAt(currentIndex);
+ isInSingleLineComment = isInSingleLineComment(currentChar, previousChar, isCurrentSubstringBetweenQuotes,
+ isInSingleLineComment);
+ isInMultipleLinesComment = isInMultipleLinesComment(currentChar, previousChar,
+ isCurrentSubstringBetweenQuotes, isInMultipleLinesComment);
+ isInComment = isInSingleLineComment || isInMultipleLinesComment;
+ if (!isInComment) {
+ // Although the ending semicolon may have been found, we need to include any
+ // potential comments to the subquery
+ if (!isCurrentSubstringBetweenQuotes && isEndingSemicolon(currentChar, previousChar,
+ foundSubqueryEndingSemicolon, isPreviousCharInComment)) {
+ foundSubqueryEndingSemicolon = true;
+ if (isEndOfSubquery(currentChar)) {
+ subStatements.add(RawStatement.of(sql.substring(subQueryStart, currentIndex),
+ subStatementParamMarkersPositions, cleanedSubQuery.toString().trim()));
+ subStatementParamMarkersPositions = new ArrayList<>();
+ subQueryStart = currentIndex;
+ foundSubqueryEndingSemicolon = false;
+ cleanedSubQuery = new StringBuilder();
+ }
+ } else if (currentChar == '?' && !isCurrentSubstringBetweenQuotes
+ && !isCurrentSubstringBetweenDoubleQuotes) {
+ subStatementParamMarkersPositions
+ .add(new ParamMarker(++subQueryParamsCount, currentIndex - subQueryStart));
+ } else if (currentChar == '\'') {
+ isCurrentSubstringBetweenQuotes = !isCurrentSubstringBetweenQuotes;
+ } else if (currentChar == '"') {
+ isCurrentSubstringBetweenDoubleQuotes = !isCurrentSubstringBetweenDoubleQuotes;
+ }
+ if (!(isCommentStart(currentChar) && !isCurrentSubstringBetweenQuotes)) {
+ cleanedSubQuery.append(currentChar);
+ }
+ }
+ }
+ subStatements.add(RawStatement.of(sql.substring(subQueryStart, currentIndex), subStatementParamMarkersPositions,
+ cleanedSubQuery.toString().trim()));
+ return new RawStatementWrapper(subStatements);
+ }
+
+ private boolean isEndingSemicolon(char currentChar, char previousChar, boolean foundSubqueryEndingSemicolon,
+ boolean isPreviousCharInComment) {
+ if (foundSubqueryEndingSemicolon) {
+ return true;
+ }
+ return (';' == previousChar && currentChar != ';' && !isPreviousCharInComment);
+ }
+
+ private boolean isEndOfSubquery(char currentChar) {
+ return currentChar != '-' && currentChar != '/' && currentChar != ' ' && currentChar != '\n';
+ }
+
+ private boolean isCommentStart(char currentChar) {
+ return currentChar == '-' || currentChar == '/';
+ }
+
+ private static boolean isInMultipleLinesComment(char currentChar, char previousChar,
+ boolean isCurrentSubstringBetweenQuotes, boolean isInMultipleLinesComment) {
+ if (!isCurrentSubstringBetweenQuotes && (previousChar == '/' && currentChar == '*')) {
+ return true;
+ } else if ((previousChar == '*' && currentChar == '/')) {
+ return false;
+ }
+ return isInMultipleLinesComment;
+ }
+
+ /**
+ * Returns the positions of the params markers
+ *
+ * @param sql the sql statement
+ * @return the positions of the params markers
+ */
+ public Map getParamMarketsPositions(String sql) {
+ RawStatementWrapper rawStatementWrapper = parseToRawStatementWrapper(sql);
+ return rawStatementWrapper.getSubStatements().stream().map(RawStatement::getParamMarkers)
+ .flatMap(Collection::stream).collect(Collectors.toMap(ParamMarker::getId, ParamMarker::getPosition));
+ }
+
+ /**
+ * Extract the database name and the table name from the cleaned sql query
+ *
+ * @param cleanSql the clean sql query
+ * @return the database name and the table name from the sql query as a pair
+ */
+ public Pair, Optional> extractDbNameAndTableNamePairFromCleanQuery(String cleanSql) {
+ Optional from = Optional.empty();
+ if (isQuery(cleanSql)) {
+ log.debug("Extracting DB and Table name for SELECT: {}", cleanSql);
+ String withoutQuotes = StringUtils.replace(cleanSql, "'", "").trim();
+ if (StringUtils.startsWithIgnoreCase(withoutQuotes, "select")) {
+ int fromIndex = StringUtils.indexOfIgnoreCase(withoutQuotes, "from");
+ if (fromIndex != -1) {
+ from = Optional.of(withoutQuotes.substring(fromIndex + "from".length()).trim().split(" ")[0]);
+ }
+ } else if (StringUtils.startsWithIgnoreCase(withoutQuotes, "DESCRIBE")) {
+ from = Optional.of("tables");
+ } else if (StringUtils.startsWithIgnoreCase(withoutQuotes, "SHOW")) {
+ from = Optional.empty(); // Depends on the information requested
+ } else {
+ log.debug("Could not find table name for query {}. This may happen when there is no table.", cleanSql);
+ }
+ }
+ return new ImmutablePair<>(extractDbNameFromFromPartOfTheQuery(from.orElse(null)),
+ extractTableNameFromFromPartOfTheQuery(from.orElse(null)));
+ }
+
+ /**
+ * Returns a list of {@link StatementInfoWrapper} containing sql statements
+ * constructed with the sql statement and the parameters provided
+ *
+ * @param params the parameters
+ * @param sql the sql statement
+ * @return a list of sql statements containing the provided parameters
+ */
+ public static List replaceParameterMarksWithValues(@NonNull Map params,
+ @NonNull String sql) {
+ RawStatementWrapper rawStatementWrapper = parseToRawStatementWrapper(sql);
+ return replaceParameterMarksWithValues(params, rawStatementWrapper);
+ }
+
+ /**
+ * Returns a list of {@link StatementInfoWrapper} containing sql statements
+ * constructed with the {@link RawStatementWrapper} and the parameters provided
+ *
+ * @param params the parameters
+ * @param rawStatement the rawStatement
+ * @return a list of sql statements containing the provided parameters
+ */
+ public List replaceParameterMarksWithValues(@NonNull Map params,
+ @NonNull RawStatementWrapper rawStatement) {
+ List subQueries = new ArrayList<>();
+ for (int subqueryIndex = 0; subqueryIndex < rawStatement.getSubStatements().size(); subqueryIndex++) {
+ int currentPos;
+ /*
+ * As the parameter markers are being placed then the statement sql keeps
+ * getting bigger, which is why we need to keep track of the offset
+ */
+ int offset = 0;
+ RawStatement subQuery = rawStatement.getSubStatements().get(subqueryIndex);
+ String subQueryWithParams = subQuery.getSql();
+
+ if (params.size() != rawStatement.getTotalParams()) {
+ throw new IllegalArgumentException(String.format(
+ "The number of parameters passed does not equal the number of parameter markers in the SQL query. Provided: %d, Parameter markers in the SQL query: %d",
+ params.size(), rawStatement.getTotalParams()));
+ }
+ for (ParamMarker param : subQuery.getParamMarkers()) {
+ String value = params.get(param.getId());
+ if (value == null) {
+ throw new IllegalArgumentException("No value for parameter marker at position: " + param.getId());
+ }
+ currentPos = param.getPosition() + offset;
+ if (currentPos >= subQuery.getSql().length() + offset) {
+ throw new IllegalArgumentException("The position of the parameter marker provided is invalid");
+ }
+ subQueryWithParams = subQueryWithParams.substring(0, currentPos) + value
+ + subQueryWithParams.substring(currentPos + 1);
+ offset += value.length() - 1;
+ }
+ Pair additionalParams = subQuery.getStatementType() == StatementType.PARAM_SETTING
+ ? ((SetParamRawStatement) subQuery).getAdditionalProperty()
+ : null;
+ subQueries.add(new StatementInfoWrapper(subQueryWithParams, UUID.randomUUID().toString(),
+ subQuery.getStatementType(), additionalParams, subQuery));
+
+ }
+ return subQueries;
+ }
+
+ private Optional extractTableNameFromFromPartOfTheQuery(String from) {
+ return Optional.ofNullable(from).map(s -> s.replace("\"", "")).map(fromPartOfTheQuery -> {
+ if (StringUtils.contains(fromPartOfTheQuery, ".")) {
+ int indexOfTableName = StringUtils.lastIndexOf(fromPartOfTheQuery, ".");
+ return fromPartOfTheQuery.substring(indexOfTableName + 1);
+ } else {
+ return fromPartOfTheQuery;
+ }
+ });
+ }
+
+ private static Optional extractDbNameFromFromPartOfTheQuery(String from) {
+ return Optional.ofNullable(from).map(s -> s.replace("\"", ""))
+ .filter(s -> StringUtils.countMatches(s, ".") == 2).map(fromPartOfTheQuery -> {
+ int dbNameEndPos = StringUtils.indexOf(fromPartOfTheQuery, ".");
+ return fromPartOfTheQuery.substring(0, dbNameEndPos);
+ });
+ }
+
+ private boolean isInSingleLineComment(char currentChar, char previousChar, boolean isCurrentSubstringBetweenQuotes,
+ boolean isInSingleLineComment) {
+ if (!isCurrentSubstringBetweenQuotes && (previousChar == '-' && currentChar == '-')) {
+ return true;
+ } else if (currentChar == '\n') {
+ return false;
+ }
+ return isInSingleLineComment;
+ }
+
+ private Optional> extractPropertyPair(String cleanStatement, String sql) {
+ String setQuery = RegExUtils.removeFirst(cleanStatement, SET_WITH_SPACE_REGEX);
+ String[] values = StringUtils.split(setQuery, "=");
+ if (values.length == 2) {
+ String value = StringUtils.removeEnd(values[1], ";").trim();
+ if (StringUtils.isNumeric(value)){
+ return Optional.of(Pair.of(values[0].trim(), value.trim()));
+ } else {
+ return Optional.of(Pair.of(values[0].trim(), StringUtils.removeEnd(StringUtils.removeStart(value, "'"), "'")));
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Cannot parse the additional properties provided in the statement: " + sql);
+ }
+ }
+}
diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/log/DatabendLogger.java b/databend-jdbc/src/main/java/com/databend/jdbc/log/DatabendLogger.java
new file mode 100644
index 00000000..1ce7a2a4
--- /dev/null
+++ b/databend-jdbc/src/main/java/com/databend/jdbc/log/DatabendLogger.java
@@ -0,0 +1,35 @@
+package com.databend.jdbc.log;
+
+public interface DatabendLogger {
+
+ void trace(String message);
+
+ void trace(String message, Object... arguments);
+
+ void trace(String message, Throwable t);
+
+ void debug(String message);
+
+ void debug(String message, Object... arguments);
+
+ void debug(String message, Throwable t);
+
+ void info(String message);
+
+ void info(String message, Object... arguments);
+
+ void info(String message, Throwable t);
+
+ void warn(String message);
+
+ void warn(String message, Object... arguments);
+
+ void warn(String message, Throwable t);
+
+ void error(String message);
+
+ void error(String message, Object... arguments);
+
+ void error(String message, Throwable t);
+
+}
diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/log/JDKLogger.java b/databend-jdbc/src/main/java/com/databend/jdbc/log/JDKLogger.java
new file mode 100644
index 00000000..ad19f26d
--- /dev/null
+++ b/databend-jdbc/src/main/java/com/databend/jdbc/log/JDKLogger.java
@@ -0,0 +1,111 @@
+package com.firebolt.jdbc.log;
+
+import java.util.logging.Level;
+
+public class JDKLogger implements FireboltLogger {
+
+ private final java.util.logging.Logger logger;
+
+ public JDKLogger(String name) {
+ this.logger = java.util.logging.Logger.getLogger(name);
+ }
+
+ @Override
+ public void trace(String message) {
+ logger.log(Level.FINEST, message);
+ }
+
+ @Override
+ public void trace(String message, Object... arguments) {
+ logger.log(Level.FINEST, addMissingArgumentsIndexes(message), arguments);
+ }
+
+ @Override
+ public void trace(String message, Throwable t) {
+ logger.log(Level.FINEST, message, t);
+ }
+
+ @Override
+ public void debug(String message) {
+ logger.log(Level.FINE, message);
+ }
+
+ @Override
+ public void debug(String message, Object... arguments) {
+ logger.log(Level.FINE, addMissingArgumentsIndexes(message), arguments);
+ }
+
+ @Override
+ public void debug(String message, Throwable t) {
+ logger.log(Level.FINE, message, t);
+ }
+
+ @Override
+ public void info(String message) {
+ logger.log(Level.INFO, message);
+ }
+
+ @Override
+ public void info(String message, Object... arguments) {
+ logger.log(Level.INFO, addMissingArgumentsIndexes(message), arguments);
+ }
+
+ @Override
+ public void info(String message, Throwable t) {
+ logger.log(Level.INFO, message, t);
+ }
+
+ @Override
+ public void warn(String message) {
+ logger.log(Level.WARNING, message);
+ }
+
+ @Override
+ public void warn(String message, Object... arguments) {
+ logger.log(Level.WARNING, addMissingArgumentsIndexes(message), arguments);
+ }
+
+ @Override
+ public void warn(String message, Throwable t) {
+ logger.log(Level.WARNING, message, t);
+
+ }
+
+ @Override
+ public void error(String message) {
+ logger.log(Level.SEVERE, message);
+ }
+
+ @Override
+ public void error(String message, Object... arguments) {
+ logger.log(Level.SEVERE, addMissingArgumentsIndexes(message), arguments);
+ }
+
+ @Override
+ public void error(String message, Throwable t) {
+ logger.log(Level.SEVERE, message, t);
+ }
+
+ /**
+ * SLF4J and java.util.logging use a different log format. With SLF4J it is not
+ * required to have argument indexes in the logs (eg: "log.info("hello {}",
+ * "world");), but it is required for java.util.logging (eg: "log.info("hello
+ * {1}", "world");) In this project we use the SLF4J way of logging, which is
+ * why we need to add the missing indexes.
+ */
+ private String addMissingArgumentsIndexes(String message) {
+ StringBuilder result = new StringBuilder();
+ int argumentIndex = 0;
+ int i = 0;
+ while (i < message.length()) {
+ if (message.charAt(i) == '{' && i < message.length() - 1 && message.charAt(i + 1) == '}') {
+ result.append(String.format("{%d}", argumentIndex++));
+ i++;
+ } else {
+ result.append(message.charAt(i));
+ }
+ i++;
+ }
+ return result.toString();
+ }
+}
diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/log/SLF4JLogger.java b/databend-jdbc/src/main/java/com/databend/jdbc/log/SLF4JLogger.java
new file mode 100644
index 00000000..ba769bbf
--- /dev/null
+++ b/databend-jdbc/src/main/java/com/databend/jdbc/log/SLF4JLogger.java
@@ -0,0 +1,89 @@
+package com.firebolt.jdbc.log;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SLF4JLogger implements FireboltLogger {
+
+ private final Logger logger;
+
+ public SLF4JLogger(String name) {
+ logger = LoggerFactory.getLogger(name);
+ }
+
+ @Override
+ public void trace(String message) {
+ logger.trace(message);
+ }
+
+ @Override
+ public void trace(String message, Object... arguments) {
+ logger.trace(message, arguments);
+ }
+
+ @Override
+ public void trace(String message, Throwable t) {
+ logger.trace(message, t);
+ }
+
+ @Override
+ public void debug(String message) {
+ logger.debug(message);
+ }
+
+ @Override
+ public void debug(String message, Object... arguments) {
+ logger.debug(message, arguments);
+
+ }
+
+ @Override
+ public void debug(String message, Throwable t) {
+ logger.debug(message, t);
+ }
+
+ @Override
+ public void info(String message) {
+ logger.info(message);
+ }
+
+ @Override
+ public void info(String message, Object... arguments) {
+ logger.info(message, arguments);
+ }
+
+ @Override
+ public void info(String message, Throwable t) {
+ logger.info(message, t);
+ }
+
+ @Override
+ public void warn(String message) {
+ logger.warn(message);
+ }
+
+ @Override
+ public void warn(String message, Object... arguments) {
+ logger.warn(message, arguments);
+ }
+
+ @Override
+ public void warn(String message, Throwable t) {
+ logger.warn(message, t);
+ }
+
+ @Override
+ public void error(String message) {
+ logger.error(message);
+ }
+
+ @Override
+ public void error(String message, Object... arguments) {
+ logger.error(message, arguments);
+ }
+
+ @Override
+ public void error(String message, Throwable t) {
+ logger.error(message, t);
+ }
+}
diff --git a/databend-jdbc/src/test/java/com/databend/jdbc/TestBasicDriver.java b/databend-jdbc/src/test/java/com/databend/jdbc/TestBasicDriver.java
index db998ba2..7db695f3 100644
--- a/databend-jdbc/src/test/java/com/databend/jdbc/TestBasicDriver.java
+++ b/databend-jdbc/src/test/java/com/databend/jdbc/TestBasicDriver.java
@@ -6,11 +6,7 @@
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
+import java.sql.*;
import java.util.Properties;
import static org.testng.AssertJUnit.assertEquals;
@@ -81,6 +77,19 @@ public void testQueryUpdateCount()
}
}
+ @Test
+ public void testPrepareStatementQuery() throws SQLException {
+ String sql = "SELECT number from numbers(100) where number = ?";
+ Connection connection = createConnection("test_basic_driver");
+ try(PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setInt(1, 1);
+ ResultSet r = statement.executeQuery();
+ statement.execute();
+ r.next();
+ System.out.println(r.getLong("number"));
+ }
+ }
+
@Test(groups = {"IT"})
public void testBasicWithProperties() throws SQLException {
Properties p = new Properties();
diff --git a/lombok.config b/lombok.config
new file mode 100644
index 00000000..62575a36
--- /dev/null
+++ b/lombok.config
@@ -0,0 +1,4 @@
+lombok.anyConstructor.addConstructorProperties = true
+config.stopBubbling = true
+lombok.addLombokGeneratedAnnotation = true
+lombok.log.custom.declaration = com.databend.jdbc.log.DatabendLogger com.databend.jdbc.LoggerUtil.getLogger(NAME)