Improve details attached to No_Output_Columns reported from various operations (#8528)

- Closes #7635
This commit is contained in:
Radosław Waśko 2023-12-14 11:49:07 +01:00 committed by GitHub
parent c265787506
commit 7a05e679c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 81 additions and 36 deletions

View File

@ -298,8 +298,7 @@ type Table
select_blank_columns : Blank_Selector -> Boolean -> Table
select_blank_columns self (when:Blank_Selector = Blank_Selector.All_Cells) treat_nans_as_blank=False =
new_columns = self.columns_helper.select_blank_columns_helper when treat_nans_as_blank
if new_columns.length == 0 then Error.throw (No_Output_Columns) else
self.updated_columns new_columns
self.updated_columns new_columns
## ALIAS drop_missing_columns, drop_na
GROUP Standard.Base.Selections
@ -325,8 +324,7 @@ type Table
remove_blank_columns : Blank_Selector -> Boolean -> Table
remove_blank_columns self (when:Blank_Selector = Blank_Selector.All_Cells) treat_nans_as_blank=False =
new_columns = self.columns_helper.select_blank_columns_helper when treat_nans_as_blank invert_selection=True
if new_columns.length == 0 then Error.throw (No_Output_Columns) else
self.updated_columns new_columns
self.updated_columns new_columns
## GROUP Standard.Base.Selections
ICON select_column
@ -1568,12 +1566,15 @@ type Table
v : Vector -> [self] + (v.map t-> Table.from t)
single_table -> [self, single_table]
Helpers.ensure_same_connection "table" all_tables <|
problem_builder = Problem_Builder.new
matched_column_sets = Match_Columns_Helpers.match_columns all_tables match_columns keep_unmatched_columns problem_builder
## We keep separate problem builders, because if we are reporting `No_Output_Columns`,
we only want to add a cause coming from unification; matching reports problems that would not fit this error.
problem_builder_for_matching = Problem_Builder.new
problem_builder_for_unification = Problem_Builder.new
matched_column_sets = Match_Columns_Helpers.match_columns all_tables match_columns keep_unmatched_columns problem_builder_for_matching
dialect = self.connection.dialect
type_mapping = dialect.get_type_mapping
merged_columns = matched_column_sets.map column_set->
case Table_Helpers.unify_result_type_for_union column_set all_tables allow_type_widening problem_builder of
case Table_Helpers.unify_result_type_for_union column_set all_tables allow_type_widening problem_builder_for_unification of
Nothing -> Nothing
result_type : Value_Type ->
sql_type = type_mapping.value_type_to_sql result_type Problem_Behavior.Report_Error
@ -1582,8 +1583,8 @@ type Table
Illegal_State.Error "Unexpected inexact type coercion in Union. The union logic should only operate in types supported by the given backend. This is a bug in the Database library. The coercion was: "+error.to_display_text cause=error
[column_set, sql_type, result_type]
good_columns = merged_columns.filter r-> r.is_nothing.not
if good_columns.is_empty then Error.throw No_Output_Columns else
problem_builder.attach_problems_before on_problems <|
problem_builder_for_matching.attach_problems_before on_problems <| problem_builder_for_unification.attach_problems_before on_problems <|
if good_columns.is_empty then problem_builder_for_unification.raise_no_output_columns_with_cause else
queries = all_tables.map_with_index i-> t->
columns_to_select = good_columns.map description->
column_set = description.first
@ -1735,7 +1736,10 @@ type Table
problem_builder.attach_problems_before on_problems <|
problems = partitioned.first.map .value
on_problems.attach_problems_before problems <|
if new_columns.is_empty then (Error.throw No_Output_Columns) else
handle_no_output_columns =
first_problem = if problems.is_empty then Nothing else problems.first
Error.throw (No_Output_Columns.Error first_problem)
if new_columns.is_empty then handle_no_output_columns else
self.updated_context_and_columns new_ctx new_columns subquery=True
## GROUP Standard.Base.Calculations

View File

@ -38,7 +38,7 @@ match_columns tables matching_mode keep_unmatched_columns problem_builder = case
# This will only include columns that were present in all tables.
common_column_names = tables.first.column_names.filter name->
column_counts.at name == tables.length
if common_column_names.is_empty then Error.throw No_Output_Columns else
if common_column_names.is_empty then Error.throw (No_Output_Columns.Error "Unmatched columns are set to be dropped, but no common column names were found.") else
common_column_names.map name->
column_indices = tables.map table->
table.column_names.index_of name

View File

@ -440,8 +440,7 @@ type Table
select_blank_columns : Blank_Selector -> Boolean -> Table ! No_Output_Columns
select_blank_columns self (when:Blank_Selector = Blank_Selector.All_Cells) (treat_nans_as_blank : Boolean = False) =
new_columns = self.columns_helper.select_blank_columns_helper when treat_nans_as_blank
if new_columns.length == 0 then Error.throw (No_Output_Columns) else
Table.new new_columns
Table.new new_columns
## ALIAS drop_missing_columns, drop_na
GROUP Standard.Base.Selections
@ -467,8 +466,7 @@ type Table
remove_blank_columns : Blank_Selector -> Boolean -> Table ! No_Output_Columns
remove_blank_columns self (when:Blank_Selector = Blank_Selector.All_Cells) (treat_nans_as_blank : Boolean = False) =
new_columns = self.columns_helper.select_blank_columns_helper when treat_nans_as_blank invert_selection=True
if new_columns.length == 0 then Error.throw (No_Output_Columns) else
Table.new new_columns
Table.new new_columns
## GROUP Standard.Base.Selections
ICON select_column
@ -2122,18 +2120,22 @@ type Table
v : Vector -> [self] + (v.map t-> Table.from t)
single_table -> [self, single_table]
all_tables.if_not_error <|
problem_builder = Problem_Builder.new
matched_column_sets = Match_Columns_Helpers.match_columns all_tables match_columns keep_unmatched_columns problem_builder
## We keep separate problem builders, because if we are reporting `No_Output_Columns`,
we only want to add a cause coming from unification; matching reports problems that would not fit this error.
problem_builder_for_matching = Problem_Builder.new
problem_builder_for_unification = Problem_Builder.new
matched_column_sets = Match_Columns_Helpers.match_columns all_tables match_columns keep_unmatched_columns problem_builder_for_matching
result_row_count = all_tables.fold 0 c-> t-> c + t.row_count
merged_columns = matched_column_sets.map column_set->
case Table_Helpers.unify_result_type_for_union column_set all_tables allow_type_widening problem_builder of
case Table_Helpers.unify_result_type_for_union column_set all_tables allow_type_widening problem_builder_for_unification of
Nothing -> Nothing
result_type : Value_Type ->
concat_columns column_set all_tables result_type result_row_count on_problems
good_columns = merged_columns.filter Filter_Condition.Not_Nothing
if good_columns.is_empty then Error.throw No_Output_Columns else
problem_builder.attach_problems_before on_problems <|
Table.new good_columns
problem_builder_for_matching.attach_problems_before on_problems <|
problem_builder_for_unification.attach_problems_before on_problems <|
if good_columns.is_empty then problem_builder_for_unification.raise_no_output_columns_with_cause else
Table.new good_columns
## ALIAS drop_missing_rows, dropna
GROUP Standard.Base.Selections

View File

@ -99,12 +99,16 @@ type Duplicate_Output_Column_Names
## No columns in the output result.
type No_Output_Columns
## PRIVATE
Error (cause : Any | Nothing = Nothing)
## PRIVATE
Pretty prints the no output columns error.
to_display_text : Text
to_display_text self =
"The result contains no columns."
if self.cause.is_nothing then "The result would contain no columns." else
"No columns in the result, because of another problem: "+self.cause.to_display_text
## Indicates that one column has been matched by multiple selectors, resulting
in ambiguous new names.

View File

@ -90,7 +90,7 @@ prepare_aggregate_columns naming_helper aggregates table error_on_missing_column
## If none were found, we raise a generic error (this may
happen primarily when an empty list is provided to the
aggregate method).
Error.throw No_Output_Columns
Error.throw No_Output_Columns.Error
False ->
problem_builder.report_unique_name_strategy unique
Validated_Aggregate_Columns.Value unique_key_columns renamed_columns problem_builder.get_problemset_throwing_distinguished_errors

View File

@ -2,7 +2,7 @@ from Standard.Base import all
import Standard.Base.Runtime.Ref.Ref
import project.Internal.Vector_Builder.Vector_Builder
from project.Errors import Duplicate_Output_Column_Names, Invalid_Aggregate_Column, Invalid_Column_Names, Missing_Input_Columns, Truncated_Column_Names
from project.Errors import Duplicate_Output_Column_Names, Invalid_Aggregate_Column, Invalid_Column_Names, Missing_Input_Columns, Truncated_Column_Names, No_Output_Columns
## PRIVATE
type Problem_Builder
@ -78,6 +78,14 @@ type Problem_Builder
Nothing -> problems
problem -> Error.throw problem
## PRIVATE
Raises `No_Output_Columns` error, with cause set to the first problem
from this builder (if any were reported).
raise_no_output_columns_with_cause : Any ! No_Output_Columns
raise_no_output_columns_with_cause self =
first_problem = self.build_problemset.get 0 if_missing=Nothing
Error.throw (No_Output_Columns.Error first_problem)
## PRIVATE
Creates a new helper object for aggregating problems to report.

View File

@ -67,7 +67,7 @@ type Table_Column_Helper
problem_builder = Problem_Builder.new error_on_missing_columns=error_on_missing_columns
result = self.select_columns_helper selectors case_sensitivity reorder problem_builder
problem_builder.attach_problems_before on_problems <|
if error_on_empty && result.is_empty then Error.throw No_Output_Columns else result
if error_on_empty && result.is_empty then problem_builder.raise_no_output_columns_with_cause else result
## PRIVATE
A helper function encapsulating shared code for `remove_columns`
@ -97,7 +97,7 @@ type Table_Column_Helper
result = self.internal_columns.filter column->
should_be_removed = selected_names.get column.name False
should_be_removed.not
if result.is_empty then Error.throw No_Output_Columns else
if result.is_empty then Error.throw No_Output_Columns.Error else
problem_builder.attach_problems_after on_problems result
## PRIVATE
@ -236,7 +236,7 @@ type Table_Column_Helper
Panic.throw (Illegal_State.Error message=msg cause=error)
counts = materialized_result.rows.first
self.internal_columns.filter_with_index i-> _->
new_columns = self.internal_columns.filter_with_index i-> _->
include = case counts.at i of
## No rows in input, so treating as blank by convention.
Nothing -> True
@ -245,6 +245,9 @@ type Table_Column_Helper
unexpected ->
Panic.throw (Illegal_State.Error "Unexpected result: "+unexpected.to_display_text+". Perhaps an implementation bug of `select_blank_columns_helper`.")
if invert_selection then include.not else include
if new_columns.is_empty.not then new_columns else
message = if invert_selection then "All columns were blank." else "No columns were blank."
Error.throw (No_Output_Columns.Error message)
## PRIVATE
A helper function encapsulating shared code for `rename_columns`

View File

@ -1272,6 +1272,14 @@ spec setup =
problems = [Unsupported_Database_Operation.Error "`First` aggregation requires at least one `order_by` column.", Unsupported_Database_Operation.Error "`Last` aggregation requires at least one `order_by` column."]
Problems.test_problem_handling action problems tester
Test.specify "will include unsupported feature problem in No_Output_Columns" <|
table = table_builder [["A", [1,2,Nothing,3]]]
r1 = table.aggregate [First "A"]
r1.should_fail_with No_Output_Columns
r1.catch.cause . should_be_a Unsupported_Database_Operation
r1.to_display_text . should_contain "No columns in the result"
r1.to_display_text . should_contain "`First`"
Test.group prefix+"Table.aggregate+Expressions" <|
## TODO we probably should check all kinds of aggregate columns
to verify that all of them correctly support expressions.
@ -1317,11 +1325,11 @@ spec setup =
t1.should_fail_with No_Output_Columns
t2 = table.aggregate [Sum "MISSING"]
t2 . should_fail_with Invalid_Aggregate_Column
t2.should_fail_with Invalid_Aggregate_Column
t2.catch.name.should_equal "MISSING"
t3 = table.aggregate [Sum 42]
t3 . should_fail_with Missing_Input_Columns
t3.should_fail_with Missing_Input_Columns
t3.catch.criteria.should_equal [42]
Test.specify "should raise a warning when can't find a column by name, but a hard error if the missing column is in a Group_By" <|

View File

@ -93,6 +93,7 @@ spec setup =
t4 = t1.union [t2, t3] keep_unmatched_columns=False on_problems=Problem_Behavior.Ignore
t4.should_fail_with No_Output_Columns
t4.catch.to_display_text . should_equal "No columns in the result, because of another problem: Unmatched columns are set to be dropped, but no common column names were found."
Test.specify "should ignore column names when matching by position" <|
t1 = table_builder [["A", [1, 2, 3]], ["Y", ["a", "b", "c"]]]
@ -389,12 +390,13 @@ spec setup =
t12.at "Y" . to_vector . should_equal ['aa', 'bb', 'cc', 'x', 'y', 'z']
Test.specify "should fail to find a common type if widening is not allowed (2)" <|
# ToDo No_Output_Columns should have Column_Type_Mismatch as its cause for better readability (#7635)
t1.union t2 allow_type_widening=False . should_fail_with No_Output_Columns
r1 = t1.union t2 allow_type_widening=False
r1.should_fail_with No_Output_Columns
r1.catch.cause . should_be_a Column_Type_Mismatch
r1.catch.to_display_text . should_equal "No columns in the result, because of another problem: The column [X] expects type Integer (16 bits) but one of the provided tables had type Integer (32 bits) which is not compatible with it."
# And this should report Column_Type_Mismatch as the more important error too.
#t1.union t2 allow_type_widening=False on_problems=Problem_Behavior.Report_Error . should_fail_with Column_Type_Mismatch
t1.union t2 allow_type_widening=False on_problems=Problem_Behavior.Report_Error . should_fail_with No_Output_Columns
# And this should report Column_Type_Mismatch as the more important error too:
t1.union t2 allow_type_widening=False on_problems=Problem_Behavior.Report_Error . should_fail_with Column_Type_Mismatch
Test.specify "should gracefully handle tables from different backends" <|
t1 = table_builder [["A", [1, 2, 3]], ["B", ["a", "b", "c"]]]

View File

@ -152,7 +152,13 @@ spec setup =
no_rows.at "X" . to_vector . should_equal []
no_rows.select_blank_columns . columns . map .name . should_equal ["X"]
no_rows.remove_blank_columns . columns . map .name . should_fail_with No_Output_Columns
r2 = no_rows.remove_blank_columns
r2.should_fail_with No_Output_Columns
r2.catch.to_display_text . should_equal "No columns in the result, because of another problem: All columns were blank."
r3 = t.select_blank_columns
r3.should_fail_with No_Output_Columns
r3.catch.to_display_text . should_equal "No columns in the result, because of another problem: No columns were blank."
Test.group prefix+"Filling Missing Values" <|
Test.specify "should coerce long and double types to double" <|

View File

@ -131,9 +131,15 @@ spec setup =
[Problem_Behavior.Ignore, Problem_Behavior.Report_Warning, Problem_Behavior.Report_Error].each pb->
t = table.select_columns [] on_problems=pb
t.should_fail_with No_Output_Columns
# Just selecting [] means the No_Output_Columns does not have an additional cause.
t.catch.cause . should_equal Nothing
t.catch.to_display_text . should_equal "The result would contain no columns."
table.select_columns ["hmmm"] . should_fail_with Missing_Input_Columns
table.select_columns ["hmmm"] error_on_missing_columns=False . should_fail_with No_Output_Columns
r2 = table.select_columns ["hmmm"] error_on_missing_columns=False
r2.should_fail_with No_Output_Columns
r2.catch.cause . should_be_a Missing_Input_Columns
r2.catch.to_display_text . should_equal "No columns in the result, because of another problem: The criteria 'hmmm' did not match any columns."
Test.group prefix+"Table.remove_columns" <|
Test.specify "should work as shown in the doc examples" <|
@ -215,6 +221,8 @@ spec setup =
selector_2 = [".*".to_regex, "hmmm".to_regex]
t1 = table.remove_columns selector_2
t1.should_fail_with No_Output_Columns
# No cause specified - even if some criteria were unmatched, that is not the reason for the No_Output_Columns (the reason is all other columns got deleted, by other criteria that _did_ match).
t1.catch.cause . should_equal Nothing
Test.group prefix+"Table.reorder_columns" <|
Test.specify "should work as shown in the doc examples" <|