-
Notifications
You must be signed in to change notification settings - Fork 3
Backport to branch(3) : Support time-related data types in generic function #211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,20 @@ | ||
package com.scalar.dl.genericcontracts.object; | ||
|
||
import static java.time.temporal.ChronoField.DAY_OF_MONTH; | ||
import static java.time.temporal.ChronoField.HOUR_OF_DAY; | ||
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; | ||
import static java.time.temporal.ChronoField.MONTH_OF_YEAR; | ||
import static java.time.temporal.ChronoField.NANO_OF_SECOND; | ||
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; | ||
import static java.time.temporal.ChronoField.YEAR; | ||
|
||
import java.time.ZoneOffset; | ||
import java.time.chrono.IsoChronology; | ||
import java.time.format.DateTimeFormatter; | ||
import java.time.format.DateTimeFormatterBuilder; | ||
import java.time.format.ResolverStyle; | ||
import java.time.format.SignStyle; | ||
|
||
public class Constants { | ||
|
||
// Object authenticity management | ||
|
@@ -49,5 +64,62 @@ public class Constants { | |
public static final String COLLECTION_ID_IS_MISSING_OR_INVALID = | ||
"The collection ID is not specified in the arguments or is invalid."; | ||
public static final String INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT = | ||
"The specified format of the PutMutable function argument is invalid."; | ||
"The specified format of the PutToMutableDatabase function argument is invalid."; | ||
|
||
/** A formatter for a DATE literal. The format is "YYYY-MM-DD". For example, "2020-03-04". */ | ||
public static final DateTimeFormatter DATE_FORMATTER = | ||
new DateTimeFormatterBuilder() | ||
.appendValue(YEAR, 4, 4, SignStyle.NEVER) | ||
.appendLiteral('-') | ||
.appendValue(MONTH_OF_YEAR, 2) | ||
.appendLiteral('-') | ||
.appendValue(DAY_OF_MONTH, 2) | ||
.toFormatter() | ||
.withResolverStyle(ResolverStyle.STRICT) | ||
.withChronology(IsoChronology.INSTANCE); | ||
/** | ||
* A formatter for a TIME literal. The format is "HH:MM:SS[.FFFFFF]". For example, | ||
* "12:34:56.123456". The fractional second is optional. | ||
*/ | ||
public static final DateTimeFormatter TIME_FORMATTER = | ||
new DateTimeFormatterBuilder() | ||
.appendValue(HOUR_OF_DAY, 2) | ||
.appendLiteral(':') | ||
.appendValue(MINUTE_OF_HOUR, 2) | ||
.optionalStart() | ||
.appendLiteral(':') | ||
.appendValue(SECOND_OF_MINUTE, 2) | ||
.optionalStart() | ||
.appendFraction(NANO_OF_SECOND, 0, 6, true) | ||
.toFormatter() | ||
.withResolverStyle(ResolverStyle.STRICT) | ||
.withChronology(IsoChronology.INSTANCE); | ||
/** | ||
* A formatter for a TIMESTAMP literal. The format is "YYYY-MM-DD HH:MM:SS[.FFF]". For example, | ||
* "2020-03-04 12:34:56.123". The fractional second is optional. | ||
*/ | ||
public static final DateTimeFormatter TIMESTAMP_FORMATTER = | ||
new DateTimeFormatterBuilder() | ||
.append(DATE_FORMATTER) | ||
.appendLiteral(' ') | ||
.appendValue(HOUR_OF_DAY, 2) | ||
.appendLiteral(':') | ||
.appendValue(MINUTE_OF_HOUR, 2) | ||
.optionalStart() | ||
.appendLiteral(':') | ||
.appendValue(SECOND_OF_MINUTE, 2) | ||
.optionalStart() | ||
.appendFraction(NANO_OF_SECOND, 0, 3, true) | ||
.toFormatter(); | ||
/** | ||
* A formatter for a TIMESTAMPTZ literal. The format is "YYYY-MM-DD HH:MM:SS[.FFF] Z". For | ||
* example, "2020-03-04 12:34:56.123 Z". The fractional second is optional. | ||
*/ | ||
public static final DateTimeFormatter TIMESTAMPTZ_FORMATTER = | ||
new DateTimeFormatterBuilder() | ||
.append(TIMESTAMP_FORMATTER) | ||
.appendLiteral(' ') | ||
.appendLiteral('Z') | ||
.toFormatter() | ||
.withZone(ZoneOffset.UTC); | ||
Comment on lines
+123
to
+124
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,15 +13,24 @@ | |
import com.scalar.db.io.BooleanColumn; | ||
import com.scalar.db.io.Column; | ||
import com.scalar.db.io.DataType; | ||
import com.scalar.db.io.DateColumn; | ||
import com.scalar.db.io.DoubleColumn; | ||
import com.scalar.db.io.FloatColumn; | ||
import com.scalar.db.io.IntColumn; | ||
import com.scalar.db.io.Key; | ||
import com.scalar.db.io.TextColumn; | ||
import com.scalar.db.io.TimeColumn; | ||
import com.scalar.db.io.TimestampColumn; | ||
import com.scalar.db.io.TimestampTZColumn; | ||
import com.scalar.dl.ledger.database.Database; | ||
import com.scalar.dl.ledger.exception.ContractContextException; | ||
import com.scalar.dl.ledger.function.JacksonBasedFunction; | ||
import java.io.IOException; | ||
import java.time.Instant; | ||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
import java.time.LocalTime; | ||
import java.time.format.DateTimeParseException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import javax.annotation.Nullable; | ||
|
@@ -193,6 +202,42 @@ private Column<?> getColumn(JsonNode jsonColumn) { | |
} | ||
} | ||
|
||
if (dataType.equals(DataType.DATE) | ||
|| dataType.equals(DataType.TIME) | ||
|| dataType.equals(DataType.TIMESTAMP) | ||
|| dataType.equals(DataType.TIMESTAMPTZ)) { | ||
if (!value.isTextual()) { | ||
throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); | ||
} | ||
return getTimeRelatedColumn(columnName, value.textValue(), dataType); | ||
} | ||
|
||
throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); | ||
} | ||
|
||
private Column<?> getTimeRelatedColumn(String columnName, String value, DataType dataType) { | ||
try { | ||
if (dataType.equals(DataType.DATE)) { | ||
return DateColumn.of(columnName, LocalDate.parse(value, Constants.DATE_FORMATTER)); | ||
} | ||
|
||
if (dataType.equals(DataType.TIME)) { | ||
return TimeColumn.of(columnName, LocalTime.parse(value, Constants.TIME_FORMATTER)); | ||
} | ||
|
||
if (dataType.equals(DataType.TIMESTAMP)) { | ||
return TimestampColumn.of( | ||
columnName, LocalDateTime.parse(value, Constants.TIMESTAMP_FORMATTER)); | ||
} | ||
|
||
if (dataType.equals(DataType.TIMESTAMPTZ)) { | ||
return TimestampTZColumn.of( | ||
columnName, Constants.TIMESTAMPTZ_FORMATTER.parse(value, Instant::from)); | ||
} | ||
} catch (DateTimeParseException e) { | ||
throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); | ||
} | ||
|
||
throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
} | ||
|
||
|
@@ -227,6 +272,22 @@ private Column<?> createNullColumn(String columnName, DataType dataType) { | |
return BlobColumn.ofNull(columnName); | ||
} | ||
|
||
if (dataType.equals(DataType.DATE)) { | ||
return DateColumn.ofNull(columnName); | ||
} | ||
|
||
if (dataType.equals(DataType.TIME)) { | ||
return TimeColumn.ofNull(columnName); | ||
} | ||
|
||
if (dataType.equals(DataType.TIMESTAMP)) { | ||
return TimestampColumn.ofNull(columnName); | ||
} | ||
|
||
if (dataType.equals(DataType.TIMESTAMPTZ)) { | ||
return TimestampTZColumn.ofNull(columnName); | ||
} | ||
|
||
throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); | ||
} | ||
|
||
|
@@ -246,6 +307,14 @@ private DataType getDataType(String dataType) { | |
return DataType.TEXT; | ||
case "BLOB": | ||
return DataType.BLOB; | ||
case "DATE": | ||
return DataType.DATE; | ||
case "TIME": | ||
return DataType.TIME; | ||
case "TIMESTAMP": | ||
return DataType.TIMESTAMP; | ||
case "TIMESTAMPTZ": | ||
return DataType.TIMESTAMPTZ; | ||
default: | ||
throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency with
DATE_FORMATTER
andTIME_FORMATTER
, and to ensure strict parsing,TIMESTAMP_FORMATTER
should be configured withResolverStyle.STRICT
andIsoChronology.INSTANCE
. The current implementation uses the defaultSMART
resolver style, which might lead to lenient parsing of invalid timestamp strings.