From 7f650729c021063784462890611edd35dc4d6741 Mon Sep 17 00:00:00 2001 From: hantmac Date: Thu, 19 Oct 2023 16:57:49 +0800 Subject: [PATCH 1/7] feat: impl prepareStatement method --- databend-jdbc/pom.xml | 16 + .../databend/jdbc/NonQueryRawStatement.java | 25 ++ .../java/com/databend/jdbc/ParamMarker.java | 10 + .../com/databend/jdbc/QueryRawStatement.java | 39 +++ .../java/com/databend/jdbc/RawStatement.java | 57 ++++ .../databend/jdbc/RawStatementWrapper.java | 29 ++ .../databend/jdbc/SetParamRawStatement.java | 32 ++ .../databend/jdbc/StatementInfoWrapper.java | 52 +++ .../java/com/databend/jdbc/StatementType.java | 6 + .../java/com/databend/jdbc/StatementUtil.java | 305 ++++++++++++++++++ .../com/databend/jdbc/log/DatabendLogger.java | 35 ++ .../java/com/databend/jdbc/log/JDKLogger.java | 111 +++++++ .../com/databend/jdbc/log/SLF4JLogger.java | 89 +++++ .../com/databend/jdbc/TestBasicDriver.java | 19 +- lombok.config | 4 + 15 files changed, 824 insertions(+), 5 deletions(-) create mode 100644 databend-jdbc/src/main/java/com/databend/jdbc/NonQueryRawStatement.java create mode 100644 databend-jdbc/src/main/java/com/databend/jdbc/ParamMarker.java create mode 100644 databend-jdbc/src/main/java/com/databend/jdbc/QueryRawStatement.java create mode 100644 databend-jdbc/src/main/java/com/databend/jdbc/RawStatement.java create mode 100644 databend-jdbc/src/main/java/com/databend/jdbc/RawStatementWrapper.java create mode 100644 databend-jdbc/src/main/java/com/databend/jdbc/SetParamRawStatement.java create mode 100644 databend-jdbc/src/main/java/com/databend/jdbc/StatementInfoWrapper.java create mode 100644 databend-jdbc/src/main/java/com/databend/jdbc/StatementType.java create mode 100644 databend-jdbc/src/main/java/com/databend/jdbc/StatementUtil.java create mode 100644 databend-jdbc/src/main/java/com/databend/jdbc/log/DatabendLogger.java create mode 100644 databend-jdbc/src/main/java/com/databend/jdbc/log/JDKLogger.java create mode 100644 databend-jdbc/src/main/java/com/databend/jdbc/log/SLF4JLogger.java create mode 100644 lombok.config 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) From 8acd3d0481de3aeb9a83b12cebb699632ddc2f10 Mon Sep 17 00:00:00 2001 From: hantmac Date: Thu, 19 Oct 2023 17:05:51 +0800 Subject: [PATCH 2/7] fix --- databend-jdbc/pom.xml | 7 +++++++ .../main/java/com/databend/jdbc/StatementInfoWrapper.java | 2 +- .../src/main/java/com/databend/jdbc/log/JDKLogger.java | 4 ++-- .../src/main/java/com/databend/jdbc/log/SLF4JLogger.java | 4 ++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/databend-jdbc/pom.xml b/databend-jdbc/pom.xml index dc5699ed..b59b417d 100644 --- a/databend-jdbc/pom.xml +++ b/databend-jdbc/pom.xml @@ -45,6 +45,13 @@ 1.18.30 provided + + + org.slf4j + slf4j-api + 2.0.6 + + diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/StatementInfoWrapper.java b/databend-jdbc/src/main/java/com/databend/jdbc/StatementInfoWrapper.java index 282e3813..7f88beb4 100644 --- a/databend-jdbc/src/main/java/com/databend/jdbc/StatementInfoWrapper.java +++ b/databend-jdbc/src/main/java/com/databend/jdbc/StatementInfoWrapper.java @@ -13,7 +13,7 @@ import lombok.NonNull; /** - * This represents a statement that is ready to be sent to Firebolt or executed + * This represents a statement that is ready to be sent to Databend or executed * internally to set a param */ @Data 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 index ad19f26d..4ae6000e 100644 --- a/databend-jdbc/src/main/java/com/databend/jdbc/log/JDKLogger.java +++ b/databend-jdbc/src/main/java/com/databend/jdbc/log/JDKLogger.java @@ -1,8 +1,8 @@ -package com.firebolt.jdbc.log; +package com.databend.jdbc.log; import java.util.logging.Level; -public class JDKLogger implements FireboltLogger { +public class JDKLogger implements DatabendLogger { private final java.util.logging.Logger logger; 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 index ba769bbf..9876f869 100644 --- a/databend-jdbc/src/main/java/com/databend/jdbc/log/SLF4JLogger.java +++ b/databend-jdbc/src/main/java/com/databend/jdbc/log/SLF4JLogger.java @@ -1,9 +1,9 @@ -package com.firebolt.jdbc.log; +package com.databend.jdbc.log; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SLF4JLogger implements FireboltLogger { +public class SLF4JLogger implements DatabendLogger { private final Logger logger; From 9c5375ecf867f90db3857bc6ca6ac0cac4d1ae44 Mon Sep 17 00:00:00 2001 From: hantmac Date: Thu, 19 Oct 2023 17:18:08 +0800 Subject: [PATCH 3/7] fix --- .../java/com/databend/jdbc/LoggerUtil.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 databend-jdbc/src/main/java/com/databend/jdbc/LoggerUtil.java diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/LoggerUtil.java b/databend-jdbc/src/main/java/com/databend/jdbc/LoggerUtil.java new file mode 100644 index 00000000..12725b0c --- /dev/null +++ b/databend-jdbc/src/main/java/com/databend/jdbc/LoggerUtil.java @@ -0,0 +1,74 @@ +package com.databend.jdbc; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; + +import com.databend.jdbc.log.DatabendLogger; +import com.databend.jdbc.log.JDKLogger; +import com.databend.jdbc.log.SLF4JLogger; + +import lombok.CustomLog; +import lombok.experimental.UtilityClass; + +@UtilityClass +@CustomLog +public class LoggerUtil { + + private static Boolean slf4jAvailable; + + /** + * Provides a {@link DatabendLogger} based on whether SLF4J is available or not. + * + * @param name logger name + * @return a {@link DatabendLogger} + */ + public static DatabendLogger getLogger(String name) { + if (slf4jAvailable == null) { + slf4jAvailable = isSlf4jJAvailable(); + } + + if (slf4jAvailable) { + return new SLF4JLogger(name); + } else { + return new JDKLogger(name); + } + } + + /** + * Logs the {@link InputStream} + * + * @param is the {@link InputStream} + * @return a copy of the {@link InputStream} provided + */ + public InputStream logInputStream(InputStream is) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + byte[] buffer = new byte[1024]; + int len; + while ((len = is.read(buffer)) > -1) { + baos.write(buffer, 0, len); + } + baos.flush(); + InputStream streamToLog = new ByteArrayInputStream(baos.toByteArray()); + String text = new BufferedReader(new InputStreamReader(streamToLog, StandardCharsets.UTF_8)).lines() + .collect(Collectors.joining("\n")); + log.info("======================================"); + log.info(text); + log.info("======================================"); + return new ByteArrayInputStream(baos.toByteArray()); + } catch (Exception ex) { + log.warn("Could not log the stream", ex); + } + return new ByteArrayInputStream(baos.toByteArray()); + } + + private static boolean isSlf4jJAvailable() { + try { + Class.forName("org.slf4j.Logger"); + return true; + } catch (ClassNotFoundException ex) { + return false; + } + } +} From a6ce95c12477505b2acbdd15b2826e6d1c12aac9 Mon Sep 17 00:00:00 2001 From: hantmac Date: Thu, 19 Oct 2023 17:34:03 +0800 Subject: [PATCH 4/7] fix --- .../com/databend/jdbc/TestBasicDriver.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) 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 7db695f3..8b4b519f 100644 --- a/databend-jdbc/src/test/java/com/databend/jdbc/TestBasicDriver.java +++ b/databend-jdbc/src/test/java/com/databend/jdbc/TestBasicDriver.java @@ -77,18 +77,18 @@ 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 +// 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 { From 0aedadd5ecf55820c59dcda797d5e75546926ae7 Mon Sep 17 00:00:00 2001 From: hantmac Date: Wed, 15 Nov 2023 16:32:38 +0800 Subject: [PATCH 5/7] fix testPrepareStatementQuery --- databend-jdbc/pom.xml | 11 +++++ .../jdbc/DatabendPreparedStatement.java | 4 +- .../java/com/databend/jdbc/StatementUtil.java | 12 +++--- .../jdbc/parser/BatchInsertUtils.java | 9 +++- .../com/databend/jdbc/StatementUtilTest.java | 41 +++++++++++++++++++ .../com/databend/jdbc/TestBasicDriver.java | 26 ++++++------ 6 files changed, 82 insertions(+), 21 deletions(-) create mode 100644 databend-jdbc/src/test/java/com/databend/jdbc/StatementUtilTest.java diff --git a/databend-jdbc/pom.xml b/databend-jdbc/pom.xml index b59b417d..15b5c18d 100644 --- a/databend-jdbc/pom.xml +++ b/databend-jdbc/pom.xml @@ -97,6 +97,17 @@ gson 2.6.2 + + junit + junit + 4.13.2 + test + + + org.junit.jupiter + junit-jupiter + test + diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/DatabendPreparedStatement.java b/databend-jdbc/src/main/java/com/databend/jdbc/DatabendPreparedStatement.java index 4e2b084a..30cdef88 100644 --- a/databend-jdbc/src/main/java/com/databend/jdbc/DatabendPreparedStatement.java +++ b/databend-jdbc/src/main/java/com/databend/jdbc/DatabendPreparedStatement.java @@ -52,6 +52,7 @@ import static com.databend.jdbc.ObjectCasts.castToInt; import static com.databend.jdbc.ObjectCasts.castToLong; import static com.databend.jdbc.ObjectCasts.castToShort; +import static com.databend.jdbc.StatementUtil.replaceParameterMarksWithValues; import static java.lang.String.format; import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE; import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME; @@ -346,7 +347,8 @@ public int[] executeBatch() throws SQLException { @Override public ResultSet executeQuery() throws SQLException { - this.executeBatch(); + String sql = replaceParameterMarksWithValues(batchInsertUtils.get().getProvideParams(), this.originalSql).get(0).getSql(); + internalExecute(sql, null); return getResultSet(); } diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/StatementUtil.java b/databend-jdbc/src/main/java/com/databend/jdbc/StatementUtil.java index d5c41e11..9d0052f2 100644 --- a/databend-jdbc/src/main/java/com/databend/jdbc/StatementUtil.java +++ b/databend-jdbc/src/main/java/com/databend/jdbc/StatementUtil.java @@ -19,8 +19,8 @@ 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" }; + 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). @@ -41,7 +41,7 @@ public static boolean isQuery(String cleanSql) { * 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 + * @param sql the sql statement * @return an optional parameter represented with a pair of key/value */ public Optional> extractParamFromSetStatement(@NonNull String cleanSql, String sql) { @@ -200,7 +200,7 @@ public Pair, Optional> extractDbNameAndTableNamePairFro * constructed with the sql statement and the parameters provided * * @param params the parameters - * @param sql the sql statement + * @param sql the sql statement * @return a list of sql statements containing the provided parameters */ public static List replaceParameterMarksWithValues(@NonNull Map params, @@ -213,7 +213,7 @@ public static List replaceParameterMarksWithValues(@NonNul * Returns a list of {@link StatementInfoWrapper} containing sql statements * constructed with the {@link RawStatementWrapper} and the parameters provided * - * @param params the parameters + * @param params the parameters * @param rawStatement the rawStatement * @return a list of sql statements containing the provided parameters */ @@ -292,7 +292,7 @@ private Optional> extractPropertyPair(String cleanStatement String[] values = StringUtils.split(setQuery, "="); if (values.length == 2) { String value = StringUtils.removeEnd(values[1], ";").trim(); - if (StringUtils.isNumeric(value)){ + 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, "'"), "'"))); diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/parser/BatchInsertUtils.java b/databend-jdbc/src/main/java/com/databend/jdbc/parser/BatchInsertUtils.java index fb0012fc..ef393933 100644 --- a/databend-jdbc/src/main/java/com/databend/jdbc/parser/BatchInsertUtils.java +++ b/databend-jdbc/src/main/java/com/databend/jdbc/parser/BatchInsertUtils.java @@ -1,6 +1,5 @@ package com.databend.jdbc.parser; -import com.databend.jdbc.DatabendPreparedStatement; import de.siegmar.fastcsv.writer.CsvWriter; import de.siegmar.fastcsv.writer.LineDelimiter; @@ -50,6 +49,14 @@ public String getSql() { return sql; } + public Map getProvideParams() { + Map m = new TreeMap<>(); + for (Map.Entry elem : placeHolderEntries.entrySet()) { + m.put(elem.getKey() + 1, elem.getValue()); + } + return m; + } + public String getDatabaseTableName() { Pattern pattern = Pattern.compile("^INSERT INTO\\s+((?:[\\w-]+\\.)?([\\w-]+))(?:\\s*\\((?:[^()]|\\([^()]*\\))*\\))?", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(sql.replace("`", "")); diff --git a/databend-jdbc/src/test/java/com/databend/jdbc/StatementUtilTest.java b/databend-jdbc/src/test/java/com/databend/jdbc/StatementUtilTest.java new file mode 100644 index 00000000..d374e309 --- /dev/null +++ b/databend-jdbc/src/test/java/com/databend/jdbc/StatementUtilTest.java @@ -0,0 +1,41 @@ +package com.databend.jdbc; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static com.databend.jdbc.StatementUtil.replaceParameterMarksWithValues; +import static org.junit.jupiter.api.Assertions.*; +public class StatementUtilTest { + @Test + void shouldGetAllQueryParamsFromIn() { + String sql = "SElECT * FROM EMPLOYEES WHERE id IN (?,?)"; + assertEquals(ImmutableMap.of(1, 37, 2, 39), StatementUtil.getParamMarketsPositions(sql)); + assertEquals(1, StatementUtil.parseToRawStatementWrapper(sql).getSubStatements().size()); + } + @Test + void shouldGetAllQueryParams() { + String sql = "SElECT * FROM EMPLOYEES WHERE id = ?"; + assertEquals(ImmutableMap.of(1, 35), StatementUtil.getParamMarketsPositions(sql)); + assertEquals(1, StatementUtil.parseToRawStatementWrapper(sql).getSubStatements().size()); + } + + @Test + void shouldReplaceAQueryParam() { + String sql = "SElECT * FROM EMPLOYEES WHERE id is ?"; + String expectedSql = "SElECT * FROM EMPLOYEES WHERE id is 5"; + Map params = ImmutableMap.of(1, "5"); + System.out.println(replaceParameterMarksWithValues(params, sql)); + assertEquals(expectedSql, replaceParameterMarksWithValues(params, sql).get(0).getSql()); + } + + @Test + void shouldReplaceMultipleQueryParams() { + String sql = "SElECT * FROM EMPLOYEES WHERE id = ? AND name LIKE ? AND dob = ? "; + String expectedSql = "SElECT * FROM EMPLOYEES WHERE id = 5 AND name LIKE 'George' AND dob = '1980-05-22' "; + Map params = ImmutableMap.of(1, "5", 2, "'George'", 3, "'1980-05-22'"); + assertEquals(expectedSql, replaceParameterMarksWithValues(params, sql).get(0).getSql()); + } +} 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 8b4b519f..68d94959 100644 --- a/databend-jdbc/src/test/java/com/databend/jdbc/TestBasicDriver.java +++ b/databend-jdbc/src/test/java/com/databend/jdbc/TestBasicDriver.java @@ -77,19 +77,6 @@ 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(); @@ -112,6 +99,19 @@ public void testBasicWithProperties() throws SQLException { } } + @Test + public void testPrepareStatementQuery() throws SQLException { + String sql = "SELECT number from numbers(100) where number = ? or number = ?"; + Connection conn = createConnection("test_basic_driver"); + try (PreparedStatement statement = conn.prepareStatement(sql)) { + statement.setInt(1, 1); + statement.setInt(2, 2); + ResultSet r = statement.executeQuery(); + r.next(); + System.out.println(r.getLong("number")); + } + } + @Test(groups = {"IT"}) public void testBasicWithDatabase() throws SQLException { From a42df62390f04d8a2d688d1e469b296c9d58ca03 Mon Sep 17 00:00:00 2001 From: hantmac Date: Wed, 15 Nov 2023 19:54:13 +0800 Subject: [PATCH 6/7] fix prestatement execute --- .../jdbc/DatabendPreparedStatement.java | 35 ++++++++++++++++--- .../databend/jdbc/TestPrepareStatement.java | 15 ++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/DatabendPreparedStatement.java b/databend-jdbc/src/main/java/com/databend/jdbc/DatabendPreparedStatement.java index 30cdef88..4af2c46a 100644 --- a/databend-jdbc/src/main/java/com/databend/jdbc/DatabendPreparedStatement.java +++ b/databend-jdbc/src/main/java/com/databend/jdbc/DatabendPreparedStatement.java @@ -5,6 +5,7 @@ import com.databend.jdbc.cloud.DatabendStage; import com.databend.jdbc.parser.BatchInsertUtils; import com.solidfire.gson.Gson; +import lombok.NonNull; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; @@ -42,6 +43,7 @@ import java.util.*; import java.util.function.Consumer; import java.util.logging.Logger; +import java.util.stream.Collectors; import static com.databend.jdbc.ObjectCasts.castToBigDecimal; import static com.databend.jdbc.ObjectCasts.castToBinary; @@ -61,6 +63,7 @@ public class DatabendPreparedStatement extends DatabendStatement implements PreparedStatement { private static final Logger logger = Logger.getLogger(DatabendPreparedStatement.class.getPackage().getName()); static final DateTimeFormatter DATE_FORMATTER = ISODateTimeFormat.date(); + private final RawStatementWrapper rawStatement; static final DateTimeFormatter TIME_FORMATTER = DateTimeFormat.forPattern("HH:mm:ss.SSS"); static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS"); private static final java.time.format.DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = @@ -86,6 +89,7 @@ public class DatabendPreparedStatement extends DatabendStatement implements Prep this.originalSql = requireNonNull(sql, "sql is null"); this.batchValues = new ArrayList<>(); this.batchInsertUtils = BatchInsertUtils.tryParseInsertSql(sql); + this.rawStatement = StatementUtil.parseToRawStatementWrapper(sql); } private static String formatBooleanLiteral(boolean x) { @@ -352,6 +356,32 @@ public ResultSet executeQuery() return getResultSet(); } + private List prepareSQL(@NonNull Map params) { + return replaceParameterMarksWithValues(params, this.rawStatement); + } + + @Override + public boolean execute() + throws SQLException { + return this.execute(prepareSQL(batchInsertUtils.get().getProvideParams())).isPresent(); + } + + protected Optional execute(List statements) throws SQLException { + Optional resultSet = Optional.empty(); + try { + for (int i = 0; i < statements.size(); i++) { + if (i == 0) { + internalExecute(statements.get(i).getSql(), null); + resultSet = Optional.ofNullable(getResultSet()); + } else { + internalExecute(statements.get(i).getSql(), null); + } + } + } finally { + } + return resultSet; + } + @Override public int executeUpdate() throws SQLException { @@ -632,11 +662,6 @@ public static String convertArrayListToString(ArrayList arrayList) { return builder.toString(); } - @Override - public boolean execute() - throws SQLException { - return false; - } @Override public void addBatch() diff --git a/databend-jdbc/src/test/java/com/databend/jdbc/TestPrepareStatement.java b/databend-jdbc/src/test/java/com/databend/jdbc/TestPrepareStatement.java index 9879b737..c0454969 100644 --- a/databend-jdbc/src/test/java/com/databend/jdbc/TestPrepareStatement.java +++ b/databend-jdbc/src/test/java/com/databend/jdbc/TestPrepareStatement.java @@ -1,5 +1,6 @@ package com.databend.jdbc; +import org.junit.jupiter.api.Assertions; import org.testng.Assert; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -282,4 +283,18 @@ public void TestBatchReplaceInto() throws SQLException { System.out.println(r.getInt(2)); } } + + @Test + public void testPrepareStatementExecute() throws SQLException { + Connection conn = createConnection(); + String sql = "SELECT number from numbers(100) where number = ?"; + try(PreparedStatement statement = conn.prepareStatement(sql)) { + statement.setInt(1, 1); + statement.execute(); + ResultSet r = statement.getResultSet(); + r.next(); + Assertions.assertEquals(1, r.getLong("number")); + System.out.println(r.getLong("number")); + } + } } From 582a03b7e4bdbd116ddc419088c2c034a02fb97c Mon Sep 17 00:00:00 2001 From: hantmac Date: Thu, 16 Nov 2023 10:42:11 +0800 Subject: [PATCH 7/7] fix ExecuteUpdate --- .../jdbc/DatabendPreparedStatement.java | 6 +++--- .../databend/jdbc/TestPrepareStatement.java | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/databend-jdbc/src/main/java/com/databend/jdbc/DatabendPreparedStatement.java b/databend-jdbc/src/main/java/com/databend/jdbc/DatabendPreparedStatement.java index 4af2c46a..ed0dfb2f 100644 --- a/databend-jdbc/src/main/java/com/databend/jdbc/DatabendPreparedStatement.java +++ b/databend-jdbc/src/main/java/com/databend/jdbc/DatabendPreparedStatement.java @@ -383,9 +383,9 @@ protected Optional execute(List statements) thr } @Override - public int executeUpdate() - throws SQLException { - return 0; + public int executeUpdate() throws SQLException { + this.execute(prepareSQL(batchInsertUtils.get().getProvideParams())).isPresent(); + return batchInsertUtils.get().getProvideParams().size(); } @Override diff --git a/databend-jdbc/src/test/java/com/databend/jdbc/TestPrepareStatement.java b/databend-jdbc/src/test/java/com/databend/jdbc/TestPrepareStatement.java index c0454969..b420fc96 100644 --- a/databend-jdbc/src/test/java/com/databend/jdbc/TestPrepareStatement.java +++ b/databend-jdbc/src/test/java/com/databend/jdbc/TestPrepareStatement.java @@ -15,6 +15,7 @@ import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ThreadLocalRandom; public class TestPrepareStatement { private Connection createConnection() @@ -88,8 +89,8 @@ public void TestConvertSQLWithBatchValues() throws SQLException { List batchValues1 = new ArrayList<>(); // Add string arrays to batchValues - String[] values3 = {"1","2"}; - String[] values4 = {"3","4"}; + String[] values3 = {"1", "2"}; + String[] values4 = {"3", "4"}; batchValues1.add(values3); batchValues1.add(values4); @@ -288,7 +289,7 @@ public void TestBatchReplaceInto() throws SQLException { public void testPrepareStatementExecute() throws SQLException { Connection conn = createConnection(); String sql = "SELECT number from numbers(100) where number = ?"; - try(PreparedStatement statement = conn.prepareStatement(sql)) { + try (PreparedStatement statement = conn.prepareStatement(sql)) { statement.setInt(1, 1); statement.execute(); ResultSet r = statement.getResultSet(); @@ -297,4 +298,16 @@ public void testPrepareStatementExecute() throws SQLException { System.out.println(r.getLong("number")); } } + + @Test + public void testPrepareStatementExecuteUpdate() throws SQLException { + String sql = "insert into test_prepare_statement values (?,?)"; + Connection conn = createConnection(); + try (PreparedStatement statement = conn.prepareStatement(sql)) { + statement.setInt(1, 1); + statement.setInt(2, 2); + int result = statement.executeUpdate(); + Assertions.assertEquals(2, result); + } + } }