mirror of
https://github.com/enso-org/enso.git
synced 2024-11-23 08:08:34 +03:00
Adjusting First and Last order_by to use Sort_Column_Selector (#3517)
This commit is contained in:
parent
654793c246
commit
e97d27e1e0
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 ")"
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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`.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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<Object> 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<Object> 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<Integer> 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);
|
||||
|
@ -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<Object> 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<Object> 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<Integer> 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);
|
||||
|
@ -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)]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user