diff --git a/src/mizugaki/analyzer/details/analyze_query_expression.cpp b/src/mizugaki/analyzer/details/analyze_query_expression.cpp index 3fd0aeb..57543da 100644 --- a/src/mizugaki/analyzer/details/analyze_query_expression.cpp +++ b/src/mizugaki/analyzer/details/analyze_query_expression.cpp @@ -495,7 +495,111 @@ class engine { return { op.output(), std::move(info) }; } - // FIXME: impl binary_expression + [[nodiscard]] result_type operator()( + ast::query::binary_expression const& expr, + optional_ptr const& parent, + row_value_context const& val) { + using kind = ast::query::binary_operator; + switch (*expr.operator_kind()) { + case kind::union_: + return process_union(expr, parent, val); + + case kind::except: + case kind::intersect: + case kind::outer_union: + default: + break; + } + context_.report( + sql_analyzer_code::unsupported_feature, + string_builder {} + << "unsupported binary operator: " + << expr.operator_kind() + << string_builder::to_string, + expr.region()); + return {}; + } + + [[nodiscard]] result_type process_union( + ast::query::binary_expression const& expr, + optional_ptr const& parent, + row_value_context const& val) { + if (expr.corresponding()) { + context_.report( + sql_analyzer_code::unsupported_feature, + "CORRESPONDING clause is not yet supported", + expr.region()); + return {}; + } + auto left = process(*expr.left(), parent, val); + if (!left) { + return {}; + } + auto right = process(*expr.right(), parent, val); + if (!right) { + return {}; + } + + auto&& left_info = left.relation(); + auto&& right_info = right.relation(); + + auto left_columns = extract_exported_columns(left_info.columns()); + auto right_columns = extract_exported_columns(right_info.columns()); + if (left_columns.size() != right_columns.size()) { + context_.report( + sql_analyzer_code::inconsistent_columns, + "column count mismatch", + expr.operator_kind().region()); + return {}; + } + + trelation::set_quantifier quantifier { trelation::set_quantifier::distinct }; + if (expr.quantifier() == ast::query::set_quantifier::all) { + quantifier = trelation::set_quantifier::all; + } + + relation_info output_info {}; + output_info.reserve(left_info.columns().size()); + + std::vector mappings {}; + mappings.reserve(left_info.columns().size()); + for (std::size_t index = 0, size = left_columns.size(); index < size; ++index) { + auto&& left_column = *left_columns[index]; + auto&& right_column = *right_columns[index]; + auto output_column = factory_.stream_variable( + // FIXME: debug name + ); + mappings.emplace_back(left_column.variable(), right_column.variable(), output_column); + output_info.add({ + {}, + std::move(output_column), + left_column.identifier(), // NOTE: pick the column identifier from the left-most query + }); + } + auto&& result = graph_.insert(context_.create( + expr.operator_kind().region(), + quantifier, + std::move(mappings))); + result.left() << left.output(); + result.right() << right.output(); + if (!context_.resolve(result)) { + return {}; + } + return { result.output(), std::move(output_info) }; + } + + [[nodiscard]] std::vector extract_exported_columns( + ::takatori::util::sequence_view columns) { + std::vector results {}; + results.reserve(columns.size()); + for (auto&& c : columns) { + if (c.exported()) { + results.emplace_back(std::addressof(c)); + } + } + return results; + } + // FIXME: impl with_expression // table expressions diff --git a/src/mizugaki/analyzer/details/analyzer_context.cpp b/src/mizugaki/analyzer/details/analyzer_context.cpp index 25d1e0c..21e9c93 100644 --- a/src/mizugaki/analyzer/details/analyzer_context.cpp +++ b/src/mizugaki/analyzer/details/analyzer_context.cpp @@ -113,6 +113,13 @@ void analyzer_context::resolve_as( expression_analyzer_.variables().bind(variable, std::move(resolution), true); } +::yugawara::compiled_info analyzer_context::test_info() { + return { + expression_analyzer_.shared_expressions().ownership(), + expression_analyzer_.shared_variables().ownership(), + }; +} + sql_analyzer_code analyzer_context::convert_code( ::yugawara::analyzer::expression_analyzer_code code) noexcept { using kind = decltype(code); diff --git a/src/mizugaki/analyzer/details/analyzer_context.h b/src/mizugaki/analyzer/details/analyzer_context.h index 669779e..a9e99b9 100644 --- a/src/mizugaki/analyzer/details/analyzer_context.h +++ b/src/mizugaki/analyzer/details/analyzer_context.h @@ -11,6 +11,8 @@ #include +#include + #include #include @@ -78,6 +80,8 @@ class analyzer_context { ::takatori::descriptor::variable const& variable, ::yugawara::analyzer::variable_resolution resolution); + [[nodiscard]] ::yugawara::compiled_info test_info(); + void clear_expression_resolution(); void clear_expression_resolution(::takatori::scalar::expression const& expression); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 30c2685..8160786 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -67,6 +67,7 @@ add_analyzer_test_executable(mizugaki/analyzer/details/analyze_query_expression_ add_analyzer_test_executable(mizugaki/analyzer/details/analyze_query_expression_join_test.cpp) add_analyzer_test_executable(mizugaki/analyzer/details/analyze_query_expression_aggregate_test.cpp) add_analyzer_test_executable(mizugaki/analyzer/details/analyze_query_expression_subquery_test.cpp) +add_analyzer_test_executable(mizugaki/analyzer/details/analyze_query_expression_binary_test.cpp) add_analyzer_test_executable(mizugaki/analyzer/details/analyze_statement_dml_test.cpp) add_analyzer_test_executable(mizugaki/analyzer/details/analyze_statement_select_test.cpp) add_analyzer_test_executable(mizugaki/analyzer/details/analyze_statement_insert_test.cpp) diff --git a/test/mizugaki/analyzer/details/analyze_query_expression_binary_test.cpp b/test/mizugaki/analyzer/details/analyze_query_expression_binary_test.cpp new file mode 100644 index 0000000..67652c1 --- /dev/null +++ b/test/mizugaki/analyzer/details/analyze_query_expression_binary_test.cpp @@ -0,0 +1,545 @@ +#include + +#include + +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "test_parent.h" + +namespace mizugaki::analyzer::details { + +using namespace ::mizugaki::analyzer::testing; + +using ::yugawara::binding::extract; + +class analyze_query_expression_binary_test : public test_parent { +protected: + void invalid(ast::query::expression const& expression) { + trelation::graph_type graph {}; + auto r = analyze_query_expression( + context(), + graph, + expression, + {}, + {}); + EXPECT_FALSE(r); + EXPECT_NE(count_error(), 0); + } + + void invalid(sql_analyzer_code code, ast::query::expression const& expression) { + invalid(expression); + EXPECT_TRUE(find_error(code)) << diagnostics(); + clear_error(); + } +}; + +TEST_F(analyze_query_expression_binary_test, union_all) { + auto table_a = install_table("a"); + auto table_b = install_table("b"); + trelation::graph_type graph {}; + + auto r = analyze_query_expression( + context(), + graph, + ast::query::binary_expression { + ast::query::query { + { + ast::query::select_asterisk {}, + }, + { + ast::table::table_reference { + id("a"), + }, + }, + }, + ast::query::binary_operator::union_, + ast::query::set_quantifier::all, + ast::query::query { + { + ast::query::select_asterisk {}, + }, + { + ast::table::table_reference { + id("b"), + }, + }, + }, + }, + {}, + {}); + ASSERT_TRUE(r) << diagnostics(); + expect_no_error(); + + EXPECT_EQ(graph.size(), 5); + EXPECT_FALSE(r.output().opposite()); + + auto&& relation = r.relation(); + EXPECT_FALSE(relation.declaration()); + EXPECT_EQ(relation.identifier(), ""); + + auto relation_columns = relation.columns(); + ASSERT_EQ(relation_columns.size(), 4); + { + auto&& column = relation_columns[0]; + EXPECT_EQ(column.identifier(), "k"); + EXPECT_FALSE(column.declaration()); + EXPECT_TRUE(column.exported()); + } + { + auto&& column = relation_columns[1]; + EXPECT_EQ(column.identifier(), "v"); + EXPECT_FALSE(column.declaration()); + EXPECT_TRUE(column.exported()); + } + { + auto&& column = relation_columns[2]; + EXPECT_EQ(column.identifier(), "w"); + EXPECT_FALSE(column.declaration()); + EXPECT_TRUE(column.exported()); + } + { + auto&& column = relation_columns[3]; + EXPECT_EQ(column.identifier(), "x"); + EXPECT_FALSE(column.declaration()); + EXPECT_TRUE(column.exported()); + } + + // (scan - project)x2 - union[all] + auto&& union_ = downcast(r.output().owner()); + auto&& left_project = *find_prev(union_.left()); + auto&& left_scan = *find_prev(left_project); + auto&& right_project = *find_prev(union_.right()); + auto&& right_scan = *find_prev(right_project); + + ASSERT_EQ(left_project.columns().size(), 0); + + EXPECT_EQ(extract<::yugawara::storage::index>(left_scan.source()).table(), *table_a); + auto&& left_columns = left_scan.columns(); + ASSERT_EQ(left_columns.size(), 4); + + ASSERT_EQ(right_project.columns().size(), 0); + + EXPECT_EQ(extract<::yugawara::storage::index>(right_scan.source()).table(), *table_b); + auto&& right_columns = right_scan.columns(); + ASSERT_EQ(right_columns.size(), 4); + + EXPECT_EQ(union_.quantifier(), trelation::set_quantifier::all); + auto&& union_columns = union_.mappings(); + ASSERT_EQ(union_columns.size(), 4); + { + auto&& column = union_columns[0]; + EXPECT_EQ(column.left(), left_columns[0].destination()); + EXPECT_EQ(column.right(), right_columns[0].destination()); + } + { + auto&& column = union_columns[1]; + EXPECT_EQ(column.left(), left_columns[1].destination()); + EXPECT_EQ(column.right(), right_columns[1].destination()); + } + { + auto&& column = union_columns[2]; + EXPECT_EQ(column.left(), left_columns[2].destination()); + EXPECT_EQ(column.right(), right_columns[2].destination()); + } + { + auto&& column = union_columns[3]; + EXPECT_EQ(column.left(), left_columns[3].destination()); + EXPECT_EQ(column.right(), right_columns[3].destination()); + } + + EXPECT_EQ(union_columns[0].destination(), relation_columns[0].variable()); + EXPECT_EQ(union_columns[1].destination(), relation_columns[1].variable()); + EXPECT_EQ(union_columns[2].destination(), relation_columns[2].variable()); + EXPECT_EQ(union_columns[3].destination(), relation_columns[3].variable()); +} + +TEST_F(analyze_query_expression_binary_test, union_distinct) { + auto table_a = install_table("a"); + auto table_b = install_table("b"); + trelation::graph_type graph {}; + + auto r = analyze_query_expression( + context(), + graph, + ast::query::binary_expression { + ast::query::query { + { + ast::query::select_asterisk {}, + }, + { + ast::table::table_reference { + id("a"), + }, + }, + }, + ast::query::binary_operator::union_, + ast::query::set_quantifier::distinct, + ast::query::query { + { + ast::query::select_asterisk {}, + }, + { + ast::table::table_reference { + id("b"), + }, + }, + }, + }, + {}, + {}); + ASSERT_TRUE(r) << diagnostics(); + expect_no_error(); + + EXPECT_EQ(graph.size(), 5); + EXPECT_FALSE(r.output().opposite()); + + auto&& relation = r.relation(); + EXPECT_FALSE(relation.declaration()); + EXPECT_EQ(relation.identifier(), ""); + + auto relation_columns = relation.columns(); + ASSERT_EQ(relation_columns.size(), 4); + { + auto&& column = relation_columns[0]; + EXPECT_EQ(column.identifier(), "k"); + EXPECT_FALSE(column.declaration()); + EXPECT_TRUE(column.exported()); + } + { + auto&& column = relation_columns[1]; + EXPECT_EQ(column.identifier(), "v"); + EXPECT_FALSE(column.declaration()); + EXPECT_TRUE(column.exported()); + } + { + auto&& column = relation_columns[2]; + EXPECT_EQ(column.identifier(), "w"); + EXPECT_FALSE(column.declaration()); + EXPECT_TRUE(column.exported()); + } + { + auto&& column = relation_columns[3]; + EXPECT_EQ(column.identifier(), "x"); + EXPECT_FALSE(column.declaration()); + EXPECT_TRUE(column.exported()); + } + + // (scan - project)x2 - union[all] + auto&& union_ = downcast(r.output().owner()); + auto&& left_project = *find_prev(union_.left()); + auto&& left_scan = *find_prev(left_project); + auto&& right_project = *find_prev(union_.right()); + auto&& right_scan = *find_prev(right_project); + + ASSERT_EQ(left_project.columns().size(), 0); + + EXPECT_EQ(extract<::yugawara::storage::index>(left_scan.source()).table(), *table_a); + auto&& left_columns = left_scan.columns(); + ASSERT_EQ(left_columns.size(), 4); + + ASSERT_EQ(right_project.columns().size(), 0); + + EXPECT_EQ(extract<::yugawara::storage::index>(right_scan.source()).table(), *table_b); + auto&& right_columns = right_scan.columns(); + ASSERT_EQ(right_columns.size(), 4); + + EXPECT_EQ(union_.quantifier(), trelation::set_quantifier::distinct); + auto&& union_columns = union_.mappings(); + ASSERT_EQ(union_columns.size(), 4); + { + auto&& column = union_columns[0]; + EXPECT_EQ(column.left(), left_columns[0].destination()); + EXPECT_EQ(column.right(), right_columns[0].destination()); + } + { + auto&& column = union_columns[1]; + EXPECT_EQ(column.left(), left_columns[1].destination()); + EXPECT_EQ(column.right(), right_columns[1].destination()); + } + { + auto&& column = union_columns[2]; + EXPECT_EQ(column.left(), left_columns[2].destination()); + EXPECT_EQ(column.right(), right_columns[2].destination()); + } + { + auto&& column = union_columns[3]; + EXPECT_EQ(column.left(), left_columns[3].destination()); + EXPECT_EQ(column.right(), right_columns[3].destination()); + } + + EXPECT_EQ(union_columns[0].destination(), relation_columns[0].variable()); + EXPECT_EQ(union_columns[1].destination(), relation_columns[1].variable()); + EXPECT_EQ(union_columns[2].destination(), relation_columns[2].variable()); + EXPECT_EQ(union_columns[3].destination(), relation_columns[3].variable()); +} + +TEST_F(analyze_query_expression_binary_test, union_default) { + auto table_a = install_table("a"); + auto table_b = install_table("b"); + trelation::graph_type graph {}; + + auto r = analyze_query_expression( + context(), + graph, + ast::query::binary_expression { + ast::query::query { + { + ast::query::select_asterisk {}, + }, + { + ast::table::table_reference { + id("a"), + }, + }, + }, + ast::query::binary_operator::union_, + {}, + ast::query::query { + { + ast::query::select_asterisk {}, + }, + { + ast::table::table_reference { + id("b"), + }, + }, + }, + }, + {}, + {}); + ASSERT_TRUE(r) << diagnostics(); + expect_no_error(); + + EXPECT_EQ(graph.size(), 5); + EXPECT_FALSE(r.output().opposite()); + + auto&& relation = r.relation(); + EXPECT_FALSE(relation.declaration()); + EXPECT_EQ(relation.identifier(), ""); + + auto relation_columns = relation.columns(); + ASSERT_EQ(relation_columns.size(), 4); + + // (scan - project)x2 - union[all] + auto&& union_ = downcast(r.output().owner()); + auto&& left_project = *find_prev(union_.left()); + auto&& left_scan = *find_prev(left_project); + auto&& right_project = *find_prev(union_.right()); + auto&& right_scan = *find_prev(right_project); + + ASSERT_EQ(left_project.columns().size(), 0); + + EXPECT_EQ(extract<::yugawara::storage::index>(left_scan.source()).table(), *table_a); + auto&& left_columns = left_scan.columns(); + ASSERT_EQ(left_columns.size(), 4); + + ASSERT_EQ(right_project.columns().size(), 0); + + EXPECT_EQ(extract<::yugawara::storage::index>(right_scan.source()).table(), *table_b); + auto&& right_columns = right_scan.columns(); + ASSERT_EQ(right_columns.size(), 4); + + EXPECT_EQ(union_.quantifier(), trelation::set_quantifier::distinct); + auto&& union_columns = union_.mappings(); + ASSERT_EQ(union_columns.size(), 4); + + EXPECT_EQ(union_columns[0].destination(), relation_columns[0].variable()); + EXPECT_EQ(union_columns[1].destination(), relation_columns[1].variable()); + EXPECT_EQ(union_columns[2].destination(), relation_columns[2].variable()); + EXPECT_EQ(union_columns[3].destination(), relation_columns[3].variable()); +} + +TEST_F(analyze_query_expression_binary_test, union_type_unification) { + auto&& ctxt = context(); + trelation::graph_type graph {}; + + auto r = analyze_query_expression( + ctxt, + graph, + ast::query::binary_expression { + ast::query::table_value_constructor { + ast::scalar::value_constructor { + ast::scalar::cast_expression { + ast::scalar::cast_operator::cast, + literal(number("100")), + ast::type::simple { + ast::type::kind::big_integer, + }, + }, + }, + }, + ast::query::binary_operator::union_, + ast::query::set_quantifier::all, + ast::query::table_value_constructor { + ast::scalar::value_constructor { + ast::scalar::cast_expression { + ast::scalar::cast_operator::cast, + literal(number("100")), + ast::type::simple { + ast::type::kind::float_, + }, + }, + }, + }, + }, + {}, + {}); + ASSERT_TRUE(r) << diagnostics(); + expect_no_error(); + + EXPECT_EQ(graph.size(), 3); + EXPECT_FALSE(r.output().opposite()); + + auto&& relation = r.relation(); + EXPECT_FALSE(relation.declaration()); + EXPECT_EQ(relation.identifier(), ""); + + auto relation_columns = relation.columns(); + ASSERT_EQ(relation_columns.size(), 1); + { + auto&& column = relation_columns[0]; + EXPECT_EQ(column.identifier(), ""); + EXPECT_FALSE(column.declaration()); + EXPECT_TRUE(column.exported()); + } + + // (values)x2 - union[all] + auto&& union_ = downcast(r.output().owner()); + auto&& left_values = *find_prev(union_.left()); + auto&& right_values = *find_prev(union_.right()); + + auto&& left_columns = left_values.columns(); + ASSERT_EQ(left_columns.size(), 1); + + auto&& right_columns = right_values.columns(); + ASSERT_EQ(right_columns.size(), 1); + + auto&& union_columns = union_.mappings(); + ASSERT_EQ(union_columns.size(), 1); + + auto info = ctxt.test_info(); + auto&& type = info.type_of(union_columns[0].destination()); + EXPECT_EQ(type, ttype::float8 {}); +} + +TEST_F(analyze_query_expression_binary_test, union_corresponding_unsupported) { + invalid(sql_analyzer_code::unsupported_feature, ast::query::binary_expression { + ast::query::table_value_constructor { + ast::scalar::value_constructor { + literal(number("100")), + }, + }, + ast::query::binary_operator::union_, + ast::query::set_quantifier::all, + {}, + ast::query::table_value_constructor { + ast::scalar::value_constructor { + literal(number("100")), + }, + }, + }); +} + +TEST_F(analyze_query_expression_binary_test, union_left_broken) { + invalid(ast::query::binary_expression { + ast::query::table_value_constructor { + ast::scalar::value_constructor { + vref(id("MISSING")), + }, + }, + ast::query::binary_operator::union_, + ast::query::set_quantifier::all, + ast::query::table_value_constructor { + ast::scalar::value_constructor { + literal(number("100")), + }, + }, + }); +} + +TEST_F(analyze_query_expression_binary_test, union_right_broken) { + invalid(ast::query::binary_expression { + ast::query::table_value_constructor { + ast::scalar::value_constructor { + literal(number("100")), + }, + }, + ast::query::binary_operator::union_, + ast::query::set_quantifier::all, + ast::query::table_value_constructor { + ast::scalar::value_constructor { + vref(id("MISSING")), + }, + }, + }); +} + +TEST_F(analyze_query_expression_binary_test, union_inconsistent_columns) { + invalid(sql_analyzer_code::inconsistent_columns, ast::query::binary_expression { + ast::query::table_value_constructor { + ast::scalar::value_constructor { + literal(number("100")), + }, + }, + ast::query::binary_operator::union_, + ast::query::set_quantifier::all, + ast::query::table_value_constructor { + ast::scalar::value_constructor { + literal(number("100")), + literal(number("200")), + }, + }, + }); +} + + +TEST_F(analyze_query_expression_binary_test, union_inconsistent_type) { + auto table_a = install_table("a"); + auto table_b = install_table("b"); + invalid(sql_analyzer_code::inconsistent_type, ast::query::binary_expression { + ast::query::query { + { + ast::query::select_column { vref(id("k")) }, + }, + { + ast::table::table_reference { + id("a"), + }, + }, + }, + ast::query::binary_operator::union_, + ast::query::set_quantifier::all, + ast::query::query { + { + ast::query::select_column { vref(id("v")) }, + }, + { + ast::table::table_reference { + id("b"), + }, + }, + }, + }); +} + +} // namespace mizugaki::analyzer::details diff --git a/test/mizugaki/analyzer/utils.h b/test/mizugaki/analyzer/utils.h index 913acbe..bd92234 100644 --- a/test/mizugaki/analyzer/utils.h +++ b/test/mizugaki/analyzer/utils.h @@ -169,9 +169,9 @@ inline ::takatori::util::optional_ptr find_next(U const& node) { return {}; } -template -inline ::takatori::util::optional_ptr find_prev(U const& node) { - if (auto opposite = node.input().opposite()) { +template +inline ::takatori::util::optional_ptr find_prev(trelation::expression::input_port_type const& port) { + if (auto opposite = port.opposite()) { if (auto ptr = downcast(&opposite->owner())) { return ::takatori::util::optional_ptr { ptr }; } @@ -179,4 +179,9 @@ inline ::takatori::util::optional_ptr find_prev(U const& node) { return {}; } +template +inline ::takatori::util::optional_ptr find_prev(U const& node) { + return find_prev(node.input()); +} + } // namespace mizugaki::analyzer::testing