Implement Table.order_by for SQLite and the common scaffolding for all backends (#3502)

Implements the common and SQLite parts of https://www.pivotaltracker.com/story/show/182195405
This commit is contained in:
Radosław Waśko 2022-06-06 12:56:52 +02:00 committed by GitHub
parent 0e867c663e
commit 7d94efa6f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 773 additions and 180 deletions

View File

@ -132,6 +132,7 @@
- [Implemented a `Table.from Text` conversion allowing to parse strings
representing `Delimited` files without storing them on the filesystem.][3478]
- [Added rank data, correlation and covariance statistics for `Vector`][3484]
- [Implemented `Table.order_by` for the SQLite backend.][3502]
[debug-shortcuts]:
https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug
@ -206,6 +207,7 @@
[3486]: https://github.com/enso-org/enso/pull/3486
[3478]: https://github.com/enso-org/enso/pull/3478
[3484]: https://github.com/enso-org/enso/pull/3484
[3502]: https://github.com/enso-org/enso/pull/3502
#### Enso Compiler

View File

@ -92,6 +92,23 @@ type Range
map function =
Vector.new this.length (i -> function (this.start + i*this.step))
## Returns a vector of all elements of this range which satisfy a predicate.
Arguments:
- predicate: A function that takes a list element and returns a boolean
value that says whether that value satisfies the conditions of the
function.
> Example
Selecting all elements that are greater than 3.
(0.up_to 7).filter (> 3)
filter : (Any -> Boolean) -> Vector Any
filter predicate =
builder = this.fold Vector.new_builder builder-> elem->
if predicate elem then builder.append elem else builder
builder.to_vector
## Applies a function for each element in the range.
Arguments:

View File

@ -1065,10 +1065,10 @@ Text.repeat count=1 =
> Examples
Various different ways to take part of "Hello World!"
"Hello World!".take First.new == "H"
"Hello World!".take First == "H"
"Hello World!".take (First 5) == "Hello"
"Hello World!".take (First 0) == ""
"Hello World!".take Last.new == "!"
"Hello World!".take Last == "!"
"Hello World!".take (Last 6) == "World!"
"Hello World!".take (Before " ") == "Hello"
"Hello World!".take (Before_Last "o") == "Hello W"
@ -1105,10 +1105,10 @@ Text.take range =
> Examples
Various different ways to take part of "Hello World!"
"Hello World!".drop First.new == "ello World!"
"Hello World!".drop First == "ello World!"
"Hello World!".drop (First 5) == " World!"
"Hello World!".drop (First 0) == "Hello World!"
"Hello World!".drop Last.new == "Hello World"
"Hello World!".drop Last == "Hello World"
"Hello World!".drop (Last 6) == "Hello "
"Hello World!".drop (Before " ") == " World!"
"Hello World!".drop (Before_Last "o") == "orld!"

View File

@ -205,48 +205,78 @@ match_criteria_callback matcher objects criteria problem_callback reorder=False
problem_callback unmatched_criteria
result.first
type Match_Matrix
## PRIVATE
A helper type holding a matrix of matches.
type Match_Matrix matrix criteria objects
# Checks if the ith object is matched by any criterion.
is_object_matched_by_anything : Integer -> Boolean
is_object_matched_by_anything i =
this.matrix.at i . any x->x
# Checks if the ith criterion matches any objects.
does_criterion_match_anything : Integer -> Boolean
does_criterion_match_anything i =
this.matrix.map (col -> col.at i) . any x->x
## PRIVATE
Extracts the list of criteria that did not have any matches.
unmatched_criteria =
checked_criteria = this.criteria.map_with_index j-> criterion->
has_matches = this.does_criterion_match_anything j
Pair has_matches criterion
checked_criteria.filter (p -> p.first.not) . map .second
## PRIVATE
Returns the list of criteria that match the ith object.
criteria_matching_object : Integer -> Vector
criteria_matching_object i =
this.criteria.filter_with_index j-> _->
this.matrix . at i . at j
## PRIVATE
Returns the list of criteria indices that match the ith object.
criteria_indices_matching_object : Integer -> Vector
criteria_indices_matching_object i =
(0.up_to this.criteria.length).filter j->
this.matrix . at i . at j
## PRIVATE
Generates a matrix specifying which criteria match which object.
The returned `match_matrix` satisfies the following condition:
`match_matrix . at i . at j` is `True` if and only if `objects.at i` matches
`criteria.at j`.
make_match_matrix matcher objects criteria object_name_mapper=(x->x) criterion_mapper=(x->x) =
matrix = objects.map obj->
criteria.map criterion->
matcher.match_single_criterion (object_name_mapper obj) (criterion_mapper criterion)
Match_Matrix matrix criteria objects
## PRIVATE
internal_match_criteria_implementation matcher objects criteria reorder=False name_mapper=(x->x) = Panic.catch Wrapped_Dataflow_Error (handler = x-> x.payload.unwrap) <|
## TODO [RW] discuss: this line of code also shows an issue we had with ensuring input dataflow-errors are correctly propagated, later on we stopped doing that and testing for that as it was too cumbersome. Maybe it could be helped with an @Accepts_Error annotation similar to the one from the interpreter???
[matcher, objects, criteria, reorder, name_mapper] . each v->
Panic.rethrow (v.map_error Wrapped_Dataflow_Error)
# match_matrix . at i . at j specifies whether objects.at i matches criteria.at j
match_matrix = objects.map obj->
criteria.map criterion->
name = name_mapper obj
matcher.match_single_criterion name criterion
# Checks if the ith object is matched by any criterion.
is_object_matched_by_anything : Integer -> Boolean
is_object_matched_by_anything i =
match_matrix.at i . any x->x
# Checks if the ith criterion matches any columns.
does_criterion_match_anything : Integer -> Boolean
does_criterion_match_anything i =
match_matrix.map (col -> col.at i) . any x->x
match_matrix = here.make_match_matrix matcher objects criteria name_mapper
unmatched_criteria = match_matrix.unmatched_criteria
# Selects object indices which satisfy the provided predicate.
select_matching_indices : (Integer -> Boolean) -> Vector Text
select_matching_indices matcher =
0.up_to objects.length . to_vector . filter matcher
# Check consistency
checked_criteria = criteria.map_with_index j-> criterion->
has_matches = does_criterion_match_anything j
Pair has_matches criterion
unmatched_criteria = checked_criteria.filter (p -> p.first.not) . map .second
selected_indices = case reorder of
True ->
nested_indices = 0.up_to criteria.length . map j->
is_object_matched_by_this_criterion i =
match_matrix.at i . at j
match_matrix.matrix.at i . at j
select_matching_indices is_object_matched_by_this_criterion
nested_indices.flat_map x->x . distinct
False ->
select_matching_indices is_object_matched_by_anything
select_matching_indices match_matrix.is_object_matched_by_anything
result = selected_indices.map objects.at
Pair result unmatched_criteria

View File

@ -6,4 +6,4 @@ from Standard.Base import all
- sort_digits_as_numbers: Sort digits in the text as numbers. Setting this to
`True` results in a "Natural" ordering.
- case_sensitive: Specifies if the ordering should be case case sensitive.
type Text_Ordering (sort_digits_as_numbers:Bool=False) (case_sensitive:(True|Case_Insensitive)=True)
type Text_Ordering (sort_digits_as_numbers:Boolean=False) (case_sensitive:(True|Case_Insensitive)=True)

View File

@ -102,25 +102,3 @@ type Text_Sub_Range
predicate (Text_Utils.substring text start end) . not
if indices.first == -1 then (Range 0 indices.second) else
Range 0 indices.first
## UNSTABLE
A temporary workaround to allow the `First` constructor to work with default
arguments.
It is needed, because there are issues with relying on default arguments of
Atom constructors, as described in the following issue:
https://github.com/enso-org/enso/issues/1600
Once that issue is fixed, it can be removed.
First.new : Integer -> First
First.new (count = 1) = First count
## UNSTABLE
A temporary workaround to allow the `Last` constructor to work with default
arguments.
It is needed, because there are issues with relying on default arguments of
Atom constructors, as described in the following issue:
https://github.com/enso-org/enso/issues/1600
Once that issue is fixed, it can be removed.
Last.new : Integer -> Last
Last.new (count = 1) = Last count

View File

@ -3,6 +3,7 @@ from Standard.Base import all
import Standard.Base.Error.Common as Errors
import Standard.Table.Data.Aggregate_Column
import Standard.Database.Data.Sql
import Standard.Database.Data.Internal.IR
import Standard.Database.Data.Dialect.Postgres
import Standard.Database.Data.Dialect.Redshift
import Standard.Database.Data.Dialect.Sqlite as Sqlite_Module
@ -39,6 +40,14 @@ type Dialect
resolve_target_sql_type : Aggregate_Column -> Sql_Type
resolve_target_sql_type = Errors.unimplemented "This is an interface only."
## PRIVATE
Prepares an ordering descriptor.
One of the purposes of this method is to verify if the expected ordering
settings are supported by the given database backend.
prepare_order_descriptor : Connection -> IR.Internal_Column -> Sort_Direction -> Text_Ordering -> IR.Order_Descriptor
prepare_order_descriptor = Errors.unimplemented "This is an interface only."
## PRIVATE
A vector of SQL dialects supported by the Database library.

View File

@ -1,5 +1,6 @@
from Standard.Base import all
import Standard.Base.Error.Common as Errors
from Standard.Table.Data.Aggregate_Column import all
from Standard.Database.Data.Sql import Sql_Type
import Standard.Database.Data.Dialect
@ -43,6 +44,16 @@ type Postgresql_Dialect
resolve_target_sql_type : Aggregate_Column -> Sql_Type
resolve_target_sql_type aggregate = here.resolve_target_sql_type aggregate
## PRIVATE
Prepares an ordering descriptor.
One of the purposes of this method is to verify if the expected ordering
settings are supported by the given database backend.
prepare_order_descriptor : Connection -> IR.Internal_Column -> Sort_Direction -> Text_Ordering -> IR.Order_Descriptor
prepare_order_descriptor connection internal_column sort_direction text_ordering =
_ = [connection, internal_column, sort_direction, text_ordering]
Errors.unimplemented "TODO"
## PRIVATE
make_internal_generator_dialect =
text = [here.starts_with, here.contains, here.ends_with, here.agg_shortest, here.agg_longest]+here.concat_ops

View File

@ -1,5 +1,6 @@
from Standard.Base import all
import Standard.Base.Error.Common as Errors
import Standard.Table.Data.Aggregate_Column
import Standard.Database.Data.Sql
import Standard.Database.Data.Dialect
@ -42,3 +43,13 @@ type Redshift_Dialect
resolve_target_sql_type : Aggregate_Column -> Sql_Type
resolve_target_sql_type aggregate =
Postgres.resolve_target_sql_type aggregate
## PRIVATE
Prepares an ordering descriptor.
One of the purposes of this method is to verify if the expected ordering
settings are supported by the given database backend.
prepare_order_descriptor : Connection -> IR.Internal_Column -> Sort_Direction -> Text_Ordering -> IR.Order_Descriptor
prepare_order_descriptor connection internal_column sort_direction text_ordering =
_ = [connection, internal_column, sort_direction, text_ordering]
Errors.unimplemented "TODO"

View File

@ -5,6 +5,7 @@ from Standard.Database.Data.Sql import Sql_Type
import Standard.Database.Data.Dialect
import Standard.Database.Data.Dialect.Helpers
import Standard.Database.Data.Internal.Base_Generator
import Standard.Database.Data.Internal.IR
from Standard.Database.Error as Database_Errors import Unsupported_Database_Operation_Error
## PRIVATE
@ -43,6 +44,20 @@ type Sqlite_Dialect
resolve_target_sql_type : Aggregate_Column -> Sql_Type
resolve_target_sql_type aggregate = here.resolve_target_sql_type aggregate
## PRIVATE
Prepares an ordering descriptor.
One of the purposes of this method is to verify if the expected ordering
settings are supported by the given database backend.
prepare_order_descriptor : Connection -> IR.Internal_Column -> Sort_Direction -> Text_Ordering -> IR.Order_Descriptor
prepare_order_descriptor _ internal_column sort_direction text_ordering = case internal_column.sql_type.is_likely_text of
True ->
if text_ordering.sort_digits_as_numbers then Error.throw (Unsupported_Database_Operation_Error "Natural ordering is not supported by the SQLite backend. You may need to materialize the Table to perform this operation.") else
if text_ordering.case_sensitive != True then Error.throw (Unsupported_Database_Operation_Error "Case insensitive ordering is not supported by the SQLite backend. You may need to materialize the Table to perform this operation.") else
IR.Order_Descriptor internal_column.expression sort_direction collation=Nothing
False ->
IR.Order_Descriptor internal_column.expression sort_direction collation=Nothing
## PRIVATE
make_internal_generator_dialect =
text = [here.starts_with, here.contains, here.ends_with]+here.concat_ops

View File

@ -235,18 +235,22 @@ generate_from_part dialect from_spec = case from_spec of
Arguments:
- dialect: The SQL dialect for which the code is generated.
- order_description: A description of the ORDER clause.
# generate_order : Internal_Dialect -> [Expression, IR.Order_Direction, IR.Nulls_Order] -> Sql.Builder
generate_order : Internal_Dialect -> Vector Any -> Sql.Builder
generate_order dialect order_description =
order_suffix = case order_description.second of
IR.Ascending -> Sql.code " ASC"
IR.Descending -> Sql.code " DESC"
nulls_suffix = case order_description.at 2 of
- order_descriptor: A description of the ORDER clause.
generate_order : Internal_Dialect -> Order_Descriptor -> Sql.Builder
generate_order dialect order_descriptor =
order_suffix = case order_descriptor.direction of
Sort_Direction.Ascending -> Sql.code " ASC"
Sort_Direction.Descending -> Sql.code " DESC"
nulls_suffix = case order_descriptor.nulls_order of
Nothing -> Sql.empty
IR.Nulls_First -> Sql.code " NULLS FIRST"
IR.Nulls_Last -> Sql.code " NULLS LAST"
(here.generate_expression dialect (order_description.first)) ++ order_suffix ++ nulls_suffix
collation = case order_descriptor.collation of
Nothing -> Sql.empty
collation_name -> Sql.code ' COLLATE "'+collation_name+'"'
base_expression = here.generate_expression dialect order_descriptor.expression
base_expression ++ collation ++ order_suffix ++ nulls_suffix
## PRIVATE

View File

@ -17,8 +17,7 @@ type Expression
originates from, it corresponds to the `alias` field in `from_spec`.
- name: the name of the column directly in the table or its alias in a
sub-query.
# type Column (origin : Text) (name : Text)
type Column origin name
type Column (origin : Text) (name : Text)
## PRIVATE
@ -30,8 +29,7 @@ type Expression
It is usually inferred from the expression's context.
- value: the value to be interpolated; it should be a simple Number, Text
or other types that are serializable for JDBC.
# type Constant (sql_type : Sql.Sql_Type) (value : Any)
type Constant sql_type value
type Constant (sql_type : Sql.Sql_Type) (value : Any)
## PRIVATE
@ -44,8 +42,7 @@ type Expression
dialect.
- expression: a list of expressions which are arguments to the operation;
different operations support different amounts of arguments.
# type Operation (kind : Text) (expressions : Vector Expression)
type Operation kind expressions
type Operation (kind : Text) (expressions : Vector Expression)
type Internal_Column
## PRIVATE
@ -94,11 +91,7 @@ type Context
- meta_index: a list of internal columns to use for joining or grouping.
- limit: an optional maximum number of elements that the equery should
return.
# type Context (from_spec : From_Spec) (where_filters : Vector Expression)
# (orders : Vector [Expression, Order_Direction, Nulls_Order])
# (groups : Vector Expression) (meta_index : Vector Internal_Column)
# (limit : Nothing | Integer)
type Context from_spec where_filters orders groups meta_index limit
type Context (from_spec : From_Spec) (where_filters : Vector Expression) (orders : Vector Order_Descriptor) (groups : Vector Expression) (meta_index : Vector Internal_Column) (limit : Nothing | Integer)
## PRIVATE
@ -126,11 +119,27 @@ type Context
Arguments:
- new_orders: The new ordering clauses to set in the query.
# set_orders : Vector [Expression, Order_Direction] -> Context
set_orders : Vector Any -> Context
set_orders : Vector Order_Descriptor -> Context
set_orders new_orders =
Context this.from_spec this.where_filters new_orders this.groups this.meta_index this.limit
## PRIVATE
Returns a copy of the context with added `orders`.
The orderings are modified in such a way that the newly added ordering
takes precedence, but if any orderings were already present they are also
taken into account to break ties within the new ordering.
In practice this means, that the old orderings are preserved, but the new
ones are added to the beginning of the list so that they take precedence.
Arguments:
- new_orders: The new ordering clauses to add to the query.
add_orders : Vector Order_Descriptor -> Context
add_orders new_orders =
Context this.from_spec this.where_filters new_orders+this.orders this.groups this.meta_index this.limit
## PRIVATE
Returns a copy of the context with changed `groups`.
@ -197,8 +206,7 @@ type From_Spec
parts of the query, this is especially useful for example in
self-joins, allowing to differentiate between different instances of
the same table.
# type From_Table (table_name : Text) (alias : Text)
type From_Table table_name alias
type From_Table (table_name : Text) (alias : Text)
## PRIVATE
@ -211,9 +219,7 @@ type From_Spec
- on: a list of expressions that will be used as join conditions, these
are usually be equalities between expressions from the left and right
sources.
# type Join (kind : Join_Kind) (left_spec : From_Spec)
# (right_spec : From_Spec) (on : Vector Expression)
type Join kind left_spec right_spec on
type Join (kind : Join_Kind) (left_spec : From_Spec) (right_spec : From_Spec) (on : Vector Expression)
## PRIVATE
@ -226,9 +232,7 @@ type From_Spec
- context: the context for the sub-query.
- alias: the name upon which the results of this sub-query can be
referred to in other parts of the query.
# type Sub_Query (columns : Vector [Text, Expression])
# (context : Context) (alias : Text)
type Sub_Query columns context alias
type Sub_Query (columns : Vector (Pair Text Expression)) (context : Context) (alias : Text)
## PRIVATE
@ -273,20 +277,9 @@ type Join_Kind
in the query can be used to filter the results.
type Join_Cross
## PRIVATE
Specifies the direction of the ordering.
type Order_Direction
## PRIVATE
Orders elements in ascending order.
type Ascending
## PRIVATE
Orders elements in descending order.
type Descending
type Order_Descriptor (expression : Expression) (direction : Sort_Direction) (nulls_order : Nothing | Nulls_Order = Nothing) (collation : Nothing | Text = Nothing)
## PRIVATE
@ -317,8 +310,7 @@ type Query
is a pair whose first element is the name of the materialized column
and the second element is the expression to compute.
- context: The query context, see `Context` for more detail.
# type Select (expressions : [Text, Expression]) (context : Context)
type Select columns context
type Select (expressions : Vector (Pair Text Expression)) (context : Context)
## PRIVATE

View File

@ -147,6 +147,14 @@ type Sql_Type
is_definitely_text =
[Types.VARCHAR, Types.LONGVARCHAR, Types.NVARCHAR, Types.LONGNVARCHAR].contains this.typeid
## PRIVATE
Returns True if this type represents a Text, using heuristics that may
match more possible types.
is_likely_text : Boolean
is_likely_text =
this.is_definitely_text || this.name.contains "text" (Text_Matcher Case_Insensitive)
## UNSTABLE
A fragment of a SQL query.

View File

@ -22,6 +22,8 @@ from Standard.Base.Error.Problem_Behavior as Problem_Behavior_Module import Prob
from Standard.Database.Error as Database_Errors import Unsupported_Database_Operation_Error
import Standard.Table.Data.Column_Mapping
import Standard.Table.Data.Position
import Standard.Table.Data.Sort_Column_Selector
import Standard.Table.Data.Sort_Column
polyglot java import java.sql.JDBCType
@ -114,7 +116,7 @@ type Table
> Example
Select columns by name.
table.select_columns (By_Name.new ["bar", "foo"])
table.select_columns (By_Name ["bar", "foo"])
## TODO [RW] default arguments do not work on atoms, once this is fixed,
the above should be replaced with just `By_Name`.
@ -164,7 +166,7 @@ type Table
> Example
Remove columns with given names.
table.remove_columns (By_Name.new ["bar", "foo"])
table.remove_columns (By_Name ["bar", "foo"])
## TODO [RW] default arguments do not work on atoms, once this is fixed,
the above should be replaced with just `By_Name`.
@ -213,7 +215,7 @@ type Table
> Example
Move a column with a specified name to back.
table.reorder_columns (By_Name.new ["foo"]) position=After_Other_Columns
table.reorder_columns (By_Name ["foo"]) position=After_Other_Columns
## TODO [RW] default arguments do not work on atoms, once this is fixed,
the above should be replaced with just `By_Name`.
@ -443,6 +445,51 @@ type Table
if len == 0 then Error.throw Materialized_Table.No_Index_Set_Error else
if len == 1 then ixes.at 0 else ixes
## Sorts the rows of the table according to the specified columns and order.
Arguments:
- columns: The columns and order to sort the table.
- text_ordering: The ordering method to use on text values.
- on_problems: Specifies how to handle if a problem occurs, raising as a
warning by default. The following problems can occur:
- If a column in `columns` is not present in the input table, a
`Missing_Input_Columns`.
- If duplicate columns, names or indices are provided, a
`Duplicate_Column_Selectors`.
- If a column index is out of range, a `Column_Indexes_Out_Of_Range`.
- If two distinct indices refer to the same column, an
`Input_Indices_Already_Matched`.
- If two name matchers match the same column, a
`Column_Matched_By_Multiple_Selectors`.
- If no valid columns are selected, a `No_Input_Columns_Selected`.
- If values do not implement an ordering, an
`Incomparable_Values_Error`.
> Example
Order the table by the column "alpha" in ascending order.
table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "alpha"])
> Example
Order the table by the second column in ascending order. In case of any
ties, break them based on the 7th column from the end of the table in
descending order.
table.order_by (Sort_Column_Selector.By_Index [Sort_Column.Index 1, Sort_Column.Index -7 Sort_Direction.Descending])
order_by : Sort_Column_Selector -> Text_Ordering -> Problem_Behavior -> Table
order_by (columns = (Sort_Column_Selector.By_Name [(Sort_Column.Name (this.columns.at 0 . name))])) text_ordering=Text_Ordering on_problems=Report_Warning = Panic.handle_wrapped_dataflow_error <|
columns_for_ordering = Table_Helpers.prepare_order_by this.internal_columns columns on_problems
new_order_descriptors = columns_for_ordering.map selected_column->
internal_column = selected_column.column
associated_selector = selected_column.associated_selector
## TODO [RW] this is only needed because `Vector.map` does not
propagate dataflow errors correctly. See:
https://www.pivotaltracker.com/story/show/181057718
Panic.throw_wrapped_if_error <|
this.connection.dialect.prepare_order_descriptor this.connection internal_column associated_selector.direction text_ordering
new_ctx = this.context.add_orders new_order_descriptors
this.updated_context new_ctx
## UNSTABLE
Sorts the table according to the specified rules.
@ -497,21 +544,18 @@ type Table
table.sort by=[quality_ratio, 'Rating']
sort : Text | Column | Order_Rule | Vector.Vector (Text | Column | Order_Rule) -> Sort_Direction -> Boolean -> Table
sort by order=Sort_Direction.Ascending missing_last=True = Panic.recover Any <|
order_to_ir = case _ of
Sort_Direction.Ascending -> IR.Ascending
Sort_Direction.Descending -> IR.Descending
missing_to_ir last = case last of
True -> IR.Nulls_Last
False -> IR.Nulls_First
wrap_elem elem =
[this.resolve elem . expression, order_to_ir order, missing_to_ir missing_last]
IR.Order_Descriptor (this.resolve elem . expression) order (missing_to_ir missing_last) collation=Nothing
to_ir elem = case elem of
Text -> wrap_elem elem
Column _ _ _ _ _ -> wrap_elem elem
Order_Rule elem Nothing my_order my_nulls ->
chosen_order = my_order.if_nothing order
chosen_nulls = my_nulls.if_nothing missing_last
[this.resolve elem . expression, order_to_ir chosen_order, missing_to_ir chosen_nulls]
IR.Order_Descriptor (this.resolve elem . expression) chosen_order (missing_to_ir chosen_nulls) collation=Nothing
Order_Rule _ _ _ _ ->
Error.throw <| Unsupported_Database_Operation_Error "Custom comparators are not supported in Database"
elems = Helpers.unify_vector_singleton by . map to_ir

View File

@ -31,8 +31,3 @@ type Column_Mapping
## Selects columns by position starting at the first column until the
new_names is exhausted.
type By_Position (new_names : [Text])
## UNSTABLE
A temporary workaround to allow the By_Name constructor to work with default arguments.
By_Name.new : Map Text Text -> Matcher -> By_Name
By_Name.new names (matcher = Text_Matcher) = By_Name names matcher

View File

@ -25,14 +25,3 @@ type Column_Selector
this approach can be used to match columns with the same names as a set
of columns of some other table, for example, when preparing for a join.
type By_Column (columns : Vector Column)
## UNSTABLE
A temporary workaround to allow the `By_Name` constructor to work with
default arguments.
It is needed, because there are issues with relying on default arguments of
Atom constructors, as described in the following issue:
https://github.com/enso-org/enso/issues/1600
Once that issue is fixed, it can be removed.
By_Name.new : Vector Text -> Matcher -> By_Name
By_Name.new names (matcher = Text_Matcher) = By_Name names matcher

View File

@ -0,0 +1,6 @@
from Standard.Base import all
type Sort_Column
type Name name:Text direction:Sort_Direction=Sort_Direction.Ascending
type Index index:Integer direction:Sort_Direction=Sort_Direction.Ascending
type Column column:Column direction:Sort_Direction=Sort_Direction.Ascending

View File

@ -0,0 +1,8 @@
from Standard.Base import all
import Standard.Table.Data.Sort_Column
type Sort_Column_Selector
type By_Name (columns : Vector Sort_Column.Name) (matcher:Matcher=Text_Matcher)
type By_Index (columns : Vector Sort_Column.Index)
type By_Column (columns : Vector Sort_Column.Column)

View File

@ -274,7 +274,7 @@ type Table
> Example
Select columns by name.
table.select_columns (By_Name.new ["bar", "foo"])
table.select_columns (By_Name ["bar", "foo"])
## TODO [RW] default arguments do not work on atoms, once this is fixed,
the above should be replaced with just `By_Name`.
@ -323,7 +323,7 @@ type Table
> Example
Remove columns with given names.
table.remove_columns (By_Name.new ["bar", "foo"])
table.remove_columns (By_Name ["bar", "foo"])
## TODO [RW] default arguments do not work on atoms, once this is fixed,
the above should be replaced with just `By_Name`.
@ -372,7 +372,7 @@ type Table
> Example
Move a column with a specified name to back.
table.reorder_columns (By_Name.new ["foo"]) position=After_Other_Columns
table.reorder_columns (By_Name ["foo"]) position=After_Other_Columns
## TODO [RW] default arguments do not work on atoms, once this is fixed,
the above should be replaced with just `By_Name`.

View File

@ -55,6 +55,16 @@ Duplicate_Column_Selectors.to_display_text : Text
Duplicate_Column_Selectors.to_display_text =
"The provided Column_Selector has duplicate entries: "+this.duplicate_selectors.short_display_text+"."
## Indicates that one column has been matched by multiple selectors.
In case the selectors have differing metadata and the error does not prevent
the operation from continuing, the first selector on the list is used.
type Column_Matched_By_Multiple_Selectors (column_name : Text) (selectors : [Any])
Column_Matched_By_Multiple_Selectors.to_display_text : Text
Column_Matched_By_Multiple_Selectors.to_display_text =
'The column "' + this.column_name + '" is matched by multiple selectors: ' + this.selectors.short_display_text + "."
## Indicates that the provided indices matched columns already matched by
others, so they do not introduce any new columns to the input.
@ -67,6 +77,15 @@ Input_Indices_Already_Matched.to_display_text : Text
Input_Indices_Already_Matched.to_display_text =
"The indices "+this.indices.short_display_text+" matched columns which have been matched earlier by other indices, so they did not introduce any new columns into the result."
## Indicates that no input columns were selected for the operation, so the
operation will cause no effect.
type No_Input_Columns_Selected
No_Input_Columns_Selected.to_display_text : Text
No_Input_Columns_Selected.to_display_text =
"No input columns have been selected for the operation."
## Indicates that an aggregation calculation could not be completed.
type Invalid_Aggregation (column:Text) (rows:[Integer]) (message:Text)

View File

@ -4,7 +4,7 @@ from Standard.Base.Error.Problem_Behavior as Problem_Behavior_Module import Prob
import Standard.Base.Runtime.Ref
import Standard.Table.Internal.Vector_Builder
from Standard.Table.Error as Error_Module import Missing_Input_Columns, Column_Indexes_Out_Of_Range, No_Output_Columns, Duplicate_Column_Selectors, Input_Indices_Already_Matched, Too_Many_Column_Names_Provided, Duplicate_Output_Column_Names, Invalid_Output_Column_Names
from Standard.Table.Error as Error_Module import Missing_Input_Columns, Column_Indexes_Out_Of_Range, No_Output_Columns, Duplicate_Column_Selectors, Input_Indices_Already_Matched, Too_Many_Column_Names_Provided, Duplicate_Output_Column_Names, Invalid_Output_Column_Names, Column_Matched_By_Multiple_Selectors
type Problem_Builder
type Problem_Builder oob_indices duplicate_column_selectors input_indices_already_matched missing_input_columns other
@ -21,6 +21,9 @@ type Problem_Builder
report_missing_input_columns columns =
here.append_to_ref this.missing_input_columns columns
report_column_matched_by_multiple_selectors column_name selectors =
this.report_other_warning (Column_Matched_By_Multiple_Selectors column_name selectors)
report_other_warning warning =
this.other.append warning

View File

@ -2,15 +2,18 @@ from Standard.Base import all
import Standard.Base.Warning
import Standard.Base.Data.Text.Matching
from Standard.Table.Data.Column_Selector as Column_Selector_Module import Column_Selector, By_Name, By_Index, By_Column
import Standard.Base.Data.Ordering.Vector_Lexicographic_Order
from Standard.Base.Data.Text.Text_Ordering as Text_Ordering_Module import Text_Ordering
from Standard.Base.Error.Problem_Behavior as Problem_Behavior_Module import Problem_Behavior, Report_Warning
import Standard.Table.Data.Position
from Standard.Table.Error as Error_Module import Missing_Input_Columns, Column_Indexes_Out_Of_Range, No_Output_Columns, Duplicate_Column_Selectors, Input_Indices_Already_Matched, Too_Many_Column_Names_Provided, Duplicate_Output_Column_Names, Invalid_Output_Column_Names
from Standard.Table.Error as Error_Module import Missing_Input_Columns, Column_Indexes_Out_Of_Range, No_Output_Columns, Duplicate_Column_Selectors, Input_Indices_Already_Matched, Too_Many_Column_Names_Provided, Duplicate_Output_Column_Names, Invalid_Output_Column_Names, No_Input_Columns_Selected
from Standard.Table.Data.Column_Selector as Column_Selector_Module import Column_Selector, By_Name, By_Index, By_Column
import Standard.Table.Data.Column_Mapping
import Standard.Table.Internal.Unique_Name_Strategy
import Standard.Table.Internal.Problem_Builder
import Standard.Base.Data.Ordering.Natural_Order
import Standard.Table.Data.Sort_Column_Selector
import Standard.Table.Data.Sort_Column
polyglot java import java.util.HashSet
@ -309,17 +312,27 @@ is_index_valid length ix =
actual_ix>=0 && actual_ix<length
## PRIVATE
Validates a Vector of indices returning a pair of `good_indices` and `problems`
validate_indices : Integer -> Vector -> Problem_Builder -> Vector
validate_indices length indices problem_builder =
partitioned_indices = indices.partition (here.is_index_valid length)
Validates a Vector of indices returning `good_indices` and reporting any
encountered problems.
Arguments:
- length:
- indices:
- problem_builder:
- on: a mapping from a possibly complex index selector into an integer index
associated with it. Used if the selector contains additional metadata. The
default one is an identity mapping for when the selector is just an
integer.
validate_indices : Integer -> Vector -> Problem_Builder -> (Any -> Integer) -> Vector
validate_indices length indices problem_builder on=(x->x) =
partitioned_indices = indices.partition (here.is_index_valid length << on)
inbound_indices = partitioned_indices.first
oob_indices = partitioned_indices.second
problem_builder.report_oob_indices oob_indices
problem_builder.report_oob_indices (oob_indices.map on)
uniques = here.validate_unique inbound_indices problem_builder.report_duplicate_column_selectors
uniques = here.validate_unique inbound_indices problem_builder.report_duplicate_column_selectors on=on
resolver = ix->(here.resolve_index length ix)
resolver = ix->(here.resolve_index length (on ix))
alias_uniques = here.validate_unique uniques problem_builder.report_input_indices_already_matched on=resolver
good_indices = alias_uniques.map i->[resolver i, i]
good_indices
@ -341,3 +354,186 @@ validate_unique vector problem_callback on=(x->x) =
if duplicates.not_empty then problem_callback duplicates
acc.at 1 . to_vector
## PRIVATE
A helper type used by transform helpers.
type Column_Transform_Element column associated_selector
## PRIVATE
prepare_order_by : Vector -> Problem_Behavior -> Vector Column_Transform_Element
prepare_order_by internal_columns column_selectors on_problems =
problem_builder = Problem_Builder.new
selected_elements = case column_selectors of
Sort_Column_Selector.By_Name name_selectors matcher ->
here.select_columns_by_name internal_columns name_selectors matcher problem_builder name_extractor=(_.name)
Sort_Column_Selector.By_Index index_selectors ->
here.select_columns_by_index internal_columns index_selectors problem_builder index_extractor=(_.index)
Sort_Column_Selector.By_Column column_selectors ->
here.select_columns_by_column_reference internal_columns column_selectors problem_builder column_extractor=(_.column)
if selected_elements.is_empty then
problem_builder.report_other_warning No_Input_Columns_Selected
problem_builder.attach_problems_after on_problems selected_elements
## PRIVATE
A helper function which can be used by methods that transform a subset of
columns based on some selection criteria while keeping the unselected columns
unaffected and not changing the ordering of the columns.
It returns `internal_columns` transformed in the following way: each entry
becomes a `Column_Transform_Element` which contains the original internal
column and the chosen associated selector (which may contain some additional
metadata needed to perform the transformation) or `Nothing` if the column was
not selected for transformation.
Arguments:
- internal_columns: A list of all columns in a table.
- name_selectors: A vector of selectors which contain a column name and
optionally some metadata.
- matcher: Specifies the strategy of matching names.
- problem_builder: Encapsulates the aggregation of encountered problems.
- name_extractor: A function which extracts the column name from the selector.
transform_columns_by_name : Vector -> Vector -> Text_Matcher -> Problem_Builder -> (Any -> Text) -> Vector
transform_columns_by_name internal_columns name_selectors matcher problem_builder name_extractor =
match_matrix = Matching.make_match_matrix matcher objects=internal_columns criteria=name_selectors object_name_mapper=(_.name) criterion_mapper=name_extractor
problem_builder.report_missing_input_columns match_matrix.unmatched_criteria
internal_columns.map_with_index i-> column->
matching_selectors = match_matrix.criteria_matching_object i
associated_selector = if matching_selectors.is_empty then Nothing else
if matching_selectors.length > 1 then
problem_builder.report_column_matched_by_multiple_selectors column.name matching_selectors
matching_selectors.first
Column_Transform_Element column associated_selector
## PRIVATE
A helper function which can be used by methods that transform a subset of
columns based on some selection criteria while keeping the unselected columns
unaffected and not changing the ordering of the columns.
It returns `internal_columns` transformed in the following way: each entry
becomes a `Column_Transform_Element` which contains the original internal
column and the chosen associated selector (which may contain some additional
metadata needed to perform the transformation) or `Nothing` if the column was
not selected for transformation.
Arguments:
- internal_columns: A list of all columns in a table.
- index_selectors: A vector of selectors which contain a column index and
optionally some metadata.
- problem_builder: Encapsulates the aggregation of encountered problems.
transform_columns_by_index : Vector -> Vector -> Problem_Builder -> (Any -> Integer) -> Vector
transform_columns_by_index internal_columns index_selectors problem_builder index_extractor =
good_indices = here.validate_indices internal_columns.length index_selectors problem_builder index_extractor
selectors_map = Map.from_vector good_indices
internal_columns.map_with_index i-> column->
associated_selector = selectors_map.get_or_else i Nothing
Column_Transform_Element column associated_selector
## PRIVATE
A helper function which can be used by methods that transform a subset of
columns based on some selection criteria while keeping the unselected columns
unaffected and not changing the ordering of the columns.
It returns `internal_columns` transformed in the following way: each entry
becomes a `Column_Transform_Element` which contains the original internal
column and the chosen associated selector (which may contain some additional
metadata needed to perform the transformation) or `Nothing` if the column was
not selected for transformation.
Arguments:
- internal_columns: A list of all columns in a table.
- column_selectors: A vector of column selectors which contain a column whose
name should be used as a reference to select the corresponding column in
the given table. The selectors may also optionally contain some metadata.
- problem_builder: Encapsulates the aggregation of encountered problems.
transform_columns_by_column_reference : Vector -> Vector -> Problem_Builder -> (Any -> Integer) -> Vector
transform_columns_by_column_reference internal_columns column_selectors problem_builder column_extractor =
name_extractor = selector->
column = column_extractor selector
column.name
here.transform_columns_by_name internal_columns column_selectors (Text_Matcher case_sensitive=True) problem_builder name_extractor
## PRIVATE
A helper function which can be used by methods that select a subset of
columns and need to keep some metadata coming from the selector associated
with each column.
The returned columns are in the same order as the original selectors that
matched them. A single selector may match multiple columns - in such case
these columns are all placed in the place belonging to that selector and they
keep their relative order from the table. If a column is matched by multiple
selectors a warning is raised and it only appears once in the result - in the
place associated with the first selector that matched it.
Arguments:
- internal_columns: A list of all columns in a table.
- name_selectors: A vector of selectors which contain a column name and
optionally some metadata.
- matcher: Specifies the strategy of matching names.
- problem_builder: Encapsulates the aggregation of encountered problems.
- name_extractor: A function which extracts the column name from the selector.
select_columns_by_name : Vector -> Vector -> Text_Matcher -> Problem_Builder -> (Any -> Text) -> Vector
select_columns_by_name internal_columns name_selectors matcher problem_builder name_extractor =
match_matrix = Matching.make_match_matrix matcher objects=internal_columns criteria=name_selectors object_name_mapper=(_.name) criterion_mapper=name_extractor
problem_builder.report_missing_input_columns match_matrix.unmatched_criteria
results = Vector.new_builder
internal_columns.each_with_index i-> column->
matching_selector_indices = match_matrix.criteria_indices_matching_object i
if matching_selector_indices.not_empty then
if matching_selector_indices.length > 1 then
matching_selectors = matching_selector_indices.map name_selectors.at
problem_builder.report_column_matched_by_multiple_selectors column.name matching_selectors
associated_selector_index = matching_selector_indices.first
associated_selector = name_selectors.at associated_selector_index
element = Column_Transform_Element column associated_selector
results.append (Pair element [associated_selector_index, i])
# We sort the results by the associated selector index, breaking ties by the column index.
sorted = results.to_vector.sort on=(_.second) by=Vector_Lexicographic_Order.compare
sorted.map .first
## PRIVATE
A helper function which can be used by methods that select a subset of
columns and need to keep some metadata coming from the selector associated
with each column.
The returned columns are in the same order as the original selectors that
matched them. A single selector may match multiple columns - in such case
these columns are all placed in the place belonging to that selector and they
keep their relative order from the table. If a column is matched by multiple
selectors a warning is raised and it only appears once in the result - in the
place associated with the first selector that matched it.
Arguments:
- internal_columns: A list of all columns in a table.
- index_selectors: A vector of selectors which contain a column index and
optionally some metadata.
- problem_builder: Encapsulates the aggregation of encountered problems.
select_columns_by_index : Vector -> Vector -> Problem_Builder -> (Any -> Integer) -> Vector
select_columns_by_index internal_columns index_selectors problem_builder index_extractor =
good_selectors = here.validate_indices internal_columns.length index_selectors problem_builder index_extractor
good_selectors.map pair->
Column_Transform_Element (internal_columns.at pair.first) pair.second
## PRIVATE
A helper function which can be used by methods that select a subset of
columns and need to keep some metadata coming from the selector associated
with each column.
The returned columns are in the same order as the original selectors that
matched them. A single selector may match multiple columns - in such case
these columns are all placed in the place belonging to that selector and they
keep their relative order from the table. If a column is matched by multiple
selectors a warning is raised and it only appears once in the result - in the
place associated with the first selector that matched it.
Arguments:
- internal_columns: A list of all columns in a table.
- column_selectors: A vector of column selectors which contain a column whose
name should be used as a reference to select the corresponding column in
the given table. The selectors may also optionally contain some metadata.
- problem_builder: Encapsulates the aggregation of encountered problems.
select_columns_by_column_reference : Vector -> Vector -> Problem_Builder -> (Any -> Integer) -> Vector
select_columns_by_column_reference internal_columns column_selectors problem_builder column_extractor =
name_extractor = selector->
column = column_extractor selector
column.name
here.select_columns_by_name internal_columns column_selectors (Text_Matcher case_sensitive=True) problem_builder name_extractor

View File

@ -98,13 +98,10 @@ config_from_env =
Suite.run : Any -> Suite_Config -> Any
Suite.run ~specs config =
builder = if config.should_output_junit then StringBuilder.new else Nothing
r = State.run Suite (Suite config Nil builder) <|
specs
State.get Suite
here.wrap_junit_testsuites config builder <|
r
State.run Suite (Suite config Nil builder) <|
specs
State.get Suite
## Creates a new test group, describing properties of the object
@ -240,7 +237,7 @@ Error.should_fail_with matcher frames_to_skip=0 =
caught = this.catch x->x
if caught.is_a matcher then Nothing else
loc = Meta.get_source_location 2+frames_to_skip
here.fail ("Unexpected error " + caught.to_text + " returned (at " + loc + ").")
here.fail ("Expected error "+matcher.to_text+", but error " + caught.to_text + " has been returned (at " + loc + ").")
## Expect a function to fail with the provided panic.

View File

@ -38,3 +38,10 @@ test_problem_handling action expected_problems result_checker =
warnings = Warning.get_all result_warning . map .value
## 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
warnings . should_contain_the_same_elements_as expected_problems frames_to_skip=1
## UNSTABLE
Checks if the provided value does not have any attached problems.
assume_no_problems result =
result.is_error.should_be_false
warnings = Warning.get_all result . map .value
warnings.should_equal []

View File

@ -8,6 +8,10 @@ from Standard.Table.Error as Table_Errors import all
from Standard.Table.Data.Column_Selector as Column_Selector_Module import all
from Standard.Base.Data.Text.Text_Ordering as Text_Ordering_Module import Text_Ordering
from Standard.Table.Data.Position as Position_Module import all
import Standard.Table.Data.Sort_Column_Selector
import Standard.Table.Data.Sort_Column
type Test_Selection supports_case_sensitive_columns=True order_by=True natural_ordering=False case_insensitive_ordering=False order_by_unicode_normalization_by_default=False
## A common test suite for shared operations on the Table API.
@ -21,10 +25,16 @@ from Standard.Table.Data.Position as Position_Module import all
builds a Table using the backend that is meant to be tested. Each column
description is a triple of column name, column type and a vector containing
column elements.
- materialize: A helper function which materializes a table from the tested
backend as an in-memory table. Used to easily inspect results of a
particular query/operation.
- test_selection: A selection of which suites should be run. Can be used to
skip checks for backends which do not support particular features.
- pending: An optional mark to disable all test groups. Can be used to
indicate that some tests are disabled due to missing test setup.
TODO [RW] the Any in return type of the builder should ideally be replaced with the Table interface, once that is supported.
spec : Text -> (Vector -> Any) -> Boolean -> Text -> Nothing
spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
spec prefix table_builder test_selection pending=Nothing =
table =
col1 = ["foo", [1,2,3]]
col2 = ["bar", [4,5,6]]
@ -40,7 +50,7 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
Test.group prefix+"Table.select_columns" pending=pending <|
Test.specify "should work as shown in the doc examples" <|
expect_column_names ["foo", "bar"] <| table.select_columns (By_Name.new ["bar", "foo"])
expect_column_names ["foo", "bar"] <| table.select_columns (By_Name ["bar", "foo"])
expect_column_names ["bar", "Baz", "foo_1", "foo_2"] <| table.select_columns (By_Name ["foo.+", "b.*"] (Regex_Matcher case_sensitive=Case_Insensitive))
expect_column_names ["abcd123", "foo", "bar"] <| table.select_columns (By_Index [-1, 0, 1]) reorder=True
@ -49,7 +59,7 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
expect_column_names ["Baz", "foo_1"] <| table.select_columns (By_Column [column1, column2])
Test.specify "should allow to reorder columns if asked to" <|
table_2 = table.select_columns (By_Name.new ["bar", "foo"]) reorder=True
table_2 = table.select_columns (By_Name ["bar", "foo"]) reorder=True
expect_column_names ["bar", "foo"] table_2
table_2 . at "bar" . to_vector . should_equal [4,5,6]
table_2 . at "foo" . to_vector . should_equal [1,2,3]
@ -58,13 +68,13 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
expect_column_names ["foo"] <| table.select_columns (By_Name ["foo"] Regex_Matcher)
expect_column_names ["ab.+123", "abcd123"] <| table.select_columns (By_Name ["a.*"] Regex_Matcher)
expect_column_names ["ab.+123", "abcd123"] <| table.select_columns (By_Name ["ab.+123"] Regex_Matcher)
expect_column_names ["ab.+123"] <| table.select_columns (By_Name.new ["ab.+123"])
expect_column_names ["ab.+123"] <| table.select_columns (By_Name ["ab.+123"])
expect_column_names ["abcd123"] <| table.select_columns (By_Name ["abcd123"] Regex_Matcher)
Test.specify "should allow negative indices" <|
expect_column_names ["foo", "bar", "foo_2"] <| table.select_columns (By_Index [-3, 0, 1])
if supports_case_sensitive_columns then
if test_selection.supports_case_sensitive_columns then
Test.specify "should correctly handle exact matches matching multiple names due to case insensitivity" <|
table =
col1 = ["foo", [1,2,3]]
@ -99,7 +109,14 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
Problems.test_problem_handling action problems tester
Test.specify "should correctly handle problems: duplicate names" <|
selector = By_Name.new ["foo", "foo"]
selector = By_Name ["foo", "foo"]
action = table.select_columns selector on_problems=_
tester = expect_column_names ["foo"]
problems = [Duplicate_Column_Selectors ["foo"]]
Problems.test_problem_handling action problems tester
Test.specify "should correctly handle problems: duplicate matches due to case insensitivity" pending="TODO needs fixing" <|
selector = By_Name ["FOO", "foo"] (Text_Matcher case_sensitive=Case_Insensitive)
action = table.select_columns selector on_problems=_
tester = expect_column_names ["foo"]
problems = [Duplicate_Column_Selectors ["foo"]]
@ -107,7 +124,7 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
Test.specify "should correctly handle problems: unmatched names" <|
weird_name = '.*?-!@#!"'
selector = By_Name.new ["foo", "hmm", weird_name]
selector = By_Name ["foo", "hmm", weird_name]
action = table.select_columns selector on_problems=_
tester = expect_column_names ["foo"]
problems = [Missing_Input_Columns ["hmm", weird_name]]
@ -134,14 +151,14 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
Problems.test_problem_handling action problems tester
Test.specify "should correctly handle problems: no columns in the output" <|
selector = By_Name.new []
selector = By_Name []
action = table.select_columns selector on_problems=_
tester = expect_column_names []
problems = [No_Output_Columns]
Problems.test_problem_handling action problems tester
Test.specify "should correctly handle multiple problems" <|
selector = By_Name.new ["hmmm"]
selector = By_Name ["hmmm"]
action = table.select_columns selector on_problems=_
tester = expect_column_names []
problems = [Missing_Input_Columns ["hmmm"], No_Output_Columns]
@ -154,7 +171,7 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
Test.group prefix+"Table.remove_columns" pending=pending <|
Test.specify "should work as shown in the doc examples" <|
expect_column_names ["Baz", "foo_1", "foo_2", "ab.+123", "abcd123"] <| table.remove_columns (By_Name.new ["bar", "foo"])
expect_column_names ["Baz", "foo_1", "foo_2", "ab.+123", "abcd123"] <| table.remove_columns (By_Name ["bar", "foo"])
expect_column_names ["foo", "ab.+123", "abcd123"] <| table.remove_columns (By_Name ["foo.+", "b.*"] (Regex_Matcher case_sensitive=Case_Insensitive))
expect_column_names ["Baz", "foo_1", "foo_2", "ab.+123"] <| table.remove_columns (By_Index [-1, 0, 1])
@ -168,13 +185,13 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
first_ones = ["foo", "bar", "Baz", "foo_1", "foo_2"]
expect_column_names first_ones <| table.remove_columns (By_Name ["a.*"] Regex_Matcher)
expect_column_names first_ones <| table.remove_columns (By_Name ["ab.+123"] Regex_Matcher)
expect_column_names first_ones+["abcd123"] <| table.remove_columns (By_Name.new ["ab.+123"])
expect_column_names first_ones+["abcd123"] <| table.remove_columns (By_Name ["ab.+123"])
expect_column_names first_ones+["ab.+123"] <| table.remove_columns (By_Name ["abcd123"] Regex_Matcher)
Test.specify "should allow negative indices" <|
expect_column_names ["Baz", "foo_1", "ab.+123"] <| table.remove_columns (By_Index [-1, -3, 0, 1])
if supports_case_sensitive_columns then
if test_selection.supports_case_sensitive_columns then
Test.specify "should correctly handle exact matches matching multiple names due to case insensitivity" <|
table =
col1 = ["foo", [1,2,3]]
@ -208,7 +225,14 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
Problems.test_problem_handling action problems tester
Test.specify "should correctly handle problems: duplicate names" <|
selector = By_Name.new ["foo", "foo"]
selector = By_Name ["foo", "foo"]
action = table.remove_columns selector on_problems=_
tester = expect_column_names ["bar", "Baz", "foo_1", "foo_2", "ab.+123", "abcd123"]
problems = [Duplicate_Column_Selectors ["foo"]]
Problems.test_problem_handling action problems tester
Test.specify "should correctly handle problems: duplicate matches due to case insensitivity" pending="TODO needs fixing" <|
selector = By_Name ["FOO", "foo"] (Text_Matcher case_sensitive=Case_Insensitive)
action = table.remove_columns selector on_problems=_
tester = expect_column_names ["bar", "Baz", "foo_1", "foo_2", "ab.+123", "abcd123"]
problems = [Duplicate_Column_Selectors ["foo"]]
@ -216,7 +240,7 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
Test.specify "should correctly handle problems: unmatched names" <|
weird_name = '.*?-!@#!"'
selector = By_Name.new ["foo", "hmm", weird_name]
selector = By_Name ["foo", "hmm", weird_name]
action = table.remove_columns selector on_problems=_
tester = expect_column_names ["bar", "Baz", "foo_1", "foo_2", "ab.+123", "abcd123"]
problems = [Missing_Input_Columns ["hmm", weird_name]]
@ -263,7 +287,7 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
Test.group prefix+"Table.reorder_columns" pending=pending <|
Test.specify "should work as shown in the doc examples" <|
expect_column_names ["bar", "Baz", "foo_1", "foo_2", "ab.+123", "abcd123", "foo"] <| table.reorder_columns (By_Name.new ["foo"]) position=After_Other_Columns
expect_column_names ["bar", "Baz", "foo_1", "foo_2", "ab.+123", "abcd123", "foo"] <| table.reorder_columns (By_Name ["foo"]) position=After_Other_Columns
expect_column_names ["foo_1", "foo_2", "bar", "Baz", "foo", "ab.+123", "abcd123"] <| table.reorder_columns (By_Name ["foo.+", "b.*"] (Regex_Matcher case_sensitive=Case_Insensitive))
expect_column_names ["bar", "foo", "Baz", "foo_1", "foo_2", "ab.+123", "abcd123"] <| table.reorder_columns (By_Index [1, 0]) position=Before_Other_Columns
expect_column_names ["bar", "Baz", "foo_1", "foo_2", "ab.+123", "abcd123", "foo"] <| table.reorder_columns (By_Index [0]) position=After_Other_Columns
@ -277,13 +301,13 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
rest = ["foo", "bar", "Baz", "foo_1", "foo_2"]
expect_column_names ["ab.+123", "abcd123"]+rest <| table.reorder_columns (By_Name ["a.*"] Regex_Matcher)
expect_column_names ["ab.+123", "abcd123"]+rest <| table.reorder_columns (By_Name ["ab.+123"] Regex_Matcher)
expect_column_names ["ab.+123"]+rest+["abcd123"] <| table.reorder_columns (By_Name.new ["ab.+123"])
expect_column_names ["ab.+123"]+rest+["abcd123"] <| table.reorder_columns (By_Name ["ab.+123"])
expect_column_names ["abcd123"]+rest+["ab.+123"] <| table.reorder_columns (By_Name ["abcd123"] Regex_Matcher)
Test.specify "should allow negative indices" <|
expect_column_names ["abcd123", "foo_2", "foo", "bar", "Baz", "foo_1", "ab.+123"] <| table.reorder_columns (By_Index [-1, -3, 0, 1])
if supports_case_sensitive_columns then
if test_selection.supports_case_sensitive_columns then
Test.specify "should correctly handle exact matches matching multiple names due to case insensitivity" <|
table =
col1 = ["foo", [1,2,3]]
@ -317,7 +341,7 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
Problems.test_problem_handling action problems tester
Test.specify "should correctly handle problems: duplicate names" <|
selector = By_Name.new ["foo", "foo"]
selector = By_Name ["foo", "foo"]
action = table.reorder_columns selector position=After_Other_Columns on_problems=_
tester = expect_column_names ["bar", "Baz", "foo_1", "foo_2", "ab.+123", "abcd123", "foo"]
problems = [Duplicate_Column_Selectors ["foo"]]
@ -325,7 +349,7 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
Test.specify "should correctly handle problems: unmatched names" <|
weird_name = '.*?-!@#!"'
selector = By_Name.new ["foo", "hmm", weird_name]
selector = By_Name ["foo", "hmm", weird_name]
action = table.reorder_columns selector position=After_Other_Columns on_problems=_
tester = expect_column_names ["bar", "Baz", "foo_1", "foo_2", "ab.+123", "abcd123", "foo"]
problems = [Missing_Input_Columns ["hmm", weird_name]]
@ -441,7 +465,7 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
Test.specify "should correctly handle problems: unmatched names" <|
weird_name = '.*?-!@#!"'
map = Column_Mapping.By_Name.new (Map.from_vector [["alpha", "FirstColumn"], ["omicron", "Another"], [weird_name, "Fixed"]])
map = Column_Mapping.By_Name (Map.from_vector [["alpha", "FirstColumn"], ["omicron", "Another"], [weird_name, "Fixed"]])
action = table.rename_columns map on_problems=_
tester = expect_column_names ["FirstColumn", "beta", "gamma", "delta"]
problems = [Missing_Input_Columns [weird_name, "omicron"]]
@ -488,3 +512,222 @@ spec prefix table_builder supports_case_sensitive_columns pending=Nothing =
tester = expect_column_names ["A", "B", "C", "D"]
problems = [Too_Many_Column_Names_Provided ["E", "F"]]
Problems.test_problem_handling action problems tester
order_by_pending = if pending.is_nothing.not then pending else
if test_selection.order_by.not then "TODO: order_by is not yet supported by this backend." else
Nothing
Test.group prefix+"Table.order_by" pending=order_by_pending <|
table =
col1 = ["alpha", [3, 2, 1, 0]]
col2 = ["beta", ["a", "b", "a", "b"]]
col3 = ["gamma", [1, 2, 3, 4]]
col4 = ["delta", ["a10", "a1", "a2", "a03"]]
col5 = ["eta", ["Beta", "alpha", "bądź", "Aleph"]]
col6 = ["xi", [1.0, 1.5, Nothing, 0.5]]
col7 = ["psi", [Nothing, "c01", "c10", "C2"]]
col8 = ["phi", ["śc", Nothing, 's\u0301b', "śa"]]
col9 = ["tau", [32.0, 0.5, -0.1, 1.6]]
table_builder [col1, col2, col3, col4, col5, col6, col7, col8, col9]
Test.specify "should work as shown in the doc examples" <|
t1 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "alpha"])
t1.at "alpha" . to_vector . should_equal [0, 1, 2, 3]
t1.at "gamma" . to_vector . should_equal [4, 3, 2, 1]
t2 = table.order_by (Sort_Column_Selector.By_Index [Sort_Column.Index 1, Sort_Column.Index -7 Sort_Direction.Descending])
t2.at "beta" . to_vector . should_equal ["a", "a", "b", "b"]
t2.at "gamma" . to_vector . should_equal [3, 1, 4, 2]
t2.at "alpha" . to_vector . should_equal [1, 3, 0, 2]
Test.specify "should correctly handle regexes matching multiple names" <|
t1 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name ".*ta" Sort_Direction.Descending] Regex_Matcher)
t1.at "beta" . to_vector . should_equal ["b", "b", "a", "a"]
t1.at "delta" . to_vector . should_equal ["a1", "a03", "a2", "a10"]
t1.at "gamma" . to_vector . should_equal [2, 4, 3, 1]
Test.specify "should correctly handle problems: out of bounds indices" <|
selector = Sort_Column_Selector.By_Index [Sort_Column.Index 0, Sort_Column.Index 100, Sort_Column.Index -200, Sort_Column.Index 300]
action = table.order_by selector on_problems=_
tester table =
table.at "alpha" . to_vector . should_equal [0, 1, 2, 3]
table.at "gamma" . to_vector . should_equal [4, 3, 2, 1]
problems = [Column_Indexes_Out_Of_Range [100, -200, 300]]
Problems.test_problem_handling action problems tester
Test.specify "should correctly handle problems: duplicate indices" <|
selector = Sort_Column_Selector.By_Index [Sort_Column.Index 0, Sort_Column.Index 0, Sort_Column.Index 0 Sort_Direction.Descending]
action = table.order_by selector on_problems=_
tester table =
table.at "alpha" . to_vector . should_equal [0, 1, 2, 3]
table.at "gamma" . to_vector . should_equal [4, 3, 2, 1]
problems = [Duplicate_Column_Selectors [Sort_Column.Index 0, Sort_Column.Index 0 Sort_Direction.Descending]]
Problems.test_problem_handling action problems tester
Test.specify "should correctly handle problems: aliased indices" <|
selector = Sort_Column_Selector.By_Index [Sort_Column.Index 1, Sort_Column.Index -8 Sort_Direction.Descending, Sort_Column.Index -7 Sort_Direction.Descending, Sort_Column.Index 2 Sort_Direction.Ascending]
action = table.order_by selector on_problems=_
tester table =
table.at "beta" . to_vector . should_equal ["a", "a", "b", "b"]
table.at "gamma" . to_vector . should_equal [3, 1, 4, 2]
table.at "alpha" . to_vector . should_equal [1, 3, 0, 2]
problems = [Input_Indices_Already_Matched [Sort_Column.Index -8 Sort_Direction.Descending, Sort_Column.Index 2]]
Problems.test_problem_handling action problems tester
Test.specify "should correctly handle problems: duplicate names" <|
selector = Sort_Column_Selector.By_Name [Sort_Column.Name "alpha", Sort_Column.Name "alpha" Sort_Direction.Descending]
action = table.order_by selector on_problems=_
tester table =
table.at "alpha" . to_vector . should_equal [0, 1, 2, 3]
table.at "gamma" . to_vector . should_equal [4, 3, 2, 1]
problems = [Column_Matched_By_Multiple_Selectors "alpha" [Sort_Column.Name "alpha", Sort_Column.Name "alpha" Sort_Direction.Descending]]
Problems.test_problem_handling action problems tester
Test.specify "should correctly handle problems: duplicate matches due to case insensitivity" <|
selector = Sort_Column_Selector.By_Name [Sort_Column.Name "ALPHA", Sort_Column.Name "alpha" Sort_Direction.Descending] (Text_Matcher case_sensitive=Case_Insensitive)
action = table.order_by selector on_problems=_
tester table =
table.at "alpha" . to_vector . should_equal [0, 1, 2, 3]
table.at "gamma" . to_vector . should_equal [4, 3, 2, 1]
problems = [Column_Matched_By_Multiple_Selectors "alpha" [Sort_Column.Name "ALPHA", Sort_Column.Name "alpha" Sort_Direction.Descending]]
Problems.test_problem_handling action problems tester
Test.specify "should correctly handle problems: unmatched names" <|
weird_name = '.*?-!@#!"'
selector = Sort_Column_Selector.By_Name [Sort_Column.Name "alpha", Sort_Column.Name "hmm", Sort_Column.Name weird_name]
action = table.order_by selector on_problems=_
tester table =
table.at "alpha" . to_vector . should_equal [0, 1, 2, 3]
table.at "gamma" . to_vector . should_equal [4, 3, 2, 1]
problems = [Missing_Input_Columns [Sort_Column.Name "hmm", Sort_Column.Name weird_name]]
Problems.test_problem_handling action problems tester
Test.specify "should correctly handle problems: unmatched columns" <|
table_2 = table_builder [["alpha", [0,0,0]], ["weird_column", [0,0,0]]]
foo = table_2.at "alpha"
weird_column = table_2.at "weird_column"
bar = table.at "beta"
selector = Sort_Column_Selector.By_Column [Sort_Column.Column bar, Sort_Column.Column weird_column, Sort_Column.Column foo]
problem = table.order_by selector on_problems=Problem_Behavior.Report_Error . catch
problem.should_be_a Missing_Input_Columns
problem.criteria.map (selector-> selector.column.name) . should_equal ["weird_column"]
t2 = table.order_by selector on_problems=Problem_Behavior.Ignore
t2.at "beta" . to_vector . should_equal ["a", "a", "b", "b"]
t2.at "alpha" . to_vector . should_equal [1, 3, 0, 2]
t2.at "gamma" . to_vector . should_equal [3, 1, 4, 2]
Test.specify "should report a problem if no columns are selected for ordering" <|
action = table.order_by (Sort_Column_Selector.By_Name []) on_problems=_
tester t2 =
t2.at "alpha" . to_vector . should_equal (table.at "alpha" . to_vector)
problems = [No_Input_Columns_Selected]
Problems.test_problem_handling action problems tester
Test.specify "should stack consecutive ordering operations" <|
t1 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "alpha"])
t1.at "alpha" . to_vector . should_equal [0, 1, 2, 3]
t1.at "beta" . to_vector . should_equal ["b", "a", "b", "a"]
# Now we reverse the order
t2 = t1.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "alpha" Sort_Direction.Descending])
t2.at "alpha" . to_vector . should_equal [3, 2, 1, 0]
t2.at "beta" . to_vector . should_equal ["a", "b", "a", "b"]
# Now we add another primary ordering, but the order from t1/t2 is kept for tie breaking.
t3 = t1.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "beta"])
t3.at "beta" . to_vector . should_equal ["a", "a", "b", "b"]
t3.at "alpha" . to_vector . should_equal [1, 3, 0, 2]
t4 = t2.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "beta"])
t4.at "beta" . to_vector . should_equal ["a", "a", "b", "b"]
t4.at "alpha" . to_vector . should_equal [3, 1, 2, 0]
Test.specify "should give priority to the first selected column and use the next ones for breaking ties" <|
t1 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "beta", Sort_Column.Name "alpha" Sort_Direction.Ascending])
t1.at "beta" . to_vector . should_equal ["a", "a", "b", "b"]
t1.at "alpha" . to_vector . should_equal [1, 3, 0, 2]
t1.at "gamma" . to_vector . should_equal [3, 1, 4, 2]
t2 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "beta", Sort_Column.Name "alpha" Sort_Direction.Descending])
t2.at "beta" . to_vector . should_equal ["a", "a", "b", "b"]
t2.at "alpha" . to_vector . should_equal [3, 1, 2, 0]
t2.at "gamma" . to_vector . should_equal [1, 3, 2, 4]
t3 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "alpha", Sort_Column.Name "beta"])
t3.at "alpha" . to_vector . should_equal [0, 1, 2, 3]
t3.at "beta" . to_vector . should_equal ["b", "a", "b", "a"]
t3.at "gamma" . to_vector . should_equal [4, 3, 2, 1]
t4 = table.order_by (Sort_Column_Selector.By_Index [Sort_Column.Index 1, Sort_Column.Index 0 Sort_Direction.Ascending])
t4.at "beta" . to_vector . should_equal ["a", "a", "b", "b"]
t4.at "alpha" . to_vector . should_equal [1, 3, 0, 2]
t4.at "gamma" . to_vector . should_equal [3, 1, 4, 2]
t5 = table.order_by (Sort_Column_Selector.By_Column [Sort_Column.Column (table.at "beta"), Sort_Column.Column (table.at "alpha") Sort_Direction.Ascending])
t5.at "beta" . to_vector . should_equal ["a", "a", "b", "b"]
t5.at "alpha" . to_vector . should_equal [1, 3, 0, 2]
t5.at "gamma" . to_vector . should_equal [3, 1, 4, 2]
Test.specify "should deal with real numbers" <|
t1 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "tau"])
t1.at "tau" . to_vector . should_equal [-0.1, 0.5, 1.6, 32.0]
t1.at "alpha" . to_vector . should_equal [1, 2, 0, 3]
Test.specify "should deal with nulls" <|
t1 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "xi"])
t1.at "xi" . to_vector . should_equal [Nothing, 0.5, 1.0, 1.5]
t1.at "alpha" . to_vector . should_equal [1, 0, 3, 2]
t2 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "psi"])
t2.at "psi" . to_vector . should_equal [Nothing, "C2", "c01", "c10"]
t2.at "alpha" . to_vector . should_equal [3, 0, 2, 1]
Test.specify "should behave as expected with Unicode normalization, depending on the defaults settings" <|
t1 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "phi"])
case test_selection.order_by_unicode_normalization_by_default of
True ->
t1.at "phi" . to_vector . should_equal [Nothing, "śa", 's\u0301b', "śc"]
t1.at "alpha" . to_vector . should_equal [2, 0, 1, 3]
False ->
t1.at "phi" . to_vector . should_equal [Nothing, 's\u0301b', "śa", "śc"]
t1.at "alpha" . to_vector . should_equal [2, 1, 0, 3]
Test.specify "should support natural ordering" pending=(if test_selection.natural_ordering.not then "Natural ordering is not supported.") <|
t1 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "delta"]) text_ordering=(Text_Ordering sort_digits_as_numbers=True)
t1.at "delta" . to_vector . should_equal ["a1", "a2", "a03", "a10"]
t1.at "alpha" . to_vector . should_equal [2, 1, 0, 3]
t2 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "delta"]) text_ordering=(Text_Ordering sort_digits_as_numbers=False)
t2.at "delta" . to_vector . should_equal ["a03", "a1", "a10", "a2"]
t2.at "alpha" . to_vector . should_equal [0, 2, 3, 1]
# TODO [RW] This test must actually be verified in practice.
Test.specify "should support case insensitive ordering" pending=(if test_selection.case_insensitive_ordering.not then "Case insensitive ordering is not supported.") <|
t1 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "eta"]) text_ordering=(Text_Ordering case_sensitive=Case_Insensitive)
t1.at "eta" . to_vector . should_equal ["Aleph", "alpha", "Beta", "bądź"]
t2 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "eta"]) text_ordering=(Text_Ordering case_sensitive=Case_Insensitive (Locale.new "pl" "PL"))
t2.at "eta" . to_vector . should_equal ["Aleph", "alpha", "bądź", "Beta"]
t3 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "eta"])
t3.at "eta" . to_vector . should_equal ["Aleph", "Beta", "alpha", "bądź"]
Test.specify "should support natural and case insensitive ordering at the same time" pending=(if (test_selection.natural_ordering.not || test_selection.case_insensitive_ordering.not) then "Natural ordering or case sensitive ordering is not supported.") <|
t1 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "psi"]) text_ordering=(Text_Ordering sort_digits_as_numbers=True case_sensitive=Case_Insensitive)
t1.at "psi" . to_vector . should_equal ["c01", "C2", "c10", Nothing]
t2 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "psi"]) text_ordering=(Text_Ordering sort_digits_as_numbers=True)
t2.at "psi" . to_vector . should_equal ["C2", "c01", "c10", Nothing]
t3 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "psi"]) text_ordering=(Text_Ordering case_sensitive=Case_Insensitive)
t3.at "psi" . to_vector . should_equal ["c01", "c10", "C2", Nothing]
t4 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "psi"])
t4.at "psi" . to_vector . should_equal ["C2", "c01", "c10", Nothing]
Test.specify "text ordering settings should not affect numeric columns" <|
ordering = Text_Ordering sort_digits_as_numbers=True case_sensitive=Case_Insensitive
t1 = table.order_by (Sort_Column_Selector.By_Name [Sort_Column.Name "alpha"]) text_ordering=ordering
t1.at "alpha" . to_vector . should_equal [0, 1, 2, 3]
t1.at "gamma" . to_vector . should_equal [4, 3, 2, 1]

View File

@ -95,10 +95,12 @@ run_tests connection pending=Nothing =
table_names.each name->
sql = 'DROP TABLE "' + name + '"'
Panic.rethrow <| connection.execute_update sql
materialize = .to_dataframe
Common_Spec.spec prefix connection pending=pending
here.postgres_specific_spec connection pending=pending
Common_Table_Spec.spec prefix table_builder supports_case_sensitive_columns=True pending=pending
common_selection = Common_Table_Spec.Test_Selection supports_case_sensitive_columns=True order_by=False
Common_Table_Spec.spec prefix table_builder test_selection=common_selection pending=pending
selection = Aggregate_Spec.Test_Selection first_last_row_order=False aggregation_problems=False
agg_in_memory_table = (Enso_Project.data / "data.csv") . read
@ -106,7 +108,6 @@ run_tests connection pending=Nothing =
tables.append agg_table.name
empty_agg_table = connection.upload_table (Name_Generator.random_name "Agg_Empty") (agg_in_memory_table.take_start 0)
tables.append empty_agg_table.name
materialize = .to_dataframe
Aggregate_Spec.aggregate_spec prefix agg_table empty_agg_table table_builder materialize is_database=True selection pending=pending
clean_tables tables.to_vector

View File

@ -49,10 +49,12 @@ run_tests connection pending=Nothing =
table_names.each name->
sql = 'DROP TABLE "' + name + '"'
Panic.rethrow <| connection.execute_update sql
materialize = .to_dataframe
Common_Spec.spec prefix connection pending=pending
here.redshift_specific_spec connection pending=pending
Common_Table_Spec.spec prefix table_builder supports_case_sensitive_columns=True pending=pending
common_selection = Common_Table_Spec.Test_Selection supports_case_sensitive_columns=True order_by=False
Common_Table_Spec.spec prefix table_builder test_selection=common_selection pending=pending
selection = Aggregate_Spec.Test_Selection text_concat=False text_shortest_longest=False first_last=False first_last_row_order=False multi_distinct=False aggregation_problems=False
agg_in_memory_table = (Enso_Project.data / "data.csv") . read
@ -60,7 +62,6 @@ run_tests connection pending=Nothing =
tables.append agg_table.name
empty_agg_table = connection.upload_table (Name_Generator.random_name "Agg_Empty") (agg_in_memory_table.take_start 0)
tables.append empty_agg_table.name
materialize = .to_dataframe
Aggregate_Spec.aggregate_spec prefix agg_table empty_agg_table table_builder materialize is_database=True selection pending=pending
clean_tables tables.to_vector

View File

@ -58,10 +58,12 @@ spec =
in_mem_table = Materialized_Table.new columns
connection.upload_table name in_mem_table
materialize = .to_dataframe
Common_Spec.spec prefix connection
here.sqlite_specific_spec connection
Common_Table_Spec.spec prefix table_builder supports_case_sensitive_columns=False
common_selection = Common_Table_Spec.Test_Selection supports_case_sensitive_columns=False order_by=True natural_ordering=False case_insensitive_ordering=False
Common_Table_Spec.spec prefix table_builder test_selection=common_selection
## For now `advanced_stats`, `first_last`, `text_shortest_longest` and
`multi_distinct` remain disabled, because SQLite does not provide the
@ -75,7 +77,6 @@ spec =
agg_in_memory_table = (Enso_Project.data / "data.csv") . read
agg_table = connection.upload_table (Name_Generator.random_name "Agg1") agg_in_memory_table
empty_agg_table = connection.upload_table (Name_Generator.random_name "Agg_Empty") (agg_in_memory_table.take_start 0)
materialize = .to_dataframe
Aggregate_Spec.aggregate_spec prefix agg_table empty_agg_table table_builder materialize is_database=True selection
connection.close

View File

@ -635,7 +635,8 @@ spec =
t_3 = Table.new [c_3_1, c_3_2, c_3_3]
t_3.default_visualization.should_equal Visualization.Id.table
Common_Table_Spec.spec "[In-Memory] " Table.new supports_case_sensitive_columns=True
selection = Common_Table_Spec.Test_Selection supports_case_sensitive_columns=True order_by=False
Common_Table_Spec.spec "[In-Memory] " table_builder=Table.new test_selection=selection
Test.group "Use First Row As Names" <|
expect_column_names names table =

View File

@ -62,6 +62,9 @@ spec = Test.group "Range" <|
empty.map *2 . should_equal []
elements = 0.up_to 10
elements.map *2 . should_equal [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Test.specify "should allow to filter its elements, returning a vector" <|
elements = 0.up_to 10
elements.filter (x -> x % 2 == 0) . should_equal [0, 2, 4, 6, 8]
Test.specify "should allow iteration" <|
vec_mut = Vector.new_builder
1.up_to 6 . each (i -> vec_mut.append i)
@ -126,6 +129,7 @@ spec = Test.group "Range" <|
r.to_vector . should_equal []
build_with_each r . should_equal []
r.map x->x+1 . should_equal []
r.filter _->True . should_equal []
r.fold 0 (+) . should_equal 0
r.any _->True . should_equal False
r.all _->False . should_equal True
@ -254,6 +258,7 @@ spec = Test.group "Range" <|
r6.all (x-> x % 3 == 0) . should_equal True
r6.find (x-> x*x == 9) . should_equal 3
r6.find (x-> x*x == 25) . should_equal Nothing
r6.filter (_ < 4) . should_equal [0, 3]
verify_contains r6 [0, 3, 6, 9] [-3, -2, -1, 1, 2, 4, 5, 7, 8, 10, 11]
Test.specify "should behave correctly with negative step" <|

View File

@ -243,11 +243,11 @@ spec =
txt.drop (Last 2) . should_equal (kshi + facepalm)
Test.specify "take should work as in the examples" <|
"Hello World!".take First.new . should_equal "H"
"Hello World!".take First . should_equal "H"
"Hello World!".take (First 5) . should_equal "Hello"
"Hello World!".take (First 100) . should_equal "Hello World!"
"Hello World!".take (First 0) . should_equal ""
"Hello World!".take Last.new . should_equal "!"
"Hello World!".take Last . should_equal "!"
"Hello World!".take (Last 6) . should_equal "World!"
"Hello World!".take (Last 0) . should_equal ""
"Hello World!".take (Last 100) . should_equal "Hello World!"
@ -305,9 +305,9 @@ spec =
'He\u{302}llo\u{308} Wo\u{308}rld!'.take (Range 12 12) . should_equal ''
Test.specify "take should work on emojis" <|
'✨🚀🚧😍😃😎😙😉☺'.take First.new . should_equal '✨'
'✨🚀🚧😍😃😎😙😉☺'.take First . should_equal '✨'
'✨🚀🚧😍😃😎😙😉☺'.take (First 2) . should_equal '✨🚀'
'✨🚀🚧😍😃😎😙😉☺'.take Last.new . should_equal '☺'
'✨🚀🚧😍😃😎😙😉☺'.take Last . should_equal '☺'
'✨🚀🚧😍😃😎😙😉☺'.take (Last 0) . should_equal ''
'✨🚀🚧😍😃😎😙😉☺'.take (Last 3) . should_equal '😙😉☺'
'✨🚀🚧😍😃😍😎😙😉☺'.take (Before '😍') . should_equal '✨🚀🚧'
@ -321,8 +321,8 @@ spec =
'✨🚀🚧😍😃😍😎😙😉☺'.take (Range -3 -1) . should_equal '😙😉'
Test.specify "take should correctly handle edge cases" <|
"".take First.new . should_equal ""
"".take Last.new . should_equal ""
"".take First . should_equal ""
"".take Last . should_equal ""
"".take (After "a") . should_equal ""
"".take (After_Last "a") . should_equal ""
@ -345,11 +345,11 @@ spec =
'ABC\u{301}'.take (Before_Last "") . should_equal 'ABC\u{301}'
Test.specify "drop should work as in the examples" <|
"Hello World!".drop First.new . should_equal "ello World!"
"Hello World!".drop First . should_equal "ello World!"
"Hello World!".drop (First 5) . should_equal " World!"
"Hello World!".drop (First 100) . should_equal ""
"Hello World!".drop (First 0) . should_equal "Hello World!"
"Hello World!".drop Last.new . should_equal "Hello World"
"Hello World!".drop Last . should_equal "Hello World"
"Hello World!".drop (Last 6) . should_equal "Hello "
"Hello World!".drop (Last 100) . should_equal ""
"Hello World!".drop (Before " ") . should_equal " World!"
@ -406,9 +406,9 @@ spec =
'He\u{302}llo\u{308} Wo\u{308}rld!'.drop (Range 12 12) . should_equal 'He\u{302}llo\u{308} Wo\u{308}rld!'
Test.specify "drop should work on emojis" <|
'✨🚀🚧😍😃😎😙😉☺'.drop First.new . should_equal '🚀🚧😍😃😎😙😉☺'
'✨🚀🚧😍😃😎😙😉☺'.drop First . should_equal '🚀🚧😍😃😎😙😉☺'
'✨🚀🚧😍😃😎😙😉☺'.drop (First 2) . should_equal '🚧😍😃😎😙😉☺'
'✨🚀🚧😍😃😎😙😉☺'.drop Last.new . should_equal '✨🚀🚧😍😃😎😙😉'
'✨🚀🚧😍😃😎😙😉☺'.drop Last . should_equal '✨🚀🚧😍😃😎😙😉'
'✨🚀🚧😍😃😎😙😉☺'.drop (Last 3) . should_equal '✨🚀🚧😍😃😎'
'✨🚀🚧😍😃😍😎😙😉☺'.drop (Before '😍') . should_equal '😍😃😍😎😙😉☺'
'✨🚀🚧😍😃😍😎😙😉☺'.drop (Before_Last '😍') . should_equal '😍😎😙😉☺'
@ -421,8 +421,8 @@ spec =
'✨🚀🚧😍😃😍😎😙😉☺'.drop (Range -3 -1) . should_equal '✨🚀🚧😍😃😍😎☺'
Test.specify "drop should correctly handle edge cases" <|
"".drop First.new . should_equal ""
"".drop Last.new . should_equal ""
"".drop First . should_equal ""
"".drop Last . should_equal ""
"".drop (After "a") . should_equal ""
"".drop (After_Last "a") . should_equal ""