Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 21 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range.enso
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@ type Range
map function =
Vector.new this.length (i -> function (this.start + i*this.step))

## Returns a vector of all elements of this range which satisfy a predicate.

Arguments:
- predicate: A function that takes a list element and returns a boolean
value that says whether that value satisfies the conditions of the
function.

> Example
Selecting all elements that are greater than 3.

(0.up_to 7).filter (> 3)
filter : (Any -> Boolean) -> Vector Any
filter predicate =
builder = this.fold Vector.new_builder builder-> elem->
if predicate elem then builder.append elem else builder
builder.to_vector

## Applies a function for each element in the range.

Arguments:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1065,10 +1065,10 @@ Text.repeat count=1 =
> Examples
Various different ways to take part of "Hello World!"

"Hello World!".take First.new == "H"
"Hello World!".take First == "H"
"Hello World!".take (First 5) == "Hello"
"Hello World!".take (First 0) == ""
"Hello World!".take Last.new == "!"
"Hello World!".take Last == "!"
"Hello World!".take (Last 6) == "World!"
"Hello World!".take (Before " ") == "Hello"
"Hello World!".take (Before_Last "o") == "Hello W"
Expand Down Expand Up @@ -1105,10 +1105,10 @@ Text.take range =
> Examples
Various different ways to take part of "Hello World!"

"Hello World!".drop First.new == "ello World!"
"Hello World!".drop First == "ello World!"
"Hello World!".drop (First 5) == " World!"
"Hello World!".drop (First 0) == "Hello World!"
"Hello World!".drop Last.new == "Hello World"
"Hello World!".drop Last == "Hello World"
"Hello World!".drop (Last 6) == "Hello "
"Hello World!".drop (Before " ") == " World!"
"Hello World!".drop (Before_Last "o") == "orld!"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,48 +205,78 @@ match_criteria_callback matcher objects criteria problem_callback reorder=False
problem_callback unmatched_criteria
result.first

## PRIVATE
internal_match_criteria_implementation matcher objects criteria reorder=False name_mapper=(x->x) = Panic.catch Wrapped_Dataflow_Error (handler = x-> x.payload.unwrap) <|
## TODO [RW] discuss: this line of code also shows an issue we had with ensuring input dataflow-errors are correctly propagated, later on we stopped doing that and testing for that as it was too cumbersome. Maybe it could be helped with an @Accepts_Error annotation similar to the one from the interpreter???
[matcher, objects, criteria, reorder, name_mapper] . each v->
Panic.rethrow (v.map_error Wrapped_Dataflow_Error)

# match_matrix . at i . at j specifies whether objects.at i matches criteria.at j
match_matrix = objects.map obj->
criteria.map criterion->
name = name_mapper obj
matcher.match_single_criterion name criterion
type Match_Matrix
## PRIVATE
A helper type holding a matrix of matches.
type Match_Matrix matrix criteria objects

# Checks if the ith object is matched by any criterion.
is_object_matched_by_anything : Integer -> Boolean
is_object_matched_by_anything i =
match_matrix.at i . any x->x
this.matrix.at i . any x->x

# Checks if the ith criterion matches any columns.
# Checks if the ith criterion matches any objects.
does_criterion_match_anything : Integer -> Boolean
does_criterion_match_anything i =
match_matrix.map (col -> col.at i) . any x->x
this.matrix.map (col -> col.at i) . any x->x

## PRIVATE
Extracts the list of criteria that did not have any matches.
unmatched_criteria =
checked_criteria = this.criteria.map_with_index j-> criterion->
has_matches = this.does_criterion_match_anything j
Pair has_matches criterion
checked_criteria.filter (p -> p.first.not) . map .second

## PRIVATE
Returns the list of criteria that match the ith object.
criteria_matching_object : Integer -> Vector
criteria_matching_object i =
this.criteria.filter_with_index j-> _->
this.matrix . at i . at j

## PRIVATE
Returns the list of criteria indices that match the ith object.
criteria_indices_matching_object : Integer -> Vector
criteria_indices_matching_object i =
(0.up_to this.criteria.length).filter j->
this.matrix . at i . at j

## PRIVATE
Generates a matrix specifying which criteria match which object.

The returned `match_matrix` satisfies the following condition:
`match_matrix . at i . at j` is `True` if and only if `objects.at i` matches
`criteria.at j`.
make_match_matrix matcher objects criteria object_name_mapper=(x->x) criterion_mapper=(x->x) =
matrix = objects.map obj->
criteria.map criterion->
matcher.match_single_criterion (object_name_mapper obj) (criterion_mapper criterion)
Match_Matrix matrix criteria objects

## PRIVATE
internal_match_criteria_implementation matcher objects criteria reorder=False name_mapper=(x->x) = Panic.catch Wrapped_Dataflow_Error (handler = x-> x.payload.unwrap) <|
## TODO [RW] discuss: this line of code also shows an issue we had with ensuring input dataflow-errors are correctly propagated, later on we stopped doing that and testing for that as it was too cumbersome. Maybe it could be helped with an @Accepts_Error annotation similar to the one from the interpreter???
[matcher, objects, criteria, reorder, name_mapper] . each v->
Panic.rethrow (v.map_error Wrapped_Dataflow_Error)

match_matrix = here.make_match_matrix matcher objects criteria name_mapper
unmatched_criteria = match_matrix.unmatched_criteria

# Selects object indices which satisfy the provided predicate.
select_matching_indices : (Integer -> Boolean) -> Vector Text
select_matching_indices matcher =
0.up_to objects.length . to_vector . filter matcher

# Check consistency
checked_criteria = criteria.map_with_index j-> criterion->
has_matches = does_criterion_match_anything j
Pair has_matches criterion
unmatched_criteria = checked_criteria.filter (p -> p.first.not) . map .second

selected_indices = case reorder of
True ->
nested_indices = 0.up_to criteria.length . map j->
is_object_matched_by_this_criterion i =
match_matrix.at i . at j
match_matrix.matrix.at i . at j
select_matching_indices is_object_matched_by_this_criterion
nested_indices.flat_map x->x . distinct
False ->
select_matching_indices is_object_matched_by_anything
select_matching_indices match_matrix.is_object_matched_by_anything

result = selected_indices.map objects.at
Pair result unmatched_criteria
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ from Standard.Base import all
- sort_digits_as_numbers: Sort digits in the text as numbers. Setting this to
`True` results in a "Natural" ordering.
- case_sensitive: Specifies if the ordering should be case case sensitive.
type Text_Ordering (sort_digits_as_numbers:Bool=False) (case_sensitive:(True|Case_Insensitive)=True)
type Text_Ordering (sort_digits_as_numbers:Boolean=False) (case_sensitive:(True|Case_Insensitive)=True)
Original file line number Diff line number Diff line change
Expand Up @@ -102,25 +102,3 @@ type Text_Sub_Range
predicate (Text_Utils.substring text start end) . not
if indices.first == -1 then (Range 0 indices.second) else
Range 0 indices.first

## UNSTABLE
A temporary workaround to allow the `First` constructor to work with default
arguments.

It is needed, because there are issues with relying on default arguments of
Atom constructors, as described in the following issue:
https://github.com/enso-org/enso/issues/1600
Once that issue is fixed, it can be removed.
First.new : Integer -> First
First.new (count = 1) = First count

## UNSTABLE
A temporary workaround to allow the `Last` constructor to work with default
arguments.

It is needed, because there are issues with relying on default arguments of
Atom constructors, as described in the following issue:
https://github.com/enso-org/enso/issues/1600
Once that issue is fixed, it can be removed.
Last.new : Integer -> Last
Last.new (count = 1) = Last count
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ from Standard.Base import all
import Standard.Base.Error.Common as Errors
import Standard.Table.Data.Aggregate_Column
import Standard.Database.Data.Sql
import Standard.Database.Data.Internal.IR
import Standard.Database.Data.Dialect.Postgres
import Standard.Database.Data.Dialect.Redshift
import Standard.Database.Data.Dialect.Sqlite as Sqlite_Module
Expand Down Expand Up @@ -39,6 +40,14 @@ type Dialect
resolve_target_sql_type : Aggregate_Column -> Sql_Type
resolve_target_sql_type = Errors.unimplemented "This is an interface only."

## PRIVATE
Prepares an ordering descriptor.

One of the purposes of this method is to verify if the expected ordering
settings are supported by the given database backend.
prepare_order_descriptor : Connection -> IR.Internal_Column -> Sort_Direction -> Text_Ordering -> IR.Order_Descriptor
prepare_order_descriptor = Errors.unimplemented "This is an interface only."

## PRIVATE

A vector of SQL dialects supported by the Database library.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from Standard.Base import all

import Standard.Base.Error.Common as Errors
from Standard.Table.Data.Aggregate_Column import all
from Standard.Database.Data.Sql import Sql_Type
import Standard.Database.Data.Dialect
Expand Down Expand Up @@ -43,6 +44,16 @@ type Postgresql_Dialect
resolve_target_sql_type : Aggregate_Column -> Sql_Type
resolve_target_sql_type aggregate = here.resolve_target_sql_type aggregate

## PRIVATE
Prepares an ordering descriptor.

One of the purposes of this method is to verify if the expected ordering
settings are supported by the given database backend.
prepare_order_descriptor : Connection -> IR.Internal_Column -> Sort_Direction -> Text_Ordering -> IR.Order_Descriptor
prepare_order_descriptor connection internal_column sort_direction text_ordering =
_ = [connection, internal_column, sort_direction, text_ordering]
Errors.unimplemented "TODO"

## PRIVATE
make_internal_generator_dialect =
text = [here.starts_with, here.contains, here.ends_with, here.agg_shortest, here.agg_longest]+here.concat_ops
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from Standard.Base import all

import Standard.Base.Error.Common as Errors
import Standard.Table.Data.Aggregate_Column
import Standard.Database.Data.Sql
import Standard.Database.Data.Dialect
Expand Down Expand Up @@ -42,3 +43,13 @@ type Redshift_Dialect
resolve_target_sql_type : Aggregate_Column -> Sql_Type
resolve_target_sql_type aggregate =
Postgres.resolve_target_sql_type aggregate

## PRIVATE
Prepares an ordering descriptor.

One of the purposes of this method is to verify if the expected ordering
settings are supported by the given database backend.
prepare_order_descriptor : Connection -> IR.Internal_Column -> Sort_Direction -> Text_Ordering -> IR.Order_Descriptor
prepare_order_descriptor connection internal_column sort_direction text_ordering =
_ = [connection, internal_column, sort_direction, text_ordering]
Errors.unimplemented "TODO"
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ from Standard.Database.Data.Sql import Sql_Type
import Standard.Database.Data.Dialect
import Standard.Database.Data.Dialect.Helpers
import Standard.Database.Data.Internal.Base_Generator
import Standard.Database.Data.Internal.IR
from Standard.Database.Error as Database_Errors import Unsupported_Database_Operation_Error

## PRIVATE
Expand Down Expand Up @@ -43,6 +44,20 @@ type Sqlite_Dialect
resolve_target_sql_type : Aggregate_Column -> Sql_Type
resolve_target_sql_type aggregate = here.resolve_target_sql_type aggregate

## PRIVATE
Prepares an ordering descriptor.

One of the purposes of this method is to verify if the expected ordering
settings are supported by the given database backend.
prepare_order_descriptor : Connection -> IR.Internal_Column -> Sort_Direction -> Text_Ordering -> IR.Order_Descriptor
prepare_order_descriptor _ internal_column sort_direction text_ordering = case internal_column.sql_type.is_likely_text of
True ->
if text_ordering.sort_digits_as_numbers then Error.throw (Unsupported_Database_Operation_Error "Natural ordering is not supported by the SQLite backend. You may need to materialize the Table to perform this operation.") else
if text_ordering.case_sensitive != True then Error.throw (Unsupported_Database_Operation_Error "Case insensitive ordering is not supported by the SQLite backend. You may need to materialize the Table to perform this operation.") else
IR.Order_Descriptor internal_column.expression sort_direction collation=Nothing
False ->
IR.Order_Descriptor internal_column.expression sort_direction collation=Nothing

## PRIVATE
make_internal_generator_dialect =
text = [here.starts_with, here.contains, here.ends_with]+here.concat_ops
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,18 +235,22 @@ generate_from_part dialect from_spec = case from_spec of

Arguments:
- dialect: The SQL dialect for which the code is generated.
- order_description: A description of the ORDER clause.

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


## PRIVATE

Expand Down
Loading