mirror of
https://github.com/rbock/sqlpp11.git
synced 2024-11-16 04:47:18 +08:00
Added type tests for join and fixed a few things to make them compile
This commit is contained in:
parent
135dceeba3
commit
151af17bbc
@ -104,10 +104,13 @@ namespace sqlpp
|
||||
{
|
||||
};
|
||||
|
||||
template<typename Table>
|
||||
struct table_t;
|
||||
|
||||
template<typename Table, typename ColumnSpec>
|
||||
struct required_tables_of<column_t<Table, ColumnSpec>>
|
||||
{
|
||||
using type = detail::type_set<Table>;
|
||||
using type = detail::type_set<table_t<Table>>;
|
||||
};
|
||||
|
||||
template <typename Context, typename Table, typename ColumnSpec>
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* Copyright (c) 2013-2016, Roland Bock
|
||||
* Copyright (c) 2013, Roland Bock
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -38,7 +38,7 @@ namespace sqlpp
|
||||
using _traits = make_traits<no_value_t, tag::is_join>;
|
||||
using _nodes = detail::type_vector<PreJoin, On>;
|
||||
using _can_be_null = std::false_type;
|
||||
using _provided_tables = provided_tables_of<PreJoin>;
|
||||
using _provided_tables = provided_tables_of_t<PreJoin>;
|
||||
using _required_tables = detail::make_difference_set_t<required_tables_of_t<On>, _provided_tables>;
|
||||
|
||||
template <typename T>
|
||||
@ -81,6 +81,18 @@ namespace sqlpp
|
||||
On _on;
|
||||
};
|
||||
|
||||
template<typename PreJoin, typename On>
|
||||
struct nodes_of<join_t<PreJoin, On>>
|
||||
{
|
||||
using type = sqlpp::detail::type_vector<PreJoin, On>;
|
||||
};
|
||||
|
||||
template<typename PreJoin, typename On>
|
||||
struct provided_outer_tables_of<join_t<PreJoin, On>>
|
||||
{
|
||||
using type = provided_outer_tables_of_t<PreJoin>;
|
||||
};
|
||||
|
||||
template<typename PreJoin, typename On>
|
||||
struct is_table<join_t<PreJoin, On>> : public std::true_type{};
|
||||
|
||||
|
@ -34,28 +34,28 @@ namespace sqlpp
|
||||
{
|
||||
template <typename Lhs, typename Rhs>
|
||||
using _provided_outer_tables =
|
||||
detail::make_joined_set_t<provided_outer_tables_of<Lhs>, provided_outer_tables_of<Rhs>>;
|
||||
detail::make_joined_set_t<provided_outer_tables_of_t<Lhs>, provided_outer_tables_of_t<Rhs>>;
|
||||
|
||||
static constexpr const char* _name = " INNER";
|
||||
};
|
||||
struct outer_join_t
|
||||
{
|
||||
template <typename Lhs, typename Rhs>
|
||||
using _provided_outer_tables = detail::make_joined_set_t<provided_tables_of<Lhs>, provided_tables_of<Rhs>>;
|
||||
using _provided_outer_tables = detail::make_joined_set_t<provided_tables_of_t<Lhs>, provided_tables_of_t<Rhs>>;
|
||||
|
||||
static constexpr const char* _name = " OUTER";
|
||||
};
|
||||
struct left_outer_join_t
|
||||
{
|
||||
template <typename Lhs, typename Rhs>
|
||||
using _provided_outer_tables = detail::make_joined_set_t<provided_outer_tables_of<Lhs>, provided_tables_of<Rhs>>;
|
||||
using _provided_outer_tables = detail::make_joined_set_t<provided_outer_tables_of_t<Lhs>, provided_tables_of_t<Rhs>>;
|
||||
|
||||
static constexpr const char* _name = " LEFT OUTER";
|
||||
};
|
||||
struct right_outer_join_t
|
||||
{
|
||||
template <typename Lhs, typename Rhs>
|
||||
using _provided_outer_tables = detail::make_joined_set_t<provided_tables_of<Lhs>, provided_outer_tables_of<Rhs>>;
|
||||
using _provided_outer_tables = detail::make_joined_set_t<provided_tables_of_t<Lhs>, provided_outer_tables_of_t<Rhs>>;
|
||||
|
||||
static constexpr const char* _name = " RIGHT OUTER";
|
||||
};
|
||||
@ -63,7 +63,7 @@ namespace sqlpp
|
||||
{
|
||||
template <typename Lhs, typename Rhs>
|
||||
using _provided_outer_tables =
|
||||
detail::make_joined_set_t<provided_outer_tables_of<Lhs>, provided_outer_tables_of<Rhs>>;
|
||||
detail::make_joined_set_t<provided_outer_tables_of_t<Lhs>, provided_outer_tables_of_t<Rhs>>;
|
||||
|
||||
static constexpr const char* _name = " CROSS";
|
||||
};
|
||||
|
@ -60,6 +60,12 @@ namespace sqlpp
|
||||
using _nodes = detail::type_vector<>;
|
||||
};
|
||||
|
||||
template<typename Expression>
|
||||
struct nodes_of<on_t<Expression>>
|
||||
{
|
||||
using type = sqlpp::detail::type_vector<Expression>;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
auto to_sql_string(Context& , const on_t<unconditional_t>&) -> std::string
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* Copyright (c) 2013-2016, Roland Bock
|
||||
* Copyright (c) 2013, Roland Bock
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -28,6 +28,7 @@
|
||||
|
||||
#include <sqlpp11/core/basic/join_types.h>
|
||||
#include <sqlpp11/core/noop.h>
|
||||
#include <sqlpp11/core/query/dynamic.h>
|
||||
#include <sqlpp11/core/basic/on.h>
|
||||
#include <sqlpp11/core/basic/table_ref.h>
|
||||
|
||||
@ -45,8 +46,9 @@ namespace sqlpp
|
||||
static_check_t<is_table<Lhs>::value, assert_pre_join_lhs_table_t>,
|
||||
static_check_t<is_table<Rhs>::value, assert_pre_join_rhs_table_t>,
|
||||
static_check_t<not is_join_t<Rhs>::value, assert_pre_join_rhs_no_join_t>,
|
||||
static_check_t<detail::is_disjunct_from<detail::make_name_of_set_t<provided_tables_of<Lhs>>,
|
||||
detail::make_name_of_set_t<provided_tables_of<Rhs>>>::value,
|
||||
#warning: Since rhs is a table, we could also just get a type vector here.
|
||||
static_check_t<detail::is_disjunct_from<detail::make_name_of_set_t<provided_tables_of_t<Lhs>>,
|
||||
detail::make_name_of_set_t<provided_tables_of_t<Rhs>>>::value,
|
||||
assert_pre_join_unique_names_t>>;
|
||||
};
|
||||
|
||||
@ -66,7 +68,7 @@ namespace sqlpp
|
||||
static_check_t<is_pre_join_t<PreJoin>::value, assert_join_consist_of_pre_join_and_on_t>,
|
||||
static_check_t<is_on_t<On>::value, assert_join_consist_of_pre_join_and_on_t>,
|
||||
static_check_t<required_tables_of_t<PreJoin>::size::value == 0, assert_join_no_table_dependencies_t>,
|
||||
static_check_t<detail::is_subset_of<required_tables_of_t<On>, provided_tables_of<PreJoin>>::value,
|
||||
static_check_t<detail::is_subset_of<required_tables_of_t<On>, provided_tables_of_t<PreJoin>>::value,
|
||||
assert_join_on_no_foreign_table_dependencies_t>>;
|
||||
};
|
||||
|
||||
@ -93,13 +95,7 @@ namespace sqlpp
|
||||
using _can_be_null = std::false_type;
|
||||
using _provided_outer_tables = typename JoinType::template _provided_outer_tables<Lhs, Rhs>;
|
||||
|
||||
static_assert(is_table<Lhs>::value, "lhs argument for join() has to be a table or join");
|
||||
static_assert(is_table<Rhs>::value, "rhs argument for join() has to be a table");
|
||||
static_assert(not is_join_t<Rhs>::value, "rhs argument for join must not be a join");
|
||||
|
||||
static_assert(detail::is_disjunct_from<provided_tables_of<Lhs>, provided_tables_of<Rhs>>::value,
|
||||
"joined tables must not be identical");
|
||||
|
||||
#warning: Do we really need this?
|
||||
static_assert(required_tables_of_t<pre_join_t>::size::value == 0, "joined tables must not depend on other tables");
|
||||
|
||||
auto unconditionally() -> join_t<pre_join_t, on_t<unconditional_t>>
|
||||
@ -132,6 +128,19 @@ namespace sqlpp
|
||||
Rhs _rhs;
|
||||
};
|
||||
|
||||
template <typename JoinType, typename Lhs, typename Rhs>
|
||||
struct nodes_of<pre_join_t<JoinType, Lhs, Rhs>>
|
||||
{
|
||||
using type = sqlpp::detail::type_vector<Lhs, Rhs>;
|
||||
};
|
||||
|
||||
template <typename JoinType, typename Lhs, typename Rhs>
|
||||
struct provided_outer_tables_of<pre_join_t<JoinType, Lhs, Rhs>>
|
||||
{
|
||||
using type = typename JoinType::template _provided_outer_tables<Lhs, Rhs>;
|
||||
};
|
||||
|
||||
|
||||
template <typename Context, typename JoinType, typename Lhs, typename Rhs>
|
||||
auto to_sql_string(Context& context, const pre_join_t<JoinType, Lhs, Rhs>& t) -> std::string
|
||||
{
|
||||
@ -147,7 +156,7 @@ namespace sqlpp
|
||||
auto join_impl(consistent_t, Lhs lhs, Rhs rhs) -> pre_join_t<JoinType, from_table_t<Lhs>, from_table_t<Rhs>>;
|
||||
|
||||
template <typename JoinType, typename Lhs, typename Rhs>
|
||||
auto join_impl(Lhs lhs, Rhs rhs) -> decltype(join_impl<JoinType>(check_pre_join_t<Lhs, Rhs>{}, lhs, rhs));
|
||||
auto join_impl(Lhs lhs, Rhs rhs) -> decltype(join_impl<JoinType>(check_pre_join_t<Lhs, remove_dynamic_t<Rhs>>{}, lhs, rhs));
|
||||
} // namespace detail
|
||||
|
||||
template <typename Lhs, typename Rhs>
|
||||
|
@ -41,9 +41,6 @@ namespace sqlpp
|
||||
{
|
||||
using _traits = make_traits<no_value_t, tag::is_raw_table>;
|
||||
|
||||
using _nodes = detail::type_vector<>;
|
||||
using _provided_tables = detail::type_set<TableSpec>;
|
||||
|
||||
using _required_insert_columns = typename TableSpec::_required_insert_columns;
|
||||
#warning: Need to inherit?
|
||||
//using _column_tuple_t = std::tuple<column_t<Table, ColumnSpec>...>;
|
||||
@ -66,6 +63,12 @@ namespace sqlpp
|
||||
template <typename TableSpec>
|
||||
struct is_table<table_t<TableSpec>>: public std::true_type {};
|
||||
|
||||
template <typename TableSpec>
|
||||
struct provided_tables_of<table_t<TableSpec>>
|
||||
{
|
||||
using type = sqlpp::detail::type_set<table_t<TableSpec>>;
|
||||
};
|
||||
|
||||
template <typename Context, typename TableSpec>
|
||||
auto to_sql_string(Context& context, const table_t<TableSpec>& /*unused*/) -> std::string
|
||||
{
|
||||
|
@ -85,8 +85,8 @@ namespace sqlpp
|
||||
static_check_t<not is_pre_join_t<Table>::value, assert_from_not_pre_join_t>,
|
||||
static_check_t<is_table<Table>::value, assert_from_table_t>,
|
||||
static_check_t<required_tables_of_t<Table>::size::value == 0, assert_from_dependency_free_t>,
|
||||
static_check_t<provided_tables_of<Table>::size::value ==
|
||||
detail::make_name_of_set_t<provided_tables_of<Table>>::size::value,
|
||||
static_check_t<provided_tables_of_t<Table>::size::value ==
|
||||
detail::make_name_of_set_t<provided_tables_of_t<Table>>::size::value,
|
||||
assert_from_no_duplicates_t>>;
|
||||
};
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
*/
|
||||
|
||||
#include <sqlpp11/core/type_traits.h>
|
||||
#include <sqlpp11/core/operator/assign_expression.h>
|
||||
#include <sqlpp11/core/operator/sort_order_expression.h>
|
||||
#include <sqlpp11/core/operator/enable_as.h>
|
||||
#include <sqlpp11/core/to_sql_string.h>
|
||||
@ -115,4 +116,13 @@ namespace sqlpp
|
||||
{
|
||||
return {condition, std::move(t)};
|
||||
}
|
||||
|
||||
template <typename TableSpec>
|
||||
struct table_t;
|
||||
|
||||
template <typename TableSpec>
|
||||
auto dynamic(bool condition, table_t<TableSpec> t) -> dynamic_t<table_t<TableSpec>>
|
||||
{
|
||||
return {condition, std::move(t)};
|
||||
}
|
||||
} // namespace sqlpp
|
||||
|
@ -78,8 +78,8 @@ namespace sqlpp
|
||||
using _all_required_ctes = detail::make_joined_set_t<required_ctes_of<Policies>...>;
|
||||
using _all_provided_ctes = detail::make_joined_set_t<provided_ctes_of<Policies>...>;
|
||||
using _all_required_tables = detail::make_joined_set_t<required_tables_of_t<Policies>...>;
|
||||
using _all_provided_tables = detail::make_joined_set_t<provided_tables_of<Policies>...>;
|
||||
using _all_provided_outer_tables = detail::make_joined_set_t<provided_outer_tables_of<Policies>...>;
|
||||
using _all_provided_tables = detail::make_joined_set_t<provided_tables_of_t<Policies>...>;
|
||||
using _all_provided_outer_tables = detail::make_joined_set_t<provided_outer_tables_of_t<Policies>...>;
|
||||
using _all_provided_aggregates = detail::make_joined_set_t<provided_aggregates_of<Policies>...>;
|
||||
|
||||
template <typename Expression>
|
||||
|
@ -285,8 +285,6 @@ namespace sqlpp
|
||||
|
||||
SQLPP_RECURSIVE_TRAIT_SET_GENERATOR(required_ctes)
|
||||
SQLPP_RECURSIVE_TRAIT_SET_GENERATOR(provided_ctes)
|
||||
SQLPP_RECURSIVE_TRAIT_SET_GENERATOR(provided_tables)
|
||||
SQLPP_RECURSIVE_TRAIT_SET_GENERATOR(provided_outer_tables)
|
||||
SQLPP_RECURSIVE_TRAIT_SET_GENERATOR(provided_aggregates)
|
||||
|
||||
template <typename T>
|
||||
@ -325,6 +323,40 @@ namespace sqlpp
|
||||
|
||||
static_assert(required_tables_of_t<int>::size::value == 0, "");
|
||||
|
||||
template <typename T>
|
||||
struct provided_tables_of
|
||||
{
|
||||
using type = typename provided_tables_of<nodes_of_t<T>>::type;
|
||||
};
|
||||
|
||||
template <typename... T>
|
||||
struct provided_tables_of<detail::type_vector<T...>>
|
||||
{
|
||||
using type = detail::make_joined_set_t<typename provided_tables_of<T>::type...>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using provided_tables_of_t = typename provided_tables_of<T>::type;
|
||||
|
||||
static_assert(provided_tables_of_t<int>::size::value == 0, "");
|
||||
|
||||
template <typename T>
|
||||
struct provided_outer_tables_of
|
||||
{
|
||||
using type = typename provided_outer_tables_of<nodes_of_t<T>>::type;
|
||||
};
|
||||
|
||||
template <typename... T>
|
||||
struct provided_outer_tables_of<detail::type_vector<T...>>
|
||||
{
|
||||
using type = detail::make_joined_set_t<typename provided_outer_tables_of<T>::type...>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using provided_outer_tables_of_t = typename provided_outer_tables_of<T>::type;
|
||||
|
||||
static_assert(provided_outer_tables_of_t<int>::size::value == 0, "");
|
||||
|
||||
template <typename ValueType, typename T>
|
||||
struct is_valid_operand
|
||||
{
|
||||
|
@ -33,8 +33,4 @@ test_compile(result_row)
|
||||
test_compile(select_as)
|
||||
test_compile(value)
|
||||
|
||||
add_subdirectory(aggregate_function)
|
||||
add_subdirectory(operator)
|
||||
add_subdirectory(clause)
|
||||
add_subdirectory(detail)
|
||||
add_subdirectory(type_traits)
|
||||
add_subdirectory(basic)
|
||||
|
32
tests/core/types/basic/CMakeLists.txt
Normal file
32
tests/core/types/basic/CMakeLists.txt
Normal file
@ -0,0 +1,32 @@
|
||||
# Copyright (c) 2024, Roland Bock
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice, this
|
||||
# list of conditions and the following disclaimer in the documentation and/or
|
||||
# other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
function(test_compile name)
|
||||
set(target sqlpp11_core_types_clause_${name})
|
||||
add_executable(${target} ${name}.cpp)
|
||||
target_link_libraries(${target} PRIVATE sqlpp11::sqlpp11 sqlpp11_testing)
|
||||
endfunction()
|
||||
|
||||
test_compile(join)
|
||||
|
82
tests/core/types/basic/join.cpp
Normal file
82
tests/core/types/basic/join.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Roland Bock
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Sample.h"
|
||||
#include <sqlpp11/sqlpp11.h>
|
||||
|
||||
void test_group_by()
|
||||
{
|
||||
auto v = sqlpp::value(17);
|
||||
auto foo = test::TabFoo{};
|
||||
auto bar = test::TabBar{};
|
||||
|
||||
// Pre-join
|
||||
static_assert(not sqlpp::is_table<decltype(foo.join(bar))>::value, "");
|
||||
|
||||
// Join
|
||||
{
|
||||
using J = decltype(foo.join(bar).on(foo.id == bar.id));
|
||||
static_assert(sqlpp::is_table<J>::value, "");
|
||||
static_assert(
|
||||
std::is_same<sqlpp::provided_tables_of_t<J>, sqlpp::detail::type_set<test::TabFoo, test::TabBar>>::value, "");
|
||||
static_assert(
|
||||
std::is_same<sqlpp::provided_outer_tables_of_t<J>, sqlpp::detail::type_set<>>::value, "");
|
||||
#warning: test the provided dynamic tables of?
|
||||
}
|
||||
|
||||
{
|
||||
using J = decltype(foo.outer_join(bar).on(foo.id == bar.id));
|
||||
static_assert(sqlpp::is_table<J>::value, "");
|
||||
static_assert(
|
||||
std::is_same<sqlpp::provided_tables_of_t<J>, sqlpp::detail::type_set<test::TabFoo, test::TabBar>>::value, "");
|
||||
static_assert(
|
||||
std::is_same<sqlpp::provided_outer_tables_of_t<J>, sqlpp::detail::type_set<test::TabFoo, test::TabBar>>::value, "");
|
||||
#warning: test the provided dynamic tables of?
|
||||
}
|
||||
|
||||
// Join with dynamic table
|
||||
{
|
||||
using J = decltype(foo.join(dynamic(true, bar)).on(foo.id == bar.id));
|
||||
static_assert(sqlpp::is_table<J>::value, "");
|
||||
static_assert(
|
||||
std::is_same<sqlpp::provided_tables_of_t<J>, sqlpp::detail::type_set<test::TabFoo, test::TabBar>>::value, "");
|
||||
#warning: OUTER is the wrong term. In a left-outer join, the *right* table is the one with optional rows.
|
||||
static_assert(
|
||||
std::is_same<sqlpp::provided_outer_tables_of_t<J>, sqlpp::detail::type_set<>>::value, "");
|
||||
#warning: test the provided dynamic tables of?
|
||||
}
|
||||
|
||||
|
||||
#warning: Need to add tests with 3 tables
|
||||
|
||||
#warning: Need to add tests with CTEs
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
void test_group_by();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user