diff --git a/include/mizugaki/analyzer/sql_analyzer_code.h b/include/mizugaki/analyzer/sql_analyzer_code.h index faae16a..82d6cba 100644 --- a/include/mizugaki/analyzer/sql_analyzer_code.h +++ b/include/mizugaki/analyzer/sql_analyzer_code.h @@ -116,6 +116,8 @@ enum class sql_analyzer_code { ambiguous_type, /// @brief the set of type input is inconsistent for this operation. inconsistent_type, + /// @brief the declared elements exceeds the limit of count. + exceed_number_of_elements, /// @brief the referring variable is not resolved (internal error). unresolved_variable, /// @brief the number of values is wrong (internal error). @@ -188,6 +190,7 @@ inline constexpr std::string_view to_string_view(sql_analyzer_code value) noexce case kind::ambiguous_type: return "ambiguous_type"sv; case kind::inconsistent_type: return "inconsistent_type"sv; + case kind::exceed_number_of_elements: return "exceed_number_of_elements"sv; case kind::unresolved_variable: return "unresolved_variable"sv; case kind::inconsistent_elements: return "inconsistent_elements"sv; } diff --git a/include/mizugaki/analyzer/sql_analyzer_options.h b/include/mizugaki/analyzer/sql_analyzer_options.h index 0f34d76..a1dba80 100644 --- a/include/mizugaki/analyzer/sql_analyzer_options.h +++ b/include/mizugaki/analyzer/sql_analyzer_options.h @@ -144,6 +144,37 @@ class sql_analyzer_options { */ static constexpr std::string_view default_advance_sequence_function_name { "nextval" }; + /** + * @brief the default value of maximum number of columns in table definition. + * @see max_max_table_columns() + */ + static constexpr size_type default_max_table_columns = 1'000; + + /** + * @brief the default value of maximum number of columns in the select list. + * @see max_select_elements() + */ + static constexpr size_type default_max_select_elements = 1'000; + + /** + * @brief the default value of maximum number of rows in table value constructor. + * @see max_table_value_constructor_rows() + */ + static constexpr size_type default_max_table_value_constructor_rows = 1'000; + + /** + * @brief the default value of maximum number of columns in row value constructor. + * @see max_row_value_constructor_columns() + */ + static constexpr size_type default_max_row_value_constructor_columns = 1'000; + + /** + * @brief the default value of maximum number of alternatives in case expressions. + * @see max_case_alternatives() + * @note this also restricts the number of alternatives of `COALESCE`. + */ + static constexpr size_type default_max_case_alternatives = 300; + /** * @brief the zone offset unit type. */ @@ -483,6 +514,71 @@ class sql_analyzer_options { return system_zone_offset_; } + /** + * @brief returns the maximum number of columns in table definition. + * @return the max number of columns in table definition + */ + [[nodiscard]] size_type& max_table_columns() noexcept { + return max_table_columns_; + } + + /// @copydoc max_table_columns() + [[nodiscard]] size_type const& max_table_columns() const noexcept { + return max_table_columns_; + } + + /** + * @brief returns the maximum number of columns in the select list. + * @return the max number of columns in the select list + */ + [[nodiscard]] size_type& max_select_elements() noexcept { + return max_select_elements_; + } + + /// @copydoc max_select_elements() + [[nodiscard]] size_type const& max_select_elements() const noexcept { + return max_select_elements_; + } + + /** + * @brief returns the maximum number of rows in table value constructor. + * @return the max number of rows in table value constructor + */ + [[nodiscard]] size_type& max_table_value_constructor_rows() noexcept { + return max_table_value_constructor_rows_; + } + + /// @copydoc max_table_value_constructor_rows() + [[nodiscard]] size_type const& max_table_value_constructor_rows() const noexcept { + return max_table_value_constructor_rows_; + } + + /** + * @brief returns the maximum number of columns in row value constructor. + * @return the max number of columns in row value constructor + */ + [[nodiscard]] size_type& max_row_value_constructor_columns() noexcept { + return max_row_value_constructor_columns_; + } + + /// @copydoc max_row_value_constructor_columns() + [[nodiscard]] size_type const& max_row_value_constructor_columns() const noexcept { + return max_row_value_constructor_columns_; + } + + /** + * @brief returns the maximum number of alternatives in case expressions. + * @return the max number of alternatives in case expressions + */ + [[nodiscard]] size_type& max_case_alternatives() noexcept { + return max_case_alternatives_; + } + + /// @copydoc max_case_alternatives() + [[nodiscard]] size_type const& max_case_alternatives() const noexcept { + return max_case_alternatives_; + } + private: ::takatori::util::maybe_shared_ptr<::yugawara::schema::catalog const> catalog_; ::takatori::util::maybe_shared_ptr<::yugawara::schema::search_path const> schema_search_path_; @@ -511,6 +607,12 @@ class sql_analyzer_options { std::string_view advance_sequence_function_name_ { default_advance_sequence_function_name }; zone_offset_type system_zone_offset_ { default_system_zone_offset }; + + size_type max_table_columns_ { default_max_table_columns }; + size_type max_select_elements_ { default_max_select_elements }; + size_type max_table_value_constructor_rows_ { default_max_table_value_constructor_rows }; + size_type max_row_value_constructor_columns_ { default_max_row_value_constructor_columns }; + size_type max_case_alternatives_ { default_max_case_alternatives }; }; } // namespace mizugaki::analyzer diff --git a/src/mizugaki/analyzer/details/analyze_query_expression.cpp b/src/mizugaki/analyzer/details/analyze_query_expression.cpp index 57543da..0adbfb3 100644 --- a/src/mizugaki/analyzer/details/analyze_query_expression.cpp +++ b/src/mizugaki/analyzer/details/analyze_query_expression.cpp @@ -164,6 +164,16 @@ class engine { relation_info info; optional_ptr select_columns {}; { + if (expr.elements().size() > context_.options()->max_select_elements()) { + context_.report( + sql_analyzer_code::exceed_number_of_elements, + string_builder {} + << "too many select elements: " + << "must be less than or equal to " << context_.options()->max_select_elements() + << string_builder::to_string, + expr.region()); + return {}; + } auto&& project = graph_.emplace(std::vector {}); project.columns().reserve(std::min(expr.elements().size(), std::size_t { 8 })); if (!expr.elements().empty()) { @@ -424,10 +434,33 @@ class engine { ast::query::table_value_constructor const& expr, optional_ptr const& parent, row_value_context const& val) { + if (expr.elements().size() > context_.options()->max_table_value_constructor_rows()) { + context_.report( + sql_analyzer_code::exceed_number_of_elements, + string_builder {} + << "too many table value constructor elements: " + << "must be less than or equal to " + << context_.options()->max_table_value_constructor_rows() + << string_builder::to_string, + expr.region()); + return {}; + } + // compute row values only which is a row value constructor auto rows = create_vector(expr.elements().size()); for (auto&& row_value : expr.elements()) { if (auto row_ctor = as_row_value_constructor(*row_value)) { + if (row_ctor->elements().size() > context_.options()->max_row_value_constructor_columns()) { + context_.report( + sql_analyzer_code::exceed_number_of_elements, + string_builder {} + << "too many row value constructor elements: " + << "must be less than or equal to " + << context_.options()->max_row_value_constructor_columns() + << string_builder::to_string, + row_ctor->region()); + return {}; + } auto row = create_ref_vector(row_ctor->elements().size()); std::size_t index = 0; for (auto&& elem_expr : row_ctor->elements()) { diff --git a/src/mizugaki/analyzer/details/analyze_scalar_expression.cpp b/src/mizugaki/analyzer/details/analyze_scalar_expression.cpp index e11ab21..6b212cb 100644 --- a/src/mizugaki/analyzer/details/analyze_scalar_expression.cpp +++ b/src/mizugaki/analyzer/details/analyze_scalar_expression.cpp @@ -145,6 +145,16 @@ class engine { expr.region()); return {}; } + if (expr.when_clauses().size() > context_.options()->max_case_alternatives()) { + context_.report( + sql_analyzer_code::exceed_number_of_elements, + string_builder {} + << "too many alternatives in CASE expression: " + << "must be less than or equal to " << context_.options()->max_case_alternatives() + << string_builder::to_string, + expr.region()); + return {}; + } std::optional variable {}; if (expr.operand()) { @@ -610,6 +620,17 @@ class engine { values.region()); return {}; } + if (values.elements().size() > context_.options()->max_table_value_constructor_rows()) { + context_.report( + sql_analyzer_code::exceed_number_of_elements, + string_builder {} + << "IN predicate with too many elements: " + << "must be less than or equal to " + << context_.options()->max_table_value_constructor_rows() + << string_builder::to_string, + values.region()); + return {}; + } auto left = process(*expr.left(), {}); if (!left) { @@ -822,6 +843,16 @@ class engine { expr.region()); return {}; } + if (expr.arguments().size() > context_.options()->max_case_alternatives()) { + context_.report( + sql_analyzer_code::exceed_number_of_elements, + string_builder {} + << "too many alternatives in COALESCE expression: " + << "must be less than or equal to " << context_.options()->max_case_alternatives() + << string_builder::to_string, + expr.region()); + return {}; + } ::takatori::util::reference_vector operands {}; operands.reserve(expr.arguments().size()); diff --git a/src/mizugaki/analyzer/details/analyze_statement.cpp b/src/mizugaki/analyzer/details/analyze_statement.cpp index 77e1696..ea41998 100644 --- a/src/mizugaki/analyzer/details/analyze_statement.cpp +++ b/src/mizugaki/analyzer/details/analyze_statement.cpp @@ -785,6 +785,17 @@ class engine { } } table_columns.push_back(std::move(prototype)); + if (table_columns.size() > context_.options()->max_table_columns()) { + context_.report( + sql_analyzer_code::exceed_number_of_elements, + string_builder {} + << "too many columns in table definition: " + << table_name << " - " + << "must be less than or equal to " << context_.options()->max_table_columns() + << string_builder::to_string, + stmt.region()); + return {}; + } } if (table_columns.empty()) { context_.report(