diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Column.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Column.enso index 1ca8f083d7e0..1872b3a8eac1 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Column.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Column.enso @@ -360,7 +360,9 @@ type Column op = case other of _ : Column -> if self.sql_type.is_definitely_numeric || other.sql_type.is_definitely_numeric then 'ADD_NUMBER' else 'ADD_TEXT' _ -> if self.sql_type.is_definitely_numeric then 'ADD_NUMBER' else 'ADD_TEXT' - self.make_binary_op op other + new_type = if op == 'ADD_TEXT' then self.sql_type else + SQL_Type.merge_type self.sql_type (SQL_Type.approximate_type other) + self.make_binary_op op other new_type=new_type ## UNSTABLE @@ -373,7 +375,9 @@ type Column element of `self`. If `other` is a column, the operation is performed pairwise between corresponding elements of `self` and `other`. - : Column | Any -> Column - - self other = self.make_binary_op "-" other + - self other = + new_type = SQL_Type.merge_type self.sql_type (SQL_Type.approximate_type other) + self.make_binary_op "-" other new_type=new_type ## UNSTABLE @@ -386,7 +390,9 @@ type Column element of `self`. If `other` is a column, the operation is performed pairwise between corresponding elements of `self` and `other`. * : Column | Any -> Column - * self other = self.make_binary_op "*" other + * self other = + new_type = SQL_Type.merge_type self.sql_type (SQL_Type.approximate_type other) + self.make_binary_op "*" other new_type=new_type ## ALIAS Divide Columns @@ -452,7 +458,9 @@ type Column example_mod = Examples.integer_column % 3 % : Column | Any -> Column % self other = - self.make_binary_op "%" other + new_type = SQL_Type.merge_type self.sql_type (SQL_Type.approximate_type other) + op = if new_type == SQL_Type.integer then "%" else "mod" + self.make_binary_op op other new_type=new_type ## ALIAS Power @@ -480,7 +488,8 @@ type Column example_div = Examples.decimal_column ^ Examples.integer_column ^ : Column | Any -> Column - ^ self other = self.make_binary_op '^' other + ^ self other = + self.make_binary_op '^' other new_type=SQL_Type.double ## UNSTABLE @@ -494,7 +503,7 @@ type Column operation is performed pairwise between corresponding elements of `self` and `other`. && : Column | Any -> Column - && self other = self.make_binary_op "AND" other + && self other = self.make_binary_op "AND" other new_type=SQL_Type.boolean ## UNSTABLE @@ -508,7 +517,7 @@ type Column operation is performed pairwise between corresponding elements of `self` and `other`. || : Column | Any -> Column - || self other = self.make_binary_op "OR" other + || self other = self.make_binary_op "OR" other new_type=SQL_Type.boolean ## UNSTABLE @@ -537,8 +546,10 @@ type Column common type. left_type = get_approximate_type when_true self.sql_type right_type = get_approximate_type when_false self.sql_type - if left_type != right_type then Error.throw (Illegal_Argument.Error "when_true and when_false types do not match") else - self.make_op "IIF" [when_true, when_false] new_type=left_type + new_type = SQL_Type.merge_type left_type right_type + + if new_type.is_error then Error.throw (Illegal_Argument.Error "when_true and when_false types do not match") else + self.make_op "IIF" [when_true, when_false] new_type=new_type ## Returns a column of first non-`Nothing` value on each row of `self` and `values` list. @@ -555,8 +566,9 @@ type Column coalesce : (Any | Vector Any) -> Column coalesce self values = case values of _ : Vector -> - if values.any (v->(self.sql_type != get_approximate_type v self.sql_type)) then Error.throw (Illegal_Argument.Error "self and values types do not all match") else - self.make_op "COALESCE" values new_type=self.sql_type + fold_type = values.fold self.sql_type c->v-> SQL_Type.merge_type c (get_approximate_type v self.sql_type) + if fold_type.is_error then Error.throw (Illegal_Argument.Error "self and values types do not all match") else + self.make_op "COALESCE" values new_type=fold_type _ : Array -> self.coalesce (Vector.from_polyglot_array values) _ -> self.coalesce [values] @@ -574,8 +586,9 @@ type Column min : (Any | Vector Any) -> Column min self values = case values of _ : Vector -> - if values.any (v->(self.sql_type != get_approximate_type v self.sql_type)) then Error.throw (Illegal_Argument.Error "self and values types do not all match") else - self.make_op "ROW_MIN" values new_type=self.sql_type + fold_type = values.fold self.sql_type c->v-> SQL_Type.merge_type c (get_approximate_type v self.sql_type) + if fold_type.is_error then Error.throw (Illegal_Argument.Error "self and values types do not all match") else + self.make_op "ROW_MIN" values new_type=fold_type _ : Array -> self.min (Vector.from_polyglot_array values) _ -> self.min [values] @@ -593,8 +606,9 @@ type Column max : (Any | Vector Any) -> Column max self values = case values of _ : Vector -> - if values.any (v->(self.sql_type != get_approximate_type v self.sql_type)) then Error.throw (Illegal_Argument.Error "self and values types do not all match") else - self.make_op "ROW_MAX" values new_type=self.sql_type + fold_type = values.fold self.sql_type c->v-> SQL_Type.merge_type c (get_approximate_type v self.sql_type) + if fold_type.is_error then Error.throw (Illegal_Argument.Error "self and values types do not all match") else + self.make_op "ROW_MAX" values new_type=fold_type _ : Array -> self.max (Vector.from_polyglot_array values) _ -> self.max [values] @@ -662,7 +676,7 @@ type Column Arguments: - new_name: The name to rename `self` column to. rename : Text -> Column ! Unsupported_Name - rename self new_name = Helpers.ensure_name_is_sane new_name <| + rename self new_name = Column.Value new_name self.connection self.sql_type self.expression self.context ## UNSTABLE diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/SQL_Type.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/SQL_Type.enso index 2f5a7efb20e8..d9aa734b7c91 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/SQL_Type.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/SQL_Type.enso @@ -1,6 +1,8 @@ from Standard.Base import all import Standard.Base.Error.Illegal_Argument.Illegal_Argument +import project.Data.Column.Column + polyglot java import java.sql.Types ## Represents an internal SQL data-type. @@ -30,6 +32,10 @@ type SQL_Type bigint : SQL_Type bigint = SQL_Type.Value Types.BIGINT "BIGINT" + ## The SQL representation of the `TINYINT` type. + tinyint : SQL_Type + tinyint = SQL_Type.Value Types.TINYINT "TINYINT" + ## The SQL representation of the `SMALLINT` type. smallint : SQL_Type smallint = SQL_Type.Value Types.SMALLINT "SMALLINT" @@ -51,12 +57,6 @@ type SQL_Type numeric = SQL_Type.Value Types.NUMERIC "NUMERIC" ## The SQL type representing one of the supported textual types. - varchar : SQL_Type - varchar = SQL_Type.Value Types.VARCHAR "VARCHAR" - - ## UNSTABLE - The SQL type representing one of the supported textual types. - It seems that JDBC treats the `TEXT` and `VARCHAR` types as interchangeable. text : SQL_Type text = SQL_Type.Value Types.VARCHAR "VARCHAR" @@ -77,18 +77,38 @@ type SQL_Type date_time : SQL_Type date_time = SQL_Type.Value Types.TIMESTAMP_WITH_TIMEZONE "TIMESTAMP" + ## The SQL type representing a null column. + null : SQL_Type + null = SQL_Type.Value Types.NULL "NULL" + ## ADVANCED Given an Enso value gets the approximate SQL type. approximate_type : Any -> SQL_Type ! Illegal_Argument approximate_type value = case value of + _ : Column -> value.sql_type _ : Boolean -> SQL_Type.boolean - _ : Integer -> SQL_Type.integer + _ : Integer -> if value.abs >= 2^32 then SQL_Type.bigint else SQL_Type.integer _ : Decimal -> SQL_Type.double - _ : Text -> SQL_Type.varchar + _ : Text -> SQL_Type.text _ : Date -> SQL_Type.date - _ : Time_Of_Day -> SQL_Type.time_of_day + _ : Time_Of_Day -> SQL_Type.time _ : Date_Time -> SQL_Type.date_time - _ -> Error.throw (Illegal_Argument.Error "Unsupported type.") + Nothing -> SQL_Type.null + _ -> Error.throw (Illegal_Argument.Error "Unsupported type.") + + ## PRIVATE + Returns the SQL type that is the result of applying an operation to the + two given types. + merge_type : SQL_Type -> SQL_Type -> SQL_Type ! Illegal_Argument + merge_type left right = + if left.typeid == right.typeid then left else + if left.is_null.not && right.is_null then left else + if left.is_null && right.is_null.not then right else + case left.is_definitely_numeric && right.is_definitely_numeric of + True -> if left.is_definitely_integer && right.is_definitely_integer then merge_integer_type left right else + merge_number_type left right + False -> if left.is_definitely_text && right.is_definitely_text then SQL_Type.text else + Error.throw (Illegal_Argument.Error "Unmatched types for operation.") ## PRIVATE @@ -141,3 +161,29 @@ type SQL_Type is_likely_text : Boolean is_likely_text self = self.is_definitely_text || self.name.contains "text" Case_Sensitivity.Insensitive + + ## PRIVATE + is_null : Boolean + is_null self = self.typeid == Types.NULL + +## PRIVATE + Joins two integer SQL types into the larger one. +merge_integer_type : SQL_Type -> SQL_Type -> SQL_Type +merge_integer_type left right = + integer_types = [Types.TINYINT, Types.SMALLINT, Types.INTEGER, Types.BIGINT] + left_index = integer_types.index_of left.typeid + right_index = integer_types.index_of right.typeid + new_index = left_index.max right_index + [SQL_Type.tinyint, SQL_Type.smallint, SQL_Type.integer, SQL_Type.bigint].at new_index + +## PRIVATE + Joins two numeric SQL types into the larger one. + One of the types must be non-integer (otherwise use merge_integer_type). +merge_number_type : SQL_Type -> SQL_Type -> SQL_Type +merge_number_type left right = if left.is_definitely_integer then merge_number_type right left else + numeric_types = [Types.NUMERIC, Types.DECIMAL, Types.FLOAT, Types.REAL, Types.DOUBLE] + left_index = numeric_types.index_of left.typeid + right_index = numeric_types.index_of right.typeid + if right_index.is_nothing then left else + new_index = left_index.max right_index + [SQL_Type.numeric, SQL_Type.decimal, SQL_Type.real, SQL_Type.real, SQL_Type.double].at new_index diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso index 9f50ffed7a7d..531f86ca22c9 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso @@ -630,7 +630,7 @@ type Table _ -> column renamed = if new_name.is_nothing then resolved else resolved.rename new_name - Helpers.ensure_name_is_sane renamed.name <| + renamed.if_not_error <| index = self.internal_columns.index_of (c -> c.name == renamed.name) to_add = case set_mode of Set_Mode.Add_Or_Update -> True @@ -673,8 +673,7 @@ type Table Column.Value ("Constant_" + UUID.randomUUID.to_text) self.connection new_type other self.context new_column = Expression.evaluate expression get_column make_constant "Standard.Database.Data.Column" "Column" Column.var_args_functions problems = Warning.get_all new_column . map .value - new_name = Expression.to_column_name expression - result = new_column.rename new_name + result = new_column.rename expression on_problems.attach_problems_before problems <| Warning.set result [] diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Base_Generator.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Base_Generator.enso index 88fe3828e7fa..c44bd69672d7 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Base_Generator.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Base_Generator.enso @@ -170,7 +170,7 @@ base_dialect = unary = name -> [name, make_unary_op name] fun = name -> [name, make_function name] - arith = [["ADD_NUMBER", make_binary_op "+"], ["ADD_TEXT", make_binary_op "||"], bin "-", bin "*", bin "/", bin "%", ["^", make_function "POWER"]] + arith = [["ADD_NUMBER", make_binary_op "+"], ["ADD_TEXT", make_binary_op "||"], bin "-", bin "*", bin "/", bin "%", ["mod", make_function "MOD"], ["^", make_function "POWER"]] logic = [bin "AND", bin "OR", unary "NOT", ["IIF", make_iif], ["TRUE", make_constant "TRUE"], ["FALSE", make_constant "FALSE"]] eq = lift_binary_op "==" make_equals neq = lift_binary_op "!=" make_not_equals diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Helpers.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Helpers.enso index a8512fceac22..9663380c2835 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Helpers.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Helpers.enso @@ -49,25 +49,6 @@ unify_vector_singleton x = case x of _ : Vector -> x _ -> [x] -## PRIVATE - - This is used to check if the new name is safe for use in SQL queries. - - Arguments: - - name: The name to check for safety. - - action: The action to perform if the name is safe. - - In a future version we will decouple the internal SQL-safe names from the - external names shown to the user, but as a temporary solution we only allow - SQL-safe names for columns. - - # TODO [RW] better name handling in Tables (#1513) -ensure_name_is_sane : Text -> (Any -> Any) -> Any ! Unsupported_Name -ensure_name_is_sane name ~action = - is_safe = Pattern.matches "[A-Za-z_0-9]+" name - if is_safe then action else - Error.throw <| Unsupported_Name.Error (name + " is not a valid name for a column. Please use english letters, numbers and underscore only.") - ## PRIVATE assume_default_locale : Locale -> Any -> Any ! Unsupported_Database_Operation assume_default_locale locale ~action = diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/JDBC_Connection.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/JDBC_Connection.enso index 1df572689c51..f0290e114a9c 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/JDBC_Connection.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/JDBC_Connection.enso @@ -24,6 +24,7 @@ polyglot java import java.sql.SQLException polyglot java import java.sql.SQLTimeoutException polyglot java import org.enso.database.JDBCProxy +polyglot java import org.enso.database.JDBCUtils type JDBC_Connection ## PRIVATE @@ -179,7 +180,11 @@ set_statement_values stmt holes = holes.map_with_index ix-> obj-> position = ix + 1 case obj.first of - Nothing -> stmt.setNull position obj.second.typeid + Nothing -> + ## If we really don't have a clue what this should be, we choose a varchar for a blank column. + sql_type = if obj.second == SQL_Type.null then SQL_Type.text else obj.second + stmt.setNull position sql_type.typeid + _ : Date_Time -> stmt.setTimestamp position (JDBCUtils.getTimestamp obj.first) _ -> stmt.setObject position obj.first ## PRIVATE diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Postgres/Postgres_Dialect.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Postgres/Postgres_Dialect.enso index 22882e70620e..c1b4ee8ebb40 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Postgres/Postgres_Dialect.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Postgres/Postgres_Dialect.enso @@ -103,7 +103,7 @@ make_internal_generator_dialect = cases = [["LOWER", Base_Generator.make_function "LOWER"], ["UPPER", Base_Generator.make_function "UPPER"]] text = [starts_with, contains, ends_with, agg_shortest, agg_longest, make_case_sensitive]+concat_ops+cases counts = [agg_count_is_null, agg_count_empty, agg_count_not_empty, ["COUNT_DISTINCT", agg_count_distinct], ["COUNT_DISTINCT_INCLUDE_NULL", agg_count_distinct_include_null]] - arith_extensions = [is_nan, decimal_div] + arith_extensions = [is_nan, decimal_div, mod_op, ["ROW_MIN", Base_Generator.make_function "LEAST"], ["ROW_MAX", Base_Generator.make_function "GREATEST"]] bool = [bool_or] stddev_pop = ["STDDEV_POP", Base_Generator.make_function "stddev_pop"] @@ -303,3 +303,7 @@ bool_or = Base_Generator.lift_unary_op "BOOL_OR" arg-> ## PRIVATE decimal_div = Base_Generator.lift_binary_op "/" x-> y-> code "CAST(" ++ x ++ " AS double precision) / CAST(" ++ y ++ " AS double precision)" + +## PRIVATE +mod_op = Base_Generator.lift_binary_op "mod" x-> y-> + x ++ " - FLOOR(CAST(" ++ x ++ " AS double precision) / CAST(" ++ y ++ " AS double precision)) * " ++ y diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/SQLite/SQLite_Dialect.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/SQLite/SQLite_Dialect.enso index 1ae918c4dc4b..003352c46cf1 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/SQLite/SQLite_Dialect.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/SQLite/SQLite_Dialect.enso @@ -122,7 +122,7 @@ make_internal_generator_dialect = text = [starts_with, contains, ends_with, make_case_sensitive]+concat_ops counts = [agg_count_is_null, agg_count_empty, agg_count_not_empty, ["COUNT_DISTINCT", agg_count_distinct], ["COUNT_DISTINCT_INCLUDE_NULL", agg_count_distinct_include_null]] stats = [agg_stddev_pop, agg_stddev_samp] - arith_extensions = [decimal_div] + arith_extensions = [decimal_div, mod_op] bool = [bool_or] my_mappings = text + counts + stats + arith_extensions + bool @@ -260,3 +260,7 @@ bool_or = Base_Generator.lift_unary_op "BOOL_OR" arg-> ## PRIVATE decimal_div = Base_Generator.lift_binary_op "/" x-> y-> code "CAST(" ++ x ++ " AS REAL) / CAST(" ++ y ++ " AS REAL)" + +## PRIVATE +mod_op = Base_Generator.lift_binary_op "mod" x-> y-> + x ++ " - FLOOR(CAST(" ++ x ++ " AS REAL) / CAST(" ++ y ++ " AS REAL)) * " ++ y diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Expression.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Expression.enso index e4881b0ad0ba..16d64492261e 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Expression.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Expression.enso @@ -28,13 +28,6 @@ type Expression handle_parse_error <| handle_unsupported <| handle_arguments <| ExpressionVisitorImpl.evaluate expression get_column make_constant module_name type_name var_args_functions.to_array - ## PRIVATE - Converts an expression into a compatible column name. - to_column_name : Text -> Text - to_column_name expression = - expression.replace "[^A-Za-z_0-9]" "_" matcher=Regex_Matcher.Value - - type Expression_Error ## The expression supplied could not be parsed due to a syntax error. Syntax_Error message:Text line:Integer column:Integer diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso index bdecbeac9de4..738b64876382 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso @@ -1136,8 +1136,7 @@ type Table make_constant value = Column.from_vector_repeated (UUID.randomUUID.to_text) [value] self.row_count new_column = Expression.evaluate expression get_column make_constant "Standard.Table.Data.Column" "Column" Column.var_args_functions problems = Warning.get_all new_column . map .value - new_name = Expression.to_column_name expression - result = new_column.rename new_name + result = new_column.rename expression on_problems.attach_problems_before problems <| Warning.set result [] diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Excel/Excel_Format.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Excel/Excel_Format.enso index 3b4f65dd319f..07385a188b03 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Excel/Excel_Format.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Excel/Excel_Format.enso @@ -41,7 +41,9 @@ type Excel_Format If set to `True`, the file is read as an Excel 95-2003 format. If set to `False`, the file is read as an Excel 2007+ format. `Infer` will attempt to deduce this from the extension of the filename. - Excel (section:Excel_Section=Excel_Section.Workbook) (headers:(Boolean|Infer)=Infer) (xls_format:(Boolean|Infer)=Infer) + - default_sheet: The default sheet to use if `section` is set to + `Excel_Section.Workbook`. + Excel (section:Excel_Section=Excel_Section.Workbook) (headers:(Boolean|Infer)=Infer) (xls_format:(Boolean|Infer)=Infer) (default_sheet:Text="EnsoSheet") ## If the File_Format supports reading from the file, return a configured instance. for_file : File -> Excel_Format | Nothing @@ -90,12 +92,6 @@ type Excel_Format Excel_Section.Sheet_Names -> Error.throw (Illegal_Argument.Error "Sheet_Names cannot be used for `write`.") Excel_Section.Range_Names -> Error.throw (Illegal_Argument.Error "Range_Names cannot be used for `write`.") Excel_Section.Workbook -> - sheet_name = if file.exists.not then "Sheet1" else - names = Excel_Reader.read_file file Excel_Section.Sheet_Names False on_problems format - if names.length == 255 then Error.throw (Illegal_Argument.Error "Too many sheets in workbook.") else - name_map = Map.from_vector (names.map n-> [n, 1]) - idx = 1.up_to 256 . find n-> (name_map.contains_key ("Sheet" + n.to_text) . not) - "Sheet" + idx.to_text - Excel_Writer.write_file file table on_existing_file (Excel_Section.Worksheet sheet_name) True match_columns on_problems format + Excel_Writer.write_file file table on_existing_file (Excel_Section.Worksheet self.default_sheet) True match_columns on_problems format _ -> Excel_Writer.write_file file table on_existing_file self.section self.headers match_columns on_problems format r.if_not_error file diff --git a/std-bits/database/src/main/java/org/enso/database/JDBCUtils.java b/std-bits/database/src/main/java/org/enso/database/JDBCUtils.java new file mode 100644 index 000000000000..94ecc8581146 --- /dev/null +++ b/std-bits/database/src/main/java/org/enso/database/JDBCUtils.java @@ -0,0 +1,16 @@ +package org.enso.database; + +import java.sql.Timestamp; +import java.time.ZonedDateTime; + +public class JDBCUtils { + /** + * Converts a ZonedDateTime to a Timestamp (note loses the timezone). + * + * @param zonedDateTime the ZonedDateTime to convert + * @return the converted Timestamp + */ + public static Timestamp getTimestamp(ZonedDateTime zonedDateTime) { + return Timestamp.from(zonedDateTime.toInstant()); + } +} diff --git a/test/Table_Tests/src/Common_Table_Operations/Expression_Spec.enso b/test/Table_Tests/src/Common_Table_Operations/Expression_Spec.enso index 801c9770d1ed..560f7d823f3c 100644 --- a/test/Table_Tests/src/Common_Table_Operations/Expression_Spec.enso +++ b/test/Table_Tests/src/Common_Table_Operations/Expression_Spec.enso @@ -28,7 +28,7 @@ spec detailed setup = ## TODO Expressions were not being tested on Database backends before and many tests are failing. Disabling the tests until this is fixed, tracked by issue: https://www.pivotaltracker.com/story/show/183933832 - pending_bug = if prefix.contains "In-Memory" then Nothing else "Some of these tests are not working in Database. See: https://www.pivotaltracker.com/story/show/183933832" + pending_bug = if prefix.contains "In-Memory" then Nothing else "Date and Time support for In-Database backends is not fully implemented. https://github.com/enso-org/enso/issues/5268" epsilon=0.0000000001 @@ -46,15 +46,17 @@ spec detailed setup = _ -> e == v if match.not then values.should_equal expected - specify_test label action expression_test=tester = - case detailed of - True -> - specify_tester expression value = - Test.specify (label + ": " + expression) <| - expression_test expression value - action specify_tester - False -> - Test.specify label (action expression_test) + specify_test label action expression_test=tester pending=Nothing = case pending of + Nothing -> + case detailed of + True -> + specify_tester expression value = + Test.specify (label + ": " + expression) <| + expression_test expression value + action specify_tester + False -> + Test.specify label (action expression_test) + _ -> Test.specify label Nothing pending Test.group prefix+"Expression Integer literals" <| specify_test "should be able to add an integer column" expression_test-> @@ -93,22 +95,26 @@ spec detailed setup = expression_test "[A]" (column_a.at 1) expression_test "[Bad]] Name]" (column_odd.at 1) - Test.group prefix+"Expression Nothing literals" pending=pending_bug <| + Test.group prefix+"Expression Nothing literals" <| specify_test "should be able to add an nothing column" expression_test-> expression_test "null" Nothing expression_test "nUlL" Nothing expression_test "Nothing" Nothing expression_test "NOTHING" Nothing - Test.group prefix+"Expression Date and Time literals" pending=pending_bug <| - specify_test "should be able to add a date or time column" expression_test-> + Test.group prefix+"Expression Date and Time literals" <| + specify_test "should be able to add a date or time column" pending=pending_bug expression_test-> expression_test "#2020-12-23#" (Date.new 2020 12 23) expression_test "#12:34#" (Time_Of_Day.new 12 34) expression_test "#12:34:56#" (Time_Of_Day.new 12 34 56) - expression_test "#12:34:56.789#" (Time_Of_Day.new 12 34 56 789) - expression_test "#12:34:56.789000123#" (Time_Of_Day.new 12 34 56 789 0 123) expression_test "#2020-12-23 12:34#" (Date_Time.new 2020 12 23 12 34) expression_test "#2020-12-23 12:34:56#" (Date_Time.new 2020 12 23 12 34 56) + + specify_test "should be able to add a time column with sub-millisecond accuracy" pending=pending_bug expression_test-> + expression_test "#12:34:56.789#" (Time_Of_Day.new 12 34 56 789) + expression_test "#12:34:56.789000123#" (Time_Of_Day.new 12 34 56 789 0 123) + + specify_test "should be able to add a date time column with timezone" pending=pending_bug expression_test-> expression_test "#2020-12-23 12:34:56Z[UTC]#" (Date_Time.new 2020 12 23 12 34 56 zone=Time_Zone.utc) expression_test "#2020-12-23 12:34:56+02:30[UTC]#" (Date_Time.new 2020 12 23 10 04 56 zone=Time_Zone.utc) expression_test "#2020-12-23 12:34:56.157+01[UTC]#" (Date_Time.new 2020 12 23 11 34 56 157 zone=Time_Zone.utc) @@ -139,7 +145,7 @@ spec detailed setup = expression_test "1+1 * 2" 3 expression_test "1 + 1*2" 3 - Test.group prefix+"Column Arithmetic" pending=pending_bug <| + Test.group prefix+"Expression Column Arithmetic" <| specify_test "should be able to perform arithmetic on columns" expression_test-> expression_test "[A] + 2" [3, 4, 5, 6, 7] expression_test "[B] - 2" [-1, -0.5, 0.5, 2, 4] @@ -170,7 +176,7 @@ spec detailed setup = expression_test "([A] - [B]) ^ [A]" [0, 0.25, 0.125, 0, -1] expression_test "[A] ^ ([B] - [A])" [1, 0.707106781186547, 0.577350269189626, 1, 5] - Test.group prefix+"Comparison Operators" pending=pending_bug <| + Test.group prefix+"Expression Comparison Operators" <| specify_test "should be able to compare equality" expression_test-> expression_test "2 = 1 + 1" True expression_test "2 == 1 + 1" True @@ -224,7 +230,7 @@ spec detailed setup = expression_test "'' IS NOT EMPTY" False expression_test "Nothing IS NOT EMPTY" False - Test.group prefix+"Text Operators" <| + Test.group prefix+"Expression Text Operators" <| specify_test "should be able to concatenate text" expression_test-> expression_test "'Hello ' + 'World'" "Hello World" expression_test "[C] + ' World'" ["Hello World", "World World", "Hello World! World", " World", Nothing] @@ -241,7 +247,7 @@ spec detailed setup = expression_test "[C] LIKE 'Hello%'" [True, False, True, False, Nothing] expression_test "[C] NOT LIKE 'Hello%'" [False, True, False, True, Nothing] - Test.group prefix+"Boolean Operators" pending=pending_bug <| + Test.group prefix+"Expression Boolean Operators" <| specify_test "should be able to AND booleans" expression_test-> expression_test "True && TRUE" True expression_test "True AND False" False @@ -265,7 +271,7 @@ spec detailed setup = expression_test "IF False THEN 'A' ELSE 'B' END" 'B' expression_test "IF [Bad]] Name] THEN [A] ELSE [B] ENDIF" [1, 1.5, 3, 4, 5] - Test.group prefix+"Function invocation" pending=pending_bug <| + Test.group prefix+"Function invocation" <| specify_test "should be able to call a function with arguments" expression_test-> expression_test "Not(True)" False expression_test "not(False)" True @@ -276,7 +282,7 @@ spec detailed setup = expression_test "min(10, 3, 8)" 3 expression_test "max([A], [B], 3)" [3, 3, 3, 4, 6] - Test.group prefix+"Errors should be handled" pending=pending_bug <| + Test.group prefix+"Expression Errors should be handled" <| error_tester expression fail_type = test_table.set expression new_name="NEW_COL" . should_fail_with fail_type @@ -294,7 +300,7 @@ spec detailed setup = specify_test "should fail with Argument_Mismatch if too many arguments" expression_test=error_tester expression_test-> expression_test "Not([C], 'Hello')" Expression_Error.Argument_Mismatch - Test.group prefix+"Warnings should be reported" <| + Test.group prefix+"Expression Warnings should be reported" <| Test.specify "should report floating point equality" <| t1 = table_builder [["X", [1.5, 2.0, 0.0]]] diff --git a/test/Table_Tests/src/Database/Codegen_Spec.enso b/test/Table_Tests/src/Database/Codegen_Spec.enso index 4de328841794..fe9415948a5f 100644 --- a/test/Table_Tests/src/Database/Codegen_Spec.enso +++ b/test/Table_Tests/src/Database/Codegen_Spec.enso @@ -19,7 +19,7 @@ import project.Database.Helpers.Fake_Test_Connection spec = int = SQL_Type.integer bool = SQL_Type.boolean - str = SQL_Type.varchar + str = SQL_Type.text test_connection = table1 = ["T1", [["A", int], ["B", str], ["C", bool]]] table2 = ["T2", [["D", int], ["E", int], ["F", bool]]] @@ -63,15 +63,16 @@ spec = Test.group "[Codegen] Building Expressions" <| Test.specify "should allow building expressions from columns and constants" <| - a = t1.at "A" - b = t1.at "B" - c = t1.at "C" - arith = (a * 2) + 1 - logic = (c || c.not) && True - cmp = (a * a >= b) && (a - b < a) - arith.to_sql.prepare . should_equal ['SELECT (("T1"."A" * ?) + ?) AS "A" FROM "T1" AS "T1"', [[2, int], [1, int]]] - logic.to_sql.prepare . should_equal ['SELECT (("T1"."C" OR (NOT "T1"."C")) AND ?) AS "C" FROM "T1" AS "T1"', [[True, bool]]] - cmp.to_sql.prepare . should_equal ['SELECT ((("T1"."A" * "T1"."A") >= "T1"."B") AND (("T1"."A" - "T1"."B") < "T1"."A")) AS "A" FROM "T1" AS "T1"', []] + t2 = test_connection.query (SQL_Query.Table_Name "T2") + d = t2.at "D" + e = t2.at "E" + f = t2.at "F" + arith = (d * 2) + 1 + logic = (f || f.not) && True + cmp = (d * d >= e) && (d - e < d) + arith.to_sql.prepare . should_equal ['SELECT (("T2"."D" * ?) + ?) AS "D" FROM "T2" AS "T2"', [[2, int], [1, int]]] + logic.to_sql.prepare . should_equal ['SELECT (("T2"."F" OR (NOT "T2"."F")) AND ?) AS "F" FROM "T2" AS "T2"', [[True, bool]]] + cmp.to_sql.prepare . should_equal ['SELECT ((("T2"."D" * "T2"."D") >= "T2"."E") AND (("T2"."D" - "T2"."E") < "T2"."D")) AS "D" FROM "T2" AS "T2"', []] Test.specify "should support simple text operations" <| b = t1.at "B" diff --git a/test/Table_Tests/src/IO/Excel_Spec.enso b/test/Table_Tests/src/IO/Excel_Spec.enso index 863dbd3246bd..175b7c4ead99 100644 --- a/test/Table_Tests/src/IO/Excel_Spec.enso +++ b/test/Table_Tests/src/IO/Excel_Spec.enso @@ -79,8 +79,8 @@ spec_write suffix test_sheet_name = table.write out on_problems=Report_Error . should_succeed . should_equal out written = out.read written.sheet_count . should_equal 1 - written.sheet_names . should_equal ['Sheet1'] - written.read 'Sheet1' . should_equal table + written.sheet_names . should_equal ['EnsoSheet'] + written.read 'EnsoSheet' . should_equal table out.delete_if_exists Test.specify 'should write a table to non-existent file in append mode as a new sheet with headers' <| @@ -88,18 +88,18 @@ spec_write suffix test_sheet_name = table.write out on_existing_file=Existing_File_Behavior.Append on_problems=Report_Error . should_succeed written = out.read written.sheet_count . should_equal 1 - written.sheet_names . should_equal ['Sheet1'] - written.read 'Sheet1' . should_equal table + written.sheet_names . should_equal ['EnsoSheet'] + written.read 'EnsoSheet' . should_equal table out.delete_if_exists - Test.specify 'should write a table to existing file as a new sheet with headers' <| + Test.specify 'should write a table to existing file overriding EnsoSheet' <| out.delete_if_exists table.write out on_problems=Report_Error . should_succeed table.write out on_problems=Report_Error . should_succeed written = out.read - written.sheet_count . should_equal 2 - written.sheet_names . should_equal ['Sheet1', 'Sheet2'] - written.read 'Sheet2' . should_equal table + written.sheet_count . should_equal 1 + written.sheet_names . should_equal ['EnsoSheet'] + written.read 'EnsoSheet' . should_equal table out.delete_if_exists Test.specify 'should write a table to existing file in overwrite mode as a new sheet with headers' <|