mirror of
https://github.com/enso-org/enso.git
synced 2024-11-23 08:08:34 +03:00
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:
parent
0e867c663e
commit
7d94efa6f2
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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!"
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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"
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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)
|
@ -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`.
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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 []
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 =
|
||||
|
@ -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" <|
|
||||
|
@ -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 ""
|
||||
|
Loading…
Reference in New Issue
Block a user