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)