diff --git a/include/yugawara/compiler_code.h b/include/yugawara/compiler_code.h index 910a7b7..4a4b95f 100644 --- a/include/yugawara/compiler_code.h +++ b/include/yugawara/compiler_code.h @@ -12,6 +12,8 @@ namespace yugawara { enum class compiler_code { /// @brief unknown diagnostic. unknown = 0, + /// @brief the runtime does not support such target. + unsupported_feature, /// @brief input type is not supported in this operation. unsupported_type, /// @brief input type is not distinguished for the overloaded operations. @@ -34,6 +36,7 @@ inline constexpr std::string_view to_string_view(compiler_code value) noexcept { using kind = compiler_code; switch (value) { case kind::unknown: return "unknown"sv; + case kind::unsupported_feature: return "unsupported_feature"sv; case kind::unsupported_type: return "unsupported_type"sv; case kind::ambiguous_type: return "ambiguous_type"sv; case kind::inconsistent_type: return "inconsistent_type"sv; diff --git a/include/yugawara/compiler_options.h b/include/yugawara/compiler_options.h index 92780a4..dfa086b 100644 --- a/include/yugawara/compiler_options.h +++ b/include/yugawara/compiler_options.h @@ -4,10 +4,12 @@ #include -#include #include #include +#include "runtime_feature.h" +#include "restricted_feature.h" + namespace yugawara { /** @@ -21,6 +23,24 @@ class compiler_options { */ static constexpr runtime_feature_set default_runtime_features { runtime_feature_all }; + /** + * @brief the default list of restricted features. + * @see restricted_features() + */ + static constexpr restricted_feature_set default_restricted_features {}; + + /** + * @brief creates a new instance with default options. + * @param runtime_features the supported runtime features + * @param storage_processor the storage element prototype processor for accepting storage element definitions + * @param index_estimator the index estimator for index selection + * @see runtime_features() + * @see restricted_features() + */ + compiler_options( // NOLINT(*-explicit-constructor, *-explicit-conversions) + ::takatori::util::maybe_shared_ptr<::yugawara::storage::prototype_processor> storage_processor, + ::takatori::util::maybe_shared_ptr<::yugawara::analyzer::index_estimator const> index_estimator = {}) noexcept; + /** * @brief creates a new instance with default options. * @param runtime_features the supported runtime features @@ -35,12 +55,23 @@ class compiler_options { /** * @brief returns the available feature set of the target environment. * @return the available features + * @see restricted_features() */ [[nodiscard]] runtime_feature_set& runtime_features() noexcept; /// @copydoc runtime_features() [[nodiscard]] runtime_feature_set const& runtime_features() const noexcept; + /** + * @brief returns the restricted feature set of the target environment. + * @return the restricted features + * @see runtime_feature() + */ + [[nodiscard]] restricted_feature_set& restricted_features() noexcept; + + /// @copydoc restricted_features() + [[nodiscard]] restricted_feature_set const& restricted_features() const noexcept; + /** * @brief returns the storage element processor for handling storage element definitions. * @return the storage element prototype processor @@ -71,6 +102,7 @@ class compiler_options { private: runtime_feature_set runtime_features_ { default_runtime_features }; + restricted_feature_set restricted_features_ { default_restricted_features }; ::takatori::util::maybe_shared_ptr<::yugawara::storage::prototype_processor> storage_processor_ {}; ::takatori::util::maybe_shared_ptr index_estimator_ {}; }; diff --git a/include/yugawara/restricted_feature.h b/include/yugawara/restricted_feature.h new file mode 100644 index 0000000..1605f30 --- /dev/null +++ b/include/yugawara/restricted_feature.h @@ -0,0 +1,157 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include + +namespace yugawara { + +/** + * @brief represents restricted features of runtime. + * @attention If the compilation result includes any of these features, + * the compilation will just fail and report compiler_code::restricted_feature. + * @see runtime_feature + */ +enum class restricted_feature { + + // scalar expressions + + // relation expressions + + /// @brief restrict `buffer` operator. + relation_buffer, + + /// @brief restrict `identify` operator. + relation_identify, + + /// @brief restrict `join_find` operator. + relation_join_find, + + /// @brief restrict `join_scan` operator. + relation_join_scan, + + /// @brief restrict `write` operator with `insert` operations. + relation_write_insert, + + /// @brief restrict `values` operator. + relation_values, + + /// @brief restrict `difference` operator. + relation_difference, + + /// @brief restrict `flatten` operator. + relation_flatten, + + /// @brief restrict `intersection` operator. + relation_intersection, + + // exchanges + + /// @brief restrict `aggregate` exchange. + exchange_aggregate, + + /// @brief restrict `broadcast` exchange. + exchange_broadcast, + + /// @brief restrict `discard` exchange. + exchange_discard, + + /// @brief restrict `forward` exchange. + exchange_forward, + + /// @brief restrict `group` exchange. + exchange_group, + + // statements + + /// @brief restrict `write` statement with delete operation. + statement_write_delete, + + /// @brief restrict `write` statement with update operation. + statement_write_update, +}; + +/** + * @brief a set of intermediate_plan_optimizer_feature. + */ +using restricted_feature_set = ::takatori::util::enum_set< + restricted_feature, + restricted_feature::relation_buffer, + restricted_feature::statement_write_update>; + +/// @brief all scalar expressions of restricted_feature_set. +constexpr restricted_feature_set restricted_feature_scalar_expressions {}; + +/// @brief all relation expressions of restricted_feature_set. +constexpr restricted_feature_set restricted_feature_relation_expressions { + restricted_feature::relation_buffer, + restricted_feature::relation_identify, + restricted_feature::relation_join_find, + restricted_feature::relation_join_scan, + restricted_feature::relation_write_insert, + restricted_feature::relation_values, + restricted_feature::relation_difference, + restricted_feature::relation_flatten, + restricted_feature::relation_intersection, +}; + +/// @brief all exchange steps of restricted_feature_set. +constexpr restricted_feature_set restricted_feature_exchange_steps { + restricted_feature::exchange_aggregate, + restricted_feature::exchange_broadcast, + restricted_feature::exchange_discard, + restricted_feature::exchange_forward, + restricted_feature::exchange_group, +}; + +/// @brief all statements of restricted_feature_set. +constexpr restricted_feature_set restricted_feature_statements { + restricted_feature::statement_write_delete, + restricted_feature::statement_write_update, +}; + +/** + * @brief returns string representation of the value. + * @param value the target value + * @return the corresponded string representation + */ +inline constexpr std::string_view to_string_view(restricted_feature value) noexcept { + using namespace std::string_view_literals; + using kind = restricted_feature; + switch (value) { + case kind::relation_buffer: return "buffer operator"sv; + case kind::relation_identify: return "identify operator"sv; + case kind::relation_join_find: return "join_find operator"sv; + case kind::relation_join_scan: return "join_scan operator"sv; + case kind::relation_write_insert: return "write operator with insert operation"sv; + case kind::relation_values: return "values operator"sv; + case kind::relation_difference: return "difference operator"sv; + case kind::relation_flatten: return "flatten operator"sv; + case kind::relation_intersection: return "intersection operator"sv; + case kind::exchange_aggregate: return "aggregate exchange"sv; + case kind::exchange_broadcast: return "broadcast exchange"sv; + case kind::exchange_discard: return "discard exchange"sv; + case kind::exchange_forward: return "forward exchange"sv; + case kind::exchange_group: return "group exchange"sv; + case kind::statement_write_delete: return "write statement with delete operation"sv; + case kind::statement_write_update: return "write statement with update operation"sv; + } + std::abort(); +} + +/** + * @brief appends string representation of the given value. + * @param out the target output + * @param value the target value + * @return the output + */ +inline std::ostream& operator<<(std::ostream& out, restricted_feature value) { + return out << to_string_view(value); +} + +} // namespace yugawara \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 29f012d..8831f5e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ add_library(yugawara yugawara/compiler_options.cpp yugawara/compiled_info.cpp yugawara/compiler_result.cpp + yugawara/details/collect_restricted_features.cpp # storage information yugawara/storage/relation.cpp diff --git a/src/yugawara/compiler.cpp b/src/yugawara/compiler.cpp index b0e1634..5905867 100644 --- a/src/yugawara/compiler.cpp +++ b/src/yugawara/compiler.cpp @@ -10,8 +10,9 @@ #include #include +#include -#include "storage/resolve_prototype.h" +#include "details/collect_restricted_features.h" namespace yugawara { @@ -110,6 +111,12 @@ class engine { return result_type { diagnostics }; } } + if (!options_.restricted_features().empty()) { + auto diagnostics = details::collect_restricted_features(options_.restricted_features(), *stmt); + if (!diagnostics.empty()) { + return result_type { std::move(diagnostics) }; + } + } expression_analyzer_.resolve(*stmt, true, type_repository_); if (expression_analyzer_.has_diagnostics()) { diff --git a/src/yugawara/compiler_options.cpp b/src/yugawara/compiler_options.cpp index fec3d87..94e102d 100644 --- a/src/yugawara/compiler_options.cpp +++ b/src/yugawara/compiler_options.cpp @@ -6,6 +6,13 @@ namespace yugawara { using ::takatori::util::maybe_shared_ptr; +compiler_options::compiler_options( + maybe_shared_ptr storage_processor, + maybe_shared_ptr index_estimator) noexcept : + storage_processor_ { std::move(storage_processor) }, + index_estimator_ { std::move(index_estimator) } +{} + compiler_options::compiler_options( runtime_feature_set runtime_features, maybe_shared_ptr storage_processor, @@ -23,6 +30,14 @@ runtime_feature_set const& compiler_options::runtime_features() const noexcept { return runtime_features_; } +restricted_feature_set& compiler_options::restricted_features() noexcept { + return restricted_features_; +} + +restricted_feature_set const& compiler_options::restricted_features() const noexcept { + return restricted_features_; +} + maybe_shared_ptr compiler_options::storage_processor() const noexcept { return storage_processor_; } diff --git a/src/yugawara/details/collect_restricted_features.cpp b/src/yugawara/details/collect_restricted_features.cpp new file mode 100644 index 0000000..302a077 --- /dev/null +++ b/src/yugawara/details/collect_restricted_features.cpp @@ -0,0 +1,187 @@ +#include "collect_restricted_features.h" + +#include +#include +#include + +#include + +namespace yugawara::details { + +namespace trelation = ::takatori::relation; +namespace tplan = ::takatori::plan; +namespace tstatement = ::takatori::statement; + +using ::takatori::util::string_builder; + +using diagnostic_type = diagnostic; + +namespace { + +class engine { +public: + static constexpr restricted_feature_set mask_execute_statement + = restricted_feature_scalar_expressions + | restricted_feature_relation_expressions + | restricted_feature_exchange_steps + ; + + static constexpr restricted_feature_set mask_process_step + = restricted_feature_scalar_expressions + | restricted_feature_relation_expressions + ; + + explicit engine(restricted_feature_set features) noexcept : + features_ { features } + {} + + std::vector release() { + return std::move(diagnostics_); + } + + void process(tstatement::statement const& statement) { + tstatement::dispatch(*this, statement); + } + + void process(tplan::step const& step) { + tplan::dispatch(*this, step); + } + + void process(trelation::expression const& expression) { + trelation::step::dispatch(*this, expression); + } + + void operator()(tstatement::statement const&) { + // do nothing + } + + void operator()(tstatement::execute const& statement) { + // check if sub-elements can be restricted: execute -> (step, relation, scalar) + auto restricted = features_ & mask_execute_statement; + if (restricted.empty()) { + return; + } + + // check steps + for (auto&& sub : statement.execution_plan()) { + process(sub); + } + } + + void operator()(tstatement::write const& statement) { + if (statement.operator_kind() == tstatement::write_kind::update) { + validate(restricted_feature::statement_write_update, statement.region()); + } + if (statement.operator_kind() == tstatement::write_kind::delete_) { + validate(restricted_feature::statement_write_delete, statement.region()); + } + } + + void operator()(tplan::step const&) { + // do nothing + } + + void operator()(tplan::process const& step) { + auto restricted = features_ & mask_process_step; + if (restricted.empty()) { + return; + } + + // check relation expressions + for (auto&& sub : step.operators()) { + process(sub); + } + } + + void operator()(tplan::aggregate const&) { + validate(restricted_feature::exchange_aggregate, {}); + } + + void operator()(tplan::broadcast const&) { + validate(restricted_feature::exchange_broadcast, {}); + } + + void operator()(tplan::discard const&) { + validate(restricted_feature::exchange_discard, {}); + } + + void operator()(tplan::forward const&) { + validate(restricted_feature::exchange_forward, {}); + } + + void operator()(tplan::group const&) { + validate(restricted_feature::exchange_group, {}); + } + + void operator()(trelation::expression const&) { + // do nothing + } + + void operator()(trelation::buffer const& expression) { + validate(restricted_feature::relation_buffer, expression.region()); + } + + void operator()(trelation::identify const& expression) { + validate(restricted_feature::relation_identify, expression.region()); + } + + void operator()(trelation::join_find const& expression) { + validate(restricted_feature::relation_join_find, expression.region()); + } + + void operator()(trelation::join_scan const& expression) { + validate(restricted_feature::relation_join_scan, expression.region()); + } + + void operator()(trelation::write const& expression) { + if (expression.operator_kind() == trelation::write_kind::insert || + expression.operator_kind() == trelation::write_kind::insert_overwrite || + expression.operator_kind() == trelation::write_kind::insert_skip) { + validate(restricted_feature::relation_write_insert, expression.region()); + } + } + + void operator()(trelation::values const& expression) { + validate(restricted_feature::relation_values, expression.region()); + } + + void operator()(trelation::step::difference const& expression) { + validate(restricted_feature::relation_difference, expression.region()); + } + + void operator()(trelation::step::flatten const& expression) { + validate(restricted_feature::relation_flatten, expression.region()); + } + + void operator()(trelation::step::intersection const& expression) { + validate(restricted_feature::relation_intersection, expression.region()); + } + +private: + restricted_feature_set const features_; // NOLINT(*-avoid-const-or-ref-data-members) const for safety + std::vector diagnostics_ {}; + + void validate(restricted_feature feature, takatori::document::region region) { + if (features_.contains(feature)) { + diagnostics_.emplace_back( + compiler_code::unsupported_feature, + string_builder {} + << feature + << " is unsupported" + << string_builder::to_string, + region); + } + } +}; + +} // namespace + + +std::vector +collect_restricted_features(restricted_feature_set features, tstatement::statement const& statement) { + engine e { features }; + e.process(statement); + return e.release(); +} + +} // namespace yugawara::details \ No newline at end of file diff --git a/src/yugawara/details/collect_restricted_features.h b/src/yugawara/details/collect_restricted_features.h new file mode 100644 index 0000000..a8195ae --- /dev/null +++ b/src/yugawara/details/collect_restricted_features.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include + +#include +#include +#include + +namespace yugawara::details { + +/** + * @brief collects restricted features from the given statement. + * @param features the restricted features + * @param statement the target statement + * @return the diagnostics by detected restricted features + * @return empty if there are no restricted features in the statement + */ +[[nodiscard]] std::vector> collect_restricted_features( + restricted_feature_set features, + ::takatori::statement::statement const& statement); + +} // namespace yugawara::details diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 214a30c..20f9309 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -26,6 +26,7 @@ endfunction (add_test_executable) # root add_test_executable(yugawara/compiler_test.cpp) +add_test_executable(yugawara/details/collect_restricted_features_test.cpp) # type system add_test_executable(yugawara/type/type_category_test.cpp) diff --git a/test/yugawara/compiler_test.cpp b/test/yugawara/compiler_test.cpp index 3a5cc28..fe51c15 100644 --- a/test/yugawara/compiler_test.cpp +++ b/test/yugawara/compiler_test.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -34,8 +35,6 @@ using namespace ::yugawara::testing; namespace statement = ::takatori::statement; -using ::takatori::scalar::comparison_operator; - class compiler_test: public ::testing::Test { protected: binding::factory bindings {}; @@ -696,4 +695,27 @@ TEST_F(compiler_test, fix_left_join_cond) { dump(result); } +TEST_F(compiler_test, restricted_feature) { + relation::graph_type r; + auto c0 = bindings.stream_variable("c0"); + auto c1 = bindings.stream_variable("c1"); + auto&& in = r.insert(relation::values { + { c0, c1 }, + {}, + }); + auto&& out = r.insert(relation::emit { c0 }); + in.output() >> out.input(); + + auto opts = options(); + opts.restricted_features() += { + restricted_feature::relation_values + }; + auto result = compiler()(opts, std::move(r)); + EXPECT_FALSE(result); + + auto diagnostics = result.diagnostics(); + ASSERT_EQ(diagnostics.size(), 1); + EXPECT_EQ(diagnostics[0].code(), compiler_code::unsupported_feature); +} + } // namespace yugawara diff --git a/test/yugawara/details/collect_restricted_features_test.cpp b/test/yugawara/details/collect_restricted_features_test.cpp new file mode 100644 index 0000000..ab2d3b5 --- /dev/null +++ b/test/yugawara/details/collect_restricted_features_test.cpp @@ -0,0 +1,199 @@ +#include + +#include + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +#include + +namespace yugawara::details { + +// import test utils +using namespace ::yugawara::testing; + +class collect_restricted_features_test : public ::testing::Test { +protected: + binding::factory factory; + + std::shared_ptr storages = std::make_shared(); + std::shared_ptr indices {}; + + std::shared_ptr t0 = storages->add_table({ + "T0", + { + { "C0", t::int4() }, + { "C1", t::int4() }, + { "C2", t::int4() }, + }, + }); + storage::column const& t0c0 = t0->columns()[0]; + storage::column const& t0c1 = t0->columns()[1]; + storage::column const& t0c2 = t0->columns()[2]; + + std::shared_ptr i0 = storages->add_index({ t0, "I0", }); + + void check(diagnostic const& d, restricted_feature f) { + EXPECT_EQ(d.code(), compiler_code::unsupported_feature); + EXPECT_NE(d.message().find(std::string { to_string_view(f) }), std::string::npos) << d.message(); + } + + ::takatori::statement::execute make_statement(relation::expression&& expression) { + ::takatori::relation::graph_type operators {}; + operators.insert(std::move(expression)); + + ::takatori::plan::graph_type steps {}; + steps.insert(plan::process { std::move(operators) }); + + return ::takatori::statement::execute { + std::move(steps), + }; + } + + ::takatori::statement::execute make_statement(plan::step&& step) { + ::takatori::plan::graph_type steps {}; + steps.insert(std::move(step)); + + return ::takatori::statement::execute { + std::move(steps), + }; + } +}; + +TEST_F(collect_restricted_features_test, relation_values_restricted) { + auto result = collect_restricted_features( + { restricted_feature::relation_values }, + make_statement(relation::values { + {}, + {}, + })); + ASSERT_EQ(result.size(), 1); + check(result[0], restricted_feature::relation_values); +} + +TEST_F(collect_restricted_features_test, relation_values_not_restricted) { + auto result = collect_restricted_features( + { restricted_feature::relation_buffer }, + make_statement(relation::values { + {}, + {}, + })); + ASSERT_EQ(result.size(), 0); +} + +TEST_F(collect_restricted_features_test, relation_write_insert_restricted) { + auto result = collect_restricted_features( + { restricted_feature::relation_write_insert }, + make_statement(relation::write { + relation::write_kind::insert, + factory(*i0), + {}, + {}, + })); + ASSERT_EQ(result.size(), 1); + check(result[0], restricted_feature::relation_write_insert); +} + +TEST_F(collect_restricted_features_test, relation_write_insert_overwrite_restricted) { + auto result = collect_restricted_features( + { restricted_feature::relation_write_insert }, + make_statement(relation::write { + relation::write_kind::insert_overwrite, + factory(*i0), + {}, + {}, + })); + ASSERT_EQ(result.size(), 1); + check(result[0], restricted_feature::relation_write_insert); +} + +TEST_F(collect_restricted_features_test, relation_write_insert_skip_restricted) { + auto result = collect_restricted_features( + { restricted_feature::relation_write_insert }, + make_statement(relation::write { + relation::write_kind::insert_skip, + factory(*i0), + {}, + {}, + })); + ASSERT_EQ(result.size(), 1); + check(result[0], restricted_feature::relation_write_insert); +} + +TEST_F(collect_restricted_features_test, relation_write_insert_not_restricted) { + auto result = collect_restricted_features( + { restricted_feature::relation_write_insert }, + make_statement(relation::write { + relation::write_kind::update, + factory(*i0), + {}, + {}, + })); + ASSERT_EQ(result.size(), 0); +} + +TEST_F(collect_restricted_features_test, exchange_forward_restricted) { + auto result = collect_restricted_features( + { restricted_feature::exchange_forward }, + make_statement(plan::forward {})); + ASSERT_EQ(result.size(), 1); + check(result[0], restricted_feature::exchange_forward); +} + +TEST_F(collect_restricted_features_test, exchange_forward_not_restricted) { + auto result = collect_restricted_features( + { restricted_feature::exchange_group }, + make_statement(plan::forward {})); + ASSERT_EQ(result.size(), 0); +} + +TEST_F(collect_restricted_features_test, statement_write_update_restricted) { + auto result = collect_restricted_features( + { restricted_feature::statement_write_update }, + ::takatori::statement::write { + ::takatori::statement::write_kind::update, + factory(*i0), + {}, + {}, + }); + ASSERT_EQ(result.size(), 1); + check(result[0], restricted_feature::statement_write_update); +} + +TEST_F(collect_restricted_features_test, statement_write_delete_restricted) { + auto result = collect_restricted_features( + { restricted_feature::statement_write_delete }, + ::takatori::statement::write { + ::takatori::statement::write_kind::delete_, + factory(*i0), + {}, + {}, + }); + ASSERT_EQ(result.size(), 1); + check(result[0], restricted_feature::statement_write_delete); +} + +TEST_F(collect_restricted_features_test, statement_write_update_not_restricted) { + auto result = collect_restricted_features( + { restricted_feature::statement_write_update }, + make_statement(relation::write { + relation::write_kind::update, + factory(*i0), + {}, + {}, + })); + ASSERT_EQ(result.size(), 0); +} + +} // namespace yugawara::details