mirror of
https://github.com/rbock/sqlpp11.git
synced 2024-11-16 04:47:18 +08:00
Added a table consistency checks to several dynamic methods
This commit is contained in:
parent
1f3e611fdf
commit
0ffd93108b
@ -72,6 +72,11 @@ namespace sqlpp
|
|||||||
using _new_statement_t = typename _policies_insert_t<Needle, Replacement, Table, InsertValueList>::type;
|
using _new_statement_t = typename _policies_insert_t<Needle, Replacement, Table, InsertValueList>::type;
|
||||||
|
|
||||||
using _table_set = typename _table_t::_table_set;
|
using _table_set = typename _table_t::_table_set;
|
||||||
|
|
||||||
|
using _known_tables = detail::make_joined_set_t<typename _table_t::_table_set>;
|
||||||
|
|
||||||
|
template<typename Expression>
|
||||||
|
using _no_unknown_tables = detail::is_subset_of<typename Expression::_table_set, _known_tables>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
#include <sqlpp11/prepared_remove.h>
|
#include <sqlpp11/prepared_remove.h>
|
||||||
#include <sqlpp11/vendor/noop.h>
|
#include <sqlpp11/vendor/noop.h>
|
||||||
#include <sqlpp11/vendor/single_table.h>
|
#include <sqlpp11/vendor/single_table.h>
|
||||||
|
#include <sqlpp11/vendor/extra_tables.h>
|
||||||
#include <sqlpp11/vendor/using.h>
|
#include <sqlpp11/vendor/using.h>
|
||||||
#include <sqlpp11/vendor/where.h>
|
#include <sqlpp11/vendor/where.h>
|
||||||
#include <sqlpp11/vendor/policy_update.h>
|
#include <sqlpp11/vendor/policy_update.h>
|
||||||
@ -49,6 +50,7 @@ namespace sqlpp
|
|||||||
template<typename Db = void,
|
template<typename Db = void,
|
||||||
typename Table = vendor::no_single_table_t,
|
typename Table = vendor::no_single_table_t,
|
||||||
typename Using = vendor::no_using_t,
|
typename Using = vendor::no_using_t,
|
||||||
|
typename ExtraTables = vendor::no_extra_tables_t,
|
||||||
typename Where = vendor::no_where_t
|
typename Where = vendor::no_where_t
|
||||||
>
|
>
|
||||||
struct remove_policies_t
|
struct remove_policies_t
|
||||||
@ -56,12 +58,14 @@ namespace sqlpp
|
|||||||
using _database_t = Db;
|
using _database_t = Db;
|
||||||
using _table_t = Table;
|
using _table_t = Table;
|
||||||
using _using_t = Using;
|
using _using_t = Using;
|
||||||
|
using _extra_tables_t = ExtraTables;
|
||||||
using _where_t = Where;
|
using _where_t = Where;
|
||||||
|
|
||||||
using _statement_t = remove_t<Db, Table, Using, Where>;
|
using _statement_t = remove_t<Db, Table, Using, ExtraTables, Where>;
|
||||||
|
|
||||||
struct _methods_t:
|
struct _methods_t:
|
||||||
public _using_t::template _methods_t<remove_policies_t>,
|
public _using_t::template _methods_t<remove_policies_t>,
|
||||||
|
public _extra_tables_t::template _methods_t<remove_policies_t>,
|
||||||
public _where_t::template _methods_t<remove_policies_t>
|
public _where_t::template _methods_t<remove_policies_t>
|
||||||
{};
|
{};
|
||||||
|
|
||||||
@ -72,7 +76,12 @@ namespace sqlpp
|
|||||||
};
|
};
|
||||||
|
|
||||||
template<typename Needle, typename Replacement>
|
template<typename Needle, typename Replacement>
|
||||||
using _new_statement_t = typename _policies_update_t<Needle, Replacement, Table, Using, Where>::type;
|
using _new_statement_t = typename _policies_update_t<Needle, Replacement, Table, Using, ExtraTables, Where>::type;
|
||||||
|
|
||||||
|
using _known_tables = detail::make_joined_set_t<typename _table_t::_table_set, typename _using_t::_table_set, typename _extra_tables_t::_table_set>;
|
||||||
|
|
||||||
|
template<typename Expression>
|
||||||
|
using _no_unknown_tables = detail::is_subset_of<typename Expression::_table_set, _known_tables>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +96,7 @@ namespace sqlpp
|
|||||||
using _database_t = typename _policies_t::_database_t;
|
using _database_t = typename _policies_t::_database_t;
|
||||||
using _table_t = typename _policies_t::_table_t;
|
using _table_t = typename _policies_t::_table_t;
|
||||||
using _using_t = typename _policies_t::_using_t;
|
using _using_t = typename _policies_t::_using_t;
|
||||||
|
using _extra_tables_t = typename _policies_t::_extra_tables_t;
|
||||||
using _where_t = typename _policies_t::_where_t;
|
using _where_t = typename _policies_t::_where_t;
|
||||||
|
|
||||||
using _is_dynamic = typename std::conditional<std::is_same<_database_t, void>::value, std::false_type, std::true_type>::type;
|
using _is_dynamic = typename std::conditional<std::is_same<_database_t, void>::value, std::false_type, std::true_type>::type;
|
||||||
|
@ -255,13 +255,8 @@ namespace sqlpp
|
|||||||
static constexpr bool value = ::sqlpp::detail::is_subset_of<typename A::_table_set, typename _from_t::_table_set>::value;
|
static constexpr bool value = ::sqlpp::detail::is_subset_of<typename A::_table_set, typename _from_t::_table_set>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Execute
|
void _check_consistency() const
|
||||||
template<typename Database>
|
|
||||||
auto _run(Database& db) const
|
|
||||||
-> result_t<decltype(db.select(*this)), _result_row_t<Database>>
|
|
||||||
{
|
{
|
||||||
#warning: need to check in add_xy method as well
|
|
||||||
#warning: need add_wxy_without_table_check
|
|
||||||
static_assert(is_table_subset_of_from<_flag_list_t>::value, "flags require additional tables in from()");
|
static_assert(is_table_subset_of_from<_flag_list_t>::value, "flags require additional tables in from()");
|
||||||
static_assert(is_table_subset_of_from<_column_list_t>::value, "selected columns require additional tables in from()");
|
static_assert(is_table_subset_of_from<_column_list_t>::value, "selected columns require additional tables in from()");
|
||||||
static_assert(is_table_subset_of_from<_where_t>::value, "where() expression requires additional tables in from()");
|
static_assert(is_table_subset_of_from<_where_t>::value, "where() expression requires additional tables in from()");
|
||||||
@ -271,6 +266,14 @@ namespace sqlpp
|
|||||||
static_assert(is_table_subset_of_from<_limit_t>::value, "limit() expression requires additional tables in from()");
|
static_assert(is_table_subset_of_from<_limit_t>::value, "limit() expression requires additional tables in from()");
|
||||||
static_assert(is_table_subset_of_from<_offset_t>::value, "offset() expression requires additional tables in from()");
|
static_assert(is_table_subset_of_from<_offset_t>::value, "offset() expression requires additional tables in from()");
|
||||||
static_assert(not _table_set::size::value, "one sub expression contains tables which are not in the from()");
|
static_assert(not _table_set::size::value, "one sub expression contains tables which are not in the from()");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute
|
||||||
|
template<typename Database>
|
||||||
|
auto _run(Database& db) const
|
||||||
|
-> result_t<decltype(db.select(*this)), _result_row_t<Database>>
|
||||||
|
{
|
||||||
|
_check_consistency();
|
||||||
static_assert(_get_static_no_of_parameters() == 0, "cannot run select directly with parameters, use prepare instead");
|
static_assert(_get_static_no_of_parameters() == 0, "cannot run select directly with parameters, use prepare instead");
|
||||||
|
|
||||||
return {db.select(*this), get_dynamic_names()};
|
return {db.select(*this), get_dynamic_names()};
|
||||||
@ -281,6 +284,7 @@ namespace sqlpp
|
|||||||
auto _prepare(Database& db) const
|
auto _prepare(Database& db) const
|
||||||
-> prepared_select_t<Database, select_t>
|
-> prepared_select_t<Database, select_t>
|
||||||
{
|
{
|
||||||
|
_check_consistency();
|
||||||
|
|
||||||
return {{}, get_dynamic_names(), db.prepare_select(*this)};
|
return {{}, get_dynamic_names(), db.prepare_select(*this)};
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,12 @@ namespace sqlpp
|
|||||||
|
|
||||||
template<typename Needle, typename Replacement>
|
template<typename Needle, typename Replacement>
|
||||||
using _new_statement_t = typename _policies_update_t<Needle, Replacement, Table, UpdateList, Where>::type;
|
using _new_statement_t = typename _policies_update_t<Needle, Replacement, Table, UpdateList, Where>::type;
|
||||||
|
|
||||||
|
using _known_tables = detail::make_joined_set_t<typename _table_t::_table_set>;
|
||||||
|
|
||||||
|
template<typename Expression>
|
||||||
|
using _no_unknown_tables = detail::is_subset_of<typename Expression::_table_set, _known_tables>;
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
include/sqlpp11/vendor/group_by.h
vendored
7
include/sqlpp11/vendor/group_by.h
vendored
@ -70,10 +70,17 @@ namespace sqlpp
|
|||||||
struct _methods_t
|
struct _methods_t
|
||||||
{
|
{
|
||||||
template<typename Expression>
|
template<typename Expression>
|
||||||
|
void add_group_by_ntc(Expression expression)
|
||||||
|
{
|
||||||
|
add_group_by<Expression, std::false_type>(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Expression, typename TableCheckRequired = std::true_type>
|
||||||
void add_group_by(Expression expression)
|
void add_group_by(Expression expression)
|
||||||
{
|
{
|
||||||
static_assert(_is_dynamic::value, "add_group_by must not be called for static group_by");
|
static_assert(_is_dynamic::value, "add_group_by must not be called for static group_by");
|
||||||
static_assert(is_expression_t<Expression>::value, "invalid expression argument in add_group_by()");
|
static_assert(is_expression_t<Expression>::value, "invalid expression argument in add_group_by()");
|
||||||
|
static_assert(TableCheckRequired::value or Policies::template _no_unknown_tables<Expression>::value, "expression uses tables unknown to this statement in add_group_by()");
|
||||||
|
|
||||||
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t, _is_dynamic, is_expression_t<Expression>>;
|
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t, _is_dynamic, is_expression_t<Expression>>;
|
||||||
|
|
||||||
|
7
include/sqlpp11/vendor/having.h
vendored
7
include/sqlpp11/vendor/having.h
vendored
@ -67,10 +67,17 @@ namespace sqlpp
|
|||||||
struct _methods_t
|
struct _methods_t
|
||||||
{
|
{
|
||||||
template<typename Expression>
|
template<typename Expression>
|
||||||
|
void add_having_ntc(Expression expression)
|
||||||
|
{
|
||||||
|
add_having<Expression, std::false_type>(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Expression, typename TableCheckRequired = std::true_type>
|
||||||
void add_having(Expression expression)
|
void add_having(Expression expression)
|
||||||
{
|
{
|
||||||
static_assert(_is_dynamic::value, "add_having must not be called for static having");
|
static_assert(_is_dynamic::value, "add_having must not be called for static having");
|
||||||
static_assert(is_expression_t<Expression>::value, "invalid expression argument in add_having()");
|
static_assert(is_expression_t<Expression>::value, "invalid expression argument in add_having()");
|
||||||
|
static_assert(TableCheckRequired::value or Policies::template _no_unknown_tables<Expression>::value, "expression uses tables unknown to this statement in add_having()");
|
||||||
|
|
||||||
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t, _is_dynamic, is_expression_t<Expression>>;
|
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t, _is_dynamic, is_expression_t<Expression>>;
|
||||||
|
|
||||||
|
1
include/sqlpp11/vendor/limit.h
vendored
1
include/sqlpp11/vendor/limit.h
vendored
@ -93,6 +93,7 @@ namespace sqlpp
|
|||||||
template<typename Limit>
|
template<typename Limit>
|
||||||
void set_limit(Limit value)
|
void set_limit(Limit value)
|
||||||
{
|
{
|
||||||
|
// FIXME: Make sure that Limit does not require external tables? Need to read up on SQL
|
||||||
using arg_t = typename wrap_operand<Limit>::type;
|
using arg_t = typename wrap_operand<Limit>::type;
|
||||||
static_cast<typename Policies::_statement_t*>(this)->_limit._value = arg_t{value};
|
static_cast<typename Policies::_statement_t*>(this)->_limit._value = arg_t{value};
|
||||||
static_cast<typename Policies::_statement_t*>(this)->_limit._initialized = true;
|
static_cast<typename Policies::_statement_t*>(this)->_limit._initialized = true;
|
||||||
|
1
include/sqlpp11/vendor/offset.h
vendored
1
include/sqlpp11/vendor/offset.h
vendored
@ -92,6 +92,7 @@ namespace sqlpp
|
|||||||
template<typename Offset>
|
template<typename Offset>
|
||||||
void set_offset(Offset value)
|
void set_offset(Offset value)
|
||||||
{
|
{
|
||||||
|
// FIXME: Make sure that Offset does not require external tables? Need to read up on SQL
|
||||||
using arg_t = typename wrap_operand<Offset>::type;
|
using arg_t = typename wrap_operand<Offset>::type;
|
||||||
static_cast<typename Policies::_statement_t*>(this)->_offset._value = arg_t{value};
|
static_cast<typename Policies::_statement_t*>(this)->_offset._value = arg_t{value};
|
||||||
static_cast<typename Policies::_statement_t*>(this)->_offset._initialized = true;
|
static_cast<typename Policies::_statement_t*>(this)->_offset._initialized = true;
|
||||||
|
7
include/sqlpp11/vendor/order_by.h
vendored
7
include/sqlpp11/vendor/order_by.h
vendored
@ -69,10 +69,17 @@ namespace sqlpp
|
|||||||
struct _methods_t
|
struct _methods_t
|
||||||
{
|
{
|
||||||
template<typename Expression>
|
template<typename Expression>
|
||||||
|
void add_order_by_ntc(Expression expression)
|
||||||
|
{
|
||||||
|
add_order_by<Expression, std::false_type>(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Expression, typename TableCheckRequired = std::true_type>
|
||||||
void add_order_by(Expression expression)
|
void add_order_by(Expression expression)
|
||||||
{
|
{
|
||||||
static_assert(_is_dynamic::value, "add_order_by must not be called for static order_by");
|
static_assert(_is_dynamic::value, "add_order_by must not be called for static order_by");
|
||||||
static_assert(is_sort_order_t<Expression>::value, "invalid expression argument in add_order_by()");
|
static_assert(is_sort_order_t<Expression>::value, "invalid expression argument in add_order_by()");
|
||||||
|
static_assert(TableCheckRequired::value or Policies::template _no_unknown_tables<Expression>::value, "expression uses tables unknown to this statement in add_order_by()");
|
||||||
|
|
||||||
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t, _is_dynamic, is_sort_order_t<Expression>>;
|
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t, _is_dynamic, is_sort_order_t<Expression>>;
|
||||||
|
|
||||||
|
6
include/sqlpp11/vendor/select_column_list.h
vendored
6
include/sqlpp11/vendor/select_column_list.h
vendored
@ -188,15 +188,15 @@ namespace sqlpp
|
|||||||
template<typename NamedExpression>
|
template<typename NamedExpression>
|
||||||
void add_column_ntc(NamedExpression namedExpression)
|
void add_column_ntc(NamedExpression namedExpression)
|
||||||
{
|
{
|
||||||
add_column<NamedExpression, std::true_type>(namedExpression);
|
add_column<NamedExpression, std::false_type>(namedExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename NamedExpression, typename Ntc = std::false_type>
|
template<typename NamedExpression, typename TableCheckRequired = std::true_type>
|
||||||
void add_column(NamedExpression namedExpression)
|
void add_column(NamedExpression namedExpression)
|
||||||
{
|
{
|
||||||
static_assert(_is_dynamic::value, "add_column can only be called for dynamic_column");
|
static_assert(_is_dynamic::value, "add_column can only be called for dynamic_column");
|
||||||
static_assert(is_named_expression_t<NamedExpression>::value, "invalid named expression argument in add_column()");
|
static_assert(is_named_expression_t<NamedExpression>::value, "invalid named expression argument in add_column()");
|
||||||
static_assert(Ntc::value or Policies::template _no_unknown_tables<NamedExpression>::value, "named expression uses tables unknown to this statement in add_column()");
|
static_assert(TableCheckRequired::value or Policies::template _no_unknown_tables<NamedExpression>::value, "named expression uses tables unknown to this statement in add_column()");
|
||||||
|
|
||||||
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t, _is_dynamic, is_named_expression_t<NamedExpression>>;
|
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t, _is_dynamic, is_named_expression_t<NamedExpression>>;
|
||||||
|
|
||||||
|
7
include/sqlpp11/vendor/select_flag_list.h
vendored
7
include/sqlpp11/vendor/select_flag_list.h
vendored
@ -66,10 +66,17 @@ namespace sqlpp
|
|||||||
struct _methods_t
|
struct _methods_t
|
||||||
{
|
{
|
||||||
template<typename Flag>
|
template<typename Flag>
|
||||||
|
void add_flag_ntc(Flag flag)
|
||||||
|
{
|
||||||
|
add_flag<Flag, std::false_type>(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Flag, typename TableCheckRequired = std::true_type>
|
||||||
void add_flag(Flag flag)
|
void add_flag(Flag flag)
|
||||||
{
|
{
|
||||||
static_assert(_is_dynamic::value, "add_flag must not be called for static select flags");
|
static_assert(_is_dynamic::value, "add_flag must not be called for static select flags");
|
||||||
static_assert(is_select_flag_t<Flag>::value, "invalid select flag argument in add_flag()");
|
static_assert(is_select_flag_t<Flag>::value, "invalid select flag argument in add_flag()");
|
||||||
|
static_assert(TableCheckRequired::value or Policies::template _no_unknown_tables<Flag>::value, "flag uses tables unknown to this statement in add_flag()");
|
||||||
|
|
||||||
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t, _is_dynamic, is_select_flag_t<Flag>>;
|
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t, _is_dynamic, is_select_flag_t<Flag>>;
|
||||||
|
|
||||||
|
9
include/sqlpp11/vendor/update_list.h
vendored
9
include/sqlpp11/vendor/update_list.h
vendored
@ -72,11 +72,18 @@ namespace sqlpp
|
|||||||
struct _methods_t
|
struct _methods_t
|
||||||
{
|
{
|
||||||
template<typename Assignment>
|
template<typename Assignment>
|
||||||
|
void add_set_ntc(Assignment assignment)
|
||||||
|
{
|
||||||
|
add_set<Assignment, std::false_type>(assignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Assignment, typename TableCheckRequired = std::true_type>
|
||||||
void add_set(Assignment assignment)
|
void add_set(Assignment assignment)
|
||||||
{
|
{
|
||||||
static_assert(_is_dynamic::value, "add_set must not be called for static from()");
|
static_assert(_is_dynamic::value, "add_set must not be called for static from()");
|
||||||
static_assert(is_assignment_t<Assignment>::value, "invalid assignment argument in add_set()");
|
static_assert(is_assignment_t<Assignment>::value, "invalid assignment argument in add_set()");
|
||||||
static_assert(sqlpp::detail::not_t<must_not_update_t, typename Assignment::_column_t>::value, "set() argument must not be updated");
|
static_assert(sqlpp::detail::not_t<must_not_update_t, typename Assignment::_column_t>::value, "add_set() argument must not be updated");
|
||||||
|
static_assert(TableCheckRequired::value or Policies::template _no_unknown_tables<Assignment>::value, "assignment uses tables unknown to this statement in add_set()");
|
||||||
|
|
||||||
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t,
|
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t,
|
||||||
_is_dynamic,
|
_is_dynamic,
|
||||||
|
2
include/sqlpp11/vendor/using.h
vendored
2
include/sqlpp11/vendor/using.h
vendored
@ -51,6 +51,8 @@ namespace sqlpp
|
|||||||
|
|
||||||
static_assert(::sqlpp::detail::all_t<is_table_t, Tables...>::value, "at least one argument is not an table in using()");
|
static_assert(::sqlpp::detail::all_t<is_table_t, Tables...>::value, "at least one argument is not an table in using()");
|
||||||
|
|
||||||
|
using _table_set = ::sqlpp::detail::make_joined_set_t<typename Tables::_table_set...>;
|
||||||
|
|
||||||
using_t(Tables... tables):
|
using_t(Tables... tables):
|
||||||
_tables(tables...)
|
_tables(tables...)
|
||||||
{}
|
{}
|
||||||
|
7
include/sqlpp11/vendor/where.h
vendored
7
include/sqlpp11/vendor/where.h
vendored
@ -68,10 +68,17 @@ namespace sqlpp
|
|||||||
struct _methods_t
|
struct _methods_t
|
||||||
{
|
{
|
||||||
template<typename Expression>
|
template<typename Expression>
|
||||||
|
void add_where_ntc(Expression expression)
|
||||||
|
{
|
||||||
|
add_where<Expression, std::false_type>(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Expression, typename TableCheckRequired = std::true_type>
|
||||||
void add_where(Expression expression)
|
void add_where(Expression expression)
|
||||||
{
|
{
|
||||||
static_assert(_is_dynamic::value, "add_where can only be called for dynamic_where");
|
static_assert(_is_dynamic::value, "add_where can only be called for dynamic_where");
|
||||||
static_assert(is_expression_t<Expression>::value, "invalid expression argument in add_where()");
|
static_assert(is_expression_t<Expression>::value, "invalid expression argument in add_where()");
|
||||||
|
static_assert(TableCheckRequired::value or Policies::template _no_unknown_tables<Expression>::value, "expression uses tables unknown to this statement in add_where()");
|
||||||
|
|
||||||
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t, _is_dynamic, is_expression_t<Expression>>;
|
using ok = ::sqlpp::detail::all_t<sqlpp::detail::identity_t, _is_dynamic, is_expression_t<Expression>>;
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ int main()
|
|||||||
serialize(update(t).set(t.beta = "opaque").where(t.beta != t.beta), printer).str();
|
serialize(update(t).set(t.beta = "opaque").where(t.beta != t.beta), printer).str();
|
||||||
auto u = dynamic_update(db, t).dynamic_set(t.gamma = false).dynamic_where();
|
auto u = dynamic_update(db, t).dynamic_set(t.gamma = false).dynamic_where();
|
||||||
u.add_set(t.gamma = false);
|
u.add_set(t.gamma = false);
|
||||||
|
u.add_where(t.gamma != false);
|
||||||
serialize(u, printer).str();
|
serialize(u, printer).str();
|
||||||
|
|
||||||
db(u);
|
db(u);
|
||||||
|
Loading…
Reference in New Issue
Block a user