Skip to content

Commit

Permalink
check types more
Browse files Browse the repository at this point in the history
  • Loading branch information
radeusgd committed Oct 4, 2022
1 parent 8c4e64f commit 646e473
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,11 @@ type Problem_Behavior
`Report_Warning`, the error is turned into a warning, and the fallback
value is returned with that error attached to it as a warning. If it is
`Ignore`, the fallback value is returned and the error is discarded.
handle_errors : Any -> Any -> Any
handle_errors self result ~fallback = result.catch Any error-> case self of

The `error_type` parameter can be overridden to catch only some types of
errors. By default `Any` error is caught.
handle_errors : Any -> Any -> Any -> Any
handle_errors self result ~fallback error_type=Any = result.catch error_type error-> case self of
Ignore -> fallback
Report_Warning -> Warning.attach error fallback
Report_Error -> result
17 changes: 13 additions & 4 deletions distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso
Original file line number Diff line number Diff line change
Expand Up @@ -388,20 +388,29 @@ type Table
filter : (Column | Text | Integer) -> (Filter_Condition|(Any->Boolean)) -> Problem_Behavior -> Table
filter self column filter=Filter_Condition.Is_True on_problems=Report_Warning = case column of
_ : Column ->
filter_column = case Meta.type_of filter of
Filter_Condition -> make_filter_column column filter
Function -> Error.throw (Unsupported_Database_Operation_Error_Data "Filtering with a custom predicate is not supported in the database.")
case Helpers.check_integrity self filter_column of
mask filter_column = case Helpers.check_integrity self filter_column of
False ->
Error.throw (Integrity_Error_Data "Column "+filter_column.name)
True ->
new_filters = self.context.where_filters + [filter_column.expression]
new_ctx = self.context.set_where_filters new_filters
self.updated_context new_ctx
case Meta.type_of filter of
Filter_Condition ->
on_problems.handle_errors fallback=self.with_no_rows <|
mask (make_filter_column column filter)
Function -> Error.throw (Unsupported_Database_Operation_Error_Data "Filtering with a custom predicate is not supported in the database.")
_ -> case on_problems.handle_errors (self.at column) fallback=Nothing of
Nothing -> self
resolved_column -> self.filter resolved_column filter on_problems

## PRIVATE
with_no_rows self =
false_expression = IR.Operation "=" [IR.Constant SQL_Type.integer 1, IR.Constant SQL_Type.integer 2]
new_filters = self.context.where_filters + [false_expression]
new_ctx = self.context.set_where_filters new_filters
self.updated_context new_ctx

## UNSTABLE
Creates a new Table with the specified range of rows from the input
Table.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from Standard.Base import all
from Standard.Table.Data.Table import Table

from Standard.Table.Data.Column import Column
from Standard.Table.Data.Table import Table
from Standard.Table.Data.Value_Type import Value_Type

from Standard.Table.Data.Filter_Condition.Filter_Condition import all
Expand Down Expand Up @@ -28,13 +29,13 @@ type Filter_Condition
Between lower:(Column|Any) upper:(Column|Any)

## Does the value start with a prefix (Text only)?
Starts_With prefix:Text
Starts_With (prefix:Text|Column)

## Does the value end with a suffix (Text only)?
Ends_With suffix:Text
Ends_With (suffix:Text|Column)

## Does the value contain the needle (Text only)?
Contains substring:Text
Contains (substring:Text|Column)

## Is equal to Nothing?
Is_Nothing
Expand Down Expand Up @@ -63,20 +64,38 @@ make_filter_column source_column filter_condition = case filter_condition of
Greater value -> (source_column > value)
Not_Equal value -> (source_column != value)
Between lower upper -> ((source_column >= lower) && (source_column <= upper))
Starts_With prefix -> case source_column.value_type of
Value_Type.Char _ _ -> source_column.starts_with prefix
_ -> Error.throw (Illegal_Argument_Error "`Starts_With` expected a text column.")
Ends_With suffix -> case source_column.value_type of
Value_Type.Char _ _ -> source_column.ends_with suffix
_ -> Error.throw (Illegal_Argument_Error "`Ends_With` expected a text column.")
Contains substring -> case source_column.value_type of
Value_Type.Char _ _ -> source_column.contains substring
_ -> Error.throw (Illegal_Argument_Error "`Contains` expected a text column.")
Starts_With prefix ->
Value_Type.expect_text source_column.value_type <|
expect_column_or_value_as_text "prefix" prefix <|
source_column.starts_with prefix
Ends_With suffix ->
Value_Type.expect_text source_column.value_type <|
expect_column_or_value_as_text "suffix" suffix <|
source_column.ends_with suffix
Contains substring ->
Value_Type.expect_text source_column.value_type <|
expect_column_or_value_as_text "substring" substring <|
source_column.contains substring
Is_Nothing -> source_column.is_missing
Not_Nothing -> source_column.is_missing.not
Is_True -> case source_column.value_type of
Value_Type.Boolean -> source_column
_ -> Error.throw (Illegal_Argument_Error "`Is_True` expected a Boolean column.")
Is_False -> case source_column.value_type of
Value_Type.Boolean -> source_column.not
_ -> Error.throw (Illegal_Argument_Error "`Is_False` expected a Boolean column.")
Is_True ->
Value_Type.expect_boolean source_column.value_type <| source_column
Is_False ->
Value_Type.expect_boolean source_column.value_type <| source_column.not

## PRIVATE
expect_column_or_value_as_text field_name column_or_value ~action = case column_or_value of
_ : Text -> action
## A bit of a hack, because due to lack of interfaces we cannot check if the
thing is a Column (as there are various column implementations based on
the backend). So we assume it is a column and if it doesn't quack like a
column, we fall back to a type error.
maybe_column ->
result = Panic.catch No_Such_Method_Error_Data (Value_Type.expect_text maybe_column.value_type True) _->
Error.throw (Type_Error_Data Text (Meta.type_of maybe_column) field_name)
## We don't run the action above, to avoid catching spurious
`No_Such_Method_Error` from the action itself. Instead we just return
True there and if it went through successfully we can then execute
the action. If it fails, we forward the dataflow error instead.
case result of
True -> action
13 changes: 9 additions & 4 deletions distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso
Original file line number Diff line number Diff line change
Expand Up @@ -832,14 +832,19 @@ type Table
filter : (Column | Text | Integer) -> (Filter_Condition|(Any->Boolean)) -> Problem_Behavior -> Table
filter self column filter=(Filter_Condition.Is_True) on_problems=Report_Warning = case column of
_ : Column.Column ->
filter_column = case Meta.type_of filter of
Filter_Condition -> make_filter_column column filter
Function -> column.map filter
Table_Data (self.java_table.mask filter_column.java_column)
mask filter_column = Table_Data (self.java_table.mask filter_column.java_column)
case Meta.type_of filter of
Filter_Condition ->
on_problems.handle_errors fallback=self.with_no_rows <|
mask (make_filter_column column filter)
Function -> mask (column.map filter)
_ -> case on_problems.handle_errors (self.at column) fallback=Nothing of
Nothing -> self
resolved_column -> self.filter resolved_column filter on_problems

## PRIVATE
with_no_rows self = self.take (First 0)

## Creates a new Table with the specified range of rows from the input
Table.

Expand Down
24 changes: 22 additions & 2 deletions distribution/lib/Standard/Table/0.0.0-dev/src/Data/Value_Type.enso
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from Standard.Base import all

## TODO This is a prototype based on the current pending design, used to proceed
with handling of types in the `filter` component and others. It will be
revisited when proper type support is implemented.

from Standard.Base import all

from Standard.Table.Errors import Invalid_Value_Type

## Type to represent the different sizes of integer or float possible within a database.
type Bits
## 16-bit (2 byte) value
Expand Down Expand Up @@ -77,3 +79,21 @@ type Value_Type

In-Memory and SQLite tables support this.
Mixed

## ADVANCED
UNSTABLE
Checks if the provided value type is a textual type (with any settings)
and runs the following action or reports a type error.
expect_text : Value_Type -> Any -> Any ! Invalid_Value_Type
expect_text value_type ~action = case value_type of
Value_Type.Char _ _ -> action
_ -> Error.throw (Invalid_Value_Type.Invalid_Value_Type_Data Value_Type.Char value_type)

## ADVANCED
UNSTABLE
Checks if the provided value type is a boolean type and runs the
following action or reports a type error.
expect_boolean : Value_Type -> Any -> Any ! Invalid_Value_Type
expect_boolean value_type ~action = case value_type of
Value_Type.Boolean -> action
_ -> Error.throw (Invalid_Value_Type.Invalid_Value_Type_Data Value_Type.Boolean value_type)
56 changes: 44 additions & 12 deletions test/Table_Tests/src/Common_Table_Spec.enso
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from Standard.Base import all
from Standard.Base.Data.Index_Sub_Range import While, Sample, Every
import Standard.Base.Data.Index_Sub_Range
from Standard.Base.Error.Problem_Behavior import Report_Error

from Standard.Table import Column_Name_Mapping, Sort_Column, Sort_Column_Selector
from Standard.Table.Data.Value_Type import Value_Type
from Standard.Table.Errors import all
from Standard.Table.Data.Column_Selector import all
from Standard.Table.Data.Position import all
Expand Down Expand Up @@ -1063,6 +1065,12 @@ spec prefix table_builder test_selection pending=Nothing =
three.drop (While (_ > 10)) . should_equal three
three.drop (While (_ < 10)) . should_equal empty

check_empty expected_column_names table =
table.columns.map .name . should_equal expected_column_names
expected_column_names.each x->
table.at x . to_vector . should_equal []
table.row_count . should_equal 0

## Currently these tests rely on filtering preserving the insertion ordering
within tables. This is not necessarily guaranteed by RDBMS, so we may
adapt this in the future. For now we implicitly assume the ordering is
Expand Down Expand Up @@ -1119,32 +1127,56 @@ spec prefix table_builder test_selection pending=Nothing =
Test.specify "by text search (contains, starts_with, ends_with)" <|
t = table_builder [["ix", [1, 2, 3, 4, 5]], ["X", ["abb", "baca", "banana", Nothing, "nana"]], ["Y", ["a", "b", "b", "c", "a"]]]

t.filter "X" (Filter_Condition.Starts_With "ba") . at "X" . to_vector . should_equal ["baca", "banana"]
t.filter "X" (Filter_Condition.Ends_With "na") . at "X" . to_vector . should_equal ["banana", "nana"]
t.filter "X" (Filter_Condition.Contains "ac") . at "X" . to_vector . should_equal ["baca"]

t.filter "X" (Filter_Condition.Starts_With (t.at "Y")) . at "X" . to_vector . should_equal ["abb", "baca", "banana"]
t.filter "X" (Filter_Condition.Ends_With (t.at "Y")) . at "X" . to_vector . should_equal ["nana"]
t.filter "X" (Filter_Condition.Contains (t.at "Y")) . at "X" . to_vector . should_equal ["abb", "baca", "banana", "nana"]
t.filter "X" (Filter_Condition.Starts_With "ba") on_problems=Report_Error . at "X" . to_vector . should_equal ["baca", "banana"]
t.filter "X" (Filter_Condition.Ends_With "na") on_problems=Report_Error . at "X" . to_vector . should_equal ["banana", "nana"]
t.filter "X" (Filter_Condition.Contains "ac") on_problems=Report_Error . at "X" . to_vector . should_equal ["baca"]

t.filter "X" (Filter_Condition.Starts_With (t.at "Y")) on_problems=Report_Error . at "X" . to_vector . should_equal ["abb", "baca", "banana"]
t.filter "X" (Filter_Condition.Ends_With (t.at "Y")) on_problems=Report_Error . at "X" . to_vector . should_equal ["nana"]
t.filter "X" (Filter_Condition.Contains (t.at "Y")) on_problems=Report_Error . at "X" . to_vector . should_equal ["abb", "baca", "banana", "nana"]

check_column_type_error_handling action =
tester = check_empty ["ix", "X", "Y"]
problems = [Invalid_Value_Type.Invalid_Value_Type_Data Value_Type.Char Value_Type.Integer]
Problems.test_problem_handling action problems tester
check_column_type_error_handling (t.filter "X" (Filter_Condition.Starts_With (t.at "ix")) on_problems=_)
check_column_type_error_handling (t.filter "X" (Filter_Condition.Ends_With (t.at "ix")) on_problems=_)
check_column_type_error_handling (t.filter "X" (Filter_Condition.Contains (t.at "ix")) on_problems=_)
check_column_type_error_handling (t.filter "ix" (Filter_Condition.Starts_With "A") on_problems=_)
check_column_type_error_handling (t.filter "ix" (Filter_Condition.Ends_With "A") on_problems=_)
check_column_type_error_handling (t.filter "ix" (Filter_Condition.Contains "A") on_problems=_)

check_scalar_type_error_handling name action =
tester = check_empty ["ix", "X", "Y"]
problems = [Type_Error_Data Text Integer name]
Problems.test_problem_handling action problems tester
check_scalar_type_error_handling "prefix" (t.filter "X" (Filter_Condition.Starts_With 42) on_problems=_)
check_scalar_type_error_handling "suffix" (t.filter "X" (Filter_Condition.Ends_With 42) on_problems=_)
check_scalar_type_error_handling "substring" (t.filter "X" (Filter_Condition.Contains 42) on_problems=_)

Test.specify "by nulls" <|
t = table_builder [["ix", [1, 2, 3, 4]], ["X", [Nothing, 1, Nothing, 4]]]
t1 = t.filter "X" (Filter_Condition.Is_Nothing)
t1 = t.filter "X" (Filter_Condition.Is_Nothing) on_problems=Report_Error
t1.at "ix" . to_vector . should_equal [1, 3]
t1.at "X" . to_vector . should_equal [Nothing, Nothing]

t2 = t.filter "X" (Filter_Condition.Not_Nothing)
t2 = t.filter "X" (Filter_Condition.Not_Nothing) on_problems=Report_Error
t2.at "ix" . to_vector . should_equal [2, 4]
t2.at "X" . to_vector . should_equal [1, 4]

Test.specify "by a boolean mask" <|
t = table_builder [["ix", [1, 2, 3, 4, 5]], ["b", [True, False, Nothing, True, True]]]
t.filter "b" . at "ix" . to_vector . should_equal [1, 4, 5]
t.filter "b" Filter_Condition.Is_False . at "ix" . to_vector . should_equal [2]
t.filter "b" on_problems=Report_Error . at "ix" . to_vector . should_equal [1, 4, 5]
t.filter "b" Filter_Condition.Is_False on_problems=Report_Error . at "ix" . to_vector . should_equal [2]

tester = check_empty ["ix", "b"]
problems = [Invalid_Value_Type.Invalid_Value_Type_Data Value_Type.Boolean Value_Type.Integer]
Problems.test_problem_handling (t.filter "ix" Filter_Condition.Is_True on_problems=_) problems tester
Problems.test_problem_handling (t.filter "ix" Filter_Condition.Is_False on_problems=_) problems tester

Test.specify "by a custom expression built from table's columns" <|
t = table_builder [["ix", [1, 2, 3, 4, 5]], ["X", [10, 20, 13, 4, 5]], ["Y", [0, -100, 8, 2, 5]]]
t.filter (t.at "X" + t.at "Y" > 9) . at "ix" . to_vector . should_equal [1, 3, 5]
t.filter (t.at "X" + t.at "Y" > 9) on_problems=Report_Error . at "ix" . to_vector . should_equal [1, 3, 5]

Test.specify "should handle selection errors: unknown column name" <|
t = table_builder [["X", [10, 20, 13, 4, 5]]]
Expand Down

0 comments on commit 646e473

Please sign in to comment.