mirror of
https://github.com/MariaDB/server.git
synced 2025-01-15 19:42:28 +01:00
MDEV-35854 dict_get_referenced_table() got too many parameters
dict_get_referenced_table(): Make the callers responsible for converting the database and table names.
This commit is contained in:
parent
d5a417b9d5
commit
301ee7bccb
4 changed files with 180 additions and 133 deletions
|
@ -3222,66 +3222,17 @@ foreign constraint parser to get the referenced table.
|
|||
heap memory passed in */
|
||||
char*
|
||||
dict_get_referenced_table(
|
||||
const char* name, /*!< in: foreign key table name */
|
||||
const char* database_name, /*!< in: table db name */
|
||||
ulint database_name_len, /*!< in: db name length */
|
||||
const char* table_name, /*!< in: table name */
|
||||
ulint table_name_len, /*!< in: table name length */
|
||||
dict_table_t** table, /*!< out: table object or NULL */
|
||||
mem_heap_t* heap, /*!< in/out: heap memory */
|
||||
CHARSET_INFO* from_cs) /*!< in: table name charset */
|
||||
LEX_CSTRING database_name, /*!< in: table db name */
|
||||
LEX_CSTRING table_name, /*!< in: table name */
|
||||
dict_table_t** table, /*!< out: table object or NULL */
|
||||
mem_heap_t* heap) noexcept /*!< in/out: heap memory */
|
||||
{
|
||||
char* ref;
|
||||
char db_name[MAX_DATABASE_NAME_LEN];
|
||||
char tbl_name[MAX_TABLE_NAME_LEN];
|
||||
CHARSET_INFO* to_cs = &my_charset_filename;
|
||||
uint errors;
|
||||
ut_ad(database_name || name);
|
||||
ut_ad(table_name);
|
||||
|
||||
if (!strncmp(table_name, srv_mysql50_table_name_prefix,
|
||||
sizeof(srv_mysql50_table_name_prefix) - 1)) {
|
||||
/* This is a pre-5.1 table name
|
||||
containing chars other than [A-Za-z0-9].
|
||||
Discard the prefix and use raw UTF-8 encoding. */
|
||||
table_name += sizeof(srv_mysql50_table_name_prefix) - 1;
|
||||
table_name_len -= sizeof(srv_mysql50_table_name_prefix) - 1;
|
||||
|
||||
to_cs = system_charset_info;
|
||||
}
|
||||
|
||||
table_name_len = strconvert(from_cs, table_name, table_name_len, to_cs,
|
||||
tbl_name, MAX_TABLE_NAME_LEN, &errors);
|
||||
table_name = tbl_name;
|
||||
|
||||
if (database_name) {
|
||||
to_cs = &my_charset_filename;
|
||||
if (!strncmp(database_name, srv_mysql50_table_name_prefix,
|
||||
sizeof(srv_mysql50_table_name_prefix) - 1)) {
|
||||
database_name
|
||||
+= sizeof(srv_mysql50_table_name_prefix) - 1;
|
||||
database_name_len
|
||||
-= sizeof(srv_mysql50_table_name_prefix) - 1;
|
||||
to_cs = system_charset_info;
|
||||
}
|
||||
|
||||
database_name_len = strconvert(
|
||||
from_cs, database_name, database_name_len, to_cs,
|
||||
db_name, MAX_DATABASE_NAME_LEN, &errors);
|
||||
database_name = db_name;
|
||||
} else {
|
||||
/* Use the database name of the foreign key table */
|
||||
|
||||
database_name = name;
|
||||
database_name_len = dict_get_db_name_len(name);
|
||||
}
|
||||
|
||||
/* Copy database_name, '/', table_name, '\0' */
|
||||
const size_t len = database_name_len + table_name_len + 1;
|
||||
ref = static_cast<char*>(mem_heap_alloc(heap, len + 1));
|
||||
memcpy(ref, database_name, database_name_len);
|
||||
ref[database_name_len] = '/';
|
||||
memcpy(ref + database_name_len + 1, table_name, table_name_len + 1);
|
||||
const size_t len = database_name.length + table_name.length + 1;
|
||||
char* ref = static_cast<char*>(mem_heap_alloc(heap, len + 1));
|
||||
memcpy(ref, database_name.str, database_name.length);
|
||||
ref[database_name.length] = '/';
|
||||
memcpy(ref + database_name.length + 1, table_name.str,
|
||||
table_name.length + 1);
|
||||
|
||||
/* Values; 0 = Store and compare as given; case sensitive
|
||||
1 = Store and compare in lower; case insensitive
|
||||
|
@ -3289,10 +3240,10 @@ dict_get_referenced_table(
|
|||
if (lower_case_table_names == 2) {
|
||||
innobase_casedn_str(ref);
|
||||
*table = dict_sys.load_table({ref, len});
|
||||
memcpy(ref, database_name, database_name_len);
|
||||
ref[database_name_len] = '/';
|
||||
memcpy(ref + database_name_len + 1, table_name, table_name_len + 1);
|
||||
|
||||
memcpy(ref, database_name.str, database_name.length);
|
||||
ref[database_name.length] = '/';
|
||||
memcpy(ref + database_name.length + 1,
|
||||
table_name.str, table_name.length + 1);
|
||||
} else {
|
||||
#ifndef _WIN32
|
||||
if (lower_case_table_names == 1) {
|
||||
|
|
|
@ -12404,6 +12404,8 @@ create_table_info_t::create_foreign_keys()
|
|||
const char* ref_column_names[MAX_COLS_PER_FK];
|
||||
char create_name[MAX_DATABASE_NAME_LEN + 1 +
|
||||
MAX_TABLE_NAME_LEN + 1];
|
||||
char db_name[MAX_DATABASE_NAME_LEN + 1];
|
||||
char t_name[MAX_TABLE_NAME_LEN + 1];
|
||||
dict_index_t* index = NULL;
|
||||
fkerr_t index_error = FK_SUCCESS;
|
||||
dict_index_t* err_index = NULL;
|
||||
|
@ -12411,59 +12413,84 @@ create_table_info_t::create_foreign_keys()
|
|||
const bool tmp_table = m_flags2 & DICT_TF2_TEMPORARY;
|
||||
const CHARSET_INFO* cs = thd_charset(m_thd);
|
||||
const char* operation = "Create ";
|
||||
const char* name = m_table_name;
|
||||
|
||||
enum_sql_command sqlcom = enum_sql_command(thd_sql_command(m_thd));
|
||||
LEX_CSTRING name= {m_table_name, strlen(m_table_name)};
|
||||
|
||||
if (sqlcom == SQLCOM_ALTER_TABLE) {
|
||||
dict_table_t* table_to_alter;
|
||||
mem_heap_t* heap = mem_heap_create(10000);
|
||||
ulint highest_id_so_far;
|
||||
char* n = dict_get_referenced_table(
|
||||
name, LEX_STRING_WITH_LEN(m_form->s->db),
|
||||
LEX_STRING_WITH_LEN(m_form->s->table_name),
|
||||
&table_to_alter, heap, cs);
|
||||
LEX_CSTRING table_name = m_form->s->table_name;
|
||||
CHARSET_INFO* to_cs = &my_charset_filename;
|
||||
|
||||
if (!strncmp(table_name.str, srv_mysql50_table_name_prefix,
|
||||
sizeof srv_mysql50_table_name_prefix - 1)) {
|
||||
table_name.str
|
||||
+= sizeof srv_mysql50_table_name_prefix - 1;
|
||||
table_name.length
|
||||
-= sizeof srv_mysql50_table_name_prefix - 1;
|
||||
to_cs = system_charset_info;
|
||||
}
|
||||
|
||||
uint errors;
|
||||
LEX_CSTRING t;
|
||||
t.str = t_name;
|
||||
t.length = strconvert(cs, LEX_STRING_WITH_LEN(table_name),
|
||||
to_cs, t_name, MAX_TABLE_NAME_LEN,
|
||||
&errors);
|
||||
LEX_CSTRING d = m_form->s->db;
|
||||
|
||||
if (!strncmp(d.str, srv_mysql50_table_name_prefix,
|
||||
sizeof srv_mysql50_table_name_prefix - 1)) {
|
||||
d.str += sizeof srv_mysql50_table_name_prefix - 1;
|
||||
d.length -= sizeof srv_mysql50_table_name_prefix - 1;
|
||||
to_cs = system_charset_info;
|
||||
} else {
|
||||
to_cs = &my_charset_filename;
|
||||
}
|
||||
|
||||
d.length = strconvert(cs, LEX_STRING_WITH_LEN(d), to_cs,
|
||||
db_name, MAX_DATABASE_NAME_LEN,
|
||||
&errors);
|
||||
d.str = db_name;
|
||||
|
||||
dict_table_t* alter_table;
|
||||
char* n = dict_get_referenced_table(d, t, &alter_table, heap);
|
||||
|
||||
/* Starting from 4.0.18 and 4.1.2, we generate foreign key id's
|
||||
in the format databasename/tablename_ibfk_[number], where
|
||||
[number] is local to the table; look for the highest [number]
|
||||
for table_to_alter, so that we can assign to new constraints
|
||||
for alter_table, so that we can assign to new constraints
|
||||
higher numbers. */
|
||||
|
||||
/* If we are altering a temporary table, the table name after
|
||||
ALTER TABLE does not correspond to the internal table name, and
|
||||
table_to_alter is NULL. TODO: should we fix this somehow? */
|
||||
alter_table=nullptr. But, we do not support FOREIGN KEY
|
||||
constraints for temporary tables. */
|
||||
|
||||
if (table_to_alter) {
|
||||
n = table_to_alter->name.m_name;
|
||||
highest_id_so_far = dict_table_get_highest_foreign_id(
|
||||
table_to_alter);
|
||||
} else {
|
||||
highest_id_so_far = 0;
|
||||
if (alter_table) {
|
||||
n = alter_table->name.m_name;
|
||||
number = 1 + dict_table_get_highest_foreign_id(
|
||||
alter_table);
|
||||
}
|
||||
|
||||
char* bufend = innobase_convert_name(
|
||||
create_name, sizeof create_name, n, strlen(n), m_thd);
|
||||
create_name[bufend - create_name] = '\0';
|
||||
number = highest_id_so_far + 1;
|
||||
*innobase_convert_name(create_name, sizeof create_name,
|
||||
n, strlen(n), m_thd) = '\0';
|
||||
mem_heap_free(heap);
|
||||
operation = "Alter ";
|
||||
} else if (strstr(name, "#P#") || strstr(name, "#p#")) {
|
||||
} else if (strstr(m_table_name, "#P#")
|
||||
|| strstr(m_table_name, "#p#")) {
|
||||
/* Partitioned table */
|
||||
create_name[0] = '\0';
|
||||
} else {
|
||||
char* bufend = innobase_convert_name(create_name,
|
||||
sizeof create_name,
|
||||
name,
|
||||
strlen(name), m_thd);
|
||||
create_name[bufend - create_name] = '\0';
|
||||
*innobase_convert_name(create_name, sizeof create_name,
|
||||
LEX_STRING_WITH_LEN(name), m_thd)= '\0';
|
||||
}
|
||||
|
||||
Alter_info* alter_info = m_create_info->alter_info;
|
||||
ut_ad(alter_info);
|
||||
List_iterator_fast<Key> key_it(alter_info->key_list);
|
||||
|
||||
dict_table_t* table = dict_sys.find_table({name,strlen(name)});
|
||||
dict_table_t* table = dict_sys.find_table({name.str, name.length});
|
||||
if (!table) {
|
||||
ib_foreign_warn(m_trx, DB_CANNOT_ADD_CONSTRAINT, create_name,
|
||||
"%s table %s foreign key constraint"
|
||||
|
@ -12510,27 +12537,27 @@ create_table_info_t::create_foreign_keys()
|
|||
col->field_name.length);
|
||||
success = find_col(table, column_names + i);
|
||||
if (!success) {
|
||||
key_text k(fk);
|
||||
ib_foreign_warn(
|
||||
m_trx, DB_CANNOT_ADD_CONSTRAINT,
|
||||
create_name,
|
||||
"%s table %s foreign key %s constraint"
|
||||
" failed. Column %s was not found.",
|
||||
operation, create_name, k.str(),
|
||||
operation, create_name,
|
||||
key_text(fk).str(),
|
||||
column_names[i]);
|
||||
dict_foreign_free(foreign);
|
||||
return (DB_CANNOT_ADD_CONSTRAINT);
|
||||
}
|
||||
++i;
|
||||
if (i >= MAX_COLS_PER_FK) {
|
||||
key_text k(fk);
|
||||
ib_foreign_warn(
|
||||
m_trx, DB_CANNOT_ADD_CONSTRAINT,
|
||||
create_name,
|
||||
"%s table %s foreign key %s constraint"
|
||||
" failed. Too many columns: %u (%u "
|
||||
"allowed).",
|
||||
operation, create_name, k.str(), i,
|
||||
operation, create_name,
|
||||
key_text(fk).str(), i,
|
||||
MAX_COLS_PER_FK);
|
||||
dict_foreign_free(foreign);
|
||||
return (DB_CANNOT_ADD_CONSTRAINT);
|
||||
|
@ -12542,9 +12569,9 @@ create_table_info_t::create_foreign_keys()
|
|||
&index_error, &err_col, &err_index);
|
||||
|
||||
if (!index) {
|
||||
key_text k(fk);
|
||||
foreign_push_index_error(m_trx, operation, create_name,
|
||||
k.str(), column_names,
|
||||
key_text(fk).str(),
|
||||
column_names,
|
||||
index_error, err_col,
|
||||
err_index, table);
|
||||
dict_foreign_free(foreign);
|
||||
|
@ -12610,32 +12637,68 @@ create_table_info_t::create_foreign_keys()
|
|||
memcpy(foreign->foreign_col_names, column_names,
|
||||
i * sizeof(void*));
|
||||
|
||||
foreign->referenced_table_name = dict_get_referenced_table(
|
||||
name, LEX_STRING_WITH_LEN(fk->ref_db),
|
||||
LEX_STRING_WITH_LEN(fk->ref_table),
|
||||
&foreign->referenced_table, foreign->heap, cs);
|
||||
LEX_CSTRING table_name = fk->ref_table;
|
||||
CHARSET_INFO* to_cs = &my_charset_filename;
|
||||
uint errors;
|
||||
LEX_CSTRING t = table_name;
|
||||
LEX_CSTRING d = fk->ref_db;
|
||||
|
||||
if (!foreign->referenced_table_name) {
|
||||
return (DB_OUT_OF_MEMORY);
|
||||
if (!d.str) {
|
||||
d = {table->name.m_name, table->name.dblen()};
|
||||
}
|
||||
|
||||
if (!strncmp(table_name.str, srv_mysql50_table_name_prefix,
|
||||
sizeof srv_mysql50_table_name_prefix - 1)) {
|
||||
table_name.str
|
||||
+= sizeof srv_mysql50_table_name_prefix - 1;
|
||||
table_name.length
|
||||
-= sizeof srv_mysql50_table_name_prefix - 1;
|
||||
to_cs = system_charset_info;
|
||||
}
|
||||
|
||||
t.str = t_name;
|
||||
t.length = strconvert(cs, LEX_STRING_WITH_LEN(table_name),
|
||||
to_cs, t_name,
|
||||
MAX_TABLE_NAME_LEN, &errors);
|
||||
|
||||
if (!strncmp(d.str, srv_mysql50_table_name_prefix,
|
||||
sizeof srv_mysql50_table_name_prefix - 1)) {
|
||||
d.str += sizeof srv_mysql50_table_name_prefix - 1;
|
||||
d.length -= sizeof srv_mysql50_table_name_prefix - 1;
|
||||
to_cs = system_charset_info;
|
||||
} else if (d.str == table->name.m_name) {
|
||||
goto name_converted;
|
||||
} else {
|
||||
to_cs = &my_charset_filename;
|
||||
}
|
||||
|
||||
if (d.str != table->name.m_name) {
|
||||
d.length = strconvert(cs, LEX_STRING_WITH_LEN(d),
|
||||
to_cs, db_name,
|
||||
MAX_DATABASE_NAME_LEN,
|
||||
&errors);
|
||||
d.str = db_name;
|
||||
}
|
||||
name_converted:
|
||||
foreign->referenced_table_name = dict_get_referenced_table(
|
||||
d, t, &foreign->referenced_table, foreign->heap);
|
||||
|
||||
if (!foreign->referenced_table && m_trx->check_foreigns) {
|
||||
char buf[MAX_TABLE_NAME_LEN + 1] = "";
|
||||
char* bufend;
|
||||
|
||||
bufend = innobase_convert_name(
|
||||
*innobase_convert_name(
|
||||
buf, MAX_TABLE_NAME_LEN,
|
||||
foreign->referenced_table_name,
|
||||
strlen(foreign->referenced_table_name), m_thd);
|
||||
buf[bufend - buf] = '\0';
|
||||
key_text k(fk);
|
||||
strlen(foreign->referenced_table_name), m_thd)
|
||||
= '\0';
|
||||
ib_foreign_warn(m_trx, DB_CANNOT_ADD_CONSTRAINT,
|
||||
create_name,
|
||||
"%s table %s with foreign key %s "
|
||||
"constraint failed. Referenced table "
|
||||
"%s not found in the data dictionary.",
|
||||
operation, create_name, k.str(), buf);
|
||||
return (DB_CANNOT_ADD_CONSTRAINT);
|
||||
operation, create_name,
|
||||
key_text(fk).str(), buf);
|
||||
return DB_CANNOT_ADD_CONSTRAINT;
|
||||
}
|
||||
|
||||
/* Don't allow foreign keys on partitioned tables yet. */
|
||||
|
@ -12658,7 +12721,6 @@ create_table_info_t::create_foreign_keys()
|
|||
success = find_col(foreign->referenced_table,
|
||||
ref_column_names + j);
|
||||
if (!success) {
|
||||
key_text k(fk);
|
||||
ib_foreign_warn(
|
||||
m_trx,
|
||||
DB_CANNOT_ADD_CONSTRAINT,
|
||||
|
@ -12667,9 +12729,9 @@ create_table_info_t::create_foreign_keys()
|
|||
"constraint failed. "
|
||||
"Column %s was not found.",
|
||||
operation, create_name,
|
||||
k.str(), ref_column_names[j]);
|
||||
|
||||
return (DB_CANNOT_ADD_CONSTRAINT);
|
||||
key_text(fk).str(),
|
||||
ref_column_names[j]);
|
||||
return DB_CANNOT_ADD_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
++j;
|
||||
|
@ -12689,16 +12751,15 @@ create_table_info_t::create_foreign_keys()
|
|||
&err_index);
|
||||
|
||||
if (!index) {
|
||||
key_text k(fk);
|
||||
foreign_push_index_error(
|
||||
m_trx, operation, create_name, k.str(),
|
||||
m_trx, operation, create_name,
|
||||
key_text(fk).str(),
|
||||
column_names, index_error, err_col,
|
||||
err_index, foreign->referenced_table);
|
||||
|
||||
return (DB_CANNOT_ADD_CONSTRAINT);
|
||||
return DB_CANNOT_ADD_CONSTRAINT;
|
||||
}
|
||||
} else {
|
||||
ut_a(m_trx->check_foreigns == FALSE);
|
||||
ut_a(!m_trx->check_foreigns);
|
||||
index = NULL;
|
||||
}
|
||||
|
||||
|
@ -12735,7 +12796,6 @@ create_table_info_t::create_foreign_keys()
|
|||
NULL
|
||||
if the column is not allowed to be
|
||||
NULL! */
|
||||
key_text k(fk);
|
||||
ib_foreign_warn(
|
||||
m_trx,
|
||||
DB_CANNOT_ADD_CONSTRAINT,
|
||||
|
@ -12746,9 +12806,9 @@ create_table_info_t::create_foreign_keys()
|
|||
"but column '%s' is defined as "
|
||||
"NOT NULL.",
|
||||
operation, create_name,
|
||||
k.str(), col_name);
|
||||
key_text(fk).str(), col_name);
|
||||
|
||||
return (DB_CANNOT_ADD_CONSTRAINT);
|
||||
return DB_CANNOT_ADD_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ Smart ALTER TABLE
|
|||
#include <sql_class.h>
|
||||
#include <sql_table.h>
|
||||
#include <mysql/plugin.h>
|
||||
#include <strfunc.h>
|
||||
|
||||
/* Include necessary InnoDB headers */
|
||||
#include "btr0sea.h"
|
||||
|
@ -3231,6 +3232,8 @@ innobase_get_foreign_key_info(
|
|||
ulint num_fk = 0;
|
||||
Alter_info* alter_info = ha_alter_info->alter_info;
|
||||
const CHARSET_INFO* cs = thd_charset(trx->mysql_thd);
|
||||
char db_name[MAX_DATABASE_NAME_LEN + 1];
|
||||
char t_name[MAX_TABLE_NAME_LEN + 1];
|
||||
|
||||
DBUG_ENTER("innobase_get_foreign_key_info");
|
||||
|
||||
|
@ -3295,14 +3298,51 @@ innobase_get_foreign_key_info(
|
|||
|
||||
add_fk[num_fk] = dict_mem_foreign_create();
|
||||
|
||||
LEX_CSTRING table_name = fk_key->ref_table;
|
||||
CHARSET_INFO* to_cs = &my_charset_filename;
|
||||
|
||||
if (!strncmp(table_name.str, srv_mysql50_table_name_prefix,
|
||||
sizeof srv_mysql50_table_name_prefix - 1)) {
|
||||
table_name.str
|
||||
+= sizeof srv_mysql50_table_name_prefix - 1;
|
||||
table_name.length
|
||||
-= sizeof srv_mysql50_table_name_prefix - 1;
|
||||
to_cs = system_charset_info;
|
||||
}
|
||||
|
||||
uint errors;
|
||||
LEX_CSTRING t;
|
||||
t.str = t_name;
|
||||
t.length = strconvert(cs, LEX_STRING_WITH_LEN(table_name),
|
||||
to_cs, t_name, MAX_TABLE_NAME_LEN,
|
||||
&errors);
|
||||
LEX_CSTRING d = fk_key->ref_db;
|
||||
if (!d.str) {
|
||||
d.str = table->name.m_name;
|
||||
d.length = table->name.dblen();
|
||||
}
|
||||
|
||||
if (!strncmp(d.str, srv_mysql50_table_name_prefix,
|
||||
sizeof srv_mysql50_table_name_prefix - 1)) {
|
||||
d.str += sizeof srv_mysql50_table_name_prefix - 1;
|
||||
d.length -= sizeof srv_mysql50_table_name_prefix - 1;
|
||||
to_cs = system_charset_info;
|
||||
} else if (d.str == table->name.m_name) {
|
||||
goto name_converted;
|
||||
} else {
|
||||
to_cs = &my_charset_filename;
|
||||
}
|
||||
|
||||
d.length = strconvert(cs, LEX_STRING_WITH_LEN(d), to_cs,
|
||||
db_name, MAX_DATABASE_NAME_LEN,
|
||||
&errors);
|
||||
d.str = db_name;
|
||||
|
||||
name_converted:
|
||||
dict_sys.lock(SRW_LOCK_CALL);
|
||||
|
||||
referenced_table_name = dict_get_referenced_table(
|
||||
table->name.m_name,
|
||||
LEX_STRING_WITH_LEN(fk_key->ref_db),
|
||||
LEX_STRING_WITH_LEN(fk_key->ref_table),
|
||||
&referenced_table,
|
||||
add_fk[num_fk]->heap, cs);
|
||||
d, t, &referenced_table, add_fk[num_fk]->heap);
|
||||
|
||||
/* Test the case when referenced_table failed to
|
||||
open, if trx->check_foreigns is not set, we should
|
||||
|
|
|
@ -62,15 +62,11 @@ foreign constraint parser to get the referenced table.
|
|||
heap memory passed in */
|
||||
char*
|
||||
dict_get_referenced_table(
|
||||
/*======================*/
|
||||
const char* name, /*!< in: foreign key table name */
|
||||
const char* database_name, /*!< in: table db name */
|
||||
ulint database_name_len,/*!< in: db name length */
|
||||
const char* table_name, /*!< in: table name */
|
||||
ulint table_name_len, /*!< in: table name length */
|
||||
LEX_CSTRING database_name, /*!< in: table db name */
|
||||
LEX_CSTRING table_name, /*!< in: table name */
|
||||
dict_table_t** table, /*!< out: table object or NULL */
|
||||
mem_heap_t* heap, /*!< in: heap memory */
|
||||
CHARSET_INFO* from_cs); /*!< in: table name charset */
|
||||
mem_heap_t* heap) /*!< in/out: heap memory */
|
||||
noexcept MY_ATTRIBUTE((nonnull));
|
||||
/*********************************************************************//**
|
||||
Frees a foreign key struct. */
|
||||
void
|
||||
|
|
Loading…
Reference in a new issue