fix -4016 error and add TENANT_ID in CDB_OB_DEADLOCK_EVENT_HISTORY
This commit is contained in:
parent
3648a21c43
commit
8c5ebeee3c
@ -402,7 +402,8 @@ int LsElectionReferenceInfoRow::get_row_from_table_()
|
||||
LC_TIME_GUARD(1_s);
|
||||
#define PRINT_WRAPPER KR(ret), K(*this)
|
||||
int ret = OB_SUCCESS;
|
||||
const char *columns[5] = { "tenant_id", "ls_id", "zone_priority", "manual_leader_server", "blacklist" };
|
||||
constexpr int64_t culumn_size = 5;
|
||||
const char *columns[culumn_size] = { "tenant_id", "ls_id", "zone_priority", "manual_leader_server", "blacklist" };
|
||||
char buffer[STACK_BUFFER_SIZE] = {0};
|
||||
int64_t pos = 0;
|
||||
if (CLICK_FAIL(databuff_printf(buffer, STACK_BUFFER_SIZE, pos, "where tenant_id=%ld and ls_id=%ld for update", tenant_id_, ls_id_.id()))) {
|
||||
@ -412,8 +413,10 @@ int LsElectionReferenceInfoRow::get_row_from_table_()
|
||||
COORDINATOR_LOG_(WARN, "transaction is not started yet");
|
||||
} else {
|
||||
HEAP_VAR(ObMySQLProxy::MySQLResult, res) {
|
||||
common::sqlclient::ObMySQLResult *result = ObTableAccessHelper::get_my_sql_result_(columns, share::OB_ALL_LS_ELECTION_REFERENCE_INFO_TNAME, buffer, trans_, exec_tenant_id_, res);
|
||||
if (OB_NOT_NULL(result)) {
|
||||
common::sqlclient::ObMySQLResult *result = nullptr;
|
||||
if (OB_FAIL(ObTableAccessHelper::get_my_sql_result_(columns, culumn_size, share::OB_ALL_LS_ELECTION_REFERENCE_INFO_TNAME, buffer, trans_, exec_tenant_id_, res, result))) {
|
||||
COORDINATOR_LOG_(WARN, "fail to get mysql result");
|
||||
} else if (OB_NOT_NULL(result)) {
|
||||
int64_t iter_times = 0;
|
||||
while (OB_SUCC(ret) && OB_SUCC(result->next())) {
|
||||
if (++iter_times > 1) {
|
||||
|
@ -2360,7 +2360,7 @@ int ObInnerTableSchema::cdb_ob_deadlock_event_history_schema(ObTableSchema &tabl
|
||||
table_schema.set_collation_type(ObCharset::get_default_collation(ObCharset::get_default_charset()));
|
||||
|
||||
if (OB_SUCC(ret)) {
|
||||
if (OB_FAIL(table_schema.set_view_definition(R"__( SELECT EVENT_ID, SVR_IP, SVR_PORT, DETECTOR_ID, REPORT_TIME, CYCLE_IDX, CYCLE_SIZE, ROLE, PRIORITY_LEVEL, PRIORITY, CREATE_TIME, START_DELAY AS START_DELAY_US, MODULE, VISITOR, OBJECT, EXTRA_NAME1, EXTRA_VALUE1, EXTRA_NAME2, EXTRA_VALUE2, EXTRA_NAME3, EXTRA_VALUE3 FROM OCEANBASE.__ALL_VIRTUAL_DEADLOCK_EVENT_HISTORY )__"))) {
|
||||
if (OB_FAIL(table_schema.set_view_definition(R"__( SELECT TENANT_ID, EVENT_ID, SVR_IP, SVR_PORT, DETECTOR_ID, REPORT_TIME, CYCLE_IDX, CYCLE_SIZE, ROLE, PRIORITY_LEVEL, PRIORITY, CREATE_TIME, START_DELAY AS START_DELAY_US, MODULE, VISITOR, OBJECT, EXTRA_NAME1, EXTRA_VALUE1, EXTRA_NAME2, EXTRA_VALUE2, EXTRA_NAME3, EXTRA_VALUE3 FROM OCEANBASE.__ALL_VIRTUAL_DEADLOCK_EVENT_HISTORY )__"))) {
|
||||
LOG_ERROR("fail to set view_definition", K(ret));
|
||||
}
|
||||
}
|
||||
|
@ -23257,7 +23257,8 @@ def_table_schema(
|
||||
normal_columns = [],
|
||||
view_definition =
|
||||
"""
|
||||
SELECT EVENT_ID,
|
||||
SELECT TENANT_ID,
|
||||
EVENT_ID,
|
||||
SVR_IP,
|
||||
SVR_PORT,
|
||||
DETECTOR_ID,
|
||||
|
@ -39,6 +39,13 @@
|
||||
|
||||
namespace oceanbase
|
||||
{
|
||||
namespace logservice
|
||||
{
|
||||
namespace coordinator
|
||||
{
|
||||
class LsElectionReferenceInfoRow;
|
||||
}
|
||||
}
|
||||
namespace common
|
||||
{
|
||||
|
||||
@ -46,6 +53,7 @@ constexpr int STACK_BUFFER_SIZE = 512;
|
||||
#define OB_LOG_(args...) OB_LOG(args, PRINT_WRAPPER)
|
||||
class ObTableAccessHelper
|
||||
{
|
||||
friend class logservice::coordinator::LsElectionReferenceInfoRow;
|
||||
public:
|
||||
static int split_string_by_char(const ObStringHolder &arg_str, const char character, ObIArray<ObStringHolder> &result)
|
||||
{
|
||||
@ -147,36 +155,40 @@ public:
|
||||
const ObString &where_condition,
|
||||
T &...values)
|
||||
{
|
||||
TIMEGUARD_INIT(OCCAM, 1_s, 60_s);
|
||||
#define PRINT_WRAPPER KR(ret), K(MTL_ID()), K(columns), K(table), K(where_condition)
|
||||
static_assert(N > 0, "columns size must greater than 0");
|
||||
static_assert(sizeof...(T) == N, "number of value size must equal than N");
|
||||
int ret = common::OB_SUCCESS;
|
||||
if (OB_ISNULL(GCTX.sql_proxy_)) {
|
||||
OB_LOG_(WARN, "GCTX.sql_proxy_ is null");
|
||||
return read_and_convert_to_values_(tenant_id,
|
||||
columns,
|
||||
N,
|
||||
table,
|
||||
where_condition,
|
||||
values...);
|
||||
}
|
||||
template <typename ...T>
|
||||
static int read_single_row(const uint64_t tenant_id,
|
||||
const std::initializer_list<const char *> &columns,
|
||||
const ObString &table,
|
||||
const ObString &where_condition,
|
||||
T &...values)
|
||||
{
|
||||
#define PRINT_WRAPPER KR(ret), K(MTL_ID()), K(table), K(where_condition)
|
||||
int ret = OB_SUCCESS;
|
||||
if (columns.size() != sizeof...(T)) {
|
||||
ret = OB_SIZE_OVERFLOW;
|
||||
OB_LOG_(WARN, "sizeof... values should equal to columns size");
|
||||
} else {
|
||||
HEAP_VAR(ObMySQLProxy::MySQLResult, res) {
|
||||
common::sqlclient::ObMySQLResult *result = get_my_sql_result_(columns, table, where_condition, *GCTX.sql_proxy_, tenant_id, res);
|
||||
if (OB_NOT_NULL(result)) {
|
||||
int64_t iter_times = 0;
|
||||
while (OB_SUCC(ret) && OB_SUCC(result->next())) {
|
||||
if (++iter_times > 1) {
|
||||
ret = OB_ERR_MORE_THAN_ONE_ROW;
|
||||
OB_LOG_(WARN, "there are more than one row been selected");
|
||||
break;
|
||||
} else if (CLICK_FAIL(get_values_from_row_<0>(result, columns, values...))) {
|
||||
OB_LOG_(WARN, "failed to get column from row");
|
||||
}
|
||||
}
|
||||
if (OB_ITER_END == ret && 1 == iter_times) {
|
||||
ret = OB_SUCCESS;
|
||||
} else {
|
||||
OB_LOG_(WARN, "iter failed", K(iter_times));
|
||||
}
|
||||
} else {
|
||||
ret = OB_ERR_UNEXPECTED;
|
||||
OB_LOG_(WARN, "get mysql result failed");
|
||||
}
|
||||
const char * columns_array[columns.size()];
|
||||
auto iter = std::begin(columns);
|
||||
for (int64_t idx = 0; iter != std::end(columns); ++idx && ++iter) {
|
||||
columns_array[idx] = *iter;
|
||||
}
|
||||
if (OB_FAIL(read_and_convert_to_values_(tenant_id,
|
||||
columns_array,
|
||||
columns.size(),
|
||||
table,
|
||||
where_condition,
|
||||
values...))) {
|
||||
OB_LOG_(WARN, "fail to read and convert to values");
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@ -190,36 +202,40 @@ public:
|
||||
const ObString &condition,
|
||||
common::ObIArray<ObTuple<T...>> &output_array)
|
||||
{
|
||||
TIMEGUARD_INIT(OCCAM, 1_s, 60_s);
|
||||
#define PRINT_WRAPPER KR(ret), K(MTL_ID()), K(columns), K(table), K(condition)
|
||||
static_assert(N > 0, "columns size must greater than 0");
|
||||
static_assert(sizeof...(T) == N, "number of value size must equal than N");
|
||||
int ret = common::OB_SUCCESS;
|
||||
if (OB_ISNULL(GCTX.sql_proxy_)) {
|
||||
OB_LOG_(WARN, "GCTX.sql_proxy_ is null");
|
||||
return read_and_convert_to_tuples_(tenant_id,
|
||||
columns,
|
||||
N,
|
||||
table,
|
||||
condition,
|
||||
output_array);
|
||||
}
|
||||
template <int N, typename ...T>
|
||||
static int read_multi_row(const uint64_t tenant_id,
|
||||
const std::initializer_list<const char *> &columns,
|
||||
const ObString &table,
|
||||
const ObString &condition,
|
||||
common::ObIArray<ObTuple<T...>> &output_array)
|
||||
{
|
||||
#define PRINT_WRAPPER KR(ret), K(MTL_ID()), K(table), K(condition)
|
||||
int ret = OB_SUCCESS;
|
||||
if (columns.size() != sizeof...(T)) {
|
||||
ret = OB_SIZE_OVERFLOW;
|
||||
OB_LOG_(WARN, "sizeof... values should equal to columns size");
|
||||
} else {
|
||||
HEAP_VAR(ObMySQLProxy::MySQLResult, res) {
|
||||
common::sqlclient::ObMySQLResult *result = get_my_sql_result_(columns, table, condition, *GCTX.sql_proxy_, tenant_id, res);
|
||||
if (OB_NOT_NULL(result)) {
|
||||
int64_t iter_times = 0;
|
||||
while (OB_SUCC(ret) && OB_SUCC(result->next())) {
|
||||
if (CLICK_FAIL(output_array.push_back(ObTuple<T...>()))) {
|
||||
OB_LOG_(WARN, "push new tuple to array failed", K(iter_times));
|
||||
} else if (OB_SUCCESS != (ret = AccessHelper<sizeof...(T) - 1, N, T...>::
|
||||
get_values_to_tuple_from_row(result, columns, output_array.at(iter_times)))) {
|
||||
OB_LOG_(WARN, "failed to get values from row", K(iter_times));
|
||||
}
|
||||
iter_times++;
|
||||
}
|
||||
if (OB_ITER_END == ret && iter_times > 0) {
|
||||
ret = OB_SUCCESS;
|
||||
} else {
|
||||
OB_LOG_(WARN, "iter failed", K(iter_times));
|
||||
}
|
||||
} else {
|
||||
ret = OB_ERR_UNEXPECTED;
|
||||
OB_LOG_(WARN, "get mysql result failed");
|
||||
}
|
||||
const char * columns_array[columns.size()];
|
||||
auto iter = std::begin(columns);
|
||||
for (int64_t idx = 0; iter != std::end(columns); ++idx && ++iter) {
|
||||
columns_array[idx] = *iter;
|
||||
}
|
||||
if (OB_FAIL(read_and_convert_to_tuples_(tenant_id,
|
||||
columns_array,
|
||||
columns.size(),
|
||||
table,
|
||||
condition,
|
||||
output_array))) {
|
||||
OB_LOG_(WARN, "fail to read and convert to tuples");
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@ -295,24 +311,110 @@ public:
|
||||
return ret;
|
||||
#undef PRINT_WRAPPER
|
||||
}
|
||||
public:
|
||||
template <int N>
|
||||
static common::sqlclient::ObMySQLResult *get_my_sql_result_(const char* (&columns)[N],
|
||||
const ObString &table,
|
||||
const ObString &condition,
|
||||
ObISQLClient &proxy,
|
||||
const uint64_t tenant_id,
|
||||
ObMySQLProxy::MySQLResult &res)
|
||||
private:
|
||||
template <typename ...T>
|
||||
static int read_and_convert_to_values_(const uint64_t tenant_id,
|
||||
const char **columns,
|
||||
const int64_t culumn_size,
|
||||
const ObString &table,
|
||||
const ObString &condition,
|
||||
T &...values)
|
||||
{
|
||||
TIMEGUARD_INIT(OCCAM, 1_s, 60_s);
|
||||
#define PRINT_WRAPPER KR(ret), K(MTL_ID()), K(table), K(condition)
|
||||
int ret = common::OB_SUCCESS;
|
||||
if (OB_ISNULL(GCTX.sql_proxy_)) {
|
||||
OB_LOG_(WARN, "GCTX.sql_proxy_ is null");
|
||||
} else {
|
||||
HEAP_VAR(ObMySQLProxy::MySQLResult, res) {
|
||||
common::sqlclient::ObMySQLResult *result = nullptr;
|
||||
if (OB_FAIL(get_my_sql_result_(columns, culumn_size, table, condition, *GCTX.sql_proxy_, tenant_id, res, result))) {
|
||||
OB_LOG_(WARN, "fail to get ObMySQLResult");
|
||||
} else if (OB_NOT_NULL(result)) {
|
||||
int64_t iter_times = 0;
|
||||
while (OB_SUCC(ret) && OB_SUCC(result->next())) {
|
||||
if (++iter_times > 1) {
|
||||
ret = OB_ERR_MORE_THAN_ONE_ROW;
|
||||
OB_LOG_(WARN, "there are more than one row been selected");
|
||||
break;
|
||||
} else if (CLICK_FAIL(get_values_from_row_<0>(result, columns, values...))) {
|
||||
OB_LOG_(WARN, "failed to get column from row");
|
||||
}
|
||||
}
|
||||
if (OB_ITER_END == ret && 1 == iter_times) {
|
||||
ret = OB_SUCCESS;
|
||||
} else {
|
||||
OB_LOG_(WARN, "iter failed", K(iter_times));
|
||||
}
|
||||
} else {
|
||||
ret = OB_ERR_UNEXPECTED;
|
||||
OB_LOG_(WARN, "get mysql result failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
#undef PRINT_WRAPPER
|
||||
}
|
||||
template <typename ...T>
|
||||
static int read_and_convert_to_tuples_(const uint64_t tenant_id,
|
||||
const char **columns,
|
||||
const int64_t culumn_size,
|
||||
const ObString &table,
|
||||
const ObString &condition,
|
||||
common::ObIArray<ObTuple<T...>> &output_array)
|
||||
{
|
||||
TIMEGUARD_INIT(OCCAM, 1_s, 60_s);
|
||||
#define PRINT_WRAPPER KR(ret), K(MTL_ID()), K(table), K(condition)
|
||||
int ret = common::OB_SUCCESS;
|
||||
if (OB_ISNULL(GCTX.sql_proxy_)) {
|
||||
OB_LOG_(WARN, "GCTX.sql_proxy_ is null");
|
||||
} else {
|
||||
HEAP_VAR(ObMySQLProxy::MySQLResult, res) {
|
||||
common::sqlclient::ObMySQLResult *result = nullptr;
|
||||
if (OB_FAIL(get_my_sql_result_(columns, culumn_size, table, condition, *GCTX.sql_proxy_, tenant_id, res, result))) {
|
||||
OB_LOG_(WARN, "fail to get ObMySQLResult");
|
||||
} else if (OB_NOT_NULL(result)) {
|
||||
int64_t iter_times = 0;
|
||||
while (OB_SUCC(ret) && OB_SUCC(result->next())) {
|
||||
if (CLICK_FAIL(output_array.push_back(ObTuple<T...>()))) {
|
||||
OB_LOG_(WARN, "push new tuple to array failed", K(iter_times));
|
||||
} else if (OB_SUCCESS != (ret = AccessHelper<sizeof...(T) - 1, T...>::
|
||||
get_values_to_tuple_from_row(result, columns, output_array.at(iter_times)))) {
|
||||
OB_LOG_(WARN, "failed to get values from row", K(iter_times));
|
||||
}
|
||||
iter_times++;
|
||||
}
|
||||
if (OB_ITER_END == ret && iter_times > 0) {
|
||||
ret = OB_SUCCESS;
|
||||
} else {
|
||||
OB_LOG_(WARN, "iter failed", K(iter_times));
|
||||
}
|
||||
} else {
|
||||
ret = OB_ERR_UNEXPECTED;
|
||||
OB_LOG_(WARN, "get mysql result failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
#undef PRINT_WRAPPER
|
||||
}
|
||||
static int get_my_sql_result_(const char **columns,
|
||||
const int64_t column_size,
|
||||
const ObString &table,
|
||||
const ObString &condition,
|
||||
ObISQLClient &proxy,
|
||||
const uint64_t tenant_id,
|
||||
ObMySQLProxy::MySQLResult &res,
|
||||
common::sqlclient::ObMySQLResult *&result)
|
||||
{
|
||||
TIMEGUARD_INIT(OCCAM, 1_s, 60_s);
|
||||
#define PRINT_WRAPPER KR(ret), K(MTL_ID()), K(tenant_id), K(columns), K(table), K(condition), K(sql), K(columns_str)
|
||||
int ret = OB_SUCCESS;
|
||||
common::sqlclient::ObMySQLResult *result = NULL;
|
||||
ObSqlString sql;
|
||||
char columns_str[STACK_BUFFER_SIZE] = {0};
|
||||
int64_t pos = 0;
|
||||
for (int i = 0; i < N; ++i) {
|
||||
if (i != N - 1) {
|
||||
for (int i = 0; i < column_size; ++i) {
|
||||
if (i != column_size - 1) {
|
||||
if (CLICK_FAIL(databuff_printf(&columns_str[0], STACK_BUFFER_SIZE, pos, "%s,", columns[i]))) {
|
||||
OB_LOG_(WARN, "failed to format column string");
|
||||
}
|
||||
@ -332,7 +434,7 @@ public:
|
||||
OB_LOG_(WARN, "failed to get result");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return ret;
|
||||
#undef PRINT_WRAPPER
|
||||
}
|
||||
template <typename T>
|
||||
@ -411,6 +513,36 @@ public:
|
||||
return ret;
|
||||
#undef PRINT_WRAPPER
|
||||
}
|
||||
static int get_signle_column_from_signle_row_(common::sqlclient::ObMySQLResult *row,
|
||||
const char *column,
|
||||
share::ObLSID &ls_id)
|
||||
{
|
||||
TIMEGUARD_INIT(OCCAM, 1_s, 60_s);
|
||||
int ret = common::OB_SUCCESS;
|
||||
int64_t value = 0;
|
||||
if (CLICK_FAIL(row->get_int(column, value))) {
|
||||
OB_LOG(WARN, "get_column_from_signle_row failed", KR(ret), K(MTL_ID()), K(column));
|
||||
} else {
|
||||
ls_id = share::ObLSID(value);
|
||||
OB_LOG(TRACE, "get_column_from_signle_row success", KR(ret), K(MTL_ID()), K(column), K(value));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
static int get_signle_column_from_signle_row_(common::sqlclient::ObMySQLResult *row,
|
||||
const char *column,
|
||||
common::ObTabletID &tablet_id)
|
||||
{
|
||||
TIMEGUARD_INIT(OCCAM, 1_s, 60_s);
|
||||
int ret = common::OB_SUCCESS;
|
||||
int64_t value = 0;
|
||||
if (CLICK_FAIL(row->get_int(column, value))) {
|
||||
OB_LOG(WARN, "get_column_from_signle_row failed", KR(ret), K(MTL_ID()), K(column));
|
||||
} else {
|
||||
tablet_id = common::ObTabletID(value);
|
||||
OB_LOG(TRACE, "get_column_from_signle_row success", KR(ret), K(MTL_ID()), K(column), K(value));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
static int get_signle_column_from_signle_row_(common::sqlclient::ObMySQLResult *row,
|
||||
const char *column,
|
||||
int64_t &value)
|
||||
@ -452,17 +584,17 @@ public:
|
||||
return ret;
|
||||
}
|
||||
// 可变参数模版展开的递归基
|
||||
template <int FLOOR, int N>
|
||||
static int get_values_from_row_(common::sqlclient::ObMySQLResult *row, const char* (&columns)[N])
|
||||
template <int FLOOR>
|
||||
static int get_values_from_row_(common::sqlclient::ObMySQLResult *row, const char **columns)
|
||||
{
|
||||
UNUSED(row);
|
||||
UNUSED(columns);
|
||||
return OB_SUCCESS;
|
||||
}
|
||||
// 可变参数模版展开,从行中获取每一个入参
|
||||
template <int FLOOR, int N, typename V, typename ...T>
|
||||
template <int FLOOR, typename V, typename ...T>
|
||||
static int get_values_from_row_(common::sqlclient::ObMySQLResult *row,
|
||||
const char* (&columns)[N],
|
||||
const char **columns,
|
||||
V &value,
|
||||
T &...others)
|
||||
{
|
||||
@ -477,37 +609,35 @@ public:
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
template <int FLOOR, int N, typename ...T>
|
||||
template <int FLOOR, typename ...T>
|
||||
friend class AccessHelper;
|
||||
// 便特化必须靠类定义来协助
|
||||
template <int FLOOR, int N, typename ...T>
|
||||
template <int FLOOR, typename ...T>
|
||||
struct AccessHelper
|
||||
{
|
||||
// 从行中获取元组中的每一个元素
|
||||
static int get_values_to_tuple_from_row(common::sqlclient::ObMySQLResult *row,
|
||||
const char* (&columns)[N],
|
||||
const char **columns,
|
||||
ObTuple<T...> &tuple)
|
||||
{
|
||||
TIMEGUARD_INIT(OCCAM, 1_s, 60_s);
|
||||
static_assert(FLOOR > 0 && FLOOR <= N, "unexpected compile error");
|
||||
static_assert(FLOOR > 0 && FLOOR <= sizeof...(T), "unexpected compile error");
|
||||
int ret = common::OB_SUCCESS;
|
||||
if (FLOOR != N) {
|
||||
if (CLICK_FAIL(get_signle_column_from_signle_row_(row, columns[FLOOR], std::get<FLOOR>(tuple.tuple())))) {
|
||||
OB_LOG(WARN, "get value failed", KR(ret), K(MTL_ID()), K(columns[FLOOR]));
|
||||
} else {
|
||||
ret = AccessHelper<FLOOR - 1, N, T...>::get_values_to_tuple_from_row(row, columns, tuple);
|
||||
}
|
||||
if (CLICK_FAIL(get_signle_column_from_signle_row_(row, columns[FLOOR], std::get<FLOOR>(tuple.tuple())))) {
|
||||
OB_LOG(WARN, "get value failed", KR(ret), K(MTL_ID()), K(columns[FLOOR]));
|
||||
} else {
|
||||
ret = AccessHelper<FLOOR - 1, T...>::get_values_to_tuple_from_row(row, columns, tuple);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
// 模版偏特化递归基
|
||||
template <int N, typename ...T>
|
||||
struct AccessHelper<0, N, T...>
|
||||
template <typename ...T>
|
||||
struct AccessHelper<0, T...>
|
||||
{
|
||||
// 从行中获取元组中的每一个元素的递归基
|
||||
static int get_values_to_tuple_from_row(common::sqlclient::ObMySQLResult *row,
|
||||
const char* (&columns)[N],
|
||||
const char **columns,
|
||||
ObTuple<T...> &tuple)
|
||||
{
|
||||
TIMEGUARD_INIT(OCCAM, 1_s, 60_s);
|
||||
|
Loading…
x
Reference in New Issue
Block a user