diff --git a/calcite-rs-jni/calcite b/calcite-rs-jni/calcite index 7a71388..ca4decf 160000 --- a/calcite-rs-jni/calcite +++ b/calcite-rs-jni/calcite @@ -1 +1 @@ -Subproject commit 7a71388926ac09d6faa02b6f5885524bd2886cff +Subproject commit ca4decfd4aa9270c9a8ccdd0c3d90ac468999c09 diff --git a/calcite-rs-jni/jni/src/main/java/com/hasura/StatementPreparer.java b/calcite-rs-jni/jni/src/main/java/com/hasura/StatementPreparer.java index b328a6c..e1a771f 100644 --- a/calcite-rs-jni/jni/src/main/java/com/hasura/StatementPreparer.java +++ b/calcite-rs-jni/jni/src/main/java/com/hasura/StatementPreparer.java @@ -4,13 +4,15 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; +import java.sql.Date; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; - /** * The StatementPreparer class is responsible for preparing a SQL statement by replacing * marked up strings and indexed question marks with the actual values. @@ -41,6 +43,8 @@ public static PreparedStatement prepare(String input, Connection connection) thr preparedStatement.setString(i + 1, (String) item); } else if (item instanceof Timestamp) { preparedStatement.setTimestamp(i + 1, (Timestamp) item); + } else if (item instanceof Date) { + preparedStatement.setDate(i + 1, (Date) item); } } return preparedStatement; @@ -81,35 +85,116 @@ private static ArrayList extractMarkedUpStrings(String input) { * But makes a special exception for UTC formatted dates, in which case it * converts them to ANSI SQL dates in local time. * - * @param input The SQL statement to process. + * @param input The SQL statement to process. * @param extractedStrings The list of extracted marked-up strings. * @return The input SQL statement with marked up strings replaced by indexed question marks. */ private static String replaceWithIndexedQuestionMarks(String input, ArrayList extractedStrings) { Pattern pattern = Pattern.compile("^\\d{4}-\\d{1,2}-\\d{1,2}([T\\s]\\d{2}:\\d{2}:\\d{2}(\\.\\d{3}))Z$"); DateTimeFormatter rfcFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX"); - DateTimeFormatter localFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); for (int i = 0; i < extractedStrings.size(); ++i) { Matcher matcher = pattern.matcher((String) extractedStrings.get(i)); if (matcher.matches()) { - // if it seems like it is a date - we are going to tyr and make it a date constant + // if it seems like it is a date - we are going to try and make it a date constant try { // if the pattern follows the exact pattern that comes from the hasura NDC // we will convert it to a ANSI SQL timestamp. // Otherwise, we will convert it to a string constant verbatim. ZonedDateTime zonedDateTime = ZonedDateTime.parse((String) extractedStrings.get(i), rfcFormatter); + zonedDateTime = zonedDateTime.withZoneSameInstant(ZoneOffset.UTC); // adjust timezone to UTC + + Object convertedDateTime; // This will store either a Date or a Timestamp + if (zonedDateTime.toLocalTime().toSecondOfDay() == 0) { + // Represents the start of the day in UTC + convertedDateTime = Date.valueOf(zonedDateTime.toLocalDate()); // Convert to java.sql.Date with LocalDate + } else { + // Does not represent the start of the day in UTC + convertedDateTime = Timestamp.from(zonedDateTime.toInstant()); // Convert to Timestamp + } + input = input.replace( STRING_MARKER + extractedStrings.get(i) + STRING_MARKER, PARAM_MARKER + i + PARAM_MARKER ); - extractedStrings.set(i, Timestamp.from(zonedDateTime.toInstant())); - } catch(Exception ignored) { + extractedStrings.set(i, convertedDateTime); + } catch (Exception ignored) { input = input.replace( STRING_MARKER + extractedStrings.get(i) + STRING_MARKER, PARAM_MARKER + i + PARAM_MARKER ); } - // if it's not a date constant, we will convert it into string parameter + } else if (((String) extractedStrings.get(i)).matches("\\d{4}-\\d{2}-\\d{2}")) { // Match YYYY-MM-DD pattern + try { + Date date = Date.valueOf((String) extractedStrings.get(i)); // Convert to java.sql.Date + input = input.replace( + STRING_MARKER + extractedStrings.get(i) + STRING_MARKER, + PARAM_MARKER + i + PARAM_MARKER + ); + extractedStrings.set(i, date); + } catch (Exception ignored) { + input = input.replace( + STRING_MARKER + extractedStrings.get(i) + STRING_MARKER, + PARAM_MARKER + i + PARAM_MARKER + ); + } + } else if (((String) extractedStrings.get(i)).startsWith("DATE::")) { + try { + String dateStr = ((String) extractedStrings.get(i)).replace("DATE::", ""); + Date date = Date.valueOf(dateStr); + input = input.replace( + STRING_MARKER + extractedStrings.get(i) + STRING_MARKER, + PARAM_MARKER + i + PARAM_MARKER + ); + extractedStrings.set(i, date); + } catch (Exception ignored) { + input = input.replace( + STRING_MARKER + extractedStrings.get(i) + STRING_MARKER, + PARAM_MARKER + i + PARAM_MARKER + ); + } + // if it's not a date constant, we will convert it into string parameter + } else if (((String) extractedStrings.get(i)).startsWith("TIMESTAMP::")) { + String rfcDateString = ((String) extractedStrings.get(i)).replace("TIMESTAMP::", ""); + DateTimeFormatter RFC_1123_DATE_TIME = DateTimeFormatter.RFC_1123_DATE_TIME; + DateTimeFormatter RFC_3339_DATE_TIME = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .appendPattern("yyyy-MM-dd'T'HH:mm:ss") + .optionalStart() + .appendPattern(".SSS") + .optionalEnd() + .appendPattern("XXX") + .toFormatter(); + try { + ZonedDateTime zonedDateTime = ZonedDateTime.parse(rfcDateString, RFC_1123_DATE_TIME); + Timestamp timestamp = Timestamp.from(zonedDateTime.toInstant()); + input = input.replace( + STRING_MARKER + extractedStrings.get(i) + STRING_MARKER, + PARAM_MARKER + i + PARAM_MARKER + ); + extractedStrings.set(i, timestamp); + } catch (Exception ignored) { + try { + ZonedDateTime zonedDateTime = ZonedDateTime.parse(rfcDateString, RFC_3339_DATE_TIME); + Timestamp timestamp = Timestamp.from(zonedDateTime.toInstant()); + input = input.replace( + STRING_MARKER + extractedStrings.get(i) + STRING_MARKER, + PARAM_MARKER + i + PARAM_MARKER + ); + extractedStrings.set(i, timestamp); + } catch (Exception ignore) { + input = input.replace( + STRING_MARKER + extractedStrings.get(i) + STRING_MARKER, + PARAM_MARKER + i + PARAM_MARKER + ); + } + } + // if it's not a date constant, we will convert it into string parameter + } else if (((String) extractedStrings.get(i)).startsWith("STRING::")) { + String rfcDateString = ((String) extractedStrings.get(i)).replace("STRING::", ""); + input = input.replace( + STRING_MARKER + extractedStrings.get(i) + STRING_MARKER, + PARAM_MARKER + i + PARAM_MARKER + ); } else { input = input.replace( STRING_MARKER + extractedStrings.get(i) + STRING_MARKER,