diff --git a/CHANGELOG.md b/CHANGELOG.md index f22ba1edfd..01271438bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -137,6 +137,7 @@ - [Implemented `Table.order_by` for the in-memory table.][3515] - [Renamed `File_Format.Text` to `Plain_Text`, updated `File_Format.Delimited` API and added builders for customizing less common settings.][3516] +- [Allow control of sort direction in `First` and `Last` aggregations.][3517] [debug-shortcuts]: https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug @@ -215,6 +216,7 @@ [3514]: https://github.com/enso-org/enso/pull/3514 [3515]: https://github.com/enso-org/enso/pull/3515 [3516]: https://github.com/enso-org/enso/pull/3516 +[3517]: https://github.com/enso-org/enso/pull/3517 #### Enso Compiler diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Ordering/Sort_Direction.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Ordering/Sort_Direction.enso index 9618ffa478..eb5172680b 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Ordering/Sort_Direction.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Ordering/Sort_Direction.enso @@ -19,3 +19,8 @@ type Sort_Direction Sort_Direction.Descending type Descending + ## Convert into the sign of the direction + to_sign : Integer + to_sign = case this of + Ascending -> 1 + Descending -> -1 diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Dialect/Postgres.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Dialect/Postgres.enso index 0401d6258a..ac60a463c3 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Dialect/Postgres.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Dialect/Postgres.enso @@ -151,14 +151,12 @@ first_last_aggregators = make_first_aggregator reverse ignore_null args = if args.length < 2 then Error.throw (Illegal_State_Error "Insufficient number of arguments for the operation.") else result_expr = args.head - order_exprs = args.tail + order_bys = args.tail filter_clause = if ignore_null.not then Sql.code "" else Sql.code " FILTER (WHERE " ++ result_expr.paren ++ Sql.code " IS NOT NULL)" - modified_order_exprs = - order_exprs.map expr-> expr ++ Sql.code " ASC NULLS FIRST" order_clause = - Sql.code " ORDER BY " ++ Sql.join "," modified_order_exprs + Sql.code " ORDER BY " ++ Sql.join "," order_bys index_expr = case reverse of True -> if ignore_null.not then Sql.code "COUNT(*)" else Sql.code "COUNT(" ++ result_expr ++ Sql.code ")" diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Internal/Aggregate_Helper.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Internal/Aggregate_Helper.enso index df63005f2e..3e1f2b809e 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Internal/Aggregate_Helper.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Internal/Aggregate_Helper.enso @@ -1,4 +1,5 @@ from Standard.Base import all +from Standard.Base.Data.Text.Text_Ordering as Text_Ordering_Module import Text_Ordering from Standard.Table.Data.Aggregate_Column import all import Standard.Database.Data.Internal.IR @@ -14,15 +15,15 @@ from Standard.Database.Error as Database_Errors import Unsupported_Database_Oper make_aggregate_column : Table -> Aggregate_Column -> Text -> IR.Internal_Column make_aggregate_column table aggregate new_name = sql_type = table.connection.dialect.resolve_target_sql_type aggregate - expression = here.make_expression aggregate + expression = here.make_expression aggregate table.connection.dialect IR.Internal_Column new_name sql_type expression ## PRIVATE Creates an Internal Representation of the expression that computes a requested statistic. -make_expression : Aggregate_Column -> IR.Expression -make_expression aggregate = - is_non_empty_vector v = if v.is_nothing then False else v.not_empty +make_expression : Aggregate_Column -> Dialect -> IR.Expression +make_expression aggregate dialect = + is_non_empty_selector v = if v.is_nothing then False else v.columns.not_empty case aggregate of Group_By c _ -> c.expression Count _ -> IR.Operation "COUNT_ROWS" [] @@ -36,20 +37,20 @@ make_expression aggregate = Count_Empty c _ -> IR.Operation "COUNT_EMPTY" [c.expression] Percentile p c _ -> IR.Operation "PERCENTILE" [IR.Constant Sql_Type.double p, c.expression] Mode c _ -> IR.Operation "MODE" [c.expression] - First c _ ignore_nothing order_by -> case is_non_empty_vector order_by of + First c _ ignore_nothing order_by -> case is_non_empty_selector order_by of False -> Error.throw (Unsupported_Database_Operation_Error "`First` aggregation requires at least one `order_by` column.") True -> - order_exprs = order_by.map .expression + order_bys = order_by.columns.map c-> dialect.prepare_order_descriptor c.column.as_internal c.direction Text_Ordering case ignore_nothing of - False -> IR.Operation "FIRST" [c.expression]+order_exprs - True -> IR.Operation "FIRST_NOT_NULL" [c.expression]+order_exprs - Last c _ ignore_nothing order_by -> case is_non_empty_vector order_by of + False -> IR.Operation "FIRST" [c.expression]+order_bys + True -> IR.Operation "FIRST_NOT_NULL" [c.expression]+order_bys + Last c _ ignore_nothing order_by -> case is_non_empty_selector order_by of False -> Error.throw (Unsupported_Database_Operation_Error "`Last` aggregation requires at least one `order_by` column.") True -> - order_exprs = order_by.map .expression + order_bys = order_by.columns.map c-> dialect.prepare_order_descriptor c.column.as_internal c.direction Text_Ordering case ignore_nothing of - False -> IR.Operation "LAST" [c.expression]+order_exprs - True -> IR.Operation "LAST_NOT_NULL" [c.expression]+order_exprs + False -> IR.Operation "LAST" [c.expression]+order_bys + True -> IR.Operation "LAST_NOT_NULL" [c.expression]+order_bys Maximum c _ -> IR.Operation "MAX" [c.expression] Minimum c _ -> IR.Operation "MIN" [c.expression] Shortest c _ -> IR.Operation "SHORTEST" [c.expression] diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Internal/Base_Generator.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Internal/Base_Generator.enso index 2c8fa0678f..b6fef9c792 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Internal/Base_Generator.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Internal/Base_Generator.enso @@ -190,6 +190,7 @@ generate_expression dialect expr = case expr of op = dialect.operation_map.get_or_else kind (Error.throw <| Unsupported_Database_Operation_Error kind) parsed_args = arguments.map (here.generate_expression dialect) op parsed_args + IR.Order_Descriptor _ _ _ _ -> here.generate_order dialect expr ## PRIVATE diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Internal/IR.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Internal/IR.enso index e6e4137c59..abea5017d1 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Internal/IR.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Internal/IR.enso @@ -40,7 +40,7 @@ type Expression - kind: the name of the operation, these can be both functions or infix operators, the actual implementation is determined by a specific dialect. - - expression: a list of expressions which are arguments to the operation; + - expressions: a list of expressions which are arguments to the operation different operations support different amounts of arguments. type Operation (kind : Text) (expressions : Vector Expression) @@ -277,7 +277,6 @@ type Join_Kind in the query can be used to filter the results. type Join_Cross - ## PRIVATE type Order_Descriptor (expression : Expression) (direction : Sort_Direction) (nulls_order : Nothing | Nulls_Order = Nothing) (collation : Nothing | Text = Nothing) diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso index 8329515bc0..fdb28b90f2 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso @@ -8,6 +8,7 @@ import Standard.Table.Data.Column as Materialized_Column import Standard.Table.Data.Table as Materialized_Table import Standard.Table.Internal.Java_Exports import Standard.Table.Internal.Table_Helpers +import Standard.Table.Internal.Problem_Builder import Standard.Table.Data.Aggregate_Column import Standard.Table.Internal.Aggregate_Column_Helper @@ -480,17 +481,19 @@ type Table 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 internal_column associated_selector.direction text_ordering - new_ctx = this.context.add_orders new_order_descriptors - this.updated_context new_ctx + problem_builder = Problem_Builder.new + columns_for_ordering = Table_Helpers.prepare_order_by this.columns columns problem_builder + problem_builder.attach_problems_before 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 internal_column associated_selector.direction text_ordering + new_ctx = this.context.add_orders new_order_descriptors + this.updated_context new_ctx ## UNSTABLE diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Aggregate_Column.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Aggregate_Column.enso index 0e3e3a8e27..09ffe4d069 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Aggregate_Column.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Aggregate_Column.enso @@ -1,6 +1,8 @@ from Standard.Base import all from Standard.Table.Data.Column as Column_Module import Column +import Standard.Table.Data.Column_Selector +import Standard.Table.Data.Sort_Column_Selector ## Defines an Aggregate Column type Aggregate_Column @@ -140,7 +142,7 @@ type Aggregate_Column not missing value returned. - order_by: required for database tables. Specifies how to order the results within the group. - type First (column:Column|Text|Integer) (new_name:Text|Nothing=Nothing) (ignore_nothing:Boolean=True) (order_by:Column_Selector|Nothing=Nothing) + type First (column:Column|Text|Integer) (new_name:Text|Nothing=Nothing) (ignore_nothing:Boolean=True) (order_by:Sort_Column_Selector|Nothing=Nothing) ## Creates a new column with the last value in each group. If no rows, evaluates to `Nothing`. @@ -153,7 +155,7 @@ type Aggregate_Column not missing value returned. - order_by: required for database tables. Specifies how to order the results within the group. - type Last (column:Column|Text|Integer) (new_name:Text|Nothing=Nothing) (ignore_nothing:Boolean=True) (order_by:Column_Selector|Nothing=Nothing) + type Last (column:Column|Text|Integer) (new_name:Text|Nothing=Nothing) (ignore_nothing:Boolean=True) (order_by:Sort_Column_Selector|Nothing=Nothing) ## Creates a new column with the maximum value in each group. If no rows, evaluates to `Nothing`. diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso index 1a138a67b6..ddbe1527aa 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso @@ -11,6 +11,7 @@ import Standard.Table.Internal.Table_Helpers import Standard.Table.Internal.Aggregate_Column_Helper import Standard.Table.Internal.Parse_Values_Helper import Standard.Table.Internal.Delimited_Reader +import Standard.Table.Internal.Problem_Builder from Standard.Table.Data.Order_Rule as Order_Rule_Module import Order_Rule from Standard.Table.Data.Column_Selector as Column_Selector_Module import Column_Selector, By_Index @@ -572,16 +573,14 @@ type Table 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 = - columns_for_ordering = Table_Helpers.prepare_order_by this.columns columns on_problems - selected_columns = columns_for_ordering.map c->c.column.java_column - ordering = columns_for_ordering.map c-> - case c.associated_selector.direction of - Sort_Direction.Ascending -> 1 - Sort_Direction.Descending -> -1 - comparator = Comparator.for_text_ordering text_ordering - Table <| - this.java_table.orderBy selected_columns.to_array ordering.to_array comparator - + problem_builder = Problem_Builder.new + columns_for_ordering = Table_Helpers.prepare_order_by this.columns columns problem_builder + problem_builder.attach_problems_before on_problems <| + selected_columns = columns_for_ordering.map c->c.column.java_column + ordering = columns_for_ordering.map c->c.associated_selector.direction.to_sign + comparator = Comparator.for_text_ordering text_ordering + java_table = this.java_table.orderBy selected_columns.to_array ordering.to_array comparator + Table java_table ## Parses columns within a Table to a specific value type. By default, it looks at all `Text` columns and attempts to deduce the diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Aggregate_Column_Helper.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Aggregate_Column_Helper.enso index 3b6d84cec9..ea4e59eee6 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Aggregate_Column_Helper.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Aggregate_Column_Helper.enso @@ -8,6 +8,9 @@ import Standard.Table.Internal.Problem_Builder import Standard.Table.Internal.Unique_Name_Strategy import Standard.Table.Internal.Table_Helpers +import Standard.Table.Data.Sort_Column_Selector +import Standard.Table.Data.Sort_Column + import Standard.Base.Data.Ordering.Comparator from Standard.Table.Error as Error_Module import Missing_Input_Columns, Column_Indexes_Out_Of_Range, No_Output_Columns, Duplicate_Output_Column_Names, Invalid_Output_Column_Names, Invalid_Aggregation, Floating_Point_Grouping, Unquoted_Delimiter, Additional_Warnings @@ -103,6 +106,9 @@ default_aggregate_column_name aggregate_column = indices or column references potentially from a different table) are replaced with column references from the provided table. + Sort_Column_Selectors are resolved to Sort_Column_Select.By_Column with the + matched columns coming from the provided table. + This preprocess step is required by some helper function, to avoid having to pass the table reference and resolve the column descriptors all the time. @@ -123,9 +129,13 @@ resolve_aggregate table problem_builder aggregate_column = resolved = Table_Helpers.select_columns_helper table_columns selector reorder=True problem_builder if resolved.is_empty then Error.throw Internal_Missing_Column_Error else resolved - resolve_selector_or_nothing selector = case selector of + resolve_order_by selector = case selector of Nothing -> Nothing - _ -> resolve_selector_to_vector selector + _ -> + columns_for_ordering = Table_Helpers.prepare_order_by table_columns selector problem_builder + sort_columns = columns_for_ordering.map c-> + Sort_Column.Column c.column c.associated_selector.direction + Sort_Column_Selector.By_Column sort_columns result = case aggregate_column of Group_By c new_name -> Group_By (resolve c) new_name @@ -149,8 +159,8 @@ resolve_aggregate table problem_builder aggregate_column = Mode c new_name -> Mode (resolve c) new_name Standard_Deviation c new_name population -> Standard_Deviation (resolve c) new_name population Concatenate c new_name separator prefix suffix quote_char -> Concatenate (resolve c) new_name separator prefix suffix quote_char - First c new_name ignore_nothing order_by -> First (resolve c) new_name ignore_nothing (resolve_selector_or_nothing order_by) - Last c new_name ignore_nothing order_by -> Last (resolve c) new_name ignore_nothing (resolve_selector_or_nothing order_by) + First c new_name ignore_nothing order_by -> First (resolve c) new_name ignore_nothing (resolve_order_by order_by) + Last c new_name ignore_nothing order_by -> Last (resolve c) new_name ignore_nothing (resolve_order_by order_by) Maximum c new_name -> Maximum (resolve c) new_name Minimum c new_name -> Minimum (resolve c) new_name Shortest c new_name -> Shortest (resolve c) new_name @@ -188,12 +198,14 @@ java_aggregator name column = Mode c _ -> ModeAggregator.new name c.java_column First c _ ignore_nothing ordering -> if ordering.is_nothing then FirstAggregator.new name c.java_column ignore_nothing else - ordering_array = ordering.map .java_column - FirstAggregator.new name c.java_column ignore_nothing ordering_array.to_array Comparator.new + order_columns = ordering.columns.map c->c.column.java_column + order_directions = ordering.columns.map c->c.direction.to_sign + FirstAggregator.new name c.java_column ignore_nothing order_columns.to_array order_directions.to_array Comparator.new Last c _ ignore_nothing ordering -> if ordering.is_nothing then LastAggregator.new name c.java_column ignore_nothing else - ordering_array = ordering.map .java_column - LastAggregator.new name c.java_column ignore_nothing ordering_array.to_array Comparator.new + order_columns = ordering.columns.map c->c.column.java_column + order_direction = ordering.columns.map c->c.direction.to_sign + LastAggregator.new name c.java_column ignore_nothing order_columns.to_array order_direction.to_array Comparator.new Maximum c _ -> MinOrMaxAggregator.new name c.java_column 1 Comparator.new Minimum c _ -> MinOrMaxAggregator.new name c.java_column -1 Comparator.new Shortest c _ -> ShortestOrLongestAggregator.new name c.java_column -1 diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Table_Helpers.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Table_Helpers.enso index 44134e741d..ed20dfb25f 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Table_Helpers.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Table_Helpers.enso @@ -361,9 +361,8 @@ validate_unique vector problem_callback on=(x->x) = 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 +prepare_order_by : Vector -> Problem_Builder -> Vector Column_Transform_Element +prepare_order_by internal_columns column_selectors problem_builder = 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) @@ -373,7 +372,7 @@ prepare_order_by internal_columns column_selectors on_problems = 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 + selected_elements ## PRIVATE A helper function which can be used by methods that transform a subset of diff --git a/std-bits/table/src/main/java/org/enso/table/aggregations/First.java b/std-bits/table/src/main/java/org/enso/table/aggregations/First.java index 1a9f96d047..8f027b22a5 100644 --- a/std-bits/table/src/main/java/org/enso/table/aggregations/First.java +++ b/std-bits/table/src/main/java/org/enso/table/aggregations/First.java @@ -11,33 +11,39 @@ import java.util.List; /** Aggregate Column finding the first value in a group. */ public class First extends Aggregator { private final Storage storage; - private final Storage[] ordering; + private final Storage[] orderByColumns; + private final int[] orderByDirections; private final Comparator objectComparator; private final boolean ignoreNothing; public First(String name, Column column, boolean ignoreNothing) { - this(name, column, ignoreNothing, null, null); + this(name, column, ignoreNothing, null, null, null); } public First( String name, Column column, boolean ignoreNothing, - Column[] ordering, + Column[] orderByColumns, + Long[] orderByDirections, Comparator objectComparator) { super(name, Storage.Type.OBJECT); this.storage = column.getStorage(); - this.ordering = - ordering == null + this.orderByColumns = + orderByColumns == null ? new Storage[0] - : Arrays.stream(ordering).map(Column::getStorage).toArray(Storage[]::new); + : Arrays.stream(orderByColumns).map(Column::getStorage).toArray(Storage[]::new); + this.orderByDirections = + orderByDirections == null + ? new int[0] + : Arrays.stream(orderByDirections).mapToInt(Long::intValue).toArray(); this.objectComparator = objectComparator; this.ignoreNothing = ignoreNothing; } @Override public Object aggregate(List indexes) { - if (ordering.length == 0) { + if (orderByColumns.length == 0) { return firstByRowOrder(indexes); } else { return firstBySpecifiedOrder(indexes); @@ -54,7 +60,8 @@ public class First extends Aggregator { continue; } - MultiValueKey newKey = new MultiValueKey(this.ordering, row, objectComparator); + MultiValueKey newKey = + new MultiValueKey(this.orderByColumns, row, this.orderByDirections, objectComparator); if (key == null || key.compareTo(newKey) > 0) { key = newKey; current = storage.getItemBoxed(row); diff --git a/std-bits/table/src/main/java/org/enso/table/aggregations/Last.java b/std-bits/table/src/main/java/org/enso/table/aggregations/Last.java index 2b922a59fe..a5ecbc836b 100644 --- a/std-bits/table/src/main/java/org/enso/table/aggregations/Last.java +++ b/std-bits/table/src/main/java/org/enso/table/aggregations/Last.java @@ -10,33 +10,39 @@ import java.util.List; public class Last extends Aggregator { private final Storage storage; - private final Storage[] ordering; + private final Storage[] orderByColumns; + private final int[] orderByDirections; private final Comparator objectComparator; private final boolean ignoreNothing; public Last(String name, Column column, boolean ignoreNothing) { - this(name, column, ignoreNothing, null, null); + this(name, column, ignoreNothing, null, null, null); } public Last( String name, Column column, boolean ignoreNothing, - Column[] ordering, + Column[] orderByColumns, + Long[] orderByDirections, Comparator objectComparator) { super(name, Storage.Type.OBJECT); this.storage = column.getStorage(); - this.ordering = - ordering == null + this.orderByColumns = + orderByColumns == null ? new Storage[0] - : Arrays.stream(ordering).map(Column::getStorage).toArray(Storage[]::new); + : Arrays.stream(orderByColumns).map(Column::getStorage).toArray(Storage[]::new); + this.orderByDirections = + orderByDirections == null + ? new int[0] + : Arrays.stream(orderByDirections).mapToInt(Long::intValue).toArray(); this.objectComparator = objectComparator; this.ignoreNothing = ignoreNothing; } @Override public Object aggregate(List indexes) { - if (ordering.length == 0) { + if (orderByColumns.length == 0) { return lastByRowOrder(indexes); } else { return lastBySpecifiedOrder(indexes); @@ -54,7 +60,8 @@ public class Last extends Aggregator { continue; } - MultiValueKey newKey = new MultiValueKey(this.ordering, row, objectComparator); + MultiValueKey newKey = + new MultiValueKey(this.orderByColumns, row, this.orderByDirections, objectComparator); if (key == null || key.compareTo(newKey) < 0) { key = newKey; current = storage.getItemBoxed(row); diff --git a/test/Table_Tests/src/Aggregate_Spec.enso b/test/Table_Tests/src/Aggregate_Spec.enso index 33e7fe70bc..44af889f00 100644 --- a/test/Table_Tests/src/Aggregate_Spec.enso +++ b/test/Table_Tests/src/Aggregate_Spec.enso @@ -2,6 +2,8 @@ from Standard.Base import all import Standard.Table from Standard.Table.Data.Column_Selector import By_Name, By_Index +import Standard.Table.Data.Sort_Column +import Standard.Table.Data.Sort_Column_Selector from Standard.Table.Data.Aggregate_Column import all from Standard.Table.Error as Error_Module import Missing_Input_Columns, Column_Indexes_Out_Of_Range, No_Output_Columns, Duplicate_Output_Column_Names, Invalid_Output_Column_Names, Invalid_Aggregation, Floating_Point_Grouping, Unquoted_Delimiter, Additional_Warnings from Standard.Database.Error as Database_Errors import Unsupported_Database_Operation_Error @@ -142,7 +144,7 @@ aggregate_spec prefix table empty_table table_builder materialize is_database te materialized.columns.at 5 . at 0 . should_equal -17.960000 epsilon=0.000001 Test.specify "should be able to get first and last values" (pending = resolve_pending test_selection.first_last) <| - grouped = table.aggregate [First "Index" (order_by = By_Name ["Hexadecimal", "TextWithNothing"]), Last "ValueWithNothing" (order_by = By_Name ["Value"])] + grouped = table.aggregate [First "Index" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Hexadecimal", Sort_Column.Name "TextWithNothing"]), Last "ValueWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Value"])] materialized = materialize grouped grouped.row_count . should_equal 1 materialized.columns.length . should_equal 2 @@ -151,6 +153,18 @@ aggregate_spec prefix table empty_table table_builder materialize is_database te materialized.columns.at 1 . name . should_equal "Last ValueWithNothing" materialized.columns.at 1 . at 0 . should_equal -89.78 epsilon=0.000001 + Test.specify "should be able to get first and last values with mixed ordering" (pending = resolve_pending test_selection.first_last) <| + grouped = table.aggregate [First "TextWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Value" Sort_Direction.Descending, Sort_Column.Name "Code"]), First "TextWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Code", Sort_Column.Name "Value" Sort_Direction.Descending]), Last "ValueWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Value" Sort_Direction.Descending])] + materialized = materialize grouped + grouped.row_count . should_equal 1 + materialized.columns.length . should_equal 3 + materialized.columns.at 0 . name . should_equal "First TextWithNothing" + materialized.columns.at 0 . at 0 . should_equal "riwaiqq1io" + materialized.columns.at 1 . name . should_equal "First TextWithNothing_1" + materialized.columns.at 1 . at 0 . should_equal "j4i2ua7uft" + materialized.columns.at 2 . name . should_equal "Last ValueWithNothing" + materialized.columns.at 2 . at 0 . should_equal -38.56 epsilon=0.000001 + Test.specify "should be able to get first and last values with default row order" (pending = resolve_pending test_selection.first_last_row_order) <| grouped = table.aggregate [First "Index", Last "Value"] materialized = materialize grouped @@ -259,7 +273,7 @@ aggregate_spec prefix table empty_table table_builder materialize is_database te materialized.columns.at 2 . at 0 . should_equal Nothing Test.specify "should be able to get first and last values" (pending = resolve_pending test_selection.first_last) <| - grouped = empty_table.aggregate [First "Index" (order_by = By_Name ["Hexadecimal", "TextWithNothing"]), Last "ValueWithNothing" (order_by = By_Name ["Value"])] + grouped = empty_table.aggregate [First "Index" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Hexadecimal", Sort_Column.Name "TextWithNothing"]), Last "ValueWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Value"])] materialized = materialize grouped grouped.row_count . should_equal 1 materialized.columns.length . should_equal 2 @@ -363,7 +377,7 @@ aggregate_spec prefix table empty_table table_builder materialize is_database te materialized.columns.at 3 . name . should_equal "25%-ile Value" Test.specify "should be able to get first and last values" (pending = resolve_pending test_selection.first_last) <| - grouped = empty_table.aggregate [Group_By 0, First "Index" (order_by = By_Name ["Hexadecimal", "TextWithNothing"]), Last "ValueWithNothing" (order_by = By_Name ["Value"])] + grouped = empty_table.aggregate [Group_By 0, First "Index" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Hexadecimal", Sort_Column.Name "TextWithNothing"]), Last "ValueWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Value"])] materialized = materialize grouped grouped.row_count . should_equal 0 materialized.columns.length . should_equal 3 @@ -517,7 +531,7 @@ aggregate_spec prefix table empty_table table_builder materialize is_database te materialized.columns.at 6 . at idx . should_equal -18.802000 epsilon=0.000001 Test.specify "should be able to get first and last values" (pending = resolve_pending test_selection.first_last) <| - grouped = table.aggregate [Group_By "Index", First "TextWithNothing" (order_by = By_Name ["Value", "Flag"]), Last "ValueWithNothing" (order_by = By_Name ["Value"])] + grouped = table.aggregate [Group_By "Index", First "TextWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Value", Sort_Column.Name "Flag"]), Last "ValueWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Value"])] materialized = materialize grouped grouped.row_count . should_equal 10 materialized.columns.length . should_equal 3 @@ -529,6 +543,19 @@ aggregate_spec prefix table empty_table table_builder materialize is_database te materialized.columns.at 2 . name . should_equal "Last ValueWithNothing" materialized.columns.at 2 . at idx . should_equal -89.78 epsilon=0.000001 + Test.specify "should be able to get first and last values with mixed ordering" (pending = resolve_pending test_selection.first_last) <| + grouped = table.aggregate [Group_By "Index", First "TextWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Value" Sort_Direction.Descending, Sort_Column.Name "Flag"]), Last "ValueWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Value" Sort_Direction.Descending])] + materialized = materialize grouped + grouped.row_count . should_equal 10 + materialized.columns.length . should_equal 3 + materialized.columns.at 0 . name . should_equal "Index" + idx = find_row [7] materialized + idx.is_nothing . should_be_false + materialized.columns.at 1 . name . should_equal "First TextWithNothing" + materialized.columns.at 1 . at idx . should_equal "riwaiqq1io" + materialized.columns.at 2 . name . should_equal "Last ValueWithNothing" + materialized.columns.at 2 . at idx . should_equal -63.75 epsilon=0.000001 + Test.specify "should be able to get first and last values with default row order" (pending = resolve_pending test_selection.first_last_row_order) <| grouped = table.aggregate [Group_By "Index", First "TextWithNothing", Last "Value"] materialized = materialize grouped @@ -701,7 +728,7 @@ aggregate_spec prefix table empty_table table_builder materialize is_database te materialized.columns.at 7 . at idx . should_equal -17.174000 epsilon=0.000001 Test.specify "should be able to get first and last values" (pending = resolve_pending test_selection.first_last) <| - grouped = table.aggregate [Group_By "Flag", First "TextWithNothing" (order_by = By_Name ["Value", "Flag"]), Last "ValueWithNothing" (order_by = By_Name ["Value"]), Group_By "Index"] + grouped = table.aggregate [Group_By "Flag", First "TextWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Value", Sort_Column.Name "Flag"]), Last "ValueWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Value"]), Group_By "Index"] materialized = materialize grouped grouped.row_count . should_equal 20 materialized.columns.length . should_equal 4 @@ -714,6 +741,20 @@ aggregate_spec prefix table empty_table table_builder materialize is_database te materialized.columns.at 2 . name . should_equal "Last ValueWithNothing" materialized.columns.at 2 . at idx . should_equal -89.78 epsilon=0.000001 + Test.specify "should be able to get first and last values with mixed ordering" (pending = resolve_pending test_selection.first_last) <| + grouped = table.aggregate [Group_By "Flag", First "TextWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Value" Sort_Direction.Descending, Sort_Column.Name "Flag"]), Last "ValueWithNothing" (order_by = Sort_Column_Selector.By_Name [Sort_Column.Name "Value" Sort_Direction.Descending]), Group_By "Index"] + materialized = materialize grouped + grouped.row_count . should_equal 20 + materialized.columns.length . should_equal 4 + materialized.columns.at 0 . name . should_equal "Flag" + materialized.columns.at 3 . name . should_equal "Index" + idx = find_row [True, 7] materialized [0, 3] + idx.is_nothing . should_be_false + materialized.columns.at 1 . name . should_equal "First TextWithNothing" + materialized.columns.at 1 . at idx . should_equal "13dir782ah" + materialized.columns.at 2 . name . should_equal "Last ValueWithNothing" + materialized.columns.at 2 . at idx . should_equal 54.48 epsilon=0.000001 + Test.specify "should be able to get first and last values with default row order" (pending = resolve_pending test_selection.first_last_row_order) <| grouped = table.aggregate [Group_By "Flag", First "TextWithNothing", Last "Value", Group_By "Index"] materialized = materialize grouped @@ -1090,7 +1131,7 @@ aggregate_spec prefix table empty_table table_builder materialize is_database te Test.group prefix+"Table.aggregate First and Last" pending=pending <| Test.specify "should not return the same value for groups with different values but equal ordering keys" (pending = resolve_pending test_selection.first_last) <| t1 = table_builder [["G", ["a", "a"]], ["X", [1, 2]]] - order = By_Name ["G"] + order = Sort_Column_Selector.By_Name [Sort_Column.Name "G"] r1 = t1.aggregate [First "X" (order_by=order), Last "X" (order_by=order)] r1.row_count.should_equal 1 m1 = materialize r1 @@ -1294,7 +1335,7 @@ aggregate_spec prefix table empty_table table_builder materialize is_database te if test_selection.first_last.not then Test.specify "with First and Last with ordering" <| table = table_builder [["A", [3,2,1]], ["X", [1,2,3]]] - order = By_Name ["A"] + order = Sort_Column_Selector.By_Name [Sort_Column.Name "A"] expect_sum_and_unsupported_errors 2 <| table.aggregate [Sum "X", First "X" (order_by=order), Last "X" (order_by=order)]