mirror of
https://github.com/enso-org/enso.git
synced 2024-09-20 01:28:14 +03:00
Snowflake Dialect pt. 6 - Union, Distinct and other improvements (#10576)
- Part of #9486 - Fixes `Table.union`, `merge` and `distinct` tests - Replaces `distinct_on` in `Context` that was actually a Postgres specific addition leaking into the base with a more abstract `Context_Extension` mechanism. - This allows us to implement the Snowflake-specific `DISTINCT` using `QUALIFY`.
This commit is contained in:
parent
2e0fa89928
commit
7fd8701690
@ -1248,6 +1248,10 @@ type Integer
|
||||
Integer.parse "20220216"
|
||||
parse text:Text (radix=10:Integer) -> Integer ! Number_Parse_Error = integer_parse text radix
|
||||
|
||||
## PRIVATE
|
||||
fits_in_long self -> Boolean =
|
||||
self >= Long.MIN_VALUE && self <= Long.MAX_VALUE
|
||||
|
||||
## A syntax error when parsing a double.
|
||||
@Builtin_Type
|
||||
type Number_Parse_Error
|
||||
|
@ -578,8 +578,9 @@ type DB_Column
|
||||
_ : DB_Column -> other.value_type.is_decimal
|
||||
_ -> False
|
||||
either_is_decimal = self.value_type.is_decimal || other_is_decimal
|
||||
if either_is_decimal then self.make_binary_op "DECIMAL_DIV" other else
|
||||
self.make_binary_op "/" other
|
||||
new_name = self.naming_helper.binary_operation_name "/" self other
|
||||
if either_is_decimal then self.make_binary_op "DECIMAL_DIV" other new_name else
|
||||
self.make_binary_op "/" other new_name
|
||||
|
||||
## ALIAS modulo, modulus
|
||||
GROUP Standard.Base.Operators
|
||||
|
@ -1323,9 +1323,10 @@ type DB_Table
|
||||
distinct self columns=self.column_names case_sensitivity:Case_Sensitivity=..Default on_problems:Problem_Behavior=..Report_Warning =
|
||||
key_columns = self.columns_helper.select_columns columns Case_Sensitivity.Default reorder=True error_on_missing_columns=True on_problems=on_problems . catch No_Output_Columns _->
|
||||
Error.throw No_Input_Columns_Selected
|
||||
problem_builder = Problem_Builder.new
|
||||
new_table = self.connection.dialect.prepare_distinct self key_columns case_sensitivity problem_builder
|
||||
problem_builder.attach_problems_before on_problems new_table
|
||||
key_columns.if_not_error <|
|
||||
problem_builder = Problem_Builder.new
|
||||
new_table = self.connection.dialect.prepare_distinct self key_columns case_sensitivity problem_builder
|
||||
problem_builder.attach_problems_before on_problems new_table
|
||||
|
||||
## GROUP Standard.Base.Selections
|
||||
ICON preparation
|
||||
@ -2612,16 +2613,8 @@ type DB_Table
|
||||
actual_types = materialized_table.columns.map .value_type
|
||||
expected_types.zip actual_types expected_type-> actual_type->
|
||||
if expected_type == actual_type then Nothing else
|
||||
expected_type_kind = Meta.meta expected_type . constructor
|
||||
actual_type_kind = Meta.meta actual_type . constructor
|
||||
## We ignore simple approximations that our in-memory backend does - things like adding default
|
||||
timezone (because we do not have Date_Time without timezone in-memory),
|
||||
or changing Float32 to Float64 are silently ignored.
|
||||
However, bigger changes, like a Binary type column getting coerced to Mixed - _will_ still be reported.
|
||||
if expected_type_kind == actual_type_kind then Nothing else
|
||||
# If the reverse was an implicit conversion, undoing it also should not yield warnings:
|
||||
if self.connection.dialect.get_type_mapping.is_implicit_conversion actual_type expected_type then Nothing else
|
||||
warnings_builder.append (Inexact_Type_Coercion.Warning expected_type actual_type)
|
||||
if self.connection.dialect.get_type_mapping.should_warn_on_materialize expected_type actual_type then
|
||||
warnings_builder.append (Inexact_Type_Coercion.Warning expected_type actual_type)
|
||||
|
||||
result = max_rows.attach_warning materialized_table
|
||||
Problem_Behavior.Report_Warning.attach_problems_before warnings_builder.to_vector result
|
||||
|
@ -420,25 +420,46 @@ generate_order dialect order_descriptor =
|
||||
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Generates SQL code corresponding to a SELECT statement.
|
||||
|
||||
Arguments:
|
||||
- dialect: The SQL dialect for which the code is being generated.
|
||||
- ctx: A description of the SELECT clause.
|
||||
generate_select_context : Dialect -> Context -> SQL_Builder
|
||||
generate_select_context dialect ctx =
|
||||
generate_select : Dialect -> Vector | Nothing -> Context -> SQL_Builder
|
||||
generate_select dialect columns ctx =
|
||||
gen_exprs exprs = exprs.map (generate_expression dialect)
|
||||
gen_column pair = (generate_expression dialect pair.second) ++ alias dialect pair.first
|
||||
|
||||
generated_columns = case columns of
|
||||
Nothing -> SQL_Builder.code "*"
|
||||
_ -> SQL_Builder.join ", " (columns.map gen_column)
|
||||
|
||||
from_part = generate_from_part dialect ctx.from_spec
|
||||
where_part = (SQL_Builder.join " AND " (gen_exprs ctx.where_filters)) . prefix_if_present " WHERE "
|
||||
group_part = (SQL_Builder.join ", " (gen_exprs ctx.groups)) . prefix_if_present " GROUP BY "
|
||||
|
||||
orders = ctx.orders.map (generate_order dialect)
|
||||
order_part = (SQL_Builder.join ", " orders) . prefix_if_present " ORDER BY "
|
||||
|
||||
limit_part = case ctx.limit of
|
||||
Nothing -> ""
|
||||
_ : Integer -> " LIMIT " + ctx.limit.to_text
|
||||
|
||||
orders = ctx.orders.map (generate_order dialect)
|
||||
order_part = (SQL_Builder.join ", " orders) . prefix_if_present " ORDER BY "
|
||||
(SQL_Builder.code " FROM ") ++ from_part ++ where_part ++ group_part ++ order_part ++ limit_part
|
||||
extensions = ctx.extensions.map extension->
|
||||
part = extension.run_generator (gen_exprs extension.expressions)
|
||||
[extension.position, part]
|
||||
|
||||
parts = Vector.build builder->
|
||||
builder.append [100, SQL_Builder.code "SELECT "]
|
||||
builder.append [200, generated_columns]
|
||||
builder.append [300, SQL_Builder.code " FROM " ++ from_part]
|
||||
builder.append [400, where_part]
|
||||
builder.append [500, group_part]
|
||||
builder.append [600, order_part]
|
||||
builder.append [700, limit_part]
|
||||
extensions.each builder.append
|
||||
|
||||
SQL_Builder.join "" <| parts.sort on=(.first) . map .second
|
||||
|
||||
## PRIVATE
|
||||
|
||||
@ -467,18 +488,7 @@ generate_insert_query dialect table_name pairs =
|
||||
generate_query : Dialect -> Query -> SQL_Builder
|
||||
generate_query dialect query = case query of
|
||||
Query.Select columns ctx ->
|
||||
gen_column pair = (generate_expression dialect pair.second) ++ alias dialect pair.first
|
||||
cols = case columns of
|
||||
Nothing -> SQL_Builder.code "*"
|
||||
_ -> SQL_Builder.join ", " (columns.map gen_column)
|
||||
prefix = case ctx.distinct_on of
|
||||
Nothing -> SQL_Builder.code ""
|
||||
expressions : Vector ->
|
||||
## TODO I just realised this does not make sense in other backends than Postgres,
|
||||
so we should probably fail in such cases; probably rewrite into a generic modifier? or a transform?
|
||||
generated = SQL_Builder.join ", " (expressions.map (generate_expression dialect))
|
||||
SQL_Builder.code "DISTINCT ON (" ++ generated ++ ") "
|
||||
SQL_Builder.code "SELECT " ++ prefix ++ cols ++ generate_select_context dialect ctx
|
||||
generate_select dialect columns ctx
|
||||
Query.Insert table_name pairs ->
|
||||
generate_insert_query dialect table_name pairs
|
||||
Query.Create_Table name columns primary_key temporary ->
|
||||
|
@ -5,6 +5,7 @@ import project.Internal.IR.From_Spec.From_Spec
|
||||
import project.Internal.IR.Internal_Column.Internal_Column
|
||||
import project.Internal.IR.Order_Descriptor.Order_Descriptor
|
||||
import project.Internal.IR.SQL_Expression.SQL_Expression
|
||||
import project.SQL.SQL_Builder
|
||||
|
||||
## PRIVATE
|
||||
|
||||
@ -22,7 +23,7 @@ type Context
|
||||
details.
|
||||
for_table : Text -> Text -> Any -> Context
|
||||
for_table table_name alias=table_name internal_temporary_keep_alive_reference=Nothing =
|
||||
Context.Value (From_Spec.Table table_name alias internal_temporary_keep_alive_reference=internal_temporary_keep_alive_reference) [] [] [] Nothing Nothing
|
||||
Context.Value (From_Spec.Table table_name alias internal_temporary_keep_alive_reference=internal_temporary_keep_alive_reference) [] [] [] Nothing []
|
||||
|
||||
## PRIVATE
|
||||
|
||||
@ -33,7 +34,7 @@ type Context
|
||||
- alias: An alias name to use for table within the query.
|
||||
for_query : Text -> Text -> Context
|
||||
for_query raw_sql alias =
|
||||
Context.Value (From_Spec.Query raw_sql alias) [] [] [] Nothing Nothing
|
||||
Context.Value (From_Spec.Query raw_sql alias) [] [] [] Nothing []
|
||||
|
||||
## PRIVATE
|
||||
|
||||
@ -43,7 +44,7 @@ type Context
|
||||
- subquery: The subquery to lift into a context.
|
||||
for_subquery : From_Spec -> Context
|
||||
for_subquery subquery =
|
||||
Context.Value subquery [] [] [] Nothing Nothing
|
||||
Context.Value subquery [] [] [] Nothing []
|
||||
|
||||
## PRIVATE
|
||||
|
||||
@ -66,7 +67,7 @@ type Context
|
||||
grouped-by columns or aggregate expressions.
|
||||
- limit: an optional maximum number of elements that the query should
|
||||
return.
|
||||
Value (from_spec : From_Spec) (where_filters : Vector SQL_Expression) (orders : Vector Order_Descriptor) (groups : Vector SQL_Expression) (limit : Nothing | Integer) (distinct_on : Nothing | Vector SQL_Expression)
|
||||
Value (from_spec : From_Spec) (where_filters : Vector SQL_Expression) (orders : Vector Order_Descriptor) (groups : Vector SQL_Expression) (limit : Nothing | Integer) (extensions : Vector Context_Extension)
|
||||
|
||||
## PRIVATE
|
||||
|
||||
@ -76,7 +77,7 @@ type Context
|
||||
- new_filters: The new filters to set in the query.
|
||||
set_where_filters : Vector SQL_Expression -> Context
|
||||
set_where_filters self new_filters =
|
||||
Context.Value self.from_spec new_filters self.orders self.groups self.limit self.distinct_on
|
||||
Context.Value self.from_spec new_filters self.orders self.groups self.limit self.extensions
|
||||
|
||||
## PRIVATE
|
||||
|
||||
@ -87,7 +88,7 @@ type Context
|
||||
query.
|
||||
add_where_filters : Vector SQL_Expression -> Context
|
||||
add_where_filters self new_filters =
|
||||
Context.Value self.from_spec (self.where_filters+new_filters) self.orders self.groups self.limit self.distinct_on
|
||||
Context.Value self.from_spec (self.where_filters+new_filters) self.orders self.groups self.limit self.extensions
|
||||
|
||||
## PRIVATE
|
||||
|
||||
@ -97,7 +98,7 @@ type Context
|
||||
- new_orders: The new ordering clauses to set in the query.
|
||||
set_orders : Vector Order_Descriptor -> Context
|
||||
set_orders self new_orders =
|
||||
Context.Value self.from_spec self.where_filters new_orders self.groups self.limit self.distinct_on
|
||||
Context.Value self.from_spec self.where_filters new_orders self.groups self.limit self.extensions
|
||||
|
||||
## PRIVATE
|
||||
|
||||
@ -114,7 +115,7 @@ type Context
|
||||
- new_orders: The new ordering clauses to add to the query.
|
||||
add_orders : Vector Order_Descriptor -> Context
|
||||
add_orders self new_orders =
|
||||
Context.Value self.from_spec self.where_filters new_orders+self.orders self.groups self.limit self.distinct_on
|
||||
Context.Value self.from_spec self.where_filters new_orders+self.orders self.groups self.limit self.extensions
|
||||
|
||||
## PRIVATE
|
||||
|
||||
@ -124,7 +125,7 @@ type Context
|
||||
- new_groups: The new grouping clauses to set in the query.
|
||||
set_groups : Vector SQL_Expression -> Context
|
||||
set_groups self new_groups =
|
||||
Context.Value self.from_spec self.where_filters self.orders new_groups self.limit self.distinct_on
|
||||
Context.Value self.from_spec self.where_filters self.orders new_groups self.limit self.extensions
|
||||
|
||||
## PRIVATE
|
||||
|
||||
@ -134,14 +135,13 @@ type Context
|
||||
- new_limit: The new limit clauses to set in the query.
|
||||
set_limit : (Nothing | Integer) -> Context
|
||||
set_limit self new_limit =
|
||||
Context.Value self.from_spec self.where_filters self.orders self.groups new_limit self.distinct_on
|
||||
Context.Value self.from_spec self.where_filters self.orders self.groups new_limit self.extensions
|
||||
|
||||
## PRIVATE
|
||||
|
||||
Returns a copy of the context with changed `distinct_on` expressions.
|
||||
set_distinct_on : (Nothing | Vector SQL_Expression) -> Context
|
||||
set_distinct_on self new_distinct_on =
|
||||
Context.Value self.from_spec self.where_filters self.orders self.groups self.limit new_distinct_on
|
||||
Returns a copy of the context with an added extension.
|
||||
add_extension : Context_Extension -> Context
|
||||
add_extension self extension =
|
||||
Context.Value self.from_spec self.where_filters self.orders self.groups self.limit (self.extensions + [extension])
|
||||
|
||||
## PRIVATE
|
||||
|
||||
@ -176,3 +176,27 @@ type Context
|
||||
type Subquery_Setup
|
||||
## PRIVATE
|
||||
Value (subquery : From_Spec) (new_columns : Vector (Vector Internal_Column))
|
||||
|
||||
## PRIVATE
|
||||
Describes an extension to a Context that can be used to add additional custom
|
||||
SQL code as part of the query.
|
||||
type Context_Extension
|
||||
## A recipe for building the extension.
|
||||
|
||||
Arguments:
|
||||
- position: Determines where the extension code should be inserted.
|
||||
The positions of common query parts are following:
|
||||
- 100 - the SELECT keyword
|
||||
- 200 - the column descriptions
|
||||
- 300 - the FROM part
|
||||
- 400 - the WHERE part
|
||||
- 500 - the GROUP BY part
|
||||
- 600 - the ORDER BY part
|
||||
- 700 - the LIMIT part
|
||||
Setting the position to a value between these will result in it being
|
||||
sorted in the desired place.
|
||||
- expressions: Sub-expressions needed for the part. They will be
|
||||
generated and the results of that will be passed to `run_generator`.
|
||||
- run_generator: A function that takes the generated expressions and
|
||||
returns the SQL code that will be inserted at the desired position.
|
||||
Value (position : Integer) (expressions : Vector SQL_Expression) (run_generator : Vector SQL_Builder -> SQL_Builder)
|
||||
|
@ -19,6 +19,7 @@ import project.Internal.Common.Database_Distinct_Helper
|
||||
import project.Internal.Common.Database_Join_Helper
|
||||
import project.Internal.Error_Mapper.Error_Mapper
|
||||
import project.Internal.IR.Context.Context
|
||||
import project.Internal.IR.Context.Context_Extension
|
||||
import project.Internal.IR.From_Spec.From_Spec
|
||||
import project.Internal.IR.Internal_Column.Internal_Column
|
||||
import project.Internal.IR.Nulls_Order.Nulls_Order
|
||||
@ -122,7 +123,7 @@ type Postgres_Dialect
|
||||
distinct_expressions = new_key_columns.map column->
|
||||
value_type = type_mapping.sql_type_to_value_type column.sql_type_reference.get
|
||||
Database_Distinct_Helper.make_distinct_expression case_sensitivity problem_builder column value_type
|
||||
new_context = Context.for_subquery setup.subquery . set_distinct_on distinct_expressions
|
||||
new_context = Context.for_subquery setup.subquery . add_extension (make_distinct_extension distinct_expressions)
|
||||
table.updated_context_and_columns new_context new_columns subquery=True
|
||||
|
||||
## PRIVATE
|
||||
@ -764,6 +765,12 @@ as_int64 expr =
|
||||
as_int32 expr =
|
||||
SQL_Builder.code "(" ++ expr ++ "::int4)"
|
||||
|
||||
## PRIVATE
|
||||
make_distinct_extension expressions =
|
||||
run_generator sql_expressions =
|
||||
SQL_Builder.code "DISTINCT ON (" ++ (SQL_Builder.join ", " sql_expressions) ++ ") "
|
||||
Context_Extension.Value position=120 expressions=expressions run_generator=run_generator
|
||||
|
||||
## PRIVATE
|
||||
The RUNTIME_ERROR operation should allow the query to compile fine and it
|
||||
will not prevent it from running if the branch including this operation is
|
||||
|
@ -131,6 +131,10 @@ type Postgres_Type_Mapping
|
||||
_ = [source_type, target_type]
|
||||
False
|
||||
|
||||
## PRIVATE
|
||||
should_warn_on_materialize (db_type : Value_Type) (in_memory_type : Value_Type) -> Boolean =
|
||||
SQL_Type_Mapping.default_should_warn_on_materialize db_type in_memory_type
|
||||
|
||||
## PRIVATE
|
||||
is_integer_type (value_type : Value_Type) -> Boolean = value_type.is_integer
|
||||
|
||||
|
@ -99,6 +99,13 @@ type SQL_Type_Mapping
|
||||
_ = [source_type, target_type]
|
||||
Unimplemented.throw "This is an interface only."
|
||||
|
||||
## PRIVATE
|
||||
Specifies if the given type coercion should raise an
|
||||
`Inexact_Type_Coercion` warning when materializing a table into memory.
|
||||
should_warn_on_materialize (db_type : Value_Type) (in_memory_type : Value_Type) -> Boolean =
|
||||
_ = [db_type, in_memory_type]
|
||||
Unimplemented.throw "This is an interface only."
|
||||
|
||||
## PRIVATE
|
||||
Specifies if this backend recognizes the given type as an integer type.
|
||||
|
||||
@ -133,3 +140,11 @@ default_sql_type_to_text sql_type =
|
||||
if sql_type.scale.is_nothing then "(" + sql_type.precision.to_text + ")" else
|
||||
" (" + sql_type.precision.to_text + "," + sql_type.scale.to_text + ")"
|
||||
sql_type.name.trim + suffix
|
||||
|
||||
## PRIVATE
|
||||
default_should_warn_on_materialize (db_type : Value_Type) (in_memory_type : Value_Type) =
|
||||
## We ignore simple approximations that our in-memory backend does - things like adding default
|
||||
timezone (because we do not have Date_Time without timezone in-memory),
|
||||
or changing Float32 to Float64 are silently ignored.
|
||||
However, bigger changes, like a Binary type column getting coerced to Mixed - _will_ still be reported.
|
||||
(Meta.meta db_type . constructor) != (Meta.meta in_memory_type . constructor)
|
||||
|
@ -123,6 +123,10 @@ type SQLite_Type_Mapping
|
||||
_ = [source_type, target_type]
|
||||
False
|
||||
|
||||
## PRIVATE
|
||||
should_warn_on_materialize (db_type : Value_Type) (in_memory_type : Value_Type) -> Boolean =
|
||||
SQL_Type_Mapping.default_should_warn_on_materialize db_type in_memory_type
|
||||
|
||||
## PRIVATE
|
||||
is_integer_type (value_type : Value_Type) -> Boolean = value_type.is_integer
|
||||
|
||||
|
@ -4,6 +4,7 @@ import Standard.Base.Errors.Common.Dry_Run_Operation
|
||||
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
|
||||
import Standard.Base.Errors.Illegal_State.Illegal_State
|
||||
import Standard.Base.Runtime.Context
|
||||
from Standard.Base.Logging import all
|
||||
from Standard.Base.Runtime import assert
|
||||
|
||||
import Standard.Table.Internal.Problem_Builder.Problem_Builder
|
||||
@ -517,10 +518,15 @@ dry_run_row_limit = 1000
|
||||
It is a panic, because it is never expected to happen in user code - if it
|
||||
happens, it is a bug in our code.
|
||||
check_transaction_ddl_support connection =
|
||||
supports_ddl = connection.jdbc_connection.with_metadata metadata->
|
||||
metadata.supportsDataDefinitionAndDataManipulationTransactions
|
||||
if supports_ddl.not then
|
||||
Panic.throw (Illegal_State.Error "The connection "+connection.to_text+" does not support transactional DDL statements. Our current implementation of table updates relies on transactional DDL. To support this driver, the logic needs to be amended.")
|
||||
connection.jdbc_connection.with_metadata metadata->
|
||||
supports_ddl = metadata.supportsDataDefinitionAndDataManipulationTransactions && metadata.dataDefinitionIgnoredInTransactions.not
|
||||
if supports_ddl.not then
|
||||
Panic.throw (Illegal_State.Error "The connection "+connection.to_text+" does not support transactional DDL statements. Our current implementation of table updates relies on transactional DDL. To support this driver, the logic needs to be amended.")
|
||||
ddl_causes_commit = metadata.dataDefinitionCausesTransactionCommit
|
||||
if ddl_causes_commit then
|
||||
# TODO fix for Snowflake support
|
||||
#Panic.throw (Illegal_State.Error "The connection "+connection.to_text+" does not fully support DDL statements as part of complex transactions - DDL causes a commit, so we cannot compose it. To support this driver, the logic needs to be amended.")
|
||||
Nothing
|
||||
|
||||
## PRIVATE
|
||||
common_delete_rows target_table key_values_to_delete key_columns allow_duplicate_matches =
|
||||
|
@ -107,7 +107,6 @@ type Image
|
||||
_ -> [flags]
|
||||
int_flags = MatOfInt.new (write_flags.flat_map x-> [x.to_integer, x.value])
|
||||
write_to_local_file file:File =
|
||||
IO.println "Writing the image to a file: "+file.path
|
||||
Panic.catch JException (Java_Codecs.write file.path self.opencv_mat int_flags) _->
|
||||
Error.throw (File_Error.IO_Error path.file 'Failed to write to the file')
|
||||
r = if path.is_local then write_to_local_file path.file else
|
||||
|
@ -22,6 +22,7 @@ import Standard.Database.Internal.Common.Database_Join_Helper
|
||||
import Standard.Database.Internal.Error_Mapper.Error_Mapper
|
||||
import Standard.Database.Internal.Internals_Access
|
||||
import Standard.Database.Internal.IR.Context.Context
|
||||
import Standard.Database.Internal.IR.Context.Context_Extension
|
||||
import Standard.Database.Internal.IR.From_Spec.From_Spec
|
||||
import Standard.Database.Internal.IR.Internal_Column.Internal_Column
|
||||
import Standard.Database.Internal.IR.Nulls_Order.Nulls_Order
|
||||
@ -125,10 +126,12 @@ type Snowflake_Dialect
|
||||
column_mapping = Dictionary.from_vector <| new_columns.map c-> [c.name, c]
|
||||
new_key_columns = key_columns.map c-> column_mapping.at c.name
|
||||
type_mapping = self.get_type_mapping
|
||||
# TODO simpler distinct if distinct_expressions are all columns?
|
||||
distinct_expressions = new_key_columns.map column->
|
||||
value_type = type_mapping.sql_type_to_value_type column.sql_type_reference.get
|
||||
Database_Distinct_Helper.make_distinct_expression case_sensitivity problem_builder column value_type
|
||||
new_context = Context.for_subquery setup.subquery . set_distinct_on distinct_expressions
|
||||
new_context = Context.for_subquery setup.subquery
|
||||
. add_extension (make_distinct_extension distinct_expressions)
|
||||
table.updated_context_and_columns new_context new_columns subquery=True
|
||||
|
||||
## PRIVATE
|
||||
@ -311,7 +314,7 @@ make_dialect_operations =
|
||||
other = [["day_of_year", Base_Generator.make_function "DAYOFYEAR"], ["day_of_week", Base_Generator.make_function "DAYOFWEEKISO"]]
|
||||
operations = [["date_add", make_date_add], ["date_diff", make_date_diff], ["date_trunc_to_day", make_date_trunc_to_day]]
|
||||
trivial + fractional + other + operations
|
||||
other = [["IIF", make_iif], ["RUNTIME_ERROR", make_runtime_error_op]]
|
||||
other = [["IIF", make_iif], ["RUNTIME_ERROR", make_runtime_error_op], ["ROW_NUMBER_IN_GROUP", make_row_number_in_group]]
|
||||
my_mappings = text + counts + stats + arith_extensions + bool + date_ops + other
|
||||
Base_Generator.base_dialect_operations . extend_with my_mappings
|
||||
|
||||
@ -429,6 +432,16 @@ agg_count_distinct_include_null args =
|
||||
replace_null_with_marker expr =
|
||||
SQL_Builder.code "COALESCE(" ++ expr ++ ", {'enso-null-replacement-marker':'"+Random.uuid+"'}::variant)"
|
||||
|
||||
## PRIVATE
|
||||
A helper for `lookup_and_replace`, and perhaps other operation.
|
||||
It creates an expression that returns a row number within a group.
|
||||
This is a specialization for Snowflake that adds a dummy ORDER BY clause to satisfy its compiler.
|
||||
make_row_number_in_group arguments =
|
||||
if arguments.length == 0 then
|
||||
Panic.throw <| Illegal_State.Error "The operation ROW_NUMBER_IN_GROUP requires at least one argument."
|
||||
SQL_Builder.code "ROW_NUMBER() OVER (PARTITION BY " ++ (SQL_Builder.join ", " arguments) ++ " ORDER BY 1)"
|
||||
|
||||
|
||||
## PRIVATE
|
||||
starts_with = Base_Generator.lift_binary_sql_function "STARTS_WITH" "STARTSWITH"
|
||||
|
||||
@ -675,5 +688,16 @@ make_iif arguments = case arguments.length of
|
||||
_ ->
|
||||
Error.throw <| Illegal_State.Error ("Invalid amount of arguments for operation IIF")
|
||||
|
||||
## PRIVATE
|
||||
make_distinct_extension distinct_expressions =
|
||||
run_generator sql_expressions =
|
||||
joined = SQL_Builder.join ", " sql_expressions
|
||||
## We could use the ORDER BY here to ensure any previous ordering is preserved by distinct.
|
||||
But to do so we need to have a robust way of checking such ordering, even if subqueries were taken in the meantime.
|
||||
This may be related to #10321
|
||||
Once fixed, should re-enable the `distinct_returns_first_row_from_group_if_ordered` flag back to `True`.
|
||||
SQL_Builder.code " QUALIFY ROW_NUMBER() OVER (PARTITION BY " ++ joined ++ " ORDER BY 1) = 1 "
|
||||
Context_Extension.Value position=550 expressions=distinct_expressions run_generator=run_generator
|
||||
|
||||
## PRIVATE
|
||||
snowflake_dialect_name = "Snowflake"
|
||||
|
@ -137,6 +137,14 @@ type Snowflake_Type_Mapping
|
||||
target_type == integer_value_type
|
||||
_ -> False
|
||||
|
||||
## PRIVATE
|
||||
should_warn_on_materialize (db_type : Value_Type) (in_memory_type : Value_Type) -> Boolean =
|
||||
is_decimal_to_integer = db_type.is_decimal && db_type.scale == 0 && in_memory_type.is_integer
|
||||
# We don't warn about the expected conversion of small integer column to Integer in-memory.
|
||||
if is_decimal_to_integer then False else
|
||||
# For other types, we delegate to the default behavior.
|
||||
SQL_Type_Mapping.default_should_warn_on_materialize db_type in_memory_type
|
||||
|
||||
## PRIVATE
|
||||
is_integer_type (value_type : Value_Type) -> Boolean = case value_type of
|
||||
Value_Type.Integer _ -> True
|
||||
@ -162,7 +170,9 @@ complex_types_map = Dictionary.from_vector <|
|
||||
effective_size = if sql_type.precision==max_length || (sql_type.precision==9 && sql_type.scale==9) then Nothing else sql_type.precision
|
||||
Value_Type.Char size=effective_size variable_length=True
|
||||
make_char sql_type =
|
||||
Value_Type.Char size=sql_type.precision variable_length=False
|
||||
# Even the CHAR type in Snowflake is still variable length - it is just an alias for VARCHAR.
|
||||
size = sql_type.precision.if_nothing 1
|
||||
Value_Type.Char size=size variable_length=True
|
||||
make_binary variable sql_type =
|
||||
Value_Type.Binary size=sql_type.precision variable_length=variable
|
||||
handle_bit sql_type =
|
||||
|
@ -486,9 +486,9 @@ Any.should_be_a self typ =
|
||||
import Standard.Examples
|
||||
from Standard.Test import Test
|
||||
|
||||
example_should_equal = [1, 2] . should_contain_the_same_elements_as [2, 1]
|
||||
Any.should_contain_the_same_elements_as : Any -> Integer -> Spec_Result
|
||||
Any.should_contain_the_same_elements_as self that frames_to_skip=0 =
|
||||
example_should_equal = [1, 2] . should_equal_ignoring_order [2, 1]
|
||||
Any.should_equal_ignoring_order : Any -> Integer -> Spec_Result
|
||||
Any.should_equal_ignoring_order self that frames_to_skip=0 =
|
||||
loc = Meta.get_source_location 1+frames_to_skip
|
||||
that.each element->
|
||||
if self.contains element . not then
|
||||
@ -498,6 +498,12 @@ Any.should_contain_the_same_elements_as self that frames_to_skip=0 =
|
||||
if that.contains element . not then
|
||||
msg = "The collection contained an element ("+element.to_text+") which was not expected (at " + loc + ")."
|
||||
Test.fail msg
|
||||
Test.with_clue "Duplicate elements in either collection should have the same counts - checked by comparing sorted collections: " <|
|
||||
## If the vector contains vectors or incomparable values, they may not get sorted correctly.
|
||||
We normalize by converting to a text representation.
|
||||
normalize e =
|
||||
Panic.catch No_Such_Method e.pretty _->e.to_text
|
||||
(self.sort on=normalize) . should_equal (that.sort on=normalize) frames_to_skip=frames_to_skip+3
|
||||
Spec_Result.Success
|
||||
|
||||
## Asserts that `self` value contains the same elements as `that`.
|
||||
@ -520,9 +526,9 @@ Any.should_contain_the_same_elements_as self that frames_to_skip=0 =
|
||||
import Standard.Examples
|
||||
from Standard.Test import Test
|
||||
|
||||
example_should_equal = [1, 2] . should_contain_the_same_elements_as [2, 1]
|
||||
Error.should_contain_the_same_elements_as : Any -> Integer -> Spec_Result
|
||||
Error.should_contain_the_same_elements_as self that frames_to_skip=0 =
|
||||
example_should_equal = [1, 2] . should_equal_ignoring_order [2, 1]
|
||||
Error.should_equal_ignoring_order : Any -> Integer -> Spec_Result
|
||||
Error.should_equal_ignoring_order self that frames_to_skip=0 =
|
||||
_ = [that]
|
||||
Test.fail_match_on_unexpected_error self 1+frames_to_skip
|
||||
|
||||
|
@ -13,7 +13,7 @@ export project.Extensions.should_be_a
|
||||
export project.Extensions.should_be_false
|
||||
export project.Extensions.should_be_true
|
||||
export project.Extensions.should_contain
|
||||
export project.Extensions.should_contain_the_same_elements_as
|
||||
export project.Extensions.should_equal_ignoring_order
|
||||
export project.Extensions.should_end_with
|
||||
export project.Extensions.should_equal
|
||||
export project.Extensions.should_equal_type
|
||||
|
@ -23,8 +23,11 @@ get_attached_warnings v =
|
||||
the standard testing approach, like `x.should_equal y`.
|
||||
- unwrap_errors: If true, remove any wrapping errors from errors and warnings
|
||||
before checking them.
|
||||
test_problem_handling : (Problem_Behavior -> Any) -> Vector Any -> (Any -> Nothing) -> Boolean -> Nothing
|
||||
test_problem_handling action expected_problems result_checker unwrap_errors=True =
|
||||
- ignore_warning_cardinality: If true, the reported warnings may be
|
||||
duplicated and that will be accepted. If false (default), the warning count
|
||||
has to match the counts in `expected_problems`.
|
||||
test_problem_handling : (Problem_Behavior -> Any) -> Vector Any -> (Any -> Nothing) -> Boolean -> Boolean -> Nothing
|
||||
test_problem_handling action expected_problems result_checker (unwrap_errors : Boolean = True) (ignore_warning_cardinality : Boolean = False) =
|
||||
unwrap_maybe error = if unwrap_errors then Error.unwrap error else error
|
||||
|
||||
error_checker error_result =
|
||||
@ -33,9 +36,10 @@ test_problem_handling action expected_problems result_checker unwrap_errors=True
|
||||
error_result . should_fail_with first_problem_type unwrap_errors=unwrap_errors frames_to_skip=3
|
||||
(unwrap_maybe error_result.catch) . should_equal first_problem frames_to_skip=3
|
||||
warnings_checker warnings =
|
||||
## TODO [RW] we are not checking if there are no duplicate warnings, because the warnings are in fact duplicated - we should figure out how to handle that and then possibly modify the test
|
||||
Test.with_clue "The warnings were "+warnings.to_text+'.\n' <|
|
||||
warnings . map unwrap_maybe . should_contain_the_same_elements_as expected_problems frames_to_skip=5
|
||||
(if ignore_warning_cardinality then warnings.distinct else warnings)
|
||||
. map unwrap_maybe
|
||||
. should_equal_ignoring_order expected_problems frames_to_skip=5
|
||||
test_advanced_problem_handling action error_checker warnings_checker result_checker frames_to_skip=1
|
||||
|
||||
## UNSTABLE
|
||||
|
@ -429,7 +429,7 @@ add_specs suite_builder =
|
||||
|
||||
# No new file was created
|
||||
parent_dir = my_file.parent
|
||||
parent_dir.list . should_contain_the_same_elements_as [my_file, bak_file]
|
||||
parent_dir.list . should_equal_ignoring_order [my_file, bak_file]
|
||||
|
||||
# If the original file is deleted and the backup file remains, the original file should _not_ count as existing (this used to fail).
|
||||
my_file.delete
|
||||
|
@ -429,8 +429,8 @@ add_specs suite_builder =
|
||||
py_dict = py_dict_from_vec ["A", 1, "B", 2]
|
||||
dict = py_dict.insert "C" 3
|
||||
py_dict.not_empty . should_be_true
|
||||
py_dict.to_vector . should_contain_the_same_elements_as [["A", 1], ["B", 2]]
|
||||
dict.to_vector . should_contain_the_same_elements_as [["A", 1], ["B", 2], ["C", 3]]
|
||||
py_dict.to_vector . should_equal_ignoring_order [["A", 1], ["B", 2]]
|
||||
dict.to_vector . should_equal_ignoring_order [["A", 1], ["B", 2], ["C", 3]]
|
||||
py_empty_dict.is_empty.should_be_true
|
||||
py_empty_dict.insert "A" 1 . insert "A" 2 . get "A" . should_equal 2
|
||||
|
||||
@ -450,9 +450,9 @@ add_specs suite_builder =
|
||||
|
||||
group_builder.specify "should treat Enso Dictionaries as Python dicts when passed to Python" pending=pending_python_missing <|
|
||||
dict1 = Dictionary.singleton "A" 1 . insert "B" 2
|
||||
py_vec_from_map dict1 . should_contain_the_same_elements_as [["A", 1], ["B", 2]]
|
||||
py_vec_from_map dict1 . should_equal_ignoring_order [["A", 1], ["B", 2]]
|
||||
dict2 = Dictionary.singleton "A" 1 . insert Nothing 2
|
||||
py_vec_from_map dict2 . should_contain_the_same_elements_as [["A", 1], [Nothing, 2]]
|
||||
py_vec_from_map dict2 . should_equal_ignoring_order [["A", 1], [Nothing, 2]]
|
||||
|
||||
|
||||
add_common_specs suite_builder prefix:Text (pending : (Text | Nothing)) (empty_dict_fn : (Nothing -> Dictionary)) =
|
||||
|
@ -66,7 +66,7 @@ add_specs suite_builder =
|
||||
|
||||
default_warning = invalid.bytes Encoding.ascii
|
||||
default_warning.should_equal invalid_ascii_out
|
||||
Problems.get_attached_warnings default_warning . should_contain_the_same_elements_as problems
|
||||
Problems.get_attached_warnings default_warning . should_equal_ignoring_order problems
|
||||
|
||||
suite_builder.group "Default Encoding" group_builder->
|
||||
group_builder.specify "should try reading as UTF-8 by default" <|
|
||||
@ -199,7 +199,7 @@ add_specs suite_builder =
|
||||
Problems.test_problem_handling action problems tester
|
||||
|
||||
default_warning = text.utf_8
|
||||
Problems.get_attached_warnings default_warning . should_contain_the_same_elements_as problems
|
||||
Problems.get_attached_warnings default_warning . should_equal_ignoring_order problems
|
||||
|
||||
group_builder.specify "should convert an array of bytes to text via encoding" <|
|
||||
result = Text.from_bytes kshi_utf_8 Encoding.utf_8
|
||||
|
@ -407,7 +407,7 @@ add_specs suite_builder =
|
||||
|
||||
samples_1 = 0.up_to 10000 . map seed->
|
||||
"ABCD".take (..Sample 2 seed)
|
||||
samples_1.should_contain_the_same_elements_as ["AB", "BA", "AC", "CA", "AD", "DA", "BC", "CB", "BD", "DB", "CD", "DC"]
|
||||
samples_1.distinct.should_equal_ignoring_order ["AB", "BA", "AC", "CA", "AD", "DA", "BC", "CB", "BD", "DB", "CD", "DC"]
|
||||
|
||||
"ABCDEFGH".drop (..Sample 0) . should_equal "ABCDEFGH"
|
||||
"ABCDEFGH".drop (..Sample 1 seed=42) . should_equal "ABCDEGH"
|
||||
@ -418,7 +418,7 @@ add_specs suite_builder =
|
||||
|
||||
samples_2 = 0.up_to 10000 . map seed->
|
||||
"ABCD".drop (..Sample 2 seed)
|
||||
samples_2.should_contain_the_same_elements_as ["AB", "AC", "AD", "BC", "CD", "BD"]
|
||||
samples_2.distinct.should_equal_ignoring_order ["AB", "AC", "AD", "BC", "CD", "BD"]
|
||||
|
||||
group_builder.specify "should allow taking or dropping many indices or subranges (possibly overlapping)" <|
|
||||
"123"*1000 . take (..By_Index (Vector.new 3000 ix-> 2999-ix)) . should_equal "321"*1000
|
||||
|
@ -912,7 +912,9 @@ type_spec suite_builder name alter = suite_builder.group name group_builder->
|
||||
_ -> x
|
||||
input = 0.up_to 500 . map gen
|
||||
sorted = input.sort on_problems=..Report_Warning
|
||||
Warning.get_all sorted . length . should_equal 10
|
||||
warnings = Warning.get_all sorted
|
||||
Test.with_clue "(sorted - warnings = "+warnings.to_display_text+") " <|
|
||||
warnings.length . should_equal 10
|
||||
Warning.limit_reached sorted . should_equal True
|
||||
|
||||
group_builder.specify "an error thrown inside map should be caught as a Map_Error" <|
|
||||
|
@ -559,30 +559,30 @@ add_specs suite_builder =
|
||||
suite_builder.group "Header resolution" group_builder->
|
||||
group_builder.specify "Default content type and encoding" <|
|
||||
expected = [Header.content_type "text/plain; charset=UTF-8"]
|
||||
resolve_headers (Request.new HTTP_Method.Get "" [] (Request_Body.Text "")) . should_contain_the_same_elements_as expected
|
||||
resolve_headers (Request.new HTTP_Method.Get "" [] (Request_Body.Text "")) . should_equal_ignoring_order expected
|
||||
|
||||
group_builder.specify "Content type specified in body" <|
|
||||
expected = [Header.content_type "application/json; charset=UTF-8"]
|
||||
resolve_headers (Request.new HTTP_Method.Get "" [] (Request_Body.Text "" content_type="application/json")) . should_contain_the_same_elements_as expected
|
||||
resolve_headers (Request.new HTTP_Method.Get "" [] (Request_Body.Text "" content_type="application/json")) . should_equal_ignoring_order expected
|
||||
|
||||
group_builder.specify "Content type specified in header list" <|
|
||||
expected = [Header.content_type "application/json"]
|
||||
resolve_headers (Request.new HTTP_Method.Get "" [Header.content_type "application/json"] (Request_Body.Text "")) . should_contain_the_same_elements_as expected
|
||||
resolve_headers (Request.new HTTP_Method.Get "" [Header.content_type "application/json"] (Request_Body.Text "")) . should_equal_ignoring_order expected
|
||||
|
||||
group_builder.specify "Text encoding specified in body" <|
|
||||
expected = [Header.content_type "text/plain; charset=UTF-16LE"]
|
||||
resolve_headers (Request.new HTTP_Method.Get "" [] (Request_Body.Text "" encoding=Encoding.utf_16_le)) . should_contain_the_same_elements_as expected
|
||||
resolve_headers (Request.new HTTP_Method.Get "" [] (Request_Body.Text "" encoding=Encoding.utf_16_le)) . should_equal_ignoring_order expected
|
||||
|
||||
group_builder.specify "Can't specify content type in both places" <|
|
||||
resolve_headers (Request.new HTTP_Method.Get "" [Header.content_type "application/json"] (Request_Body.Text "" content_type="text/plain")) . should_fail_with Illegal_Argument
|
||||
|
||||
group_builder.specify "Custom header" <|
|
||||
expected = [Header.new "some" "header", Header.content_type "application/json; charset=UTF-8"]
|
||||
resolve_headers (Request.new HTTP_Method.Get "" [Header.new "some" "header"] (Request_Body.Text "" content_type="application/json")) . should_contain_the_same_elements_as expected
|
||||
resolve_headers (Request.new HTTP_Method.Get "" [Header.new "some" "header"] (Request_Body.Text "" content_type="application/json")) . should_equal_ignoring_order expected
|
||||
|
||||
group_builder.specify "Multiple content types in header list are ok" <|
|
||||
expected = [Header.content_type "application/json", Header.content_type "text/plain"]
|
||||
resolve_headers (Request.new HTTP_Method.Get "" [Header.content_type "application/json", Header.content_type "text/plain"] (Request_Body.Text "")) . should_contain_the_same_elements_as expected
|
||||
resolve_headers (Request.new HTTP_Method.Get "" [Header.content_type "application/json", Header.content_type "text/plain"] (Request_Body.Text "")) . should_equal_ignoring_order expected
|
||||
|
||||
suite_builder.group "Http Error handling" group_builder->
|
||||
group_builder.specify "should be able to handle request errors" <|
|
||||
|
@ -49,8 +49,8 @@ add_specs suite_builder = suite_builder.group "Random" group_builder->
|
||||
Random.set_seed seed
|
||||
Random.indices 4 2
|
||||
|
||||
two_out_of_three . should_contain_the_same_elements_as [[0, 1], [0, 2], [1, 2], [1, 0], [2, 0], [2, 1]]
|
||||
two_out_of_four . should_contain_the_same_elements_as <|
|
||||
two_out_of_three.distinct.should_equal_ignoring_order [[0, 1], [0, 2], [1, 2], [1, 0], [2, 0], [2, 1]]
|
||||
two_out_of_four.distinct.should_equal_ignoring_order <|
|
||||
[[0, 1], [0, 2], [1, 2], [1, 0], [2, 0], [2, 1], [0, 3], [1, 3], [2, 3], [3, 0], [3, 1], [3, 2]]
|
||||
|
||||
permutations = 0.up_to 100 . map _->
|
||||
@ -59,8 +59,8 @@ add_specs suite_builder = suite_builder.group "Random" group_builder->
|
||||
Random.indices 3 100
|
||||
|
||||
all_permutations = [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]]
|
||||
permutations . should_contain_the_same_elements_as all_permutations
|
||||
permutations_2 . should_contain_the_same_elements_as all_permutations
|
||||
permutations.distinct.should_equal_ignoring_order all_permutations
|
||||
permutations_2.distinct.should_equal_ignoring_order all_permutations
|
||||
|
||||
Random.indices 0 0 . should_equal []
|
||||
Random.indices 0 100 . should_equal []
|
||||
@ -77,7 +77,7 @@ add_specs suite_builder = suite_builder.group "Random" group_builder->
|
||||
|
||||
all_from_small_range = [Date.new 2023 03 01, Date.new 2023 03 02, Date.new 2023 03 03]
|
||||
dates = 0.up_to 100 . map (_-> Random.date (Date.new 2023 03 01) (Date.new 2023 03 03))
|
||||
dates.should_contain_the_same_elements_as all_from_small_range
|
||||
dates.distinct.should_equal_ignoring_order all_from_small_range
|
||||
|
||||
group_builder.specify "should allow generating random times" <|
|
||||
Random.set_seed 12345
|
||||
@ -94,7 +94,7 @@ add_specs suite_builder = suite_builder.group "Random" group_builder->
|
||||
small_range_end = Time_Of_Day.new 8 12 18
|
||||
all_from_small_range = [Time_Of_Day.new 8 12 15, Time_Of_Day.new 8 12 16, Time_Of_Day.new 8 12 17, Time_Of_Day.new 8 12 18]
|
||||
times = 0.up_to 100 . map (_-> Random.time small_range_start small_range_end)
|
||||
times.should_contain_the_same_elements_as all_from_small_range
|
||||
times.distinct.should_equal_ignoring_order all_from_small_range
|
||||
|
||||
group_builder.specify "should allow generating random UUIDs" <|
|
||||
Random.uuid . should_only_contain_elements_in "0123456789abcdef-"
|
||||
@ -104,7 +104,7 @@ add_specs suite_builder = suite_builder.group "Random" group_builder->
|
||||
vector = ["A", "B", "C"]
|
||||
shuffles = 0.up_to 100 . map _->
|
||||
Random.items vector 2
|
||||
shuffles . should_contain_the_same_elements_as [["A", "B"], ["A", "C"], ["B", "A"], ["B", "C"], ["C", "A"], ["C", "B"]]
|
||||
shuffles.distinct.should_equal_ignoring_order [["A", "B"], ["A", "C"], ["B", "A"], ["B", "C"], ["C", "A"], ["C", "B"]]
|
||||
|
||||
Random.items ["A", "A", "A"] 2 . should_equal ["A", "A"]
|
||||
Random.items ["A", "A", "A"] 0 . should_equal []
|
||||
@ -139,8 +139,8 @@ add_specs suite_builder = suite_builder.group "Random" group_builder->
|
||||
Random.permute list
|
||||
|
||||
all_permutations = [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]]
|
||||
permutations . should_contain_the_same_elements_as all_permutations
|
||||
permutations_2 . should_contain_the_same_elements_as all_permutations
|
||||
permutations.distinct.should_equal_ignoring_order all_permutations
|
||||
permutations_2.distinct.should_equal_ignoring_order all_permutations
|
||||
|
||||
group_builder.specify "should not allow using a too-large integer range" <|
|
||||
high = 9223372036854775806999
|
||||
|
@ -259,10 +259,10 @@ add_specs suite_builder =
|
||||
methods.sort . should_equal ['Value', 'create', 'factory', 'first_method', 'my_method', 'other_method', 'second_method']
|
||||
|
||||
group_builder.specify "methods of Integer" <|
|
||||
Meta.meta Integer . methods . sort . should_equal ['%', '*', '+', '-', '/', '<', '<=', '>', '>=', '^', 'abs', 'bit_and', 'bit_not', 'bit_or', 'bit_shift', 'bit_shift_l', 'bit_shift_r', 'bit_xor', 'ceil', 'div', 'floor', 'negate', 'round', 'to_decimal', 'to_float', 'truncate']
|
||||
Meta.meta Integer . methods . sort . should_equal ['%', '*', '+', '-', '/', '<', '<=', '>', '>=', '^', 'abs', 'bit_and', 'bit_not', 'bit_or', 'bit_shift', 'bit_shift_l', 'bit_shift_r', 'bit_xor', 'ceil', 'div', 'fits_in_long', 'floor', 'negate', 'round', 'to_decimal', 'to_float', 'truncate']
|
||||
|
||||
group_builder.specify "static methods of Integer" <|
|
||||
Meta.meta (Meta.type_of Integer) . methods . sort . should_equal ['%', '*', '+', '-', '/', '<', '<=', '>', '>=', '^', 'abs', 'bit_and', 'bit_not', 'bit_or', 'bit_shift', 'bit_shift_l', 'bit_shift_r', 'bit_xor', 'ceil', 'div', 'floor', 'negate', 'parse', 'round', 'to_decimal', 'to_float', 'truncate']
|
||||
Meta.meta (Meta.type_of Integer) . methods . sort . should_equal ['%', '*', '+', '-', '/', '<', '<=', '>', '>=', '^', 'abs', 'bit_and', 'bit_not', 'bit_or', 'bit_shift', 'bit_shift_l', 'bit_shift_r', 'bit_xor', 'ceil', 'div', 'fits_in_long', 'floor', 'negate', 'parse', 'round', 'to_decimal', 'to_float', 'truncate']
|
||||
|
||||
group_builder.specify "methods of Any" <|
|
||||
Meta.meta Any . methods . should_contain "to_text"
|
||||
|
@ -186,7 +186,7 @@ add_specs suite_builder = suite_builder.group "Dataflow Warnings" group_builder-
|
||||
filtered = warnings.filter x-> x.value % 2 == 0
|
||||
rewarned = Warning.set warned filtered
|
||||
rewarned.should_equal 'foo'
|
||||
Warning.get_all rewarned . map .value . should_contain_the_same_elements_as [2,4]
|
||||
Warning.get_all rewarned . map .value . should_equal_ignoring_order [2,4]
|
||||
|
||||
group_builder.specify "should allow checking for any warnings" <|
|
||||
Warning.has_warnings "foo" . should_be_false
|
||||
@ -212,20 +212,23 @@ add_specs suite_builder = suite_builder.group "Dataflow Warnings" group_builder-
|
||||
count = Warning.get_all x . length
|
||||
Warning.attach "BAZ" count
|
||||
y . should_equal 0
|
||||
Warning.get_all y . map .value . should_contain_the_same_elements_as [4, 3, 2, 1, "BAZ"]
|
||||
Warning.get_all y . map .value . should_equal_ignoring_order [4, 3, 2, 1, "BAZ"]
|
||||
|
||||
group_builder.specify "should allow to map the warnings, selectively" <|
|
||||
warned = attach_four_warnings "foo"
|
||||
mapped = map_odd_warnings warned
|
||||
mapped . should_equal 'foo'
|
||||
Warning.get_all mapped . map .value . should_contain_the_same_elements_as [11, 2, 13, 4]
|
||||
Warning.get_all mapped . map .value . should_equal_ignoring_order [11, 2, 13, 4]
|
||||
|
||||
group_builder.specify "should allow to map warnings and errors, selectively" <|
|
||||
warned = attach_four_warnings "foo"
|
||||
mapped = map_odd_warnings_and_errors warned
|
||||
mapped . should_equal 'foo'
|
||||
Warning.get_all mapped . map .value . should_contain_the_same_elements_as [11, 2, 13, 4]
|
||||
Warning.get_all mapped . map (w-> w.origin.first.name) . should_contain_the_same_elements_as ["Warnings_Spec.map_odd_warnings_and_errors", "Warnings_Spec.attach_four_warnings"]
|
||||
Warning.get_all mapped . map .value
|
||||
. should_equal_ignoring_order [11, 2, 13, 4]
|
||||
Warning.get_all mapped . map (w-> w.origin.first.name)
|
||||
. distinct
|
||||
. should_equal_ignoring_order ["Warnings_Spec.map_odd_warnings_and_errors", "Warnings_Spec.attach_four_warnings"]
|
||||
|
||||
errored_2 = Error.throw 7
|
||||
mapped_2 = map_odd_warnings_and_errors errored_2
|
||||
@ -311,7 +314,7 @@ add_specs suite_builder = suite_builder.group "Dataflow Warnings" group_builder-
|
||||
v = 0.up_to 10 . to_vector
|
||||
r = v.map x->(if x % 2 == 0 then 10*x else Warning.attach 100*x x)
|
||||
r.should_equal [0, 1, 20, 3, 40, 5, 60, 7, 80, 9]
|
||||
Warning.get_all r wrap_errors=True . map .value . should_contain_the_same_elements_as [Map_Error.Error 1 100, Map_Error.Error 3 300, Map_Error.Error 5 500, Map_Error.Error 7 700, Map_Error.Error 9 900]
|
||||
Warning.get_all r wrap_errors=True . map .value . should_equal_ignoring_order [Map_Error.Error 1 100, Map_Error.Error 3 300, Map_Error.Error 5 500, Map_Error.Error 7 700, Map_Error.Error 9 900]
|
||||
|
||||
group_builder.specify "should be preserved after operations on multi-dimensional Vector" <|
|
||||
even x = (Warning.attach x x) % 2
|
||||
@ -335,9 +338,9 @@ add_specs suite_builder = suite_builder.group "Dataflow Warnings" group_builder-
|
||||
|
||||
r = a.if_not_error b
|
||||
r.should_equal 2
|
||||
Warning.get_all r . map .value . should_contain_the_same_elements_as ["b", "a"]
|
||||
Warning.get_all a . map .value . should_contain_the_same_elements_as ["a"]
|
||||
Warning.get_all b . map .value . should_contain_the_same_elements_as ["b"]
|
||||
Warning.get_all r . map .value . should_equal_ignoring_order ["b", "a"]
|
||||
Warning.get_all a . map .value . should_equal_ignoring_order ["a"]
|
||||
Warning.get_all b . map .value . should_equal_ignoring_order ["b"]
|
||||
|
||||
group_builder.specify "should be preserved around polyglot calls" <|
|
||||
x = Warning.attach "x" 1
|
||||
@ -351,11 +354,11 @@ add_specs suite_builder = suite_builder.group "Dataflow Warnings" group_builder-
|
||||
|
||||
r1 = java_id.apply x
|
||||
r1.should_equal 1
|
||||
Warning.get_all r1 . map .value . should_contain_the_same_elements_as ["x"]
|
||||
Warning.get_all r1 . map .value . should_equal_ignoring_order ["x"]
|
||||
|
||||
r2 = javaized_f.apply x
|
||||
r2.should_equal (Pair.new "A" 11)
|
||||
Warning.get_all r2 . map .value . should_contain_the_same_elements_as ["f(1)", "x"]
|
||||
Warning.get_all r2 . map .value . should_equal_ignoring_order ["f(1)", "x"]
|
||||
|
||||
## The following will not work, as if the polyglot method expects an
|
||||
`Object` it will get converted to a Java 'primitive' losing the
|
||||
@ -375,19 +378,19 @@ add_specs suite_builder = suite_builder.group "Dataflow Warnings" group_builder-
|
||||
|
||||
r1 = CallbackHelper.runCallbackInt f x
|
||||
r1.should_equal (Pair.new "A" 11)
|
||||
Warning.get_all r1 . map .value . should_contain_the_same_elements_as ["f(1)", "x"]
|
||||
Warning.get_all r1 . map .value . should_equal_ignoring_order ["f(1)", "x"]
|
||||
|
||||
r2 = CallbackHelper.runCallbackInt g x
|
||||
r2.should_equal 11
|
||||
Warning.get_all r2 . map .value . should_contain_the_same_elements_as ["g(1)", "x"]
|
||||
Warning.get_all r2 . map .value . should_equal_ignoring_order ["g(1)", "x"]
|
||||
|
||||
r3 = CallbackHelper.runCallbackInt h x
|
||||
r3.should_equal "{x=1}"
|
||||
Warning.get_all r3 . map .value . should_contain_the_same_elements_as ["h(1)", "x"]
|
||||
Warning.get_all r3 . map .value . should_equal_ignoring_order ["h(1)", "x"]
|
||||
|
||||
r4 = CallbackHelper.runCallbackInt i x
|
||||
r4.should_equal Nothing
|
||||
Warning.get_all r4 . map .value . should_contain_the_same_elements_as ["i(1)", "x"]
|
||||
Warning.get_all r4 . map .value . should_equal_ignoring_order ["i(1)", "x"]
|
||||
|
||||
group_builder.specify "should not affect method dispatch" <|
|
||||
a = My_Fancy_Collection.Value 42
|
||||
|
@ -110,7 +110,7 @@ add_specs suite_builder =
|
||||
rse.write "23"
|
||||
result
|
||||
result . should_equal 42
|
||||
Problems.get_attached_warnings result . should_contain_the_same_elements_as ["warn:1", "warn:2"]
|
||||
Problems.get_attached_warnings result . should_equal_ignoring_order ["warn:1", "warn:2"]
|
||||
f.read_text encoding . should_equal "BAZ23"
|
||||
f.delete_if_exists
|
||||
|
||||
@ -125,7 +125,7 @@ add_specs suite_builder =
|
||||
rse.write "23"
|
||||
result
|
||||
result . should_equal Nothing
|
||||
Problems.get_attached_warnings result . should_contain_the_same_elements_as ["warn:1", "warn:2"]
|
||||
Problems.get_attached_warnings result . should_equal_ignoring_order ["warn:1", "warn:2"]
|
||||
f.read_text encoding . should_equal "BAZ23"
|
||||
f.delete_if_exists
|
||||
|
||||
|
@ -32,7 +32,7 @@ add_specs suite_builder =
|
||||
group_builder.specify "should allow converting a GeoJSON array of features into a table" <|
|
||||
fields = ['foo', 'bar', 'baz', 'longitude', 'elevation']
|
||||
t = Geo.geo_json_to_table (geo_json.get "features") fields
|
||||
t.columns.map .name . should_contain_the_same_elements_as fields
|
||||
t.columns.map .name . should_equal_ignoring_order fields
|
||||
t.at 'foo' . to_vector . should_equal [1, 2]
|
||||
t.at 'bar' . to_vector . should_equal ['value2', Nothing]
|
||||
t.at 'baz' . to_vector . should_equal [Nothing, 3]
|
||||
@ -42,7 +42,7 @@ add_specs suite_builder =
|
||||
group_builder.specify "should allow converting a GeoJSON object into a table with provided fields" <|
|
||||
fields = ['foo', 'bar', 'longitude']
|
||||
t = Geo.geo_json_to_table geo_json fields
|
||||
t.columns.map .name . should_contain_the_same_elements_as fields
|
||||
t.columns.map .name . should_equal_ignoring_order fields
|
||||
t.at 'foo' . to_vector . should_equal [1, 2]
|
||||
t.at 'bar' . to_vector . should_equal ['value2', Nothing]
|
||||
t.at 'longitude' . to_vector . should_equal [-118.58, 10.11]
|
||||
@ -50,7 +50,7 @@ add_specs suite_builder =
|
||||
group_builder.specify "should allow converting a GeoJSON object into a table containing all available fields" <|
|
||||
fields = ['bar', 'baz', 'elevation', 'foo', 'latitude', 'longitude']
|
||||
t = Geo.geo_json_to_table geo_json
|
||||
t.columns.map .name . should_contain_the_same_elements_as fields
|
||||
t.columns.map .name . should_equal_ignoring_order fields
|
||||
t.at 'foo' . to_vector . should_equal [1, 2]
|
||||
t.at 'bar' . to_vector . should_equal ['value2', Nothing]
|
||||
t.at 'baz' . to_vector . should_equal [Nothing, 3]
|
||||
|
@ -210,7 +210,7 @@ snowflake_specific_spec suite_builder default_connection db_name setup =
|
||||
|
||||
# Again, when materialized the conversion Decimal->Integer is a feature, so it should not cause warning.
|
||||
Problems.assume_no_problems in_memory1
|
||||
in_memory1.at "small_ints" . to_vector . should_contain_the_same_elements_as [1, 2, 3]
|
||||
in_memory1.at "small_ints" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
|
||||
t2 = table_builder [["big_ints", [2^100, 2^110, 1]]]
|
||||
t2.at "big_ints" . value_type . should_equal (Value_Type.Decimal 38 0)
|
||||
@ -226,11 +226,11 @@ snowflake_specific_spec suite_builder default_connection db_name setup =
|
||||
Problems.assume_no_problems in_memory2
|
||||
|
||||
# Check correctness of values
|
||||
in_memory2.at "big_ints" . to_vector . should_contain_the_same_elements_as [2^100, 2^110, 1]
|
||||
in_memory2.at "big_ints" . to_vector . should_equal_ignoring_order [2^100, 2^110, 1]
|
||||
|
||||
group_builder.specify "correctly handles Decimal and Float types" <|
|
||||
table_name = Name_Generator.random_name "DecimalFloat"
|
||||
t1 = default_connection.get.create_table table_name [Column_Description.Value "d1" (Value_Type.Decimal 38 6), Column_Description.Value "d2" (Value_Type.Decimal 10 2), Column_Description.Value "d3" (Value_Type.Decimal 24 -3), Column_Description.Value "f" (Value_Type.Float)] primary_key=[]
|
||||
t1 = default_connection.get.create_table table_name [Column_Description.Value "d1" (Value_Type.Decimal 38 6), Column_Description.Value "d2" (Value_Type.Decimal 10 2), Column_Description.Value "d3" (Value_Type.Decimal 24 -3), Column_Description.Value "f" (Value_Type.Float)] primary_key=[] temporary=True
|
||||
t1.at "d1" . value_type . should_equal (Value_Type.Decimal 38 6)
|
||||
t1.at "d2" . value_type . should_equal (Value_Type.Decimal 10 2)
|
||||
# Negative scale is not supported so we fallback to defaults:
|
||||
@ -613,7 +613,7 @@ add_snowflake_specs suite_builder create_connection_fn db_name =
|
||||
|
||||
Common_Spec.add_specs suite_builder prefix create_connection_fn
|
||||
|
||||
common_selection = Common_Table_Operations.Main.Test_Selection.Config supports_case_sensitive_columns=True order_by_unicode_normalization_by_default=True allows_mixed_type_comparisons=False fixed_length_text_columns=False different_size_integer_types=False removes_trailing_whitespace_casting_from_char_to_varchar=True supports_decimal_type=True supported_replace_params=supported_replace_params run_advanced_edge_case_tests_by_default=False supports_date_time_without_timezone=True supports_nanoseconds_in_time=True is_nan_comparable=True
|
||||
common_selection = Common_Table_Operations.Main.Test_Selection.Config supports_case_sensitive_columns=True order_by_unicode_normalization_by_default=True allows_mixed_type_comparisons=False text_length_limited_columns=True fixed_length_text_columns=False different_size_integer_types=False removes_trailing_whitespace_casting_from_char_to_varchar=False supports_decimal_type=True supported_replace_params=supported_replace_params run_advanced_edge_case_tests_by_default=False supports_date_time_without_timezone=True supports_nanoseconds_in_time=True is_nan_comparable=True distinct_returns_first_row_from_group_if_ordered=False
|
||||
aggregate_selection = Common_Table_Operations.Aggregate_Spec.Test_Selection.Config first_last=False first_last_row_order=False aggregation_problems=False text_concat=False
|
||||
agg_in_memory_table = ((Project_Description.new enso_dev.Table_Tests).data / "data.csv") . read
|
||||
|
||||
|
@ -18,7 +18,8 @@ from Standard.Test import all
|
||||
|
||||
import enso_dev.Base_Tests.Data.Round_Spec
|
||||
|
||||
from project.Common_Table_Operations.Util import run_default_backend, within_table
|
||||
import project.Common_Table_Operations.Main.Char_Max_Size_After_Substring_Behavior
|
||||
from project.Common_Table_Operations.Util import run_default_backend, within_table, is_float_or_decimal
|
||||
|
||||
main filter=Nothing = run_default_backend add_specs filter
|
||||
|
||||
@ -487,16 +488,20 @@ add_specs suite_builder setup =
|
||||
True -> actual.at "col0" . value_type . should_equal (Value_Type.Char size=3 variable_length=True)
|
||||
False -> actual.at "col0" . value_type . should_equal (Value_Type.Char variable_length=True)
|
||||
|
||||
group_builder.specify "should allow to fill_nothing a fixed width of width 1 correctly expanding ouptut types" <|
|
||||
group_builder.specify "should allow to fill_nothing a fixed width of width 1 correctly expanding output types" <|
|
||||
t = table_builder [["col0", ["a", Nothing, " "]]] . cast "col0" (Value_Type.Char size=1 variable_length=False)
|
||||
fillBlank = t.fill_nothing ["col0"] ""
|
||||
fillOneSpace = t.fill_nothing ["col0"] " "
|
||||
fillTwoSpaces = t.fill_nothing ["col0"] " "
|
||||
case setup.test_selection.length_restricted_text_columns of
|
||||
True -> fillBlank.at "col0" . value_type . should_equal (Value_Type.Char size=1 variable_length=True)
|
||||
fillOneSpace.at "col0" . value_type . should_equal (Value_Type.Char size=1 variable_length=False)
|
||||
True ->
|
||||
fillBlank.at "col0" . value_type . should_equal (Value_Type.Char size=1 variable_length=True)
|
||||
case setup.test_selection.fixed_length_text_columns of
|
||||
True -> fillOneSpace.at "col0" . value_type . should_equal (Value_Type.Char size=1 variable_length=False)
|
||||
False -> fillOneSpace.at "col0" . value_type . should_equal (Value_Type.Char size=1 variable_length=True)
|
||||
fillTwoSpaces.at "col0" . value_type . should_equal (Value_Type.Char size=2 variable_length=True)
|
||||
False -> fillBlank.at "col0" . value_type . should_equal (Value_Type.Char variable_length=True)
|
||||
False ->
|
||||
fillBlank.at "col0" . value_type . should_equal (Value_Type.Char variable_length=True)
|
||||
fillOneSpace.at "col0" . value_type . should_equal (Value_Type.Char variable_length=True)
|
||||
fillTwoSpaces.at "col0" . value_type . should_equal (Value_Type.Char variable_length=True)
|
||||
|
||||
@ -514,8 +519,12 @@ add_specs suite_builder setup =
|
||||
t = table_builder [["col0", [Nothing, "200", Nothing, "400", "500", Nothing]]] . cast "col0" (Value_Type.Char size=3 variable_length=False)
|
||||
actual = t.fill_nothing ["col0"] " "
|
||||
actual.at "col0" . to_vector . should_equal [" ", "200", " ", "400", "500", " "]
|
||||
case setup.test_selection.fixed_length_text_columns of
|
||||
True -> actual.at "col0" . value_type . should_equal (Value_Type.Char size=3 variable_length=False)
|
||||
|
||||
case setup.test_selection.length_restricted_text_columns of
|
||||
True ->
|
||||
case setup.test_selection.fixed_length_text_columns of
|
||||
True -> actual.at "col0" . value_type . should_equal (Value_Type.Char size=3 variable_length=False)
|
||||
False -> actual.at "col0" . value_type . should_equal (Value_Type.Char size=3 variable_length=True)
|
||||
False -> actual.at "col0" . value_type . should_equal (Value_Type.Char variable_length=True)
|
||||
|
||||
group_builder.specify "should allow to fill_nothing from other columns" <|
|
||||
@ -788,29 +797,30 @@ add_specs suite_builder setup =
|
||||
setup.expect_integer_type <| (data.x + data.x)
|
||||
(data.x + data.y).value_type . should_be_a (Value_Type.Float ...)
|
||||
setup.expect_integer_type <| (data.x + 2)
|
||||
(data.x + 1.5).value_type . should_be_a (Value_Type.Float ...)
|
||||
is_float_or_decimal (data.x + 1.5)
|
||||
|
||||
setup.expect_integer_type <| (data.x - data.x)
|
||||
(data.x - data.y).value_type . should_be_a (Value_Type.Float ...)
|
||||
setup.expect_integer_type <| (data.x - 2)
|
||||
(data.x - 1.5).value_type . should_be_a (Value_Type.Float ...)
|
||||
is_float_or_decimal (data.x - 1.5)
|
||||
|
||||
setup.expect_integer_type <| (data.x * data.x)
|
||||
(data.x * data.y).value_type . should_be_a (Value_Type.Float ...)
|
||||
setup.expect_integer_type <| (data.x * 2)
|
||||
(data.x * 1.5).value_type . should_be_a (Value_Type.Float ...)
|
||||
is_float_or_decimal <| (data.x * 1.5)
|
||||
|
||||
(data.x ^ data.x).value_type . is_numeric . should_be_true
|
||||
|
||||
if setup.test_selection.supports_decimal_type then
|
||||
# TODO in-memory specific tests may be added in In_Memory/Table_Spec as part of #10429
|
||||
divide_in_memory_pending = if setup.is_database.not then "TODO: https://github.com/enso-org/enso/issues/10429"
|
||||
group_builder.specify "should correctly infer the types (for decimal division)" pending=divide_in_memory_pending <|
|
||||
(data.x / data.x).value_type . is_floating_point . should_be_true
|
||||
(data.x / data.y).value_type . is_floating_point . should_be_true
|
||||
(data.x / data.z).value_type . is_decimal . should_be_true
|
||||
(data.y / data.z).value_type . is_decimal . should_be_true
|
||||
(data.x / 2).value_type . is_floating_point . should_be_true
|
||||
(data.x / 1.5).value_type . is_floating_point . should_be_true
|
||||
is_float_or_decimal (data.x / data.x)
|
||||
is_float_or_decimal (data.x / data.y)
|
||||
is_float_or_decimal (data.x / data.z)
|
||||
is_float_or_decimal (data.y / data.z)
|
||||
is_float_or_decimal (data.x / 2)
|
||||
is_float_or_decimal (data.x / 1.5)
|
||||
|
||||
group_builder.specify "should check types" <|
|
||||
t = table_builder [["X", [1, 2]], ["Y", ["a", "b"]], ["Z", [True, False]]]
|
||||
@ -889,11 +899,11 @@ add_specs suite_builder setup =
|
||||
|
||||
r2 = (t.at "A") / (t.at "B")
|
||||
r2 . to_vector . should_equal r
|
||||
r2.value_type . should_be_a (Value_Type.Float ...)
|
||||
is_float_or_decimal r2
|
||||
|
||||
r3 = (t.at "A") / 2
|
||||
r3 . to_vector . should_equal [0.5, 2.5, 5.0, 50.0]
|
||||
r3.value_type . should_be_a (Value_Type.Float ...)
|
||||
is_float_or_decimal r3
|
||||
|
||||
a2 = [1.2, 5, 10.2, 100]
|
||||
b2 = [1.2, 2, 2, 5]
|
||||
@ -906,11 +916,11 @@ add_specs suite_builder setup =
|
||||
|
||||
r5 = (t2.at "A") / (t2.at "B")
|
||||
r5 . to_vector . should_equal r4
|
||||
r5.value_type . should_be_a (Value_Type.Float ...)
|
||||
is_float_or_decimal r5
|
||||
|
||||
r6 = (t2.at "A") / 2
|
||||
r6 . to_vector . should_equal [0.6, 2.5, 5.1, 50.0]
|
||||
r6.value_type . should_be_a (Value_Type.Float ...)
|
||||
is_float_or_decimal r6
|
||||
|
||||
db_pending = if setup.is_database then "Arithmetic error handling is currently not implemented for the Database backend."
|
||||
group_builder.specify "should allow division by 0 and report warnings" pending=db_pending <|
|
||||
@ -991,9 +1001,6 @@ add_specs suite_builder setup =
|
||||
group_builder.specify "should allow round on a "+type.to_text+" column (to >0 decimal places)" <|
|
||||
table = table_builder [["x", [0.51, 0.59, 3.51, 3.59, -0.51, -0.59, -3.51, -3.59]]]
|
||||
result = table.at "x" . cast type . round 1
|
||||
# TODO why it's becoming an Int?
|
||||
IO.println (result.value_type)
|
||||
IO.println (result.cast Value_Type.Char . to_vector)
|
||||
result.to_vector.should_equal [0.5, 0.6, 3.5, 3.6, -0.5, -0.6, -3.5, -3.6]
|
||||
result.name . should_equal "round([x])"
|
||||
|
||||
@ -1152,12 +1159,15 @@ add_specs suite_builder setup =
|
||||
resRight.name . should_equal "text_right([strings], 1)"
|
||||
resLeft . to_vector . should_equal ["a", "f", "", Nothing, "c", "I"]
|
||||
resRight . to_vector . should_equal ["a", "r", "", Nothing, "é", "."]
|
||||
case setup.is_database of
|
||||
False -> resLeft . value_type . should_equal (Value_Type.Char size=286 variable_length=True)
|
||||
True -> resLeft . value_type . should_equal (Value_Type.Char variable_length=True)
|
||||
case setup.is_database of
|
||||
False -> resRight . value_type . should_equal (Value_Type.Char size=286 variable_length=True)
|
||||
True -> resRight . value_type . should_equal (Value_Type.Char variable_length=True)
|
||||
|
||||
expected_new_size = case setup.test_selection.char_max_size_after_substring of
|
||||
Char_Max_Size_After_Substring_Behavior.Reset -> Nothing
|
||||
Char_Max_Size_After_Substring_Behavior.Kept -> 286
|
||||
Char_Max_Size_After_Substring_Behavior.Reduced -> 1
|
||||
|
||||
Test.with_clue "(Expected behaviour for substring size = "+setup.test_selection.char_max_size_after_substring.to_text+".) " <|
|
||||
resLeft.value_type . should_equal (Value_Type.Char size=expected_new_size variable_length=True)
|
||||
resRight.value_type . should_equal (Value_Type.Char size=expected_new_size variable_length=True)
|
||||
|
||||
group_builder.specify "should handle operation text_left and text_right of grapheme and non-grapheme" <|
|
||||
with_mixed_columns_if_supported [["strings", ["a", "foobar", "", Nothing, "👩🔬👩🔬V👩🔬👩🔬", "café", "It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of light, it was the season of darkness, it was the spring of hope, it was the winter of despair."]]] t->
|
||||
@ -1260,8 +1270,8 @@ add_specs suite_builder setup =
|
||||
|
||||
s1.is_empty . value_type . should_equal Value_Type.Boolean
|
||||
s1.is_blank . value_type . should_equal Value_Type.Boolean
|
||||
s1.fill_empty "<>" . value_type . is_text . should_be_true
|
||||
s1.fill_empty s2 . value_type . is_text . should_be_true
|
||||
s1.fill_empty "<>" . value_type . should_be_a (Value_Type.Char ...)
|
||||
s1.fill_empty s2 . value_type . should_be_a (Value_Type.Char ...)
|
||||
|
||||
group_builder.specify "should support text concatenation with the + operator" <|
|
||||
with_mixed_columns_if_supported [["s1", ["foobar", "bar", "baz", "BAB", Nothing]], ["s2", ["foo", "ar", "a", "b", Nothing]]] t3->
|
||||
@ -1269,60 +1279,58 @@ add_specs suite_builder setup =
|
||||
s2 = t3.at "s2"
|
||||
c1 = s1 + s2
|
||||
c1.to_vector . should_equal ["foobarfoo", "barar", "baza", "BABb", Nothing]
|
||||
c1.value_type.is_text . should_be_true
|
||||
c1.value_type.should_be_a (Value_Type.Char ...)
|
||||
|
||||
c2 = s1 + "_SUF"
|
||||
c2.to_vector . should_equal ["foobar_SUF", "bar_SUF", "baz_SUF", "BAB_SUF", Nothing]
|
||||
c2.value_type.is_text . should_be_true
|
||||
c2.value_type.should_be_a (Value_Type.Char ...)
|
||||
|
||||
c3 = s1 + Nothing
|
||||
c3.to_vector . should_equal [Nothing, Nothing, Nothing, Nothing, Nothing]
|
||||
c3.value_type.is_text . should_be_true
|
||||
c3.value_type.should_be_a (Value_Type.Char ...)
|
||||
|
||||
suite_builder.group prefix+"(Column_Operations_Spec) Min/Max Operations" group_builder->
|
||||
data = Min_Max_Data.setup setup.table_builder
|
||||
group_builder.specify "should allow one or more args and return the correct type" <|
|
||||
c1 = data.a.min 2
|
||||
c1.to_vector . should_equal [1, 2, 2]
|
||||
c1.value_type.is_integer . should_be_true
|
||||
setup.expect_integer_type c1
|
||||
|
||||
c2 = data.a.max 2
|
||||
c2.to_vector . should_equal [2, 2, 3]
|
||||
c2.value_type.is_integer . should_be_true
|
||||
setup.expect_integer_type c2
|
||||
|
||||
c3 = data.a.min [2.5, 2]
|
||||
c3.to_vector . should_equal [1, 2, 2]
|
||||
Test.with_clue "c3.value_type="+c3.value_type.to_display_text <|
|
||||
c3.value_type.is_floating_point . should_be_true
|
||||
c3.value_type.should_be_a (Value_Type.Float ...)
|
||||
|
||||
c4 = data.a.max [2.5, 2]
|
||||
c4.to_vector . should_equal [2.5, 2.5, 3]
|
||||
c4.value_type.is_floating_point . should_be_true
|
||||
c4.value_type.should_be_a (Value_Type.Float ...)
|
||||
|
||||
c5 = data.a.min data.b
|
||||
c5.to_vector . should_equal [1, 2, 3]
|
||||
Test.with_clue "c5.value_type="+c5.value_type.to_display_text+": " <|
|
||||
c5.value_type.is_floating_point . should_be_true
|
||||
c5.value_type.should_be_a (Value_Type.Float ...)
|
||||
|
||||
c6 = data.a.max data.b
|
||||
c6.to_vector . should_equal [4.5, 5.5, 6.5]
|
||||
c6.value_type.is_floating_point . should_be_true
|
||||
c6.value_type.should_be_a (Value_Type.Float ...)
|
||||
|
||||
c7 = data.a.min [data.a, data.b, 1]
|
||||
c7.to_vector . should_equal [1, 1, 1]
|
||||
c7.value_type.is_floating_point . should_be_true
|
||||
c7.value_type.should_be_a (Value_Type.Float ...)
|
||||
|
||||
c8 = data.a.max [data.a, data.b, 1]
|
||||
c8.to_vector . should_equal [4.5, 5.5, 6.5]
|
||||
c8.value_type.is_floating_point . should_be_true
|
||||
c8.value_type.should_be_a (Value_Type.Float ...)
|
||||
|
||||
c9 = (data.t.at "d").min False
|
||||
c9.to_vector . should_equal [False, False, False]
|
||||
c9.value_type.is_boolean . should_be_true
|
||||
c9.value_type.should_equal Value_Type.Boolean
|
||||
|
||||
c10 = (data.t.at "d").max False
|
||||
c10.to_vector . should_equal [True, False, True]
|
||||
c10.value_type.is_boolean . should_be_true
|
||||
c10.value_type.should_equal Value_Type.Boolean
|
||||
|
||||
group_builder.specify "should check types" <|
|
||||
[(.min), (.max)].each op->
|
||||
@ -1687,12 +1695,19 @@ add_specs suite_builder setup =
|
||||
group_builder.specify "Should create a column of the correct type on a table with no rows" <|
|
||||
t = table_builder [["x", ["1", "2", "3"]]]
|
||||
empty = t.take 0
|
||||
[[False, .is_boolean], [42, .is_integer], ["42", .is_text], ["foo", .is_text], [1.1, .is_floating_point]].map pair->
|
||||
value = pair.at 0
|
||||
pred = pair.at 1
|
||||
|
||||
check value expected_type =
|
||||
c = empty.make_constant_column value
|
||||
pred c.value_type . should_be_true
|
||||
pred ((empty.set c).at c.name . value_type) . should_be_true
|
||||
c.value_type.should_be_a expected_type
|
||||
(empty.set c).at c.name . value_type . should_be_a expected_type
|
||||
|
||||
check False Value_Type.Boolean
|
||||
check "42" (Value_Type.Char ...)
|
||||
check "foo" (Value_Type.Char ...)
|
||||
check 1.1 (Value_Type.Float ...)
|
||||
|
||||
c = empty.make_constant_column 42
|
||||
setup.expect_integer_type c
|
||||
|
||||
nulls_db_pending = if setup.is_database then "Empty NULL columns are unsupported in the database backends"
|
||||
group_builder.specify "Should create a column of the correct type on a table with no rows" pending=nulls_db_pending <|
|
||||
|
@ -20,6 +20,9 @@ type My_Type
|
||||
to_text : Text
|
||||
to_text self = "{{{MY Type [x="+self.x.to_text+"] }}}"
|
||||
|
||||
type Lazy_Ref
|
||||
Value ~get
|
||||
|
||||
add_specs suite_builder setup =
|
||||
prefix = setup.prefix
|
||||
materialize = setup.materialize
|
||||
@ -27,8 +30,10 @@ add_specs suite_builder setup =
|
||||
supports_conversion_failure_reporting = setup.is_database.not
|
||||
table_builder = build_sorted_table setup
|
||||
suite_builder.group prefix+"(Conversion_Spec) Table/Column.cast - to text" group_builder->
|
||||
table = Lazy_Ref.Value <|
|
||||
table_builder [["X", [1, 2, 3000]], ["Y", [True, False, True]], ["Z", [1.5, 0.125, -2.5]], ["W", ["a", "DEF", "a slightly longer text"]]]
|
||||
group_builder.specify "should allow to cast columns of various basic types to text" <|
|
||||
t = table_builder [["X", [1, 2, 3000]], ["Y", [True, False, True]], ["Z", [1.5, 0.125, -2.5]], ["W", ["a", "DEF", "a slightly longer text"]]]
|
||||
t = table.get
|
||||
t2 = t.cast t.column_names Value_Type.Char
|
||||
t2.at "X" . value_type . is_text . should_be_true
|
||||
t2.at "Y" . value_type . is_text . should_be_true
|
||||
@ -42,7 +47,7 @@ add_specs suite_builder setup =
|
||||
t2.at "W" . to_vector . should_equal ["a", "DEF", "a slightly longer text"]
|
||||
|
||||
group_builder.specify "should allow to cast columns selecting columns by type" <|
|
||||
t = table_builder [["X", [1, 2, 3000]], ["Y", [True, False, True]], ["Z", [1.5, 0.125, -2.5]], ["W", ["a", "DEF", "a slightly longer text"]]]
|
||||
t = table.get
|
||||
t2 = t.cast [..By_Type ..Integer] Value_Type.Char
|
||||
t2.at "X" . value_type . should_be_a (Value_Type.Char ...)
|
||||
t2.at "Y" . value_type . is_text . should_be_false
|
||||
@ -79,30 +84,21 @@ add_specs suite_builder setup =
|
||||
c.to_vector . should_equal ["{{{MY Type [x=42] }}}", "{{{MY Type [x=X] }}}"]
|
||||
|
||||
group_builder.specify "should allow to cast an integer column to a decimal type" <|
|
||||
t = table_builder [["X", [1, 2, 3]]]
|
||||
t = table.get
|
||||
c = t.at "X" . cast Value_Type.Decimal
|
||||
c.value_type.is_decimal . should_be_true
|
||||
c.to_vector . should_equal [1, 2, 3]
|
||||
c.to_vector . should_equal [1, 2, 3000]
|
||||
|
||||
if setup.test_selection.fixed_length_text_columns then
|
||||
group_builder.specify "should allow to cast a text column to fixed-length" <|
|
||||
t = table_builder [["X", ["a", "DEF", "a slightly longer text"]]]
|
||||
c = t.at "X" . cast (Value_Type.Char size=3 variable_length=False)
|
||||
t = table.get
|
||||
c = t.at "W" . cast (Value_Type.Char size=3 variable_length=False)
|
||||
c.value_type . should_equal (Value_Type.Char size=3 variable_length=False)
|
||||
c.to_vector . should_equal ["a ", "DEF", "a s"]
|
||||
|
||||
# No Conversion_Failure warning here, because we started with text, so it was expected we will trim it if needed.
|
||||
Problems.assume_no_problems c
|
||||
|
||||
group_builder.specify "should allow to cast a text column to variable-length with a max size" <|
|
||||
t = table_builder [["X", ["a", "DEF", "a slightly longer text"]]]
|
||||
c = t.at "X" . cast (Value_Type.Char size=3 variable_length=True)
|
||||
c.value_type . should_equal (Value_Type.Char size=3 variable_length=True)
|
||||
c.to_vector . should_equal ["a", "DEF", "a s"]
|
||||
|
||||
# No Conversion_Failure warning here, because we started with text, so it was expected we will trim it if needed.
|
||||
Problems.assume_no_problems c
|
||||
|
||||
group_builder.specify "should allow casting a non-text column to fixed-length text" <|
|
||||
t = table_builder [["X", [1, 22, 333]]]
|
||||
c = t.at "X" . cast (Value_Type.Char size=3 variable_length=False)
|
||||
@ -127,11 +123,23 @@ add_specs suite_builder setup =
|
||||
w2.affected_rows_count . should_equal 4
|
||||
|
||||
group_builder.specify "should not allow 0-length Char type" <|
|
||||
c1 = table_builder [["X", ["a", "", "bcd"]]] . at "X"
|
||||
c1 = table.get . at "W"
|
||||
r1 = c1.cast (Value_Type.Char size=0 variable_length=False)
|
||||
r1.should_fail_with Illegal_Argument
|
||||
r1.catch.to_display_text . should_contain "positive"
|
||||
|
||||
if setup.test_selection.text_length_limited_columns then
|
||||
group_builder.specify "should allow to cast a text column to variable-length with a max size" <|
|
||||
t = table_builder [["X", ["a", "DEF", "a slightly longer text"]]]
|
||||
c = t.at "X" . cast (Value_Type.Char size=3 variable_length=True)
|
||||
c.value_type . should_equal (Value_Type.Char size=3 variable_length=True)
|
||||
c.to_vector . should_equal ["a", "DEF", "a s"]
|
||||
|
||||
# No Conversion_Failure warning here, because we started with text, so it was expected we will trim it if needed.
|
||||
Problems.assume_no_problems c
|
||||
|
||||
group_builder.specify "should not allow 0-length Char type" <|
|
||||
c1 = table.get . at "W"
|
||||
r2 = c1.cast (Value_Type.Char size=0 variable_length=True)
|
||||
r2.should_fail_with Illegal_Argument
|
||||
|
||||
@ -500,7 +508,7 @@ add_specs suite_builder setup =
|
||||
c3.to_vector . should_equal ["1", "2.25", "3", "-45.25", "2.0", "X", "2020-01-01", date_time_str, "12:30:00", "{{{MY Type [x=42] }}}", "True", Nothing, "True"]
|
||||
Problems.assume_no_problems c3
|
||||
|
||||
if setup.test_selection.fixed_length_text_columns then
|
||||
if setup.test_selection.text_length_limited_columns then
|
||||
c3_2 = t2.cast "super-mix" (Value_Type.Char size=2) . at "super-mix"
|
||||
c3_2.value_type . should_equal (Value_Type.Char size=2)
|
||||
c3_2.to_vector . should_equal ["1", "2.", "3", "-4", "2.", "X", "20", "20", "12", "{{", "Tr", Nothing, "Tr"]
|
||||
|
@ -59,7 +59,7 @@ add_specs suite_builder setup =
|
||||
[0.3, 0.4, 0.6].contains (cv.at 1) . should_be_true
|
||||
|
||||
if setup.test_selection.distinct_returns_first_row_from_group_if_ordered then
|
||||
group_builder.specify "should allow to select distinct rows based on a subset of columns, returning any first from each group if the table was ordered" <|
|
||||
group_builder.specify "should allow to select distinct rows based on a subset of columns, returning first from each group if the table was ordered" <|
|
||||
a = ["A", ["a", "a", "a", "a", "a", "a"]]
|
||||
b = ["B", [1, 1, 2, 2, 1, 2]]
|
||||
c = ["C", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]]
|
||||
|
@ -149,7 +149,7 @@ add_specs suite_builder setup =
|
||||
t4 = t.filter "B" (Filter_Condition.Equal 5)
|
||||
t5 = t4.distinct ["A"] on_problems=..Report_Error
|
||||
r5 = t5 |> materialize
|
||||
r5.at "A" . to_vector . should_contain_the_same_elements_as ["b", "a"]
|
||||
r5.at "A" . to_vector . should_equal_ignoring_order ["b", "a"]
|
||||
r5.at "B" . to_vector . should_equal [5, 5]
|
||||
|
||||
## This mostly checks that various operations handle all kinds of Integer storage implementations
|
||||
|
@ -45,7 +45,7 @@ add_specs suite_builder setup =
|
||||
expected_rows = [r0, r1, r2, r3]
|
||||
|
||||
case setup.is_database of
|
||||
True -> r.should_contain_the_same_elements_as expected_rows
|
||||
True -> r.should_equal_ignoring_order expected_rows
|
||||
False -> r.should_equal expected_rows
|
||||
|
||||
group_builder.specify "should work correctly with empty tables" <|
|
||||
@ -97,7 +97,7 @@ add_specs suite_builder setup =
|
||||
r4 = [1, 4, 'b']
|
||||
expected_rows = [r1, r3, r1, r3, r2, r4, r2, r4, r1, r3, r1, r3, r1, r3, r1, r3]
|
||||
case setup.is_database of
|
||||
True -> r.should_contain_the_same_elements_as expected_rows
|
||||
True -> r.should_equal_ignoring_order expected_rows
|
||||
False -> r.should_equal expected_rows
|
||||
|
||||
group_builder.specify "should allow self-joins" <|
|
||||
@ -114,7 +114,7 @@ add_specs suite_builder setup =
|
||||
r3 = [2, 5, 2, 5]
|
||||
expected_rows = [r0, r1, r2, r3]
|
||||
case setup.is_database of
|
||||
True -> r.should_contain_the_same_elements_as expected_rows
|
||||
True -> r.should_equal_ignoring_order expected_rows
|
||||
False -> r.should_equal expected_rows
|
||||
|
||||
group_builder.specify "should rename columns of the right table to avoid duplicates" <|
|
||||
@ -160,7 +160,7 @@ add_specs suite_builder setup =
|
||||
r5 = [100, 4, 'a', 'x']
|
||||
expected_rows = [r0, r1, r2, r3, r4, r5]
|
||||
case setup.is_database of
|
||||
True -> r.should_contain_the_same_elements_as expected_rows
|
||||
True -> r.should_equal_ignoring_order expected_rows
|
||||
False -> r.should_equal expected_rows
|
||||
|
||||
group_builder.specify "Cross join is not possible via call to .join" <|
|
||||
|
@ -478,7 +478,7 @@ add_specs suite_builder setup =
|
||||
r4.at "Z" . to_vector . should_equal [2.0, 2.0, 2.0, 2.0]
|
||||
r4.at "W" . to_vector . should_equal [1, 3, 1, 3]
|
||||
expected_problems = [Floating_Point_Equality.Error "Z", Floating_Point_Equality.Error "X"]
|
||||
Problems.get_attached_warnings r3 . should_contain_the_same_elements_as expected_problems
|
||||
Problems.get_attached_warnings r3 . should_equal_ignoring_order expected_problems
|
||||
|
||||
group_builder.specify "should correctly handle nulls in equality conditions" <|
|
||||
t1 = table_builder [["X", ["A", Nothing, "a", Nothing, "ą", "b"]], ["Y", [0, 1, 2, 3, 4, 5]]]
|
||||
|
@ -14,28 +14,12 @@ import project.Util
|
||||
|
||||
main filter=Nothing = run_default_backend add_specs filter
|
||||
|
||||
type Data
|
||||
Value ~connection
|
||||
|
||||
setup create_connection_fn =
|
||||
Data.Value (create_connection_fn Nothing)
|
||||
|
||||
teardown self = self.connection.close
|
||||
|
||||
|
||||
add_specs suite_builder setup =
|
||||
prefix = setup.prefix
|
||||
create_connection_fn = setup.create_connection_func
|
||||
materialize = setup.materialize
|
||||
table_builder = setup.table_builder
|
||||
suite_builder.group prefix+"Table.merge" group_builder->
|
||||
data = Data.setup create_connection_fn
|
||||
|
||||
group_builder.teardown <|
|
||||
data.teardown
|
||||
|
||||
table_builder cols =
|
||||
setup.table_builder cols connection=data.connection
|
||||
|
||||
group_builder.specify "should allow to simply update columns based on a lookup table" <|
|
||||
lookup = table_builder [["Y", ["A", "B", "A"]], ["X", [1, 2, 3]]]
|
||||
my_table = table_builder [["X", [1, 2, 3, 2]], ["Y", ["Z", "ZZ", "ZZZ", "ZZZZ"]], ["Z", [10, 20, 30, 40]]]
|
||||
@ -292,9 +276,10 @@ add_specs suite_builder setup =
|
||||
|
||||
r2 = my_table.merge lookup key_columns="X"
|
||||
r2.should_fail_with No_Common_Type
|
||||
r2.catch.to_display_text . should_contain "Integer"
|
||||
r2.catch.to_display_text . should_contain "Char"
|
||||
r2.catch.to_display_text . should_contain "when unifying column [Y]"
|
||||
txt = r2.catch.to_display_text
|
||||
((txt.contains "Integer") || (txt.contains "Decimal")).should_be_true
|
||||
txt.should_contain "Char"
|
||||
txt.should_contain "when unifying column [Y]"
|
||||
|
||||
group_builder.specify "will allow incompatible types if allow_unmatched_rows=False" <|
|
||||
lookup = table_builder [["X", [1, 2, 3]], ["Y", [1, 11, 111]]]
|
||||
@ -335,7 +320,7 @@ add_specs suite_builder setup =
|
||||
r2 = my_table.merge lookup key_columns=[] add_new_columns=True
|
||||
r2.should_fail_with Illegal_Argument
|
||||
|
||||
if setup.is_database.not then group_builder.specify "(in-memory only) will preserve the order of rows from the original table" <|
|
||||
if setup.is_database.not then group_builder.specify "(in-memory only) will preserve the 279table_builder of rows from the original table" <|
|
||||
lookup = table_builder [["Y", [1, 0]], ["V", ["TRUE", "FALSE"]]]
|
||||
xs = 0.up_to 50 . to_vector
|
||||
ys = xs.map x-> x%2
|
||||
@ -386,7 +371,7 @@ add_specs suite_builder setup =
|
||||
|
||||
r3.row_count . should_equal 3
|
||||
r3.at "Z" . to_vector . length . should_equal 3
|
||||
r3.at "Z" . to_vector . should_contain_the_same_elements_as [10, 20, 30]
|
||||
r3.at "Z" . to_vector . should_equal_ignoring_order [10, 20, 30]
|
||||
m3.should_fail_with Invariant_Violation
|
||||
r3.at "Z" . to_vector . should_fail_with Invariant_Violation
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
from Standard.Base import all
|
||||
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
|
||||
import Standard.Base.Runtime.Ref.Ref
|
||||
|
||||
from Standard.Table import all
|
||||
from Standard.Table.Errors import all
|
||||
@ -10,6 +11,7 @@ from Standard.Database.Errors import Unsupported_Database_Operation, Integrity_E
|
||||
from Standard.Test import all
|
||||
|
||||
from project.Common_Table_Operations.Util import all
|
||||
from project.Common_Table_Operations.Date_Time_Spec import as_local_date_time_repr
|
||||
import project.Util
|
||||
|
||||
main filter=Nothing = run_default_backend add_specs filter
|
||||
@ -17,13 +19,9 @@ main filter=Nothing = run_default_backend add_specs filter
|
||||
type My_Type
|
||||
Value x y
|
||||
|
||||
type Data
|
||||
Value ~connection
|
||||
type Lazy_Ref
|
||||
Value ~get
|
||||
|
||||
setup create_connection_fn =
|
||||
Data.Value (create_connection_fn Nothing)
|
||||
|
||||
teardown self = self.connection.close
|
||||
# the ... operator used in the calls for Table.from_union and first.union "freezes" the default arguments so that they can be specified later, allowing us to run the full suite of tests
|
||||
call_static_union tables =
|
||||
Table.from_union tables ...
|
||||
@ -48,94 +46,128 @@ add_specs suite_builder setup =
|
||||
r.catch.to_display_text . should_contain "at least 1"
|
||||
|
||||
run_union_tests group_builder setup call_union =
|
||||
create_connection_fn = setup.create_connection_func
|
||||
data = Data.setup create_connection_fn
|
||||
group_builder.teardown <|
|
||||
data.teardown
|
||||
table_builder cols =
|
||||
setup.table_builder cols connection=data.connection
|
||||
materialize = setup.materialize
|
||||
## In Database backends we cannot make assumptions about the order of rows in the result, so we sort the table after materializing.
|
||||
In the in-memory backend, we avoid the sorting on purpose, to test that the order of rows in the result is as expected.
|
||||
materialize_and_maybe_sort table column_for_sort="rowid" =
|
||||
if setup.is_database.not then table else
|
||||
# We also remove the rowid column from the result to avoid interfering with the test.
|
||||
materialize table . sort column_for_sort . remove_columns "rowid" on_problems=..Ignore
|
||||
## A paired builder to `materialize_and_maybe_sort`.
|
||||
In Database, it adds a rowid column to the table. In in-memory, it just delegates to `table_builder`.
|
||||
For convenience, to avoid having to specify the starting id on each call, we just use a global counter.
|
||||
This way, tables created next to each other will have increasing rowids, as we'd expect.
|
||||
rowid_counter = Ref.new 0
|
||||
build_table_with_maybe_rowid structure =
|
||||
if setup.is_database.not then setup.table_builder structure else
|
||||
length = structure.first.second.length
|
||||
start = rowid_counter.modify (+length)
|
||||
# The rowid column is first to work correctly with By Position matching.
|
||||
setup.table_builder ([["rowid", start.up_to (start+length) . to_vector]] + structure)
|
||||
|
||||
table_builder = setup.table_builder
|
||||
group_builder.specify "should merge columns from multiple tables" <|
|
||||
t1 = table_builder [["A", [1, 2, 3]], ["B", ["a", "b", "c"]], ["C", [True, False, True]]]
|
||||
t2 = table_builder [["A", [4, 5, 6]], ["B", ["d", "e", "f"]], ["C", [False, True, False]]]
|
||||
t3 = table_builder [["A", [7, 8, 9]], ["B", ["g", "h", "i"]], ["C", [True, False, False]]]
|
||||
|
||||
t4 = call_union [t1, t2]
|
||||
expect_column_names ["A", "B", "C"] t4
|
||||
t4.at "A" . to_vector . should_equal [1, 2, 3, 4, 5, 6]
|
||||
t4.at "B" . to_vector . should_equal ["a", "b", "c", "d", "e", "f"]
|
||||
t4.at "C" . to_vector . should_equal [True, False, True, False, True, False]
|
||||
|
||||
t5 = call_union [t3, t1, t2]
|
||||
expect_column_names ["A", "B", "C"] t5
|
||||
t5.at "A" . to_vector . should_equal [7, 8, 9, 1, 2, 3, 4, 5, 6]
|
||||
t5.at "B" . to_vector . should_equal ["g", "h", "i", "a", "b", "c", "d", "e", "f"]
|
||||
t5.at "C" . to_vector . should_equal [True, False, False, True, False, True, False, True, False]
|
||||
m4 = materialize_and_maybe_sort t4 "A"
|
||||
m4.at "A" . to_vector . should_equal [1, 2, 3, 4, 5, 6]
|
||||
m4.at "B" . to_vector . should_equal ["a", "b", "c", "d", "e", "f"]
|
||||
m4.at "C" . to_vector . should_equal [True, False, True, False, True, False]
|
||||
|
||||
# In in-memory we also check one more test with ordering:
|
||||
if setup.is_database.not then
|
||||
t3 = table_builder [["A", [7, 8, 9]], ["B", ["g", "h", "i"]], ["C", [True, False, False]]]
|
||||
t5 = call_union [t3, t1, t2]
|
||||
expect_column_names ["A", "B", "C"] t5
|
||||
t5.at "A" . to_vector . should_equal [7, 8, 9, 1, 2, 3, 4, 5, 6]
|
||||
t5.at "B" . to_vector . should_equal ["g", "h", "i", "a", "b", "c", "d", "e", "f"]
|
||||
t5.at "C" . to_vector . should_equal [True, False, False, True, False, True, False, True, False]
|
||||
|
||||
table1 = Lazy_Ref.Value <|
|
||||
build_table_with_maybe_rowid [["A", [1, 2, 3]], ["B", ["a", "b", "c"]]]
|
||||
table2 = Lazy_Ref.Value <|
|
||||
build_table_with_maybe_rowid [["C", ["d", "e", "f"]], ["A", [4, 5, 6]]]
|
||||
table3 = Lazy_Ref.Value <|
|
||||
build_table_with_maybe_rowid [["D", [Nothing, Nothing, 0]], ["C", ["g", "h", "i"]]]
|
||||
group_builder.specify "should fill unmatched columns (by name matching) with nulls and report a warning by default" <|
|
||||
t1 = table_builder [["A", [1, 2, 3]], ["B", ["a", "b", "c"]]]
|
||||
t2 = table_builder [["C", ["d", "e", "f"]], ["A", [4, 5, 6]]]
|
||||
t3 = table_builder [["D", [Nothing, Nothing, 0]], ["C", ["g", "h", "i"]]]
|
||||
t1 = table1.get
|
||||
t2 = table2.get
|
||||
t3 = table3.get
|
||||
|
||||
action = call_union [t1, t2, t3] on_problems=_
|
||||
tester table =
|
||||
expect_column_names ["A", "B", "C", "D"] table
|
||||
table.at "A" . to_vector . should_equal [1, 2, 3, 4, 5, 6, Nothing, Nothing, Nothing]
|
||||
table.at "B" . to_vector . should_equal ["a", "b", "c", Nothing, Nothing, Nothing, Nothing, Nothing, Nothing]
|
||||
table.at "C" . to_vector . should_equal [Nothing, Nothing, Nothing, "d", "e", "f", "g", "h", "i"]
|
||||
table.at "D" . to_vector . should_equal [Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, 0]
|
||||
m = materialize_and_maybe_sort table
|
||||
expect_column_names ["A", "B", "C", "D"] m
|
||||
m.at "A" . to_vector . should_equal [1, 2, 3, 4, 5, 6, Nothing, Nothing, Nothing]
|
||||
m.at "B" . to_vector . should_equal ["a", "b", "c", Nothing, Nothing, Nothing, Nothing, Nothing, Nothing]
|
||||
m.at "C" . to_vector . should_equal [Nothing, Nothing, Nothing, "d", "e", "f", "g", "h", "i"]
|
||||
m.at "D" . to_vector . should_equal [Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, 0]
|
||||
problems = [Unmatched_Columns.Error ["A", "B", "C", "D"]]
|
||||
Problems.test_problem_handling action problems tester
|
||||
|
||||
action2 = call_union [t2, t3] on_problems=_
|
||||
tester2 table =
|
||||
expect_column_names ["C", "A", "D"] table
|
||||
table.at "C" . to_vector . should_equal ["d", "e", "f", "g", "h", "i"]
|
||||
table.at "A" . to_vector . should_equal [4, 5, 6, Nothing, Nothing, Nothing]
|
||||
table.at "D" . to_vector . should_equal [Nothing, Nothing, Nothing, Nothing, Nothing, 0]
|
||||
m = materialize_and_maybe_sort table
|
||||
expect_column_names ["C", "A", "D"] m
|
||||
m.at "C" . to_vector . should_equal ["d", "e", "f", "g", "h", "i"]
|
||||
m.at "A" . to_vector . should_equal [4, 5, 6, Nothing, Nothing, Nothing]
|
||||
m.at "D" . to_vector . should_equal [Nothing, Nothing, Nothing, Nothing, Nothing, 0]
|
||||
problems2 = [Unmatched_Columns.Error ["A", "D"]]
|
||||
Problems.test_problem_handling action2 problems2 tester2
|
||||
|
||||
group_builder.specify "should fill unmatched columns with nulls with no warning, if In_Any is explicitly chosen" <|
|
||||
t1 = table_builder [["A", [1, 2, 3]], ["B", ["a", "b", "c"]]]
|
||||
t2 = table_builder [["C", ["d", "e", "f"]], ["A", [4, 5, 6]]]
|
||||
t3 = table_builder [["D", [Nothing, Nothing, 0]], ["C", ["g", "h", "i"]]]
|
||||
t1 = table1.get
|
||||
t2 = table2.get
|
||||
t3 = table3.get
|
||||
|
||||
table = call_union [t1, t2, t3] columns_to_keep=..In_Any on_problems=..Report_Error
|
||||
Problems.assume_no_problems table
|
||||
expect_column_names ["A", "B", "C", "D"] table
|
||||
table.at "A" . to_vector . should_equal [1, 2, 3, 4, 5, 6, Nothing, Nothing, Nothing]
|
||||
table.at "B" . to_vector . should_equal ["a", "b", "c", Nothing, Nothing, Nothing, Nothing, Nothing, Nothing]
|
||||
table.at "C" . to_vector . should_equal [Nothing, Nothing, Nothing, "d", "e", "f", "g", "h", "i"]
|
||||
table.at "D" . to_vector . should_equal [Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, 0]
|
||||
m = materialize_and_maybe_sort table
|
||||
expect_column_names ["A", "B", "C", "D"] m
|
||||
m.at "A" . to_vector . should_equal [1, 2, 3, 4, 5, 6, Nothing, Nothing, Nothing]
|
||||
m.at "B" . to_vector . should_equal ["a", "b", "c", Nothing, Nothing, Nothing, Nothing, Nothing, Nothing]
|
||||
m.at "C" . to_vector . should_equal [Nothing, Nothing, Nothing, "d", "e", "f", "g", "h", "i"]
|
||||
m.at "D" . to_vector . should_equal [Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, 0]
|
||||
|
||||
group_builder.specify "should drop unmatched columns and warn, if In_All is selected" <|
|
||||
t1 = table_builder [["A", [1, 2, 3]], ["B", ["a", "b", "c"]]]
|
||||
t2 = table_builder [["C", ["d", "e", "f"]], ["A", [4, 5, 6]]]
|
||||
t3 = table_builder [["A", [Nothing, Nothing, 0]], ["C", ["g", "h", "i"]]]
|
||||
t1 = table1.get # A, B
|
||||
t2 = table2.get # C, A
|
||||
t3 = table3.get.rename_columns [["D", "A"]] # A, C
|
||||
|
||||
t4 = call_union [t1, t2, t3] columns_to_keep=..In_All
|
||||
w = Problems.expect_only_warning Unmatched_Columns t4
|
||||
w.column_names.should_equal ["B", "C"]
|
||||
expect_column_names ["A"] t4
|
||||
t4.at "A" . to_vector . should_equal [1, 2, 3, 4, 5, 6, Nothing, Nothing, 0]
|
||||
m = materialize_and_maybe_sort t4
|
||||
expect_column_names ["A"] m
|
||||
m.at "A" . to_vector . should_equal [1, 2, 3, 4, 5, 6, Nothing, Nothing, 0]
|
||||
|
||||
group_builder.specify "should fail if asked to drop unmatched columns but the set of common columns is empty" <|
|
||||
t1 = table_builder [["A", [1, 2, 3]], ["B", ["a", "b", "c"]]]
|
||||
t2 = table_builder [["C", ["d", "e", "f"]], ["A", [4, 5, 6]]]
|
||||
t3 = table_builder [["D", [Nothing, Nothing, 0]], ["C", ["g", "h", "i"]]]
|
||||
# We cannot have the rowid column here as we explicitly want to have no common columns.
|
||||
t1 = table1.get.remove_columns "rowid" on_problems=..Ignore
|
||||
t2 = table2.get.remove_columns "rowid" on_problems=..Ignore
|
||||
t3 = table3.get.remove_columns "rowid" on_problems=..Ignore
|
||||
|
||||
t4 = call_union [t1, t2, t3] columns_to_keep=..In_All on_problems=..Ignore
|
||||
t4.should_fail_with No_Output_Columns
|
||||
t4.catch.to_display_text . should_equal "No columns in the result, because of another problem: Unmatched columns are set to be dropped, but no common column names were found."
|
||||
|
||||
table4 = Lazy_Ref.Value <|
|
||||
table_builder [["A", [1]], ["X", [2]], ["B", ["a"]], ["Y", [3]]]
|
||||
table5 = Lazy_Ref.Value <|
|
||||
table_builder [["A", [4]], ["Z", [5]], ["B", ["b"]], ["X", [6]]]
|
||||
group_builder.specify "should allow to select specified columns for union by In_List, using the ordering from the list" <|
|
||||
t1 = table_builder [["A", [1]], ["X", [2]], ["B", ["a"]], ["Y", [3]]]
|
||||
t2 = table_builder [["A", [4]], ["Z", [5]], ["B", ["b"]], ["X", [6]]]
|
||||
t1 = table4.get
|
||||
t2 = table5.get
|
||||
|
||||
t3 = call_union [t1, t2] columns_to_keep=(..In_List ["B", "A"])
|
||||
expect_column_names ["B", "A"] t3
|
||||
t3.at "B" . to_vector . should_equal ["a", "b"]
|
||||
t3.at "A" . to_vector . should_equal [1, 4]
|
||||
m3 = materialize_and_maybe_sort t3 "A"
|
||||
m3.at "B" . to_vector . should_equal ["a", "b"]
|
||||
m3.at "A" . to_vector . should_equal [1, 4]
|
||||
|
||||
group_builder.specify "should add a Null column for unmatched columns from In_List" <|
|
||||
t1 = table_builder [["A", [1]], ["X", [2]]]
|
||||
@ -143,18 +175,19 @@ run_union_tests group_builder setup call_union =
|
||||
|
||||
t3 = call_union [t1, t2] columns_to_keep=(..In_List ["B", "A"])
|
||||
expect_column_names ["B", "A"] t3
|
||||
t3.at "B" . to_vector . should_equal [Nothing, Nothing]
|
||||
t3.at "A" . to_vector . should_equal [1, 5]
|
||||
m3 = materialize_and_maybe_sort t3 "A"
|
||||
m3.at "B" . to_vector . should_equal [Nothing, Nothing]
|
||||
m3.at "A" . to_vector . should_equal [1, 5]
|
||||
|
||||
group_builder.specify "does not allow an empty list in In_List" <|
|
||||
t1 = table_builder [["A", [1]], ["X", [2]]]
|
||||
t2 = table_builder [["Z", [4]], ["A", [5]]]
|
||||
t1 = table1.get
|
||||
t2 = table2.get
|
||||
r = call_union [t1, t2] columns_to_keep=(..In_List [])
|
||||
r.should_fail_with Illegal_Argument
|
||||
|
||||
group_builder.specify "does not error if duplicate entries appear in the In_List" <|
|
||||
t1 = table_builder [["A", [1]], ["X", [2]], ["B", ["a"]], ["Y", [3]]]
|
||||
t2 = table_builder [["A", [4]], ["Z", [5]], ["B", ["b"]], ["X", [6]]]
|
||||
t1 = table4.get
|
||||
t2 = table5.get
|
||||
|
||||
t3 = call_union [t1, t2] columns_to_keep=(..In_List ["B", "B", "A", "A", "B"])
|
||||
expect_column_names ["B", "A"] t3
|
||||
@ -165,8 +198,9 @@ run_union_tests group_builder setup call_union =
|
||||
|
||||
t3 = call_union [t1, t2] match_columns=Match_Columns.By_Position
|
||||
expect_column_names ["A", "Y"] t3
|
||||
t3.at "A" . to_vector . should_equal [1, 2, 3, 4, 5, 6]
|
||||
t3.at "Y" . to_vector . should_equal ["a", "b", "c", "d", "e", "f"]
|
||||
m3 = materialize_and_maybe_sort t3 "A"
|
||||
m3.at "A" . to_vector . should_equal [1, 2, 3, 4, 5, 6]
|
||||
m3.at "Y" . to_vector . should_equal ["a", "b", "c", "d", "e", "f"]
|
||||
|
||||
group_builder.specify "should fill extra columns (positional matching) with nulls and report a warning by default" <|
|
||||
t1 = table_builder [["A", [1, 2, 3]], ["B", ["a", "b", "c"]]]
|
||||
@ -176,9 +210,10 @@ run_union_tests group_builder setup call_union =
|
||||
action = call_union [t1, t2, t3] match_columns=Match_Columns.By_Position on_problems=_
|
||||
tester table =
|
||||
expect_column_names ["A1", "B1", "C"] table
|
||||
table.at "A1" . to_vector . should_equal [1, 2, 3, 4, 5, 6, 10, 20, 30]
|
||||
table.at "B1" . to_vector . should_equal ["a", "b", "c", "d", "e", "f", Nothing, Nothing, Nothing]
|
||||
table.at "C" . to_vector . should_equal [Nothing, Nothing, Nothing, 7, 8, 9, Nothing, Nothing, Nothing]
|
||||
m = materialize_and_maybe_sort table "A1"
|
||||
m.at "A1" . to_vector . should_equal [1, 2, 3, 4, 5, 6, 10, 20, 30]
|
||||
m.at "B1" . to_vector . should_equal ["a", "b", "c", "d", "e", "f", Nothing, Nothing, Nothing]
|
||||
m.at "C" . to_vector . should_equal [Nothing, Nothing, Nothing, 7, 8, 9, Nothing, Nothing, Nothing]
|
||||
problems = [Column_Count_Mismatch.Error 3 1]
|
||||
Problems.test_problem_handling action problems tester
|
||||
|
||||
@ -189,7 +224,7 @@ run_union_tests group_builder setup call_union =
|
||||
|
||||
t4 = call_union [t1, t2, t3] columns_to_keep=..In_All match_columns=..By_Position
|
||||
expect_column_names ["A"] t4
|
||||
t4.at "A" . to_vector . should_equal [1, 2, 3, 4, 5, 6, 10, 20, 30]
|
||||
t4.at "A" . to_vector . should_equal_ignoring_order [1, 2, 3, 4, 5, 6, 10, 20, 30]
|
||||
w = Problems.expect_only_warning Column_Count_Mismatch t4
|
||||
w.expected.should_equal 3
|
||||
w.actual.should_equal 1
|
||||
@ -202,13 +237,14 @@ run_union_tests group_builder setup call_union =
|
||||
t4 = call_union [t1, t2, t3] columns_to_keep=..In_Any match_columns=..By_Position on_problems=..Report_Error
|
||||
Problems.assume_no_problems t4
|
||||
expect_column_names ["A1", "B1", "C"] t4
|
||||
t4.at "A1" . to_vector . should_equal [1, 2, 3, 4, 5, 6, 10, 20, 30]
|
||||
t4.at "B1" . to_vector . should_equal ["a", "b", "c", "d", "e", "f", Nothing, Nothing, Nothing]
|
||||
t4.at "C" . to_vector . should_equal [Nothing, Nothing, Nothing, 7, 8, 9, Nothing, Nothing, Nothing]
|
||||
m4 = materialize_and_maybe_sort t4 "A1"
|
||||
m4.at "A1" . to_vector . should_equal [1, 2, 3, 4, 5, 6, 10, 20, 30]
|
||||
m4.at "B1" . to_vector . should_equal ["a", "b", "c", "d", "e", "f", Nothing, Nothing, Nothing]
|
||||
m4.at "C" . to_vector . should_equal [Nothing, Nothing, Nothing, 7, 8, 9, Nothing, Nothing, Nothing]
|
||||
|
||||
group_builder.specify "does not allow In_List with positional matching" <|
|
||||
t1 = table_builder [["A", [1, 2, 3]], ["B", ["a", "b", "c"]]]
|
||||
t2 = table_builder [["A1", [4, 5, 6]], ["B1", ["d", "e", "f"]], ["C", [7, 8, 9]]]
|
||||
t1 = table4.get
|
||||
t2 = table5.get
|
||||
r = call_union [t1, t2] columns_to_keep=(..In_List ["A", "B"]) match_columns=Match_Columns.By_Position
|
||||
r.should_fail_with Illegal_Argument
|
||||
|
||||
@ -218,42 +254,58 @@ run_union_tests group_builder setup call_union =
|
||||
|
||||
check table =
|
||||
expect_column_names ["X", "A"] table
|
||||
table.at "X" . to_vector . should_equal [1, 2, 3, 4, 5, 6]
|
||||
table.at "A" . to_vector . should_equal [Nothing, Nothing, Nothing, "a", "b", "c"]
|
||||
m = materialize_and_maybe_sort table "X"
|
||||
m.at "X" . to_vector . should_equal [1, 2, 3, 4, 5, 6]
|
||||
m.at "A" . to_vector . should_equal [Nothing, Nothing, Nothing, "a", "b", "c"]
|
||||
|
||||
t3 = call_union [t1, t2] match_columns=Match_Columns.By_Position
|
||||
within_table t3 <|
|
||||
check t3
|
||||
Problems.get_attached_warnings t3 . should_equal [Column_Count_Mismatch.Error 2 1]
|
||||
|
||||
t5 = table_builder [["Y", [7, 8, 9]], ["A", ["d", "e", "f"]], ["Z", [10, 11, 12]]]
|
||||
t6 = table_builder [["W", [0]]]
|
||||
t7 = table_builder [["X", [7, 8, 9]], ["Y", ["d", "e", "f"]], ["Z", [10, 11, 12]]]
|
||||
t5 = setup.light_table_builder [["Y", [7, 8, 9]], ["A", ["d", "e", "f"]], ["Z", [10, 11, 12]]]
|
||||
t6 = setup.light_table_builder [["W", [0]]]
|
||||
t7 = setup.light_table_builder [["X", [7, 8, 9]], ["Y", ["d", "e", "f"]], ["Z", [10, 11, 12]]]
|
||||
t8 = call_union [t1, t2, t5, t6, t7] match_columns=Match_Columns.By_Position
|
||||
expect_column_names ["Y", "A", "Z"] t8
|
||||
|
||||
group_builder.specify "should allow to merge a table with itself" <|
|
||||
t1 = table_builder [["A", [1, 2, 3]], ["B", ["a", "b", "c"]]]
|
||||
t2 = call_union [t1, t1, t1]
|
||||
t1 = table1.get
|
||||
t2 = call_union [t1, t1, t1] . remove_columns "rowid" on_problems=..Ignore
|
||||
expect_column_names ["A", "B"] t2
|
||||
t2.at "A" . to_vector . should_equal [1, 2, 3, 1, 2, 3, 1, 2, 3]
|
||||
t2.at "B" . to_vector . should_equal ["a", "b", "c", "a", "b", "c", "a", "b", "c"]
|
||||
case setup.is_database of
|
||||
False ->
|
||||
t2.at "A" . to_vector . should_equal [1, 2, 3, 1, 2, 3, 1, 2, 3]
|
||||
t2.at "B" . to_vector . should_equal ["a", "b", "c", "a", "b", "c", "a", "b", "c"]
|
||||
True ->
|
||||
m2 = materialize t2
|
||||
m2.at "A" . to_vector . should_equal_ignoring_order [1, 1, 1, 2, 2, 2, 3, 3, 3]
|
||||
m2.at "B" . to_vector . should_equal_ignoring_order ["a", "a", "a", "b", "b", "b", "c", "c", "c"]
|
||||
|
||||
group_builder.specify "should not de-duplicate rows" <|
|
||||
# We don't use build_table_with_maybe_rowid here on purpose, as that would make the rows no longer duplicated.
|
||||
t1 = table_builder [["A", [1, 1, 3]], ["B", ["a", "a", "c"]]]
|
||||
t2 = table_builder [["A", [1, 2, 2]], ["B", ["a", "b", "b"]]]
|
||||
t3 = call_union [t1, t2]
|
||||
expect_column_names ["A", "B"] t3
|
||||
t3.at "A" . to_vector . should_equal [1, 1, 3, 1, 2, 2]
|
||||
t3.at "B" . to_vector . should_equal ["a", "a", "c", "a", "b", "b"]
|
||||
m3 = materialize t3
|
||||
case setup.is_database of
|
||||
False ->
|
||||
m3.at "A" . to_vector . should_equal [1, 1, 3, 1, 2, 2]
|
||||
m3.at "B" . to_vector . should_equal ["a", "a", "c", "a", "b", "b"]
|
||||
True ->
|
||||
sorted = m3.sort "A"
|
||||
sorted.at "A" . to_vector . should_equal [1, 1, 1, 2, 2, 3]
|
||||
sorted.at "B" . to_vector . should_equal ["a", "a", "a", "b", "b", "c"]
|
||||
|
||||
group_builder.specify "should gracefully handle the case where no tables to union were provided" <|
|
||||
t1 = table_builder [["A", [1, 2, 3]], ["B", ["a", "b", "c"]]]
|
||||
t1 = table1.get.remove_columns "rowid" on_problems=..Ignore
|
||||
|
||||
check_same table =
|
||||
expect_column_names ["A", "B"] table
|
||||
table.at "A" . to_vector . should_equal [1, 2, 3]
|
||||
table.at "B" . to_vector . should_equal ["a", "b", "c"]
|
||||
m = materialize_and_maybe_sort table "A"
|
||||
m.at "A" . to_vector . should_equal [1, 2, 3]
|
||||
m.at "B" . to_vector . should_equal ["a", "b", "c"]
|
||||
|
||||
check_same <| call_union [t1]
|
||||
check_same <| call_union [t1] match_columns=Match_Columns.By_Position
|
||||
@ -276,47 +328,50 @@ run_union_tests group_builder setup call_union =
|
||||
t3 = call_union [t1, t2]
|
||||
expect_column_names ["A"] t3
|
||||
Problems.assume_no_problems t3
|
||||
t3.at "A" . to_vector . should_equal ["a", "b", "c", "xyz", "abc", "def"]
|
||||
t3.at "A" . value_type . is_text . should_be_true
|
||||
Test.with_clue "t3[A].value_type="+(t3.at "A").value_type.to_display_text+": " <|
|
||||
t3.at "A" . value_type . variable_length . should_be_true
|
||||
t3.at "A" . to_vector . should_equal_ignoring_order ["a", "b", "c", "xyz", "abc", "def"]
|
||||
t3.at "A" . value_type . should_be_a (Value_Type.Char ...)
|
||||
t3.at "A" . value_type . variable_length . should_be_true
|
||||
|
||||
group_builder.specify "should find a common type that will fit the merged columns (Integer + Float)" <|
|
||||
t1 = table_builder [["A", [0, 1, 2]]]
|
||||
t2 = table_builder [["A", [1.0, 2.0, 2.5]]]
|
||||
|
||||
setup.expect_integer_type <| t1.at "A"
|
||||
t2.at "A" . value_type . is_floating_point . should_be_true
|
||||
t2.at "A" . value_type . should_be_a (Value_Type.Float ...)
|
||||
|
||||
t3 = call_union [t1, t2]
|
||||
expect_column_names ["A"] t3
|
||||
Problems.assume_no_problems t3
|
||||
t3.at "A" . value_type . is_floating_point . should_be_true
|
||||
t3.at "A" . to_vector . should_equal [0, 1, 2, 1.0, 2.0, 2.5]
|
||||
is_float_or_decimal (t3.at "A")
|
||||
t3.at "A" . to_vector . should_equal_ignoring_order [0, 1, 2, 1.0, 2.0, 2.5]
|
||||
|
||||
group_builder.specify "should find a common type that will fit the merged columns (numeric + Boolean)" <|
|
||||
t1 = table_builder [["A", [0, 1, 20]]]
|
||||
t2 = table_builder [["A", [True, False, True]]]
|
||||
t1 = build_table_with_maybe_rowid [["A", [0, 1, 20]]]
|
||||
t2 = build_table_with_maybe_rowid [["A", [True, False, True]]]
|
||||
|
||||
setup.expect_integer_type <| t1.at "A"
|
||||
t2.at "A" . value_type . should_equal Value_Type.Boolean
|
||||
|
||||
t3 = call_union [t1, t2]
|
||||
expect_column_names ["A"] t3
|
||||
Problems.assume_no_problems t3
|
||||
setup.expect_integer_type <| t3.at "A"
|
||||
t3.at "A" . to_vector . should_equal [0, 1, 20, 1, 0, 1]
|
||||
Problems.assume_no_problems t3
|
||||
|
||||
t4 = table_builder [["A", [1.5, 0.0, 2.0]]]
|
||||
m3 = materialize_and_maybe_sort t3
|
||||
expect_column_names ["A"] m3
|
||||
m3.at "A" . to_vector . should_equal [0, 1, 20, 1, 0, 1]
|
||||
|
||||
t4 = build_table_with_maybe_rowid [["A", [1.5, 0.0, 2.0]]]
|
||||
t5 = call_union [t2, t4]
|
||||
Problems.assume_no_problems t5
|
||||
t5.at "A" . value_type . is_floating_point . should_be_true
|
||||
t5.at "A" . to_vector . should_equal [1.0, 0.0, 1.0, 1.5, 0.0, 2.0]
|
||||
t5.at "A" . value_type . should_be_a (Value_Type.Float ...)
|
||||
|
||||
m5 = materialize_and_maybe_sort t5
|
||||
m5.at "A" . to_vector . should_equal [1.0, 0.0, 1.0, 1.5, 0.0, 2.0]
|
||||
|
||||
group_builder.specify "should warn about loss of precision when converting large Integer to Float" pending=(if setup.is_database then "Loss_Of_Integer_Precision not yet supported in DB.") <|
|
||||
# 2^70 is not exactly representable as a Float.
|
||||
t1 = table_builder [["A", [2^70, 2^10, 2]]]
|
||||
t2 = table_builder [["A", [1.5, 2.0, 2.5]]]
|
||||
t1 = build_table_with_maybe_rowid [["A", [2^70, 2^10, 2]]]
|
||||
t2 = build_table_with_maybe_rowid [["A", [1.5, 2.0, 2.5]]]
|
||||
t1.at "A" . value_type . is_decimal . should_be_true
|
||||
t2.at "A" . value_type . is_floating_point . should_be_true
|
||||
|
||||
@ -325,8 +380,10 @@ run_union_tests group_builder setup call_union =
|
||||
w = Problems.expect_only_warning Loss_Of_Integer_Precision t3
|
||||
# TODO should we try to include column name here for context? may be worth it...
|
||||
w.affected_rows_count.should_equal 1
|
||||
t3.at "A" . value_type . is_floating_point . should_be_true
|
||||
t3.at "A" . to_vector . should_equal [(2^70).to_float, 2^10, 2, 1.5, 2.0, 2.5]
|
||||
|
||||
t3.at "A" . value_type . should_be_a (Value_Type.Float ...)
|
||||
m3 = materialize_and_maybe_sort t3
|
||||
m3.at "A" . to_vector . should_equal [(2^70).to_float, 2^10, 2, 1.5, 2.0, 2.5]
|
||||
|
||||
group_builder.specify "should find a common type (Integer and Char of different sizes)" <|
|
||||
t1 = (table_builder [["X", [0, 1, 2]], ["Y", ['aa', 'bb', 'cc']]]) . cast "X" (Value_Type.Integer Bits.Bits_16) . cast "Y" (Value_Type.Char size=2 variable_length=False)
|
||||
@ -341,19 +398,29 @@ run_union_tests group_builder setup call_union =
|
||||
t12.at "X" . value_type . should_equal (Value_Type.Integer Bits.Bits_32)
|
||||
t12.at "Y" . value_type . should_equal (Value_Type.Char size=2 variable_length=True)
|
||||
|
||||
t12.at "X" . to_vector . should_equal [0, 1, 2, 3, 4, 5]
|
||||
t12.at "Y" . to_vector . should_equal ['aa', 'bb', 'cc', 'x', 'y', 'z']
|
||||
m12 = materialize_and_maybe_sort t12 "X"
|
||||
m12.at "X" . to_vector . should_equal [0, 1, 2, 3, 4, 5]
|
||||
m12.at "Y" . to_vector . should_equal ['aa', 'bb', 'cc', 'x', 'y', 'z']
|
||||
|
||||
date_time_pending = if setup.test_selection.date_time.not then "Date/Time operations are not supported."
|
||||
group_builder.specify "should warn when converting a Date to Date_Time" pending=date_time_pending <|
|
||||
t1 = table_builder [["D", [Date_Time.new 2024 5 16 16 48 23]]]
|
||||
t2 = table_builder [["D", [Date.new 2019 10 23, Date.new 2020]]]
|
||||
t1 = build_table_with_maybe_rowid [["D", [Date_Time.new 2024 5 16 16 48 23]]]
|
||||
t2 = build_table_with_maybe_rowid [["D", [Date.new 2019 10 23, Date.new 2020]]]
|
||||
|
||||
action = call_union [t1, t2] on_problems=_
|
||||
tester table =
|
||||
expect_column_names ["D"] table
|
||||
table.at "D" . value_type . should_equal Value_Type.Date_Time
|
||||
table.at "D" . to_vector . should_equal_tz_agnostic [Date_Time.new 2024 5 16 16 48 23, Date_Time.new 2019 10 23 0 0 0, Date_Time.new 2020 1 1 0 0 0]
|
||||
m = materialize_and_maybe_sort table
|
||||
expect_column_names ["D"] m
|
||||
m.at "D" . value_type . should_equal Value_Type.Date_Time
|
||||
## Some backends will preserve the local time but change the offset (e.g. Snowflake)
|
||||
and some will preserve the time instant but change the local time in the process of aligning offset (e.g. Postgres).
|
||||
dt_vec = m.at "D" . to_vector
|
||||
Test.with_clue <| "(D = "+dt_vec.to_display_text+") " <|
|
||||
as_local dt = dt.format "YYYY-MM-DD HH:mm:ss"
|
||||
preserves_local = (dt_vec.map as_local) == ["2024-05-16 16:48:23", "2019-10-23 00:00:00", "2020-01-01 00:00:00"]
|
||||
at_utc dt = dt.at_zone Time_Zone.utc
|
||||
preserves_instant = (dt_vec.map at_utc) == ([Date_Time.new 2024 5 16 16 48 23, Date_Time.new 2019 10 23 0 0 0, Date_Time.new 2020 1 1 0 0 0].map at_utc)
|
||||
(preserves_local || preserves_instant) . should_be_true
|
||||
problems = [Mixing_Date_Time_Types.Date_To_Date_Time "D"]
|
||||
problems.first.to_display_text . should_contain "[D]"
|
||||
Problems.test_problem_handling action problems tester
|
||||
@ -395,14 +462,15 @@ run_union_tests group_builder setup call_union =
|
||||
action = call_union [t1, t2] on_problems=_
|
||||
result_checker table =
|
||||
expect_column_names ["A", "B", "C"] table
|
||||
m = materialize_and_maybe_sort table "A"
|
||||
# If type was matched - the columns are merged as is:
|
||||
table.at "A" . to_vector . should_equal [1, 2, 3, 4, 5, 6]
|
||||
m.at "A" . to_vector . should_equal [1, 2, 3, 4, 5, 6]
|
||||
setup.expect_integer_type <| table.at "A"
|
||||
# If mixed, they are converted to text representation:
|
||||
table.at "B" . to_vector . should_equal ["a", "b", "c", "1", "2", "3"]
|
||||
m.at "B" . to_vector . should_equal ["a", "b", "c", "1", "2", "3"]
|
||||
table.at "B" . value_type . is_text . should_be_true
|
||||
|
||||
v = table.at "C" . to_vector
|
||||
v = m.at "C" . to_vector
|
||||
# The check needs to be case insensitive because various database backends may represent Booleans with lower or uppercase.
|
||||
v.take 2 . map (t -> t.to_case Case.Lower) . should_equal ["true", "false"]
|
||||
# Nothing is preserved, not converted to text because we want to preserve the meaning of 'missing value':
|
||||
@ -422,18 +490,21 @@ run_union_tests group_builder setup call_union =
|
||||
types = w.types.map value_type->
|
||||
Meta.meta value_type . constructor . name
|
||||
Test.with_clue "(should be one of...) " <|
|
||||
[["Char", "Integer"], ["Boolean", "Char"]].should_contain types
|
||||
[["Char", "Decimal"], ["Char", "Integer"], ["Boolean", "Char"]].should_contain types
|
||||
Problems.test_advanced_problem_handling action error_checker warnings_checker result_checker
|
||||
|
||||
group_builder.specify "if no common type can be found, will fall back to converting all types to text and warn (Date+Time)" pending=date_time_pending <|
|
||||
t1 = table_builder [["D", [Time_Of_Day.new 12, Time_Of_Day.new 13, Time_Of_Day.new 14]]]
|
||||
t2 = table_builder [["D", [Date.new 2019, Date.new 2020, Date.new 2021]]]
|
||||
t1 = build_table_with_maybe_rowid [["D", [Time_Of_Day.new 12, Time_Of_Day.new 13, Time_Of_Day.new 14]]]
|
||||
t2 = build_table_with_maybe_rowid [["D", [Date.new 2019, Date.new 2020, Date.new 2021]]]
|
||||
|
||||
action = call_union [t1, t2] on_problems=_
|
||||
tester table =
|
||||
expect_column_names ["D"] table
|
||||
table.at "D" . to_vector . should_equal ["12:00:00", "13:00:00", "14:00:00", "2019-01-01", "2020-01-01", "2021-01-01"]
|
||||
table.at "D" . value_type . is_text . should_be_true
|
||||
m = materialize_and_maybe_sort table
|
||||
expect_column_names ["D"] m
|
||||
# The nanoseconds suffix in time of day is optional - we strip it for the comparison.
|
||||
strip_ns s = s.replace "\.0+$".to_regex ""
|
||||
m.at "D" . to_vector . map strip_ns . should_equal ["12:00:00", "13:00:00", "14:00:00", "2019-01-01", "2020-01-01", "2021-01-01"]
|
||||
m.at "D" . value_type . is_text . should_be_true
|
||||
problems = [No_Common_Type.Warning_Convert_To_Text [Value_Type.Time, Value_Type.Date] "D"]
|
||||
Problems.test_problem_handling action problems tester
|
||||
|
||||
@ -442,13 +513,13 @@ run_union_tests group_builder setup call_union =
|
||||
t2 = table_builder [["B", ["a"]]]
|
||||
r1 = call_union [t1, t2] match_columns=Match_Columns.By_Position
|
||||
expect_column_names ["A"] r1
|
||||
r1.at "A" . value_type . is_text . should_be_true
|
||||
r1.at "A" . to_vector . should_equal ["1", "a"]
|
||||
r1.at "A" . value_type . should_be_a (Value_Type.Char ...)
|
||||
r1.at "A" . to_vector . should_equal_ignoring_order ["1", "a"]
|
||||
w = Problems.expect_only_warning No_Common_Type r1
|
||||
w.related_column_name.should_equal "A"
|
||||
|
||||
group_builder.specify "should gracefully handle tables from different backends" <|
|
||||
t1 = table_builder [["A", [1, 2, 3]], ["B", ["a", "b", "c"]]]
|
||||
t1 = table1.get
|
||||
|
||||
alternative_connection = Database.connect (SQLite.In_Memory)
|
||||
t0 = (Table.new [["A", [1, 2, 4]], ["B", ["10", "20", "30"]]]).select_into_database_table alternative_connection "T0" temporary=True
|
||||
|
@ -190,7 +190,7 @@ add_specs suite_builder setup =
|
||||
|
||||
t3 = t1.zip t2
|
||||
expected_problems = [Row_Count_Mismatch.Error 2 1, Duplicate_Output_Column_Names.Error ["Right X"]]
|
||||
Problems.get_attached_warnings t3 . should_contain_the_same_elements_as expected_problems
|
||||
Problems.get_attached_warnings t3 . should_equal_ignoring_order expected_problems
|
||||
|
||||
group_builder.specify "should allow to zip the table with itself" <|
|
||||
## Even though this does not seem very useful, we should verify that
|
||||
|
@ -118,6 +118,8 @@ type Test_Selection
|
||||
each group. Guaranteed in the in-memory backend, but may not be
|
||||
supported by all databases.
|
||||
- date_time: Specifies if the backend supports date/time operations.
|
||||
- text_length_limited_columns: Specifies if the backend supports setting
|
||||
a length limit on text columns.
|
||||
- fixed_length_text_columns: Specifies if the backend supports fixed
|
||||
length text columns.
|
||||
- length_restricted_text_columns: Specifies if the backend supports
|
||||
@ -125,6 +127,8 @@ type Test_Selection
|
||||
- removes_trailing_whitespace_casting_from_char_to_varchar: if
|
||||
SELECT concat('X', CAST(CAST(' ' AS CHAR(3)) AS VARCHAR(3)), 'X')
|
||||
returns XX then this should be set to True
|
||||
- char_max_size_after_substring: Specifies how the max size of the char
|
||||
type behaves after text_left/text_right.
|
||||
- different_size_integer_types: Specifies if the backend supports
|
||||
integer types of various sizes, like 16-bit or 32-bit integers.
|
||||
- supports_8bit_integer: Specifies if the backend supports 8-bit
|
||||
@ -153,7 +157,7 @@ type Test_Selection
|
||||
- supports_date_time_without_timezone: Specifies if the backend supports
|
||||
date/time operations without a timezone (true for most Database backends).
|
||||
Defaults to `.is_integer`.
|
||||
Config supports_case_sensitive_columns=True order_by=True natural_ordering=False case_insensitive_ordering=True order_by_unicode_normalization_by_default=False case_insensitive_ascii_only=False allows_mixed_type_comparisons=True supports_unicode_normalization=False is_nan_and_nothing_distinct=True is_nan_comparable=False distinct_returns_first_row_from_group_if_ordered=True date_time=True fixed_length_text_columns=False length_restricted_text_columns=True removes_trailing_whitespace_casting_from_char_to_varchar=False different_size_integer_types=True supports_8bit_integer=False supports_decimal_type=False supports_time_duration=False supports_nanoseconds_in_time=False supports_mixed_columns=False supported_replace_params=Nothing run_advanced_edge_case_tests_by_default=True supports_date_time_without_timezone=False
|
||||
Config supports_case_sensitive_columns=True order_by=True natural_ordering=False case_insensitive_ordering=True order_by_unicode_normalization_by_default=False case_insensitive_ascii_only=False allows_mixed_type_comparisons=True supports_unicode_normalization=False is_nan_and_nothing_distinct=True is_nan_comparable=False distinct_returns_first_row_from_group_if_ordered=True date_time=True text_length_limited_columns=False fixed_length_text_columns=False length_restricted_text_columns=True removes_trailing_whitespace_casting_from_char_to_varchar=False char_max_size_after_substring:Char_Max_Size_After_Substring_Behavior=..Kept different_size_integer_types=True supports_8bit_integer=False supports_decimal_type=False supports_time_duration=False supports_nanoseconds_in_time=False supports_mixed_columns=False supported_replace_params=Nothing run_advanced_edge_case_tests_by_default=True supports_date_time_without_timezone=False
|
||||
|
||||
## Specifies if the advanced edge case tests shall be run.
|
||||
|
||||
@ -163,6 +167,16 @@ type Test_Selection
|
||||
run_advanced_edge_case_tests self -> Boolean =
|
||||
self.run_advanced_edge_case_tests_by_default || (Environment.get "ENSO_ADVANCED_EDGE_CASE_TESTS" . is_nothing . not)
|
||||
|
||||
type Char_Max_Size_After_Substring_Behavior
|
||||
# The max size is reset to unbounded.
|
||||
Reset
|
||||
|
||||
# The max size is kept unchanged.
|
||||
Kept
|
||||
|
||||
# The max size is reduced to the length of the substring.
|
||||
Reduced
|
||||
|
||||
add_specs suite_builder setup =
|
||||
Core_Spec.add_specs suite_builder setup
|
||||
Select_Columns_Spec.add_specs suite_builder setup
|
||||
|
@ -47,7 +47,7 @@ add_specs suite_builder setup =
|
||||
if (x % 2) == 0 then Warning.attach (Illegal_State.Error "MY WARNING "+x.to_text) (2*x + 1) else 2*x + 1
|
||||
c1 = t.at "X" . map f
|
||||
warnings = Problems.get_attached_warnings c1
|
||||
warnings.map Error.unwrap . map .message . should_contain_the_same_elements_as ["MY WARNING 2", "MY WARNING 4"]
|
||||
warnings.map Error.unwrap . map .message . should_equal_ignoring_order ["MY WARNING 2", "MY WARNING 4"]
|
||||
c1.to_vector . should_equal [3, 5, 7, 9]
|
||||
|
||||
group_builder.specify "should respect the expected_value_type" <|
|
||||
|
@ -239,7 +239,7 @@ add_specs suite_builder setup =
|
||||
t1 = table . distinct ["x"]
|
||||
v = t1.at "x" . to_vector
|
||||
v . length . should_equal 3
|
||||
v . should_contain_the_same_elements_as [value, other_value, Nothing]
|
||||
v . should_equal_ignoring_order [value, other_value, Nothing]
|
||||
|
||||
group_builder.specify "Correctly handle Nothing in .distinct for Nothing" <|
|
||||
table = table_builder_typed [["x", [Nothing, Nothing, Nothing, Nothing, Nothing, Nothing]]] Value_Type.Char
|
||||
|
@ -522,7 +522,7 @@ add_specs suite_builder setup =
|
||||
tester = expect_column_names ["FirstColumn", "beta", "gamma", "delta"]
|
||||
err_checker err =
|
||||
err.catch.should_be_a Missing_Input_Columns.Error
|
||||
err.catch.criteria.should_contain_the_same_elements_as ["omicron", weird_name]
|
||||
err.catch.criteria.should_equal_ignoring_order ["omicron", weird_name]
|
||||
Problems.test_advanced_problem_handling action err_checker (x-> x) tester
|
||||
|
||||
err = data.table.rename_columns map
|
||||
@ -534,7 +534,7 @@ add_specs suite_builder setup =
|
||||
tester = expect_column_names ["FirstColumn", "beta", "gamma", "Another"]
|
||||
err_checker err =
|
||||
err.catch.should_be_a Missing_Input_Columns.Error
|
||||
err.catch.criteria.should_contain_the_same_elements_as [-200, 100, 300]
|
||||
err.catch.criteria.should_equal_ignoring_order [-200, 100, 300]
|
||||
Problems.test_advanced_problem_handling action err_checker (x-> x) tester
|
||||
|
||||
err = data.table.rename_columns map
|
||||
@ -622,7 +622,7 @@ add_specs suite_builder setup =
|
||||
tester = expect_column_names ["A", "B", "C", "D"]
|
||||
problem_checker problem =
|
||||
problem.should_be_a Too_Many_Column_Names_Provided.Error
|
||||
problem.column_names.should_contain_the_same_elements_as ["E", "F"]
|
||||
problem.column_names.should_equal_ignoring_order ["E", "F"]
|
||||
True
|
||||
err_checker err =
|
||||
problem_checker err.catch
|
||||
|
@ -1,5 +1,5 @@
|
||||
from Standard.Base import all
|
||||
from Standard.Table import Table
|
||||
from Standard.Table import Table, Value_Type
|
||||
|
||||
from Standard.Test import all
|
||||
|
||||
@ -75,3 +75,9 @@ build_sorted_table setup table_structure =
|
||||
row_count = table_structure.first.second.length
|
||||
new_structure = table_structure+[["row_id", (0.up_to row_count) . to_vector]]
|
||||
setup.table_builder new_structure . sort "row_id" . remove_columns ["row_id"]
|
||||
|
||||
## PRIVATE
|
||||
is_float_or_decimal ~column = case column.value_type of
|
||||
Value_Type.Float _ -> Nothing
|
||||
Value_Type.Decimal _ _ -> Nothing
|
||||
other -> Test.fail "Expected "+column.name+" to be a float or decimal, but got "+other.to_display_text
|
||||
|
@ -112,7 +112,7 @@ add_specs suite_builder prefix create_connection_func =
|
||||
src = Table.new [["X", [1, 2, 3]]]
|
||||
t1 = src.select_into_database_table data.connection long_name temporary=True
|
||||
Problems.assume_no_problems t1
|
||||
data.connection.query long_name . at "X" . to_vector . should_contain_the_same_elements_as [1, 2, 3]
|
||||
data.connection.query long_name . at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
|
||||
longer_name_with_same_prefix = long_name + ("z" * 10)
|
||||
data.connection.query longer_name_with_same_prefix . should_fail_with Table_Not_Found
|
||||
@ -139,12 +139,12 @@ add_specs suite_builder prefix create_connection_func =
|
||||
t13 = t1.join t3
|
||||
t1213 = t12.join t13
|
||||
t1213.row_count . should_equal 2
|
||||
t1213.at "X" . to_vector . should_contain_the_same_elements_as [1, 2]
|
||||
t1213.at "X" . to_vector . should_equal_ignoring_order [1, 2]
|
||||
Problems.expect_only_warning Duplicate_Output_Column_Names t1213
|
||||
|
||||
t11 = t1.join t1
|
||||
t11.row_count . should_equal 2
|
||||
t11.at "X" . to_vector . should_contain_the_same_elements_as [1, 2]
|
||||
t11.at "X" . to_vector . should_equal_ignoring_order [1, 2]
|
||||
Problems.expect_only_warning Duplicate_Output_Column_Names t1213
|
||||
|
||||
Test.with_clue "cross_join: " <|
|
||||
@ -152,12 +152,12 @@ add_specs suite_builder prefix create_connection_func =
|
||||
t13 = t1.cross_join t3
|
||||
t1213 = t12.cross_join t13
|
||||
t1213.row_count . should_equal 16
|
||||
t1213.at "X" . to_vector . should_contain_the_same_elements_as [1, 2]
|
||||
t1213.at "X" . to_vector . distinct . should_equal_ignoring_order [1, 2]
|
||||
Problems.expect_only_warning Duplicate_Output_Column_Names t1213
|
||||
|
||||
t11 = t1.cross_join t1
|
||||
t11.row_count . should_equal 4
|
||||
t11.at "X" . to_vector . should_contain_the_same_elements_as [1, 2]
|
||||
t11.at "X" . to_vector . should_equal_ignoring_order [1, 2, 1, 2]
|
||||
Problems.expect_only_warning Duplicate_Output_Column_Names t1213
|
||||
|
||||
Test.with_clue "union: " <|
|
||||
@ -166,12 +166,12 @@ add_specs suite_builder prefix create_connection_func =
|
||||
tx = t1.union [t12, t13] # 2 + 2*4 = 10 rows
|
||||
ty = tx.union tx # 10 + 10 = 20 rows
|
||||
ty.row_count . should_equal 20
|
||||
ty.at "X" . to_vector . should_contain_the_same_elements_as [1, 2]
|
||||
ty.at "X" . to_vector . distinct . should_equal_ignoring_order [1, 2]
|
||||
Problems.assume_no_problems ty
|
||||
|
||||
t11 = t1.union t1
|
||||
t11.row_count . should_equal 4
|
||||
t11.at "X" . to_vector . should_contain_the_same_elements_as [1, 2]
|
||||
t11.at "X" . to_vector . should_equal_ignoring_order [1, 2, 1, 2]
|
||||
Problems.assume_no_problems t11
|
||||
|
||||
group_builder.specify "should be fine operating on columns with long names" <|
|
||||
@ -200,7 +200,7 @@ add_specs suite_builder prefix create_connection_func =
|
||||
|
||||
t5 = t1.cross_join t4 right_prefix=""
|
||||
last_column = t5.at (-1)
|
||||
last_column.to_vector . should_contain_the_same_elements_as [4, 10, 18]
|
||||
last_column.to_vector.distinct.should_equal_ignoring_order [4, 10, 18]
|
||||
|
||||
if has_maximum_column_name_length.not then
|
||||
group_builder.specify "should allow to create very long column names" <|
|
||||
@ -385,7 +385,7 @@ add_specs suite_builder prefix create_connection_func =
|
||||
t2 = db_table.aggregate columns=[Aggregate_Column.Maximum name_a, Aggregate_Column.Maximum name_b]
|
||||
w1 = Problems.expect_warning Truncated_Column_Names t2
|
||||
|
||||
w1.original_names . should_contain_the_same_elements_as ["Maximum "+name_a, "Maximum "+name_b]
|
||||
w1.original_names . should_equal_ignoring_order ["Maximum "+name_a, "Maximum "+name_b]
|
||||
|
||||
t2.at (w1.find_truncated_name ("Maximum "+name_a)) . to_vector . should_equal [3]
|
||||
t2.at (w1.find_truncated_name ("Maximum "+name_b)) . to_vector . should_equal [6]
|
||||
@ -399,8 +399,8 @@ add_specs suite_builder prefix create_connection_func =
|
||||
Problems.assume_no_problems db_table2
|
||||
t3 = db_table2.aggregate columns=(names.map name-> Aggregate_Column.Maximum name)
|
||||
w2 = Problems.expect_warning Truncated_Column_Names t3
|
||||
w2.original_names . should_contain_the_same_elements_as (names.map name-> "Maximum " + name)
|
||||
t3.column_names . should_contain_the_same_elements_as w2.truncated_names
|
||||
w2.original_names . should_equal_ignoring_order (names.map name-> "Maximum " + name)
|
||||
t3.column_names . should_equal_ignoring_order w2.truncated_names
|
||||
(0.up_to 15).each i->
|
||||
t3.at (w2.find_truncated_name ("Maximum " + names.at i)) . to_vector . should_equal [200 + i]
|
||||
|
||||
@ -431,10 +431,10 @@ add_specs suite_builder prefix create_connection_func =
|
||||
|
||||
m2 = t2.read . sort [name_a, name_b]
|
||||
m2.column_names . should_equal [name_a, name_b]+w.truncated_names
|
||||
m2.at name_a . to_vector . should_contain_the_same_elements_as [1, 2, 3]
|
||||
m2.at name_b . to_vector . should_contain_the_same_elements_as [4, 5, 6]
|
||||
m2.at (w.truncated_names.at 0) . to_vector . should_contain_the_same_elements_as [1, 2, 3]
|
||||
m2.at (w.truncated_names.at 1) . to_vector . should_contain_the_same_elements_as [4, 5, 6]
|
||||
m2.at name_a . to_vector . distinct . should_equal_ignoring_order [1, 2, 3]
|
||||
m2.at name_b . to_vector . distinct . should_equal_ignoring_order [4, 5, 6]
|
||||
m2.at (w.truncated_names.at 0) . to_vector . distinct . should_equal_ignoring_order [1, 2, 3]
|
||||
m2.at (w.truncated_names.at 1) . to_vector . distinct . should_equal_ignoring_order [4, 5, 6]
|
||||
|
||||
group_builder.specify "should truncate new column names in other operations" <|
|
||||
name_a = "x" * (max_column_name_length - 1) + "A"
|
||||
|
@ -690,7 +690,7 @@ add_postgres_specs suite_builder create_connection_fn db_name =
|
||||
|
||||
Common_Spec.add_specs suite_builder prefix create_connection_fn
|
||||
|
||||
common_selection = Common_Table_Operations.Main.Test_Selection.Config supports_case_sensitive_columns=True order_by_unicode_normalization_by_default=True allows_mixed_type_comparisons=False fixed_length_text_columns=True removes_trailing_whitespace_casting_from_char_to_varchar=True supports_decimal_type=True supported_replace_params=supported_replace_params run_advanced_edge_case_tests_by_default=True supports_date_time_without_timezone=True is_nan_comparable=True
|
||||
common_selection = Common_Table_Operations.Main.Test_Selection.Config supports_case_sensitive_columns=True order_by_unicode_normalization_by_default=True allows_mixed_type_comparisons=False text_length_limited_columns=True fixed_length_text_columns=True removes_trailing_whitespace_casting_from_char_to_varchar=True char_max_size_after_substring=..Reset supports_decimal_type=True supported_replace_params=supported_replace_params run_advanced_edge_case_tests_by_default=True supports_date_time_without_timezone=True is_nan_comparable=True
|
||||
aggregate_selection = Common_Table_Operations.Aggregate_Spec.Test_Selection.Config first_last_row_order=False aggregation_problems=False
|
||||
agg_in_memory_table = (enso_project.data / "data.csv") . read
|
||||
|
||||
|
@ -331,7 +331,7 @@ sqlite_spec suite_builder prefix create_connection_func persistent_connector =
|
||||
|
||||
Common_Spec.add_specs suite_builder prefix create_connection_func
|
||||
|
||||
common_selection = Common_Table_Operations.Main.Test_Selection.Config supports_case_sensitive_columns=False natural_ordering=False case_insensitive_ordering=True case_insensitive_ascii_only=True is_nan_and_nothing_distinct=False date_time=False supported_replace_params=supported_replace_params different_size_integer_types=False length_restricted_text_columns=False run_advanced_edge_case_tests_by_default=True
|
||||
common_selection = Common_Table_Operations.Main.Test_Selection.Config supports_case_sensitive_columns=False natural_ordering=False case_insensitive_ordering=True case_insensitive_ascii_only=True is_nan_and_nothing_distinct=False date_time=False supported_replace_params=supported_replace_params different_size_integer_types=False length_restricted_text_columns=False char_max_size_after_substring=..Reset run_advanced_edge_case_tests_by_default=True
|
||||
|
||||
## For now `advanced_stats`, `first_last`, `text_shortest_longest` and
|
||||
`multi_distinct` remain disabled, because SQLite does not provide the
|
||||
|
@ -43,10 +43,6 @@ type Data
|
||||
in_memory_table = Table.new [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']]]
|
||||
[connection, in_memory_table]
|
||||
|
||||
teardown self =
|
||||
self.connection.close
|
||||
|
||||
|
||||
in_memory_table_builder args primary_key=[] connection =
|
||||
_ = [primary_key, connection]
|
||||
case args of
|
||||
@ -69,12 +65,9 @@ database_table_builder name_prefix args primary_key=[] connection =
|
||||
database, so features relying on persistence cannot really be tested.
|
||||
add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
prefix = setup.prefix
|
||||
suite_builder.group prefix+"Creating an empty table" group_builder->
|
||||
data = Data.setup make_new_connection
|
||||
|
||||
group_builder.teardown <|
|
||||
data.teardown
|
||||
|
||||
data = Data.setup make_new_connection
|
||||
suite_builder.group prefix+"(Upload_Spec) Creating an empty table" group_builder->
|
||||
group_builder.specify "should allow to specify the column names and types" <|
|
||||
t = data.connection.create_table (Name_Generator.random_name "creating-table") structure=[Column_Description.Value "X" Value_Type.Integer, Column_Description.Value "Y" Value_Type.Char] temporary=True
|
||||
t.column_names . should_equal ["X", "Y"]
|
||||
@ -98,7 +91,7 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
group_builder.specify "should allow to inherit the structure of an existing Database table" <|
|
||||
t = Table.new [["X", [1, 2]], ["Y", ['a', 'b']]]
|
||||
input_db_table = t.select_into_database_table data.connection (Name_Generator.random_name "input_table") temporary=True
|
||||
input_db_table.at "X" . to_vector . should_equal [1, 2]
|
||||
input_db_table.at "X" . to_vector . should_equal_ignoring_order [1, 2]
|
||||
|
||||
db_table = data.connection.create_table (Name_Generator.random_name "creating-table") structure=input_db_table temporary=True
|
||||
db_table.column_names . should_equal ["X", "Y"]
|
||||
@ -231,7 +224,7 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
name = Name_Generator.random_name "primary_key 1"
|
||||
db_table = data.connection.create_table table_name=name structure=[Column_Description.Value "X" Value_Type.Integer, Column_Description.Value "Y" Value_Type.Char, Column_Description.Value "Z" Value_Type.Integer, Column_Description.Value "W" Value_Type.Float] primary_key=["Y", "Z"] temporary=False
|
||||
Panic.with_finalizer (data.connection.drop_table db_table.name) <|
|
||||
db_table.get_primary_key . should_equal ["Y", "Z"]
|
||||
db_table.get_primary_key . should_equal_ignoring_order ["Y", "Z"]
|
||||
|
||||
group_builder.specify "should ensure that primary key columns specified are valid" <|
|
||||
run_with_and_without_output <|
|
||||
@ -297,17 +290,12 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
t1.at "X" . to_vector . should_fail_with SQL_Error
|
||||
|
||||
|
||||
suite_builder.group prefix+"Uploading an in-memory Table" group_builder->
|
||||
data = Data.setup make_new_connection
|
||||
|
||||
group_builder.teardown <|
|
||||
data.teardown
|
||||
|
||||
suite_builder.group prefix+"(Upload_Spec) Uploading an in-memory Table" group_builder->
|
||||
group_builder.specify "should create a database table with the same contents as the source" <|
|
||||
db_table = data.in_memory_table.select_into_database_table data.connection (Name_Generator.random_name "creating-table") temporary=True
|
||||
db_table.column_names . should_equal ["X", "Y"]
|
||||
db_table.at "X" . to_vector . should_equal [1, 2, 3]
|
||||
db_table.at "Y" . to_vector . should_equal ['a', 'b', 'c']
|
||||
db_table.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
db_table.at "Y" . to_vector . should_equal_ignoring_order ['a', 'b', 'c']
|
||||
setup.expect_integer_type <| db_table.at "X"
|
||||
db_table.at "Y" . value_type . is_text . should_be_true
|
||||
db_table.row_count . should_equal 3
|
||||
@ -316,23 +304,23 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
group_builder.specify "should include the created table in the tables directory" <|
|
||||
db_table = data.in_memory_table.select_into_database_table data.connection (Name_Generator.random_name "permanent_table 1") temporary=False
|
||||
Panic.with_finalizer (data.connection.drop_table db_table.name) <|
|
||||
db_table.at "X" . to_vector . should_equal [1, 2, 3]
|
||||
db_table.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
|
||||
data.connection.tables.at "Name" . to_vector . should_contain db_table.name
|
||||
data.connection.query db_table.name . at "X" . to_vector . should_equal [1, 2, 3]
|
||||
data.connection.query db_table.name . at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
|
||||
group_builder.specify "should include the temporary table in the tables directory" <|
|
||||
db_table = data.in_memory_table.select_into_database_table data.connection (Name_Generator.random_name "temporary_table 1") temporary=True
|
||||
db_table.at "X" . to_vector . should_equal [1, 2, 3]
|
||||
db_table.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
data.connection.tables.at "Name" . to_vector . should_contain db_table.name
|
||||
data.connection.query db_table.name . at "X" . to_vector . should_equal [1, 2, 3]
|
||||
data.connection.query db_table.name . at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
|
||||
if persistent_connector then
|
||||
group_builder.specify "should drop the temporary table after the connection is closed" <|
|
||||
tmp_connection = make_new_connection Nothing
|
||||
db_table = data.in_memory_table.select_into_database_table tmp_connection (Name_Generator.random_name "temporary_table 2") temporary=True
|
||||
name = db_table.name
|
||||
tmp_connection.query (SQL_Query.Table_Name name) . at "X" . to_vector . should_equal [1, 2, 3]
|
||||
tmp_connection.query (SQL_Query.Table_Name name) . at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
tmp_connection.close
|
||||
|
||||
wait_until_temporary_table_is_deleted_after_closing_connection data.connection name
|
||||
@ -344,9 +332,9 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
db_table = data.in_memory_table.select_into_database_table tmp_connection (Name_Generator.random_name "permanent_table 1") temporary=False
|
||||
name = db_table.name
|
||||
Panic.with_finalizer (data.connection.drop_table name) <|
|
||||
tmp_connection.query (SQL_Query.Table_Name name) . at "X" . to_vector . should_equal [1, 2, 3]
|
||||
tmp_connection.query (SQL_Query.Table_Name name) . at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
tmp_connection.close
|
||||
data.connection.query (SQL_Query.Table_Name name) . at "X" . to_vector . should_equal [1, 2, 3]
|
||||
data.connection.query (SQL_Query.Table_Name name) . at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
|
||||
group_builder.specify "should not create any table if upload fails" <|
|
||||
normal_column = Column.from_vector "Y" ((100+0).up_to (100+1000)).to_vector
|
||||
@ -354,25 +342,28 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
exploding_table = Table.new [normal_column, exploding_column]
|
||||
name = Name_Generator.random_name "rolling-back-table"
|
||||
data.connection.query (SQL_Query.Table_Name name) . should_fail_with Table_Not_Found
|
||||
Test.expect_panic_with matcher=ExplodingStoragePayload <|
|
||||
exploding_table.select_into_database_table data.connection name temporary=False primary_key=Nothing
|
||||
data.connection.query (SQL_Query.Table_Name name) . should_fail_with Table_Not_Found
|
||||
|
||||
# We ensure that we try to clean-up the table even if the test failed.
|
||||
Panic.with_finalizer (data.connection.drop_table name if_exists=True) <|
|
||||
Test.expect_panic_with matcher=ExplodingStoragePayload <|
|
||||
exploding_table.select_into_database_table data.connection name temporary=False primary_key=Nothing
|
||||
data.connection.query (SQL_Query.Table_Name name) . should_fail_with Table_Not_Found
|
||||
|
||||
group_builder.specify "should set a primary key for the table" <|
|
||||
t1 = Table.new [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']], ["Z", [1.0, 2.0, 3.0]]]
|
||||
db_table_1 = t1.select_into_database_table data.connection (Name_Generator.random_name "primary-key-1") primary_key=["Y", "X"]
|
||||
Panic.with_finalizer (data.connection.drop_table db_table_1.name) <|
|
||||
db_table_1.at "X" . to_vector . should_equal [1, 2, 3]
|
||||
db_table_1.get_primary_key . should_equal ["Y", "X"]
|
||||
db_table_1.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
db_table_1.get_primary_key . should_equal_ignoring_order ["Y", "X"]
|
||||
|
||||
db_table_2 = t1.select_into_database_table data.connection (Name_Generator.random_name "primary-key-2")
|
||||
Panic.with_finalizer (data.connection.drop_table db_table_2.name) <|
|
||||
db_table_2.at "X" . to_vector . should_equal [1, 2, 3]
|
||||
db_table_2.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
db_table_2.get_primary_key . should_equal ["X"]
|
||||
|
||||
db_table_3 = t1.select_into_database_table data.connection (Name_Generator.random_name "primary-key-3") primary_key=Nothing
|
||||
Panic.with_finalizer (data.connection.drop_table db_table_3.name) <|
|
||||
db_table_3.at "X" . to_vector . should_equal [1, 2, 3]
|
||||
db_table_3.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
db_table_3.get_primary_key . should_equal Nothing
|
||||
|
||||
group_builder.specify "should ensure that primary key columns are valid" <|
|
||||
@ -396,12 +387,12 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
r2.catch . clashing_example_key_values . should_equal ['b']
|
||||
|
||||
r3 = t1.select_into_database_table data.connection (Name_Generator.random_name "primary-key-7") temporary=True primary_key=["X", "Y"]
|
||||
r3.at "X" . to_vector . should_equal [1, 2, 1]
|
||||
r3.at "X" . to_vector . should_equal_ignoring_order [1, 2, 1]
|
||||
|
||||
t2 = Table.new [["X", [1, 2, 1]], ["Y", ['a', 'b', 'a']]]
|
||||
r4 = t2.select_into_database_table data.connection (Name_Generator.random_name "primary-key-7") temporary=True primary_key=["X", "Y"]
|
||||
r4.should_fail_with Non_Unique_Key
|
||||
r4.catch . clashing_example_key_values . should_equal [1, 'a']
|
||||
r4.catch . clashing_example_key_values . should_equal_ignoring_order [1, 'a']
|
||||
|
||||
# Will not find clashes if they are not in the first 1000 rows, in Output disabled mode.
|
||||
vec = (0.up_to 1010).to_vector
|
||||
@ -442,7 +433,7 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
tmp_name . should_not_equal base_table_name
|
||||
data.connection.query base_table_name . should_fail_with Table_Not_Found
|
||||
|
||||
t1.at "X" . to_vector . should_contain_the_same_elements_as [1, 2, 3]
|
||||
t1.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
|
||||
t2 = (Table.new [["Y", [44]]]).select_into_database_table data.connection base_table_name temporary=False primary_key=[]
|
||||
Problems.expect_only_warning Dry_Run_Operation t2
|
||||
@ -457,12 +448,7 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
# The old dry run table has been invalidated due to overwrite
|
||||
t1.at "X" . to_vector . should_fail_with SQL_Error
|
||||
|
||||
suite_builder.group prefix+"Persisting a Database Table (query)" group_builder->
|
||||
data = Data.setup make_new_connection
|
||||
|
||||
group_builder.teardown <|
|
||||
data.teardown
|
||||
|
||||
suite_builder.group prefix+"(Upload_Spec) Persisting a Database Table (query)" group_builder->
|
||||
group_builder.specify "should be able to create a persistent copy of a DB table" <|
|
||||
t = Table.new [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']], ["Z", [1.0, 2.0, 3.0]]]
|
||||
tmp_connection = make_new_connection Nothing
|
||||
@ -476,11 +462,11 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
copied_table.at "Y" . value_type . is_text . should_be_true
|
||||
copied_table.at "Z" . value_type . is_floating_point . should_be_true
|
||||
|
||||
tmp_connection.query name . at "X" . to_vector . should_equal [1, 2, 3]
|
||||
tmp_connection.query name . at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
tmp_connection.close
|
||||
|
||||
if persistent_connector then
|
||||
data.connection.query name . at "X" . to_vector . should_equal [1, 2, 3]
|
||||
data.connection.query name . at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
|
||||
group_builder.specify "should be able to persist a complex query with generated columns, joins etc." <|
|
||||
t1 = Table.new [["X", [1, 1, 2]], ["Y", [1, 2, 3]]]
|
||||
@ -495,11 +481,11 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
|
||||
copied_table = db_table_4.select_into_database_table data.connection (Name_Generator.random_name "copied-table") temporary=True primary_key=Nothing
|
||||
copied_table.column_names . should_equal ["X", "Y", "C1", "C2", "Right X", "C3"]
|
||||
copied_table.at "X" . to_vector . should_equal [1, 1, 2]
|
||||
copied_table.at "C1" . to_vector . should_equal [101, 102, 203]
|
||||
copied_table.at "C2" . to_vector . should_equal ["constant_text", "constant_text", "constant_text"]
|
||||
copied_table.at "Right X" . to_vector . should_equal [Nothing, Nothing, 2]
|
||||
copied_table.at "C3" . to_vector . should_equal [Nothing, Nothing, 5]
|
||||
copied_table.at "X" . to_vector . should_equal_ignoring_order [1, 1, 2]
|
||||
copied_table.at "C1" . to_vector . should_equal_ignoring_order [101, 102, 203]
|
||||
copied_table.at "C2" . to_vector . should_equal_ignoring_order ["constant_text", "constant_text", "constant_text"]
|
||||
copied_table.at "Right X" . to_vector . should_equal_ignoring_order [Nothing, Nothing, 2]
|
||||
copied_table.at "C3" . to_vector . should_equal_ignoring_order [Nothing, Nothing, 5]
|
||||
copied_table.is_trivial_query . should_be_true
|
||||
|
||||
# We check that this is indeed querying a simple DB table and not a complex query like `db_table_4` would be,
|
||||
@ -520,7 +506,7 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
|
||||
copied_table_accessed = tmp_connection.query name
|
||||
copied_table_accessed.column_names . should_equal ["X", "Y", "computed"]
|
||||
copied_table_accessed.at "computed" . to_vector . should_equal [401, 502, 603]
|
||||
copied_table_accessed.at "computed" . to_vector . should_equal_ignoring_order [401, 502, 603]
|
||||
tmp_connection.close
|
||||
|
||||
data.connection.query name . should_fail_with Table_Not_Found
|
||||
@ -605,12 +591,7 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
preexisting2.column_names . should_equal ["X"]
|
||||
preexisting2.at "X" . to_vector . should_equal [42]
|
||||
|
||||
suite_builder.group prefix+"Appending an in-memory table to a Database table" group_builder->
|
||||
data = Data.setup make_new_connection
|
||||
|
||||
group_builder.teardown <|
|
||||
data.teardown
|
||||
|
||||
suite_builder.group prefix+"(Upload_Spec) Appending an in-memory table to a Database table" group_builder->
|
||||
test_table_append group_builder data in_memory_table_builder (database_table_builder "target-table")
|
||||
|
||||
group_builder.specify "will issue a friendly error if using in-memory table as target" <|
|
||||
@ -620,40 +601,20 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
r1.should_fail_with Illegal_Argument
|
||||
r1.to_display_text.should_contain "in-memory tables are immutable"
|
||||
|
||||
suite_builder.group prefix+"Appending a Database table to a Database table" group_builder->
|
||||
data = Data.setup make_new_connection
|
||||
|
||||
group_builder.teardown <|
|
||||
data.teardown
|
||||
|
||||
suite_builder.group prefix+"(Upload_Spec) Appending a Database table to a Database table" group_builder->
|
||||
test_table_append group_builder data (database_table_builder "source-table") (database_table_builder "target-table")
|
||||
|
||||
suite_builder.group prefix+"Deleting rows from a Database table (source=in-memory)" group_builder->
|
||||
data = Data.setup make_new_connection
|
||||
|
||||
group_builder.teardown <|
|
||||
data.teardown
|
||||
|
||||
suite_builder.group prefix+"(Upload_Spec) Deleting rows from a Database table (source=in-memory)" group_builder->
|
||||
test_table_delete group_builder data in_memory_table_builder (database_table_builder "target-table")
|
||||
|
||||
suite_builder.group prefix+"Deleting rows from a Database table (source=Database)" group_builder->
|
||||
data = Data.setup make_new_connection
|
||||
|
||||
group_builder.teardown <|
|
||||
data.teardown
|
||||
|
||||
suite_builder.group prefix+"(Upload_Spec) Deleting rows from a Database table (source=Database)" group_builder->
|
||||
test_table_delete group_builder data (database_table_builder "source-table") (database_table_builder "target-table")
|
||||
|
||||
suite_builder.group prefix+"Deleting rows from a Database table" group_builder->
|
||||
data = Data.setup make_new_connection
|
||||
|
||||
group_builder.teardown <|
|
||||
data.teardown
|
||||
|
||||
suite_builder.group prefix+"(Upload_Spec) Deleting rows from a Database table" group_builder->
|
||||
group_builder.specify "[ADVANCED] it should be possible to truncate the whole table" <|
|
||||
name = Name_Generator.random_name "table-to-truncate"
|
||||
t = (Table.new [["X", [1, 2, 3]]]).select_into_database_table data.connection name temporary=True primary_key=[]
|
||||
t.at "X" . to_vector . should_equal [1, 2, 3]
|
||||
t.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
data.connection.truncate_table name
|
||||
t.at "X" . to_vector . should_equal []
|
||||
# The table still exists
|
||||
@ -665,7 +626,7 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
graduates = table.filter "graduation_year" (Filter_Condition.Less 2023)
|
||||
affected_rows = table.delete_rows graduates # uses the primary key by default
|
||||
affected_rows . should_equal 3
|
||||
table.rows.map .to_vector . should_equal [[1, "Alice", 2023], [2, "Bob", 2024]]
|
||||
table.rows.map .to_vector . should_equal_ignoring_order [[1, "Alice", 2023], [2, "Bob", 2024]]
|
||||
|
||||
group_builder.specify "will issue a friendly error if using in-memory table as target" <|
|
||||
t1 = Table.new [["X", [1, 2, 3]]]
|
||||
@ -680,13 +641,8 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
affected_rows . should_equal 3
|
||||
t1.rows.should_equal []
|
||||
|
||||
execution_context_group_name = prefix+"Output Execution Context for Database operations"
|
||||
execution_context_group_name = prefix+"(Upload_Spec) Output Execution Context for Database operations"
|
||||
suite_builder.group execution_context_group_name group_builder->
|
||||
data = Data.setup make_new_connection
|
||||
|
||||
group_builder.teardown <|
|
||||
data.teardown
|
||||
|
||||
group_builder.specify "should forbid executing updates" <|
|
||||
Context.Output.with_disabled <|
|
||||
r1 = data.connection.execute_update "CREATE TEMPORARY TABLE foo (x INTEGER)"
|
||||
@ -713,7 +669,7 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
data.connection.tables . at "Name" . to_vector . should_not_contain dry_run_name
|
||||
data.connection.base_connection.get_tables_advanced include_hidden=True . at "Name" . to_vector . should_contain dry_run_name
|
||||
|
||||
table.at "X" . to_vector . should_contain_the_same_elements_as [1, 2, 3]
|
||||
table.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
|
||||
## This test relies on GC behaviour which may not be fully deterministic.
|
||||
Currently this test seems to work fine, but if there are ever issues
|
||||
@ -732,7 +688,7 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
sentinel = Managed_Resource.register "payload" (cleanup_sentinel was_cleanup_performed)
|
||||
table.column_names . should_equal ["X"]
|
||||
data.connection.base_connection.get_tables_advanced include_hidden=True . at "Name" . to_vector . should_contain table.name
|
||||
table.at "X" . to_vector . should_contain_the_same_elements_as [1, 2, 3]
|
||||
table.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
name = table.name
|
||||
payload = sentinel.with x-> "transformed_"+x
|
||||
payload . should_equal "transformed_payload"
|
||||
@ -768,7 +724,7 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
# Create a table that has the same name as the dry run table normally would have.
|
||||
pre_existing_table = src.select_into_database_table data.connection dry_run_name temporary=False . should_succeed
|
||||
pre_existing_table.column_names . should_equal ["X"]
|
||||
pre_existing_table.at "X" . to_vector . should_contain_the_same_elements_as [1, 2, 3]
|
||||
pre_existing_table.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
Panic.with_finalizer (data.connection.drop_table pre_existing_table.name if_exists=True) <|
|
||||
new_dry_run_name = Context.Output.with_disabled <|
|
||||
tmp_connection2 = make_new_connection Nothing
|
||||
@ -784,7 +740,7 @@ add_specs suite_builder setup make_new_connection persistent_connector=True =
|
||||
new_dry_run_name . should_not_equal dry_run_name
|
||||
|
||||
# The pre-existing table should not have been overwritten.
|
||||
pre_existing_table.at "X" . to_vector . should_contain_the_same_elements_as [1, 2, 3]
|
||||
pre_existing_table.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
|
||||
|
||||
tests group_builder data make_new_connection in_memory_table_builder " (from memory)" persistent_connector
|
||||
@ -804,7 +760,7 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
|
||||
expected_rows = [[1, 'a'], [2, 'b'], [3, 'c'], [4, 'd'], [5, 'e'], [6, 'f']]
|
||||
rows1 = result.rows.to_vector.map .to_vector
|
||||
rows1.should_contain_the_same_elements_as expected_rows
|
||||
rows1.should_equal_ignoring_order expected_rows
|
||||
|
||||
group_builder.specify "should error if new rows clash with existing ones and mode is Insert, target table should remain unchanged" <|
|
||||
dest = target_table_builder [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']]] primary_key=["X"] connection=data.connection
|
||||
@ -817,14 +773,14 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
|
||||
group_builder.specify "should use the target table primary key for the key by default" <|
|
||||
dest1 = target_table_builder [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']], ["Z", [4, 5, 6]]] primary_key=["Y", "Z"] connection=data.connection
|
||||
default_key_columns dest1 . should_equal ["Y", "Z"]
|
||||
default_key_columns dest1 . should_equal_ignoring_order ["Y", "Z"]
|
||||
|
||||
dest2 = target_table_builder [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']]] primary_key=["Y"] connection=data.connection
|
||||
src = source_table_builder [["X", [4, 5]], ["Y", ['b', 'e']]] connection=data.connection
|
||||
# Not specifying `key_columns`, rely on `default_key_columns` inferring Y as default based on the primary key.
|
||||
r1 = dest2.update_rows src
|
||||
rows = r1.rows.to_vector.map .to_vector
|
||||
rows.should_contain_the_same_elements_as [[1, 'a'], [4, 'b'], [3, 'c'], [5, 'e']]
|
||||
rows.should_equal_ignoring_order [[1, 'a'], [4, 'b'], [3, 'c'], [5, 'e']]
|
||||
|
||||
group_builder.specify "should be able to Update existing rows in a table" <|
|
||||
dest = target_table_builder [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']]] connection=data.connection
|
||||
@ -835,7 +791,7 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
r1.should_succeed
|
||||
|
||||
rows = dest.rows.to_vector.map .to_vector
|
||||
rows.should_contain_the_same_elements_as [[1, 'a'], [2, 'ZZZ'], [3, 'c']]
|
||||
rows.should_equal_ignoring_order [[1, 'a'], [2, 'ZZZ'], [3, 'c']]
|
||||
|
||||
group_builder.specify "should fail on unmatched rows in Update mode" <|
|
||||
dest = target_table_builder [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']]] connection=data.connection
|
||||
@ -848,7 +804,7 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
|
||||
# The table should remain unchanged.
|
||||
rows = dest.rows.to_vector.map .to_vector
|
||||
rows.should_contain_the_same_elements_as [[1, 'a'], [2, 'b'], [3, 'c']]
|
||||
rows.should_equal_ignoring_order [[1, 'a'], [2, 'b'], [3, 'c']]
|
||||
|
||||
group_builder.specify "should upsert by default (update existing rows, insert new rows)" <|
|
||||
dest = target_table_builder [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']]] primary_key=["X"] connection=data.connection
|
||||
@ -858,11 +814,11 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
r1.column_names . should_equal ["X", "Y"]
|
||||
expected_rows = [[1, 'a'], [2, 'D'], [3, 'c'], [100, 'E']]
|
||||
rows1 = r1.rows.to_vector.map .to_vector
|
||||
rows1.should_contain_the_same_elements_as expected_rows
|
||||
rows1.should_equal_ignoring_order expected_rows
|
||||
|
||||
# The original table is updated too.
|
||||
rows2 = dest.rows.to_vector.map .to_vector
|
||||
rows2.should_contain_the_same_elements_as expected_rows
|
||||
rows2.should_equal_ignoring_order expected_rows
|
||||
|
||||
group_builder.specify "should allow to align an existing table with a source (upsert + delete rows missing from source)" <|
|
||||
dest = target_table_builder [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']]] primary_key=["X"] connection=data.connection
|
||||
@ -872,9 +828,9 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
r1.column_names . should_equal ["X", "Y"]
|
||||
expected_rows = [[2, 'D'], [100, 'E']]
|
||||
rows1 = r1.rows.to_vector.map .to_vector
|
||||
rows1.should_contain_the_same_elements_as expected_rows
|
||||
rows1.should_equal_ignoring_order expected_rows
|
||||
rows2 = dest.rows.to_vector.map .to_vector
|
||||
rows2.should_contain_the_same_elements_as expected_rows
|
||||
rows2.should_equal_ignoring_order expected_rows
|
||||
|
||||
group_builder.specify "should match columns by name, reordering to destination order if needed (Insert)" <|
|
||||
dest = target_table_builder [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']]] primary_key=["X"] connection=data.connection
|
||||
@ -884,7 +840,7 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
src.column_names . should_equal ["Y", "X"]
|
||||
expected_rows = [[1, 'a'], [2, 'b'], [3, 'c'], [4, 'd'], [5, 'e'], [6, 'f']]
|
||||
rows1 = result.rows.to_vector.map .to_vector
|
||||
rows1.should_contain_the_same_elements_as expected_rows
|
||||
rows1.should_equal_ignoring_order expected_rows
|
||||
|
||||
group_builder.specify "should match columns by name, reordering to destination order if needed (Upsert)" <|
|
||||
dest = target_table_builder [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']]] primary_key=["X"] connection=data.connection
|
||||
@ -894,7 +850,7 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
src.column_names . should_equal ["Y", "X"]
|
||||
expected_rows = [[1, 'd'], [2, 'b'], [3, 'c'], [5, 'e'], [6, 'f']]
|
||||
rows1 = result.rows.to_vector.map .to_vector
|
||||
rows1.should_contain_the_same_elements_as expected_rows
|
||||
rows1.should_equal_ignoring_order expected_rows
|
||||
|
||||
group_builder.specify "should match columns by name, reordering to destination order if needed (Update)" <|
|
||||
dest = target_table_builder [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']]] primary_key=["X"] connection=data.connection
|
||||
@ -904,7 +860,7 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
src.column_names . should_equal ["Y", "X"]
|
||||
expected_rows = [[1, 'f'], [2, 'e'], [3, 'd']]
|
||||
rows1 = result.rows.to_vector.map .to_vector
|
||||
rows1.should_contain_the_same_elements_as expected_rows
|
||||
rows1.should_equal_ignoring_order expected_rows
|
||||
|
||||
group_builder.specify "should match columns by name, reordering to destination order if needed (Align)" <|
|
||||
dest = target_table_builder [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']]] primary_key=["X"] connection=data.connection
|
||||
@ -914,39 +870,41 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
src.column_names . should_equal ["Y", "X"]
|
||||
expected_rows = [[1, 'e'], [2, 'd'], [6, 'f']]
|
||||
rows1 = result.rows.to_vector.map .to_vector
|
||||
rows1.should_contain_the_same_elements_as expected_rows
|
||||
rows1.should_equal_ignoring_order expected_rows
|
||||
|
||||
group_builder.specify "should allow to use a transformed table, with computed fields, as a source" <|
|
||||
dest = target_table_builder [["X", [1, 2, 3]], ["Y", ['a', 'b', 'c']]] primary_key=["X"] connection=data.connection
|
||||
t1 = source_table_builder [["Z", [10, 20]], ["Y", ['D', 'E']]] connection=data.connection
|
||||
t2 = source_table_builder [["Z", [20, 10]], ["X", [-99, 10]]] connection=data.connection
|
||||
src = t1.join t2 on=["Z"] join_kind=Join_Kind.Inner . remove_columns "Z" . set (expr "[X] + 100") "X"
|
||||
src.at "X" . to_vector . should_contain_the_same_elements_as [1, 110]
|
||||
src.at "X" . to_vector . should_equal_ignoring_order [1, 110]
|
||||
|
||||
r1 = dest.update_rows src key_columns=["X"]
|
||||
Problems.assume_no_problems r1
|
||||
r1.column_names . should_equal ["X", "Y"]
|
||||
expected_rows = [[1, 'E'], [110, 'D'], [2, 'b'], [3, 'c']]
|
||||
rows1 = r1.rows.to_vector.map .to_vector
|
||||
rows1.should_contain_the_same_elements_as expected_rows
|
||||
rows1.should_equal_ignoring_order expected_rows
|
||||
|
||||
group_builder.specify "should allow specifying no key in Insert mode" <|
|
||||
dest = target_table_builder [["X", [1, 10, 100]]] connection=data.connection
|
||||
src = source_table_builder [["X", [1, 2, 3]]] connection=data.connection
|
||||
result = dest.update_rows src update_action=Update_Action.Insert key_columns=[]
|
||||
|
||||
expected = [1, 10, 100, 1, 2, 3]
|
||||
result.column_names . should_equal ["X"]
|
||||
result.at "X" . to_vector . should_contain_the_same_elements_as expected
|
||||
result.at "X" . to_vector . should_equal_ignoring_order [1, 10, 100, 1, 2, 3]
|
||||
|
||||
r2 = dest.update_rows src update_action=Update_Action.Insert key_columns=Nothing
|
||||
bump table offset = table.set (table.at "X" + offset) "X"
|
||||
src2 = bump src 20 # 21, 22, 23
|
||||
r2 = dest.update_rows src2 update_action=Update_Action.Insert key_columns=Nothing
|
||||
r2.column_names . should_equal ["X"]
|
||||
r2.at "X" . to_vector . should_contain_the_same_elements_as expected
|
||||
r2.at "X" . to_vector . should_equal_ignoring_order [1, 10, 100, 1, 2, 3, 21, 22, 23]
|
||||
|
||||
src3 = bump src 30 # 31, 32, 33
|
||||
default_key_columns dest . should_equal Nothing
|
||||
r3 = dest.update_rows src update_action=Update_Action.Insert
|
||||
r3 = dest.update_rows src3 update_action=Update_Action.Insert
|
||||
r3.column_names . should_equal ["X"]
|
||||
r3.at "X" . to_vector . should_contain_the_same_elements_as expected
|
||||
r3.at "X" . to_vector . should_equal_ignoring_order [1, 10, 100, 1, 2, 3, 21, 22, 23, 31, 32, 33]
|
||||
|
||||
group_builder.specify "should fail if no key is specified in other modes" <|
|
||||
dest = target_table_builder [["X", [1, 10, 100]]] connection=data.connection
|
||||
@ -995,8 +953,8 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
r2 = t1.update_rows s1 update_action=Update_Action.Insert key_columns=[]
|
||||
Problems.assume_no_problems r2
|
||||
m2 = r2.read . sort "Y"
|
||||
m2.at "Y" . to_vector . should_equal ["a", "b", "c", "x", "y"]
|
||||
m2.at "X" . to_vector . should_equal [0, 10, 100, 10, Nothing]
|
||||
m2.at "Y" . to_vector . should_equal_ignoring_order ["a", "b", "c", "x", "y"]
|
||||
m2.at "X" . to_vector . should_equal_ignoring_order [0, 10, 100, 10, Nothing]
|
||||
|
||||
group_builder.specify "should fail if the key causes update of multiple values (it's not unique in the target table)" <|
|
||||
dest = target_table_builder [["X", [1, 1, 2]], ["Y", ['a', 'b', 'c']]] connection=data.connection
|
||||
@ -1044,7 +1002,7 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
r1.column_names . should_equal ["Y", "X"]
|
||||
expected_rows = [[42, 1], [42, 2], [42, 3]]
|
||||
rows1 = r1.rows.to_vector.map .to_vector
|
||||
rows1.should_contain_the_same_elements_as expected_rows
|
||||
rows1.should_equal_ignoring_order expected_rows
|
||||
data.connection.drop_table dest_name
|
||||
|
||||
group_builder.specify "should use defaults when inserting new values in upsert, but retain existing values" <|
|
||||
@ -1059,7 +1017,7 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
r1.column_names . should_equal ["Y", "X", "Z"]
|
||||
expected_rows = [[1000, 1, 100], [42, 2, 200], [42, 3, 300]]
|
||||
rows1 = r1.rows.to_vector.map .to_vector
|
||||
rows1.should_contain_the_same_elements_as expected_rows
|
||||
rows1.should_equal_ignoring_order expected_rows
|
||||
data.connection.drop_table dest_name
|
||||
|
||||
group_builder.specify "should use defaults for missing input columns for newly inserted rows when Aligning the tables, but keep existing values for existing rows" <|
|
||||
@ -1074,7 +1032,7 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
# The X=10 stays with Y=100, but the X=2 is inserted with the default Y=42
|
||||
expected_rows = [[10, 100, -1], [2, 42, -2], [3, 42, -3]]
|
||||
rows1 = r1.rows.to_vector.map .to_vector
|
||||
rows1.should_contain_the_same_elements_as expected_rows
|
||||
rows1.should_equal_ignoring_order expected_rows
|
||||
data.connection.drop_table dest_name
|
||||
|
||||
group_builder.specify "should fail if the source table is missing some columns and the column in the target has no default value" <|
|
||||
@ -1147,7 +1105,7 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
Problems.expect_warning Inexact_Type_Coercion r2
|
||||
|
||||
# But in dry run the update is not actually performed:
|
||||
r2.at "X" . to_vector . should_contain_the_same_elements_as [3.25, 4.25, 10.0]
|
||||
r2.at "X" . to_vector . should_equal_ignoring_order [3.25, 4.25, 10.0]
|
||||
|
||||
result = dest.update_rows src update_action=Update_Action.Insert key_columns=[]
|
||||
warning = Problems.expect_warning Inexact_Type_Coercion result
|
||||
@ -1155,7 +1113,7 @@ test_table_append group_builder (data : Data) source_table_builder target_table_
|
||||
warning.actual_type.is_floating_point . should_be_true
|
||||
|
||||
result.column_names . should_equal ["X"]
|
||||
result.at "X" . to_vector . should_contain_the_same_elements_as [3.25, 4.25, 10.0, 1, 2, 0]
|
||||
result.at "X" . to_vector . should_equal_ignoring_order [3.25, 4.25, 10.0, 1, 2, 0]
|
||||
|
||||
group_builder.specify "should fail if types of columns are not compatible" <|
|
||||
dest = target_table_builder [["X", ["a", "B", "c"]]] connection=data.connection
|
||||
@ -1219,7 +1177,7 @@ test_table_delete group_builder (data : Data) source_table_builder target_table_
|
||||
# key columns should automatically be discovered by the primary key
|
||||
affected_rows = table.delete_rows key_values_to_delete
|
||||
affected_rows . should_equal 2
|
||||
table.rows.map .to_vector . should_equal [[1, "Alice", 100], [2, "Bob", 100], [120, "Eve", 120]]
|
||||
table.rows.map .to_vector . should_equal_ignoring_order [[1, "Alice", 100], [2, "Bob", 100], [120, "Eve", 120]]
|
||||
|
||||
group_builder.specify "will require key_columns if no default can be used as no primary key is set" <|
|
||||
table = target_table_builder [["X", [1, 2, 3]]] primary_key=[] connection=data.connection
|
||||
@ -1241,20 +1199,20 @@ test_table_delete group_builder (data : Data) source_table_builder target_table_
|
||||
r1 = table.delete_rows key_values_to_delete key_columns=["X"] # Error: Unresolved method `delete_rows`
|
||||
r1.should_equal 0
|
||||
Problems.assume_no_problems r1
|
||||
table.at "X" . to_vector . should_equal [1, 2, 3]
|
||||
table.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
|
||||
key_values_2 = source_table_builder [["X", [4, 3, 5]]] connection=data.connection
|
||||
r2 = table.delete_rows key_values_2 key_columns=["X"]
|
||||
r2.should_equal 1
|
||||
Problems.assume_no_problems r2
|
||||
table.at "X" . to_vector . should_equal [1, 2]
|
||||
table.at "X" . to_vector . should_equal_ignoring_order [1, 2]
|
||||
|
||||
group_builder.specify "should allow to use multiple columns as key" <|
|
||||
table = target_table_builder [["X", [1, 2, 2, 3, 4, 4]], ["Y", ['a', 'b', 'c', 'd', 'e', 'f']]] primary_key=[] connection=data.connection
|
||||
keys = source_table_builder [["X", [2, 4]], ["Y", ['b', 'f']]] connection=data.connection
|
||||
affected_rows = table.delete_rows keys key_columns=["X", "Y"]
|
||||
affected_rows . should_equal 2
|
||||
table.rows.map .to_vector . should_equal [[1, "a"], [2, "c"], [3, "d"], [4, "e"]]
|
||||
table.rows.map .to_vector . should_equal_ignoring_order [[1, "a"], [2, "c"], [3, "d"], [4, "e"]]
|
||||
|
||||
group_builder.specify "should fail if key_columns are missing in source or target tables" <|
|
||||
table = target_table_builder [["X", [1, 2, 3]], ["Y", [4, 5, 6]]] primary_key=[] connection=data.connection
|
||||
@ -1291,11 +1249,11 @@ test_table_delete group_builder (data : Data) source_table_builder target_table_
|
||||
r1.catch.example_key . should_equal [2]
|
||||
r1.catch.example_count . should_equal 3
|
||||
# no changes
|
||||
table.at "X" . to_vector . should_equal [1, 2, 2, 3, 2]
|
||||
table.at "X" . to_vector . should_equal_ignoring_order [1, 2, 2, 3, 2]
|
||||
|
||||
r2 = table.delete_rows keys key_columns=["X"] allow_duplicate_matches=True
|
||||
r2.should_equal 3
|
||||
table.rows.map .to_vector . should_equal [[1, "a"], [3, "d"]]
|
||||
table.rows.map .to_vector . should_equal_ignoring_order [[1, "a"], [3, "d"]]
|
||||
|
||||
group_builder.specify "should fail if the target table does not exist" <|
|
||||
table = target_table_builder [["X", [1, 2, 3]]] connection=data.connection
|
||||
@ -1320,21 +1278,21 @@ test_table_delete group_builder (data : Data) source_table_builder target_table_
|
||||
w1.to_display_text . should_contain "Only the first 1000 distinct rows out of 1999 were used for the dry run"
|
||||
|
||||
# Target remains unchanged
|
||||
target.at "X" . to_vector . should_equal [0, 1, 500, 1500, 3500]
|
||||
target.at "X" . to_vector . should_equal_ignoring_order [0, 1, 500, 1500, 3500]
|
||||
|
||||
r2 = target.delete_rows source
|
||||
Problems.assume_no_problems r2
|
||||
# All 3 rows were deleted
|
||||
r2.should_equal 3
|
||||
target.at "X" . to_vector . should_equal [0, 3500]
|
||||
target.at "X" . to_vector . should_equal_ignoring_order [0, 3500]
|
||||
|
||||
group_builder.specify "will work fine if the target table contains NULL keys" <|
|
||||
t1 = target_table_builder [["X", ["a", "b", Nothing, "c"]], ["Y", [1, 2, 3, Nothing]]] connection=data.connection
|
||||
s1 = source_table_builder [["X", ["b", "c"]]] connection=data.connection
|
||||
t1.delete_rows s1 key_columns=["X"] . should_equal 2
|
||||
m1 = t1.read . sort "X"
|
||||
m1.at "X" . to_vector . should_equal [Nothing, "a"]
|
||||
m1.at "Y" . to_vector . should_equal [3, 1]
|
||||
m1.at "X" . to_vector . should_equal_ignoring_order [Nothing, "a"]
|
||||
m1.at "Y" . to_vector . should_equal_ignoring_order [3, 1]
|
||||
|
||||
group_builder.specify "will raise an error if they source table contains NULL keys" <|
|
||||
t2 = target_table_builder [["X", ["a", "b", "c"]], ["Y", [1, 2, Nothing]]] connection=data.connection
|
||||
@ -1355,7 +1313,7 @@ tests group_builder (data : Data) make_new_connection source_table_builder (suff
|
||||
r1.column_names . should_equal ["X"]
|
||||
r1.name . should_not_equal name
|
||||
# A small table is uploaded whole.
|
||||
r1.at "X" . to_vector . should_contain_the_same_elements_as [1, 2, 3]
|
||||
r1.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
r1.row_count . should_equal 3
|
||||
r1.is_trivial_query . should_be_true
|
||||
|
||||
@ -1381,7 +1339,7 @@ tests group_builder (data : Data) make_new_connection source_table_builder (suff
|
||||
# The target table is returned, as usually.
|
||||
r1.name . should_equal dest.name
|
||||
# But the data is not appended due to the dry-run - the table is unmodified.
|
||||
r1.at "X" . to_vector . should_contain_the_same_elements_as [1, 2, 3]
|
||||
r1.at "X" . to_vector . should_equal_ignoring_order [1, 2, 3]
|
||||
r1.is_trivial_query . should_be_true
|
||||
|
||||
group_builder.specify "should return the count of rows that would be deleted for delete_rows, but keep the table unchanged"+suffix <|
|
||||
@ -1424,7 +1382,7 @@ tests group_builder (data : Data) make_new_connection source_table_builder (suff
|
||||
# Create a table that has the same name as the dry run table normally would have.
|
||||
pre_existing_table = pre_existing_src.select_into_database_table data.connection dry_run_name temporary=False . should_succeed
|
||||
pre_existing_table.column_names . should_equal ["X"]
|
||||
pre_existing_table.at "X" . to_vector . should_contain_the_same_elements_as [4, 5, 6]
|
||||
pre_existing_table.at "X" . to_vector . should_equal_ignoring_order [4, 5, 6]
|
||||
Panic.with_finalizer (data.connection.drop_table pre_existing_table.name if_exists=True) <|
|
||||
new_dry_run_name = Context.Output.with_disabled <|
|
||||
tmp_connection2 = make_new_connection Nothing
|
||||
@ -1433,7 +1391,7 @@ tests group_builder (data : Data) make_new_connection source_table_builder (suff
|
||||
dry_run_table = src3.select_into_database_table tmp_connection2 target_name temporary=True . should_succeed
|
||||
Problems.expect_warning Dry_Run_Operation dry_run_table
|
||||
dry_run_table.column_names . should_equal ["B"]
|
||||
dry_run_table.at "B" . to_vector . should_contain_the_same_elements_as [7, 8, 9]
|
||||
dry_run_table.at "B" . to_vector . should_equal_ignoring_order [7, 8, 9]
|
||||
name = Warning.clear dry_run_table.name
|
||||
tmp_connection2.close
|
||||
name
|
||||
@ -1442,7 +1400,7 @@ tests group_builder (data : Data) make_new_connection source_table_builder (suff
|
||||
new_dry_run_name . should_not_equal dry_run_name
|
||||
|
||||
# The pre-existing table should not have been overwritten.
|
||||
pre_existing_table.at "X" . to_vector . should_contain_the_same_elements_as [4, 5, 6]
|
||||
pre_existing_table.at "X" . to_vector . should_equal_ignoring_order [4, 5, 6]
|
||||
|
||||
|
||||
## PRIVATE
|
||||
|
@ -159,7 +159,7 @@ add_specs suite_builder =
|
||||
escaped.
|
||||
ew1 = Unquoted_Characters_In_Output.Warning 'The Column "Name"' [3]
|
||||
ew2 = Unquoted_Characters_In_Output.Warning "Hello, Column?" [-1, 0, 1, 2, 3]
|
||||
warnings.should_contain_the_same_elements_as [ew1, ew2]
|
||||
warnings.should_equal_ignoring_order [ew1, ew2]
|
||||
w1 = warnings.find w-> w.column == 'Hello, Column?'
|
||||
w1.to_display_text . should_equal "The Hello, Column? at rows [the header, 0, 1, 2, 3] contains characters that need quoting, but quoting is disabled. The generated file may be corrupted."
|
||||
expected_text = normalize_lines <| """
|
||||
|
@ -81,7 +81,7 @@ spec_fmt suite_builder header file read_method sheet_count=5 =
|
||||
table.row_count . should_equal 25
|
||||
table.column_names . should_equal ["Sheet Name", "Name", "Quantity", "Price", "A", "B", "C", "D", "E", "Student Name", "Enrolment Date", "Item", "Price 1"]
|
||||
problems = [Empty_Sheet.Error, Duplicate_Output_Column_Names.Error ["Price"]]
|
||||
Problems.test_problem_handling action problems tester
|
||||
Problems.test_problem_handling action problems tester ignore_warning_cardinality=True
|
||||
|
||||
group_builder.specify "should let you read all sheets into a table of tables" <|
|
||||
wb = read_method file
|
||||
@ -90,7 +90,7 @@ spec_fmt suite_builder header file read_method sheet_count=5 =
|
||||
table.row_count . should_equal 5
|
||||
table.column_names . should_equal ["Sheet Name", "Table"]
|
||||
problems = [Empty_Sheet.Error, Duplicate_Output_Column_Names.Error ["Price"]]
|
||||
Problems.test_problem_handling action problems tester
|
||||
Problems.test_problem_handling action problems tester ignore_warning_cardinality=True
|
||||
|
||||
group_builder.specify "should let you read some sheets from xlsx" <|
|
||||
wb = read_method file
|
||||
@ -847,7 +847,7 @@ add_specs suite_builder =
|
||||
workbook.table_types . should_equal ['Worksheet', 'Named Range']
|
||||
|
||||
workbook.tables.row_count . should_equal (sheet_names.length + range_names.length)
|
||||
workbook.tables.at "Name" . to_vector . should_contain_the_same_elements_as (sheet_names + range_names)
|
||||
workbook.tables.at "Name" . to_vector . should_equal_ignoring_order (sheet_names + range_names)
|
||||
|
||||
workbook.tables types=["Worksheet"] . row_count . should_equal sheet_names.length
|
||||
workbook.tables types=["Named Range"] . row_count . should_equal range_names.length
|
||||
|
@ -16,7 +16,7 @@ type Dummy_Connection
|
||||
Nothing
|
||||
|
||||
in_memory_setup =
|
||||
selection = Common_Table_Operations.Main.Test_Selection.Config supports_case_sensitive_columns=True natural_ordering=True case_insensitive_ordering=True order_by_unicode_normalization_by_default=True supports_unicode_normalization=True supports_time_duration=True supports_nanoseconds_in_time=True supports_mixed_columns=True fixed_length_text_columns=True supports_8bit_integer=True
|
||||
selection = Common_Table_Operations.Main.Test_Selection.Config supports_case_sensitive_columns=True natural_ordering=True case_insensitive_ordering=True order_by_unicode_normalization_by_default=True supports_unicode_normalization=True supports_time_duration=True supports_nanoseconds_in_time=True supports_mixed_columns=True text_length_limited_columns=True fixed_length_text_columns=True supports_8bit_integer=True
|
||||
aggregate_selection = Common_Table_Operations.Aggregate_Spec.Test_Selection.Config
|
||||
|
||||
agg_table_fn _ = (enso_project.data / "data.csv") . read
|
||||
|
Loading…
Reference in New Issue
Block a user