Adjusting First and Last order_by to use Sort_Column_Selector (#3517)

This commit is contained in:
James Dunkerley 2022-06-10 10:59:03 +01:00 committed by GitHub
parent 654793c246
commit e97d27e1e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 152 additions and 76 deletions

View File

@ -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

View File

@ -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

View File

@ -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 ")"

View File

@ -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]

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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`.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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)]