Skip to content

Commit

Permalink
Reworked to no longer use static methods for to_char and to_timestamp
Browse files Browse the repository at this point in the history
* ToCharPgImplementor and ToTimestampPgImplementor now use a a new instance of DateFormatFunctionPg
* Moved the PostgreSQL date/time formatting and parsing methods to their own class
  • Loading branch information
normanj-bitquill committed Sep 24, 2024
1 parent 4761c94 commit b6a308f
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4849,10 +4849,12 @@ private static class ToCharPgImplementor extends AbstractRexCallImplementor {

@Override Expression implementSafe(RexToLixTranslator translator, RexCall call,
List<Expression> argValueList) {
final Expression target =
Expressions.new_(BuiltInMethod.TO_CHAR_PG.method.getDeclaringClass(),
new ParameterExpression(0, DataContext.class, "root"));
final Expression operand0 = argValueList.get(0);
final Expression operand1 = argValueList.get(1);
return Expressions.call(BuiltInMethod.TO_CHAR_PG.method, translator.getRoot(),
operand0, operand1);
return Expressions.call(target, BuiltInMethod.TO_CHAR_PG.method, operand0, operand1);
}
}

Expand All @@ -4867,9 +4869,12 @@ private static class ToTimestampPgImplementor extends AbstractRexCallImplementor

@Override Expression implementSafe(RexToLixTranslator translator, RexCall call,
List<Expression> argValueList) {
final Expression target =
Expressions.new_(method.getDeclaringClass(),
new ParameterExpression(0, DataContext.class, "root"));
final Expression operand0 = argValueList.get(0);
final Expression operand1 = argValueList.get(1);
return Expressions.call(method, translator.getRoot(), operand0, operand1);
return Expressions.call(target, method, operand0, operand1);
}
}
}
134 changes: 72 additions & 62 deletions core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -4344,20 +4344,6 @@ public static long unixDateExtract(String rangeString, long date) {
* {@code FORMAT_DATETIME}, {@code FORMAT_TIME}, {@code TO_CHAR} functions. */
@Deterministic
public static class DateFormatFunction {
// Timezone to use for PostgreSQL parsing of timestamps
private static final ZoneId LOCAL_ZONE;
static {
ZoneId zoneId;
try {
// Currently the parsed timestamps are expected to be the number of
// milliseconds since the epoch in UTC, with no timezone information
zoneId = ZoneId.of("UTC");
} catch (Exception e) {
zoneId = ZoneId.systemDefault();
}
LOCAL_ZONE = zoneId;
}

/** Work space for various functions. Clear it before you use it. */
final StringBuilder sb = new StringBuilder();

Expand All @@ -4373,11 +4359,6 @@ private static class Key extends MapEntry<FormatModel, String> {
.maximumSize(FUNCTION_LEVEL_CACHE_MAX_SIZE.value())
.build(CacheLoader.from(key -> key.t.parseNoCache(key.u)));

private static final LoadingCache<String, CompiledDateTimeFormat> FORMAT_CACHE_PG =
CacheBuilder.newBuilder()
.maximumSize(FUNCTION_LEVEL_CACHE_MAX_SIZE.value())
.build(CacheLoader.from(PostgresqlDateTimeFormatter::compilePattern));

/** Given a format string and a format model, calls an action with the
* list of elements obtained by parsing that format string. */
protected final void withElements(FormatModel formatModel, String format,
Expand Down Expand Up @@ -4408,59 +4389,16 @@ public String toChar(long timestamp, String pattern) {
return sb.toString().trim();
}

public static String toCharPg(DataContext root, long timestamp, String pattern) {
final ZoneId zoneId = DataContext.Variable.TIME_ZONE.<TimeZone>get(root).toZoneId();
final Locale locale = requireNonNull(DataContext.Variable.LOCALE.get(root));
final CompiledDateTimeFormat dateTimeFormat = FORMAT_CACHE_PG.getUnchecked(pattern);
final Timestamp sqlTimestamp = internalToTimestamp(timestamp);
final ZonedDateTime zonedDateTime =
ZonedDateTime.of(sqlTimestamp.toLocalDateTime(), zoneId);
return dateTimeFormat.formatDateTime(zonedDateTime, locale);
}

public int toDate(String dateString, String fmtString) {
return toInt(
new java.sql.Date(internalToDateTime(dateString, fmtString)));
}

public static int toDatePg(DataContext root, String dateString, String fmtString) {
try {
final Locale locale = requireNonNull(DataContext.Variable.LOCALE.get(root));
final CompiledDateTimeFormat dateTimeFormat = FORMAT_CACHE_PG.getUnchecked(fmtString);
return (int) dateTimeFormat.parseDateTime(dateString, LOCAL_ZONE, locale).getLong(
ChronoField.EPOCH_DAY);
} catch (Exception e) {
SQLException sqlEx =
new SQLException(
String.format(Locale.ROOT,
"Invalid format: '%s' for datetime string: '%s'.", fmtString,
dateString));
throw Util.toUnchecked(sqlEx);
}
}

public long toTimestamp(String timestampString, String fmtString) {
return toLong(
new java.sql.Timestamp(internalToDateTime(timestampString, fmtString)));
}

public static long toTimestampPg(DataContext root, String timestampString, String fmtString) {
try {
final Locale locale = requireNonNull(DataContext.Variable.LOCALE.get(root));
final CompiledDateTimeFormat dateTimeFormat = FORMAT_CACHE_PG.getUnchecked(fmtString);
return dateTimeFormat.parseDateTime(timestampString, LOCAL_ZONE, locale)
.toInstant()
.toEpochMilli();
} catch (Exception e) {
SQLException sqlEx =
new SQLException(
String.format(Locale.ROOT,
"Invalid format: '%s' for timestamp string: '%s'.", fmtString,
timestampString));
throw Util.toUnchecked(sqlEx);
}
}

private long internalToDateTime(String dateString, String fmtString) {
final ParsePosition pos = new ParsePosition(0);

Expand Down Expand Up @@ -4494,6 +4432,78 @@ public String formatTime(String fmtString, int time) {
}
}

/** State for {@code FORMAT_DATE}, {@code FORMAT_TIMESTAMP},
* {@code FORMAT_DATETIME}, {@code FORMAT_TIME}, {@code TO_CHAR} functions. */
@Deterministic
public static class DateFormatFunctionPg {
// Timezone to use for PostgreSQL parsing of timestamps
private static final ZoneId LOCAL_ZONE;
static {
ZoneId zoneId;
try {
// Currently the parsed timestamps are expected to be the number of
// milliseconds since the epoch in UTC, with no timezone information
zoneId = ZoneId.of("UTC");
} catch (Exception e) {
zoneId = ZoneId.systemDefault();
}
LOCAL_ZONE = zoneId;
}

private final DataContext dataContext;
private final LoadingCache<String, CompiledDateTimeFormat> formatCachePg =
CacheBuilder.newBuilder()
.maximumSize(FUNCTION_LEVEL_CACHE_MAX_SIZE.value())
.build(CacheLoader.from(PostgresqlDateTimeFormatter::compilePattern));

public DateFormatFunctionPg(DataContext dataContext) {
this.dataContext = dataContext;
}

public String toChar(long timestamp, String pattern) {
final ZoneId zoneId = DataContext.Variable.TIME_ZONE.<TimeZone>get(dataContext).toZoneId();
final Locale locale = requireNonNull(DataContext.Variable.LOCALE.get(dataContext));
final CompiledDateTimeFormat dateTimeFormat = formatCachePg.getUnchecked(pattern);
final Timestamp sqlTimestamp = internalToTimestamp(timestamp);
final ZonedDateTime zonedDateTime =
ZonedDateTime.of(sqlTimestamp.toLocalDateTime(), zoneId);
return dateTimeFormat.formatDateTime(zonedDateTime, locale);
}

public int toDate(String dateString, String fmtString) {
try {
final Locale locale = requireNonNull(DataContext.Variable.LOCALE.get(dataContext));
final CompiledDateTimeFormat dateTimeFormat = formatCachePg.getUnchecked(fmtString);
return (int) dateTimeFormat.parseDateTime(dateString, LOCAL_ZONE, locale).getLong(
ChronoField.EPOCH_DAY);
} catch (Exception e) {
SQLException sqlEx =
new SQLException(
String.format(Locale.ROOT,
"Invalid format: '%s' for datetime string: '%s'.", fmtString,
dateString));
throw Util.toUnchecked(sqlEx);
}
}

public long toTimestamp(String timestampString, String fmtString) {
try {
final Locale locale = requireNonNull(DataContext.Variable.LOCALE.get(dataContext));
final CompiledDateTimeFormat dateTimeFormat = formatCachePg.getUnchecked(fmtString);
return dateTimeFormat.parseDateTime(timestampString, LOCAL_ZONE, locale)
.toInstant()
.toEpochMilli();
} catch (Exception e) {
SQLException sqlEx =
new SQLException(
String.format(Locale.ROOT,
"Invalid format: '%s' for timestamp string: '%s'.", fmtString,
timestampString));
throw Util.toUnchecked(sqlEx);
}
}
}

/** State for {@code PARSE_DATE}, {@code PARSE_TIMESTAMP},
* {@code PARSE_DATETIME}, {@code PARSE_TIME} functions. */
@Deterministic
Expand Down
12 changes: 6 additions & 6 deletions core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -708,16 +708,16 @@ public enum BuiltInMethod {
String.class, long.class),
TO_CHAR(SqlFunctions.DateFormatFunction.class, "toChar", long.class,
String.class),
TO_CHAR_PG(SqlFunctions.DateFormatFunction.class, "toCharPg",
DataContext.class, long.class, String.class),
TO_CHAR_PG(SqlFunctions.DateFormatFunctionPg.class, "toChar", long.class,
String.class),
TO_DATE(SqlFunctions.DateFormatFunction.class, "toDate", String.class,
String.class),
TO_DATE_PG(SqlFunctions.DateFormatFunction.class, "toDatePg",
DataContext.class, String.class, String.class),
TO_DATE_PG(SqlFunctions.DateFormatFunctionPg.class, "toDate", String.class,
String.class),
TO_TIMESTAMP(SqlFunctions.DateFormatFunction.class, "toTimestamp", String.class,
String.class),
TO_TIMESTAMP_PG(SqlFunctions.DateFormatFunction.class, "toTimestampPg",
DataContext.class, String.class, String.class),
TO_TIMESTAMP_PG(SqlFunctions.DateFormatFunctionPg.class, "toTimestamp",
String.class, String.class),
FORMAT_DATE(SqlFunctions.DateFormatFunction.class, "formatDate",
String.class, int.class),
FORMAT_TIME(SqlFunctions.DateFormatFunction.class, "formatTime",
Expand Down

0 comments on commit b6a308f

Please sign in to comment.