Skip to content
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

Date/time support for Postgres. Year/month/day operations on Columns. #6153

Merged
merged 15 commits into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,8 @@
- [Removed many regex compile flags from `split`; added `only_first` and
`use_regex` flag.][6116]
- [Implemented proper support for Value Types in the Table library.][6073]
- [Added support for Date/Time columns in the Postgres backend and added
`year`/`month`/`day` operations to Table columns.][6153]

[debug-shortcuts]:
https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug
Expand Down Expand Up @@ -561,6 +563,7 @@
[5959]: https://github.com/enso-org/enso/pull/5959
[6116]: https://github.com/enso-org/enso/pull/6116
[6073]: https://github.com/enso-org/enso/pull/6073
[6153]: https://github.com/enso-org/enso/pull/6153

#### Enso Compiler

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,17 +284,6 @@ type Time_Of_Day
duration : Duration -> self.minus_builtin duration
_ : Period -> Error.throw (Time_Error.Error "Time_Of_Day does not support date intervals (periods)")

## Format this time of day as text using the default formatter.

> Example
Convert the current time to text.

from Standard.Base import Time_Of_Day

example_to_text = Time_Of_Day.now.to_text
to_text : Text
to_text self = @Builtin_Method "Time_Of_Day.to_text"
Comment on lines -287 to -296
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other date/time types do not have this.

Having it breaks the to_text invocation on type.

I.e. Time_Of_Day.to_text now works. With that shadow definition it wasn't working properly.

It's a bit of a workaround to #1538


## Convert to a JavaScript Object representing this Time_Of_Day.

> Example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Standard.Table.Internal.Java_Problems
import Standard.Table.Internal.Problem_Builder.Problem_Builder
import Standard.Table.Internal.Widget_Helpers
from Standard.Table import Sort_Column, Data_Formatter, Value_Type, Auto
from Standard.Table.Errors import Floating_Point_Equality, Inexact_Type_Coercion
from Standard.Table.Errors import Floating_Point_Equality, Inexact_Type_Coercion, Invalid_Value_Type

import project.Data.SQL_Statement.SQL_Statement
import project.Data.SQL_Type.SQL_Type
Expand Down Expand Up @@ -161,9 +161,13 @@ type Column

Arguments:
- op_kind: The kind of the unary operator.
- new_name: The name of the resulting column.
make_unary_op : Text -> Text -> Column
make_unary_op self op_kind new_name = self.make_op op_kind [] new_name
- new_name: The name of the resulting column. If nothing, will create a
name based on the operator.
make_unary_op : Text -> Text|Nothing -> Column
make_unary_op self op_kind new_name=Nothing =
effective_new_name = new_name.if_nothing <|
self.naming_helpers.function_name op_kind [self]
self.make_op op_kind [] effective_new_name

## UNSTABLE

Expand Down Expand Up @@ -812,6 +816,31 @@ type Column
new_name = self.naming_helpers.binary_operation_name "like" self pattern
self.make_binary_op "LIKE" pattern new_name

## Gets the year as a number from the date stored in the column.

Applies only to columns that hold the `Date` or `Date_Time` types.
Returns a column of `Integer` type.
year : Column -> Column ! Invalid_Value_Type
year self = Value_Type.expect_has_date self.value_type related_column=self.name <|
self.make_unary_op "year"

## Gets the month as a number (1-12) from the date stored in the column.

Applies only to columns that hold the `Date` or `Date_Time` types.
Returns a column of `Integer` type.
month : Column -> Column ! Invalid_Value_Type
month self = Value_Type.expect_has_date self.value_type related_column=self.name <|
self.make_unary_op "month"

## Gets the day of the month as a number (1-31) from the date stored in the
column.

Applies only to columns that hold the `Date` or `Date_Time` types.
Returns a column of `Integer` type.
day : Column -> Column ! Invalid_Value_Type
day self = Value_Type.expect_has_date self.value_type related_column=self.name <|
self.make_unary_op "day"

## Checks for each element of the column if it is contained within the
provided vector or column.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import Standard.Table.Data.Type.Value_Type.Value_Type
import Standard.Table.Internal.Java_Exports

polyglot java import java.sql.ResultSet
polyglot java import java.time.LocalTime as Java_Local_Time

polyglot java import org.enso.database.JDBCUtils

type Column_Fetcher
## PRIVATE
Expand Down Expand Up @@ -87,10 +90,7 @@ text_fetcher =
if rs.wasNull then Nothing else t
make_builder initial_size =
java_builder = Java_Exports.make_string_builder initial_size
append v =
if v.is_nothing then java_builder.appendNulls 1 else
java_builder.append v
Builder.Value append (seal_java_builder java_builder)
make_builder_from_java_object_builder java_builder
Column_Fetcher.Value fetch_value make_builder

## PRIVATE
Expand All @@ -107,10 +107,25 @@ fallback_fetcher =
if rs.wasNull then Nothing else v
make_builder initial_size =
java_builder = Java_Exports.make_inferred_builder initial_size
append v =
if v.is_nothing then java_builder.appendNulls 1 else
java_builder.append v
Builder.Value append (seal_java_builder java_builder)
make_builder_from_java_object_builder java_builder
Column_Fetcher.Value fetch_value make_builder

## PRIVATE
time_fetcher =
fetch_value rs i =
time = rs.getObject i Java_Local_Time.class
if rs.wasNull then Nothing else time
make_builder initial_size =
java_builder = Java_Exports.make_time_of_day_builder initial_size
make_builder_from_java_object_builder java_builder
Column_Fetcher.Value fetch_value make_builder

## PRIVATE
date_time_fetcher =
fetch_value rs i = JDBCUtils.getZonedDateTime rs i
make_builder initial_size =
java_builder = Java_Exports.make_date_time_builder initial_size
make_builder_from_java_object_builder java_builder
Column_Fetcher.Value fetch_value make_builder

## PRIVATE
Expand All @@ -128,9 +143,19 @@ default_fetcher_for_value_type value_type =
Value_Type.Float _ -> double_fetcher
Value_Type.Char _ _ -> text_fetcher
Value_Type.Boolean -> boolean_fetcher
Value_Type.Time -> time_fetcher
# We currently don't distinguish timestamps without a timezone on the Enso value side.
Value_Type.Date_Time _ -> date_time_fetcher
_ -> fallback_fetcher

## PRIVATE
seal_java_builder java_builder column_name =
storage = java_builder.seal
Java_Exports.make_column column_name storage

## PRIVATE
make_builder_from_java_object_builder java_builder =
append v =
if v.is_nothing then java_builder.appendNulls 1 else
java_builder.append v
Builder.Value append (seal_java_builder java_builder)
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ import project.Internal.SQL_Type_Mapping.SQL_Type_Mapping
import project.Internal.Statement_Setter.Statement_Setter
from project.Errors import Unsupported_Database_Operation

polyglot java import org.enso.database.JDBCUtils

## PRIVATE

The dialect of PostgreSQL databases.
Expand Down Expand Up @@ -151,7 +149,8 @@ make_internal_generator_dialect =
stddev_pop = ["STDDEV_POP", Base_Generator.make_function "stddev_pop"]
stddev_samp = ["STDDEV_SAMP", Base_Generator.make_function "stddev_samp"]
stats = [agg_median, agg_mode, agg_percentile, stddev_pop, stddev_samp]
my_mappings = text + counts + stats + first_last_aggregators + arith_extensions + bool
date_ops = [make_extract_as_int "year" "YEAR", make_extract_as_int "month" "MONTH", make_extract_as_int "day" "DAY"]
my_mappings = text + counts + stats + first_last_aggregators + arith_extensions + bool + date_ops
Base_Generator.base_dialect . extend_with my_mappings

## PRIVATE
Expand Down Expand Up @@ -324,14 +323,10 @@ mod_op = Base_Generator.lift_binary_op "mod" x-> y->
x ++ " - FLOOR(CAST(" ++ x ++ " AS double precision) / CAST(" ++ y ++ " AS double precision)) * " ++ y

## PRIVATE
postgres_statement_setter : Statement_Setter
postgres_statement_setter =
default = Statement_Setter.default
fill_hole stmt i value = case value of
# TODO [RW] Postgres date handling #6115
_ : Date_Time ->
stmt.setTimestamp i (JDBCUtils.getTimestamp value)
# _ : Date ->
# _ : Time_Of_Day ->
_ -> default.fill_hole stmt i value
Statement_Setter.Value fill_hole
make_extract_as_int enso_name sql_name =
Base_Generator.lift_unary_op enso_name arg->
extract = Builder.code "EXTRACT(" ++ sql_name ++ " FROM " ++ arg ++ ")"
Builder.code "CAST(" ++ extract ++ " AS integer)"

## PRIVATE
postgres_statement_setter = Statement_Setter.default
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import Standard.Base.Errors.Illegal_State.Illegal_State
polyglot java import java.sql.PreparedStatement
polyglot java import java.sql.Types as Java_Types

polyglot java import org.enso.database.JDBCUtils

type Statement_Setter
## PRIVATE
Encapsulates the logic for filling a hole in a prepared statement.
Expand All @@ -27,9 +29,10 @@ type Statement_Setter

## PRIVATE
fill_hole_default stmt i value = case value of
Nothing -> stmt.setNull i Java_Types.NULL
_ : Boolean -> stmt.setBoolean i value
_ : Integer -> stmt.setLong i value
_ : Decimal -> stmt.setDouble i value
_ : Text -> stmt.setString i value
_ -> stmt.setObject i value
Nothing -> stmt.setNull i Java_Types.NULL
_ : Boolean -> stmt.setBoolean i value
_ : Integer -> stmt.setLong i value
_ : Decimal -> stmt.setDouble i value
_ : Text -> stmt.setString i value
_ : Date_Time -> JDBCUtils.setZonedDateTime stmt i value
_ -> stmt.setObject i value
33 changes: 32 additions & 1 deletion distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import project.Internal.Widget_Helpers
from project.Data.Table import print_table
from project.Data.Type.Value_Type import Value_Type, Auto
from project.Data.Type.Value_Type_Helpers import ensure_valid_parse_target
from project.Errors import No_Index_Set_Error, Floating_Point_Equality
from project.Errors import No_Index_Set_Error, Floating_Point_Equality, Invalid_Value_Type
radeusgd marked this conversation as resolved.
Show resolved Hide resolved

polyglot java import org.enso.table.data.column.operation.map.MapOperationProblemBuilder
polyglot java import org.enso.table.data.column.storage.Storage as Java_Storage
Expand Down Expand Up @@ -908,6 +908,32 @@ type Column
like self pattern =
run_vectorized_binary_op self "like" (_ -> _ -> Error.throw (Illegal_State.Error "The `Like` operation should only be used on Text columns.")) pattern

## Gets the year as a number from the date stored in the column.

Applies only to columns that hold the `Date` or `Date_Time` types.
Returns a column of `Integer` type.
year : Column -> Column ! Invalid_Value_Type
year self = Value_Type.expect_has_date self.value_type related_column=self.name <|
simple_unary_op self "year"


## Gets the month as a number (1-12) from the date stored in the column.

Applies only to columns that hold the `Date` or `Date_Time` types.
Returns a column of `Integer` type.
month : Column -> Column ! Invalid_Value_Type
month self = Value_Type.expect_has_date self.value_type related_column=self.name <|
simple_unary_op self "month"

## Gets the day of the month as a number (1-31) from the date stored in the
column.

Applies only to columns that hold the `Date` or `Date_Time` types.
Returns a column of `Integer` type.
day : Column -> Column ! Invalid_Value_Type
day self = Value_Type.expect_has_date self.value_type related_column=self.name <|
simple_unary_op self "day"

## Checks for each element of the column if it is contained within the
provided vector or column.

Expand Down Expand Up @@ -1541,3 +1567,8 @@ run_vectorized_binary_case_text_op left op other case_sensitivity fallback new_n
passing the locale to it.
See: https://www.pivotaltracker.com/n/projects/2539304/stories/184093260
run_vectorized_binary_op left Nothing fallback other new_name

## PRIVATE
simple_unary_op column op_name =
new_name = Naming_Helpers.function_name op_name [column]
run_vectorized_unary_op column op_name (_ -> Error.throw (Illegal_State.Error "Missing vectorized implementation for `"+op_name+"`. This is a bug in the Table library.")) new_name
Original file line number Diff line number Diff line change
Expand Up @@ -131,24 +131,6 @@ type Value_Type
In-Memory and SQLite tables support this.
Mixed

## ADVANCED
UNSTABLE
Checks if the provided value type is a textual type (with any settings)
and runs the following action or reports a type error.
expect_text : Value_Type -> Any -> Text -> Any ! Invalid_Value_Type
expect_text value_type ~action related_column=Nothing =
if Value_Type.is_text value_type then action else
Error.throw (Invalid_Value_Type.Error Value_Type.Char value_type related_column)

## ADVANCED
UNSTABLE
Checks if the provided value type is a boolean type and runs the
following action or reports a type error.
expect_boolean : Value_Type -> Any -> Any ! Invalid_Value_Type
expect_boolean value_type ~action = case value_type of
Value_Type.Boolean -> action
_ -> Error.throw (Invalid_Value_Type.Error Value_Type.Boolean value_type)

## UNSTABLE
Checks if the `Value_Type` represents a boolean type.
is_boolean : Boolean
Expand Down Expand Up @@ -187,6 +169,42 @@ type Value_Type
Value_Type.Integer _ -> True
_ -> False

## UNSTABLE
Checks if the `Value_Type` represents a type that holds a date.

It will return true for both `Date` and `Date_Time` types.
has_date : Boolean
has_date self = case self of
Value_Type.Date -> True
Value_Type.Date_Time _ -> True
_ -> False

## ADVANCED
UNSTABLE
Checks if the provided value type is a textual type (with any settings)
and runs the following action or reports a type error.
expect_text : Value_Type -> Any -> Text -> Any ! Invalid_Value_Type
expect_text value_type ~action related_column=Nothing =
if Value_Type.is_text value_type then action else
Error.throw (Invalid_Value_Type.Error Value_Type.Char value_type related_column)

## ADVANCED
UNSTABLE
Checks if the provided value type is a boolean type and runs the
following action or reports a type error.
expect_boolean : Value_Type -> Any -> Any ! Invalid_Value_Type
expect_boolean value_type ~action = case value_type of
Value_Type.Boolean -> action
_ -> Error.throw (Invalid_Value_Type.Error Value_Type.Boolean value_type)

## ADVANCED
UNSTABLE
Checks if the provided value type is a `Date` or `Date_Time`.
expect_has_date : Value_Type -> Any -> Text -> Any ! Invalid_Value_Type
expect_has_date value_type ~action related_column=Nothing = case value_type.has_date of
True -> action
False -> Error.throw (Invalid_Value_Type.Error "Date or Date_Time" value_type related_column)

## Provides a text representation of the `Value_Type` meant for
displaying to the user.
to_display_text : Text
Expand Down
5 changes: 4 additions & 1 deletion distribution/lib/Standard/Table/0.0.0-dev/src/Errors.enso
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,10 @@ type Invalid_Value_Type
prefix = case self.related_column of
Nothing -> "Expected "
column_name -> "Expected " + column_name + " column to have "
prefix + self.expected.to_text + " type, but got " + self.actual.to_text + "."
expected_type = case self.expected of
msg : Text -> msg
other -> other.to_display_text
prefix + expected_type + " type, but got " + self.actual.to_display_text + "."

## UNSTABLE

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ polyglot java import org.enso.table.data.table.Table as Java_Table
polyglot java import org.enso.table.data.index.DefaultIndex
polyglot java import org.enso.table.data.column.storage.Storage
polyglot java import org.enso.table.data.column.builder.object.BoolBuilder
polyglot java import org.enso.table.data.column.builder.object.DateTimeBuilder
polyglot java import org.enso.table.data.column.builder.object.InferredBuilder
polyglot java import org.enso.table.data.column.builder.object.NumericBuilder
polyglot java import org.enso.table.data.column.builder.object.StringBuilder
polyglot java import org.enso.table.data.column.builder.object.TimeOfDayBuilder

## PRIVATE
make_bool_builder : BoolBuilder
Expand All @@ -29,6 +31,14 @@ make_long_builder initial_size = NumericBuilder.createLongBuilder initial_size
make_string_builder : Integer -> StringBuilder
make_string_builder initial_size = StringBuilder.new initial_size

## PRIVATE
make_time_of_day_builder : Integer -> TimeOfDayBuilder
make_time_of_day_builder initial_size = TimeOfDayBuilder.new initial_size

## PRIVATE
make_date_time_builder : Integer -> DateTimeBuilder
make_date_time_builder initial_size = DateTimeBuilder.new initial_size

## PRIVATE
make_inferred_builder : Integer -> InferredBuilder
make_inferred_builder initial_size = InferredBuilder.new initial_size
Expand Down
Loading