Merge Spider updates. Fixes
MDEV-4736 - Assertion `! is_set()' fails in Diagnostics_area::set_ok_status on UPDATE which violates constraint on a remote table
This commit is contained in:
commit
eea91f633f
@ -1878,8 +1878,9 @@ int ha_spider::index_init(
|
||||
if (result_list.lock_type == F_WRLCK)
|
||||
{
|
||||
pk_update = FALSE;
|
||||
/*
|
||||
check_and_start_bulk_update(SPD_BU_START_BY_INDEX_OR_RND_INIT);
|
||||
|
||||
*/
|
||||
if (
|
||||
update_request &&
|
||||
share->have_recovery_link &&
|
||||
@ -1941,6 +1942,7 @@ int ha_spider::index_end()
|
||||
}
|
||||
#endif
|
||||
active_index = MAX_KEY;
|
||||
/*
|
||||
#ifdef INFO_KIND_FORCE_LIMIT_BEGIN
|
||||
info_limit = 9223372036854775807LL;
|
||||
#endif
|
||||
@ -1951,6 +1953,9 @@ int ha_spider::index_end()
|
||||
(error_num = spider_trx_check_link_idx_failed(this))
|
||||
)
|
||||
DBUG_RETURN(check_error_mode(error_num));
|
||||
*/
|
||||
if ((error_num = drop_tmp_tables()))
|
||||
DBUG_RETURN(check_error_mode(error_num));
|
||||
result_list.use_union = FALSE;
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
@ -6942,9 +6947,10 @@ int ha_spider::rnd_init(
|
||||
DBUG_PRINT("info",("spider this=%p", this));
|
||||
DBUG_PRINT("info",("spider scan=%s", scan ? "TRUE" : "FALSE"));
|
||||
pushed_pos = NULL;
|
||||
/*
|
||||
if (result_list.lock_type == F_WRLCK)
|
||||
check_and_start_bulk_update(SPD_BU_START_BY_INDEX_OR_RND_INIT);
|
||||
|
||||
*/
|
||||
rnd_scan_and_first = scan;
|
||||
if (
|
||||
scan &&
|
||||
@ -7054,10 +7060,13 @@ int ha_spider::pre_rnd_init(
|
||||
|
||||
int ha_spider::rnd_end()
|
||||
{
|
||||
/*
|
||||
int error_num;
|
||||
backup_error_status();
|
||||
*/
|
||||
DBUG_ENTER("ha_spider::rnd_end");
|
||||
DBUG_PRINT("info",("spider this=%p", this));
|
||||
/*
|
||||
#ifdef INFO_KIND_FORCE_LIMIT_BEGIN
|
||||
info_limit = 9223372036854775807LL;
|
||||
#endif
|
||||
@ -7067,6 +7076,7 @@ int ha_spider::rnd_end()
|
||||
(error_num = spider_trx_check_link_idx_failed(this))
|
||||
)
|
||||
DBUG_RETURN(check_error_mode(error_num));
|
||||
*/
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
@ -8666,6 +8676,12 @@ ulonglong ha_spider::table_flags() const
|
||||
SPIDER_CAN_BG_SEARCH |
|
||||
SPIDER_CAN_BG_INSERT |
|
||||
SPIDER_CAN_BG_UPDATE |
|
||||
#ifdef HA_CAN_FORCE_BULK_UPDATE
|
||||
(share && share->force_bulk_update ? HA_CAN_FORCE_BULK_UPDATE : 0) |
|
||||
#endif
|
||||
#ifdef HA_CAN_FORCE_BULK_DELETE
|
||||
(share && share->force_bulk_delete ? HA_CAN_FORCE_BULK_DELETE : 0) |
|
||||
#endif
|
||||
(share ? share->additional_table_flags : 0)
|
||||
;
|
||||
DBUG_RETURN(flags);
|
||||
@ -11094,27 +11110,43 @@ bool ha_spider::check_and_start_bulk_update(
|
||||
THD *thd = ha_thd();
|
||||
int bulk_update_mode = spider_param_bulk_update_mode(thd,
|
||||
share->bulk_update_mode);
|
||||
/*
|
||||
longlong split_read = spider_split_read_param(this);
|
||||
*/
|
||||
result_list.bulk_update_size = spider_param_bulk_update_size(thd,
|
||||
share->bulk_update_size);
|
||||
/*
|
||||
#ifndef WITHOUT_SPIDER_BG_SEARCH
|
||||
int bgs_mode = spider_param_bgs_mode(thd, share->bgs_mode);
|
||||
#endif
|
||||
*/
|
||||
if (!support_bulk_update_sql())
|
||||
{
|
||||
result_list.bulk_update_mode = 0;
|
||||
DBUG_PRINT("info",("spider result_list.bulk_update_mode=%d 1",
|
||||
result_list.bulk_update_mode));
|
||||
/*
|
||||
} else if (
|
||||
#ifndef WITHOUT_SPIDER_BG_SEARCH
|
||||
bgs_mode ||
|
||||
#endif
|
||||
split_read != 9223372036854775807LL
|
||||
)
|
||||
) {
|
||||
result_list.bulk_update_mode = 2;
|
||||
else {
|
||||
DBUG_PRINT("info",("spider result_list.bulk_update_mode=%d 2",
|
||||
result_list.bulk_update_mode));
|
||||
*/
|
||||
} else {
|
||||
if (result_list.bulk_update_start == SPD_BU_NOT_START)
|
||||
{
|
||||
result_list.bulk_update_mode = bulk_update_mode;
|
||||
else
|
||||
DBUG_PRINT("info",("spider result_list.bulk_update_mode=%d 3",
|
||||
result_list.bulk_update_mode));
|
||||
} else {
|
||||
result_list.bulk_update_mode = 1;
|
||||
DBUG_PRINT("info",("spider result_list.bulk_update_mode=%d 4",
|
||||
result_list.bulk_update_mode));
|
||||
}
|
||||
}
|
||||
result_list.bulk_update_start = bulk_upd_start;
|
||||
DBUG_RETURN(FALSE);
|
||||
|
@ -89,8 +89,8 @@ Variable_name Value
|
||||
SELECT a, b, date_format(c, '%Y-%m-%d %H:%i:%s') FROM ta_l ORDER BY a;
|
||||
a b date_format(c, '%Y-%m-%d %H:%i:%s')
|
||||
1 a 2008-08-02 10:21:39
|
||||
2 b 2000-01-03 00:00:00
|
||||
3 x 2011-10-17 00:00:00
|
||||
2 b 2000-01-02 00:00:00
|
||||
3 x 2011-10-18 00:00:00
|
||||
4 d 2003-12-01 05:01:03
|
||||
5 c 2002-01-01 23:59:59
|
||||
delete by primary key with order and limit
|
||||
@ -100,7 +100,7 @@ Variable_name Value
|
||||
SELECT a, b, date_format(c, '%Y-%m-%d %H:%i:%s') FROM ta_l ORDER BY a;
|
||||
a b date_format(c, '%Y-%m-%d %H:%i:%s')
|
||||
1 a 2008-08-02 10:21:39
|
||||
3 x 2011-10-17 00:00:00
|
||||
3 x 2011-10-18 00:00:00
|
||||
4 d 2003-12-01 05:01:03
|
||||
5 c 2002-01-01 23:59:59
|
||||
delete by a column without index
|
||||
@ -110,7 +110,7 @@ Variable_name Value
|
||||
SELECT a, b, date_format(c, '%Y-%m-%d %H:%i:%s') FROM ta_l ORDER BY a;
|
||||
a b date_format(c, '%Y-%m-%d %H:%i:%s')
|
||||
1 a 2008-08-02 10:21:39
|
||||
3 x 2011-10-17 00:00:00
|
||||
3 x 2011-10-18 00:00:00
|
||||
4 d 2003-12-01 05:01:03
|
||||
delete by primary key
|
||||
DELETE FROM ta_l WHERE a = 3;
|
||||
|
@ -80,8 +80,8 @@ Variable_name Value
|
||||
SELECT a, b, date_format(c, '%Y-%m-%d %H:%i:%s') FROM ta_l2 ORDER BY a;
|
||||
a b date_format(c, '%Y-%m-%d %H:%i:%s')
|
||||
1 a 2008-08-02 10:21:39
|
||||
2 b 2000-01-03 00:00:00
|
||||
3 x 2011-10-17 00:00:00
|
||||
2 b 2000-01-02 00:00:00
|
||||
3 x 2011-10-18 00:00:00
|
||||
4 d 2003-12-01 05:01:03
|
||||
5 c 2002-01-01 23:59:59
|
||||
delete by primary key with order and limit
|
||||
@ -91,7 +91,7 @@ Variable_name Value
|
||||
SELECT a, b, date_format(c, '%Y-%m-%d %H:%i:%s') FROM ta_l2 ORDER BY a;
|
||||
a b date_format(c, '%Y-%m-%d %H:%i:%s')
|
||||
1 a 2008-08-02 10:21:39
|
||||
3 x 2011-10-17 00:00:00
|
||||
3 x 2011-10-18 00:00:00
|
||||
4 d 2003-12-01 05:01:03
|
||||
5 c 2002-01-01 23:59:59
|
||||
delete by a column without index
|
||||
@ -101,7 +101,7 @@ Variable_name Value
|
||||
SELECT a, b, date_format(c, '%Y-%m-%d %H:%i:%s') FROM ta_l2 ORDER BY a;
|
||||
a b date_format(c, '%Y-%m-%d %H:%i:%s')
|
||||
1 a 2008-08-02 10:21:39
|
||||
3 x 2011-10-17 00:00:00
|
||||
3 x 2011-10-18 00:00:00
|
||||
4 d 2003-12-01 05:01:03
|
||||
delete by primary key
|
||||
DELETE FROM ta_l2 WHERE a = 3;
|
||||
|
@ -89,8 +89,8 @@ Variable_name Value
|
||||
SELECT a, b, date_format(c, '%Y-%m-%d %H:%i:%s') FROM ta_l ORDER BY a;
|
||||
a b date_format(c, '%Y-%m-%d %H:%i:%s')
|
||||
1 a 2008-08-02 10:21:39
|
||||
2 b 2000-01-03 00:00:00
|
||||
3 x 2011-10-17 00:00:00
|
||||
2 b 2000-01-02 00:00:00
|
||||
3 x 2011-10-18 00:00:00
|
||||
4 d 2003-12-01 05:01:03
|
||||
5 c 2002-01-01 23:59:59
|
||||
delete by primary key with order and limit
|
||||
@ -100,7 +100,7 @@ Variable_name Value
|
||||
SELECT a, b, date_format(c, '%Y-%m-%d %H:%i:%s') FROM ta_l ORDER BY a;
|
||||
a b date_format(c, '%Y-%m-%d %H:%i:%s')
|
||||
1 a 2008-08-02 10:21:39
|
||||
3 x 2011-10-17 00:00:00
|
||||
3 x 2011-10-18 00:00:00
|
||||
4 d 2003-12-01 05:01:03
|
||||
5 c 2002-01-01 23:59:59
|
||||
delete by a column without index
|
||||
@ -110,7 +110,7 @@ Variable_name Value
|
||||
SELECT a, b, date_format(c, '%Y-%m-%d %H:%i:%s') FROM ta_l ORDER BY a;
|
||||
a b date_format(c, '%Y-%m-%d %H:%i:%s')
|
||||
1 a 2008-08-02 10:21:39
|
||||
3 x 2011-10-17 00:00:00
|
||||
3 x 2011-10-18 00:00:00
|
||||
4 d 2003-12-01 05:01:03
|
||||
delete by primary key
|
||||
DELETE FROM ta_l WHERE a = 3;
|
||||
|
@ -80,8 +80,8 @@ Variable_name Value
|
||||
SELECT a, b, date_format(c, '%Y-%m-%d %H:%i:%s') FROM ta_l2 ORDER BY a;
|
||||
a b date_format(c, '%Y-%m-%d %H:%i:%s')
|
||||
1 a 2008-08-02 10:21:39
|
||||
2 b 2000-01-03 00:00:00
|
||||
3 x 2011-10-17 00:00:00
|
||||
2 b 2000-01-02 00:00:00
|
||||
3 x 2011-10-18 00:00:00
|
||||
4 d 2003-12-01 05:01:03
|
||||
5 c 2002-01-01 23:59:59
|
||||
delete by primary key with order and limit
|
||||
@ -91,7 +91,7 @@ Variable_name Value
|
||||
SELECT a, b, date_format(c, '%Y-%m-%d %H:%i:%s') FROM ta_l2 ORDER BY a;
|
||||
a b date_format(c, '%Y-%m-%d %H:%i:%s')
|
||||
1 a 2008-08-02 10:21:39
|
||||
3 x 2011-10-17 00:00:00
|
||||
3 x 2011-10-18 00:00:00
|
||||
4 d 2003-12-01 05:01:03
|
||||
5 c 2002-01-01 23:59:59
|
||||
delete by a column without index
|
||||
@ -101,7 +101,7 @@ Variable_name Value
|
||||
SELECT a, b, date_format(c, '%Y-%m-%d %H:%i:%s') FROM ta_l2 ORDER BY a;
|
||||
a b date_format(c, '%Y-%m-%d %H:%i:%s')
|
||||
1 a 2008-08-02 10:21:39
|
||||
3 x 2011-10-17 00:00:00
|
||||
3 x 2011-10-18 00:00:00
|
||||
4 d 2003-12-01 05:01:03
|
||||
delete by primary key
|
||||
DELETE FROM ta_l2 WHERE a = 3;
|
||||
|
@ -1038,11 +1038,15 @@ int spider_db_query_for_bulk_update(
|
||||
);
|
||||
}
|
||||
if (
|
||||
error_num == ER_DUP_ENTRY ||
|
||||
error_num == ER_DUP_KEY ||
|
||||
error_num == HA_ERR_FOUND_DUPP_KEY
|
||||
spider->ignore_dup_key &&
|
||||
(
|
||||
error_num == ER_DUP_ENTRY ||
|
||||
error_num == ER_DUP_KEY ||
|
||||
error_num == HA_ERR_FOUND_DUPP_KEY
|
||||
)
|
||||
) {
|
||||
++(*dup_key_found);
|
||||
spider->trx->thd->clear_error();
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
DBUG_RETURN(error_num);
|
||||
|
@ -728,6 +728,12 @@ typedef struct st_spider_share
|
||||
#ifdef HA_CAN_BULK_ACCESS
|
||||
int bulk_access_free;
|
||||
#endif
|
||||
#ifdef HA_CAN_FORCE_BULK_UPDATE
|
||||
int force_bulk_update;
|
||||
#endif
|
||||
#ifdef HA_CAN_FORCE_BULK_DELETE
|
||||
int force_bulk_delete;
|
||||
#endif
|
||||
|
||||
int bka_mode;
|
||||
char *bka_engine;
|
||||
|
@ -1783,6 +1783,12 @@ int spider_parse_connect_info(
|
||||
#ifdef HA_CAN_BULK_ACCESS
|
||||
share->bulk_access_free = -1;
|
||||
#endif
|
||||
#ifdef HA_CAN_FORCE_BULK_UPDATE
|
||||
share->force_bulk_update = -1;
|
||||
#endif
|
||||
#ifdef HA_CAN_FORCE_BULK_DELETE
|
||||
share->force_bulk_delete = -1;
|
||||
#endif
|
||||
|
||||
#ifdef WITH_PARTITION_STORAGE_ENGINE
|
||||
for (roop_count = 4; roop_count > 0; roop_count--)
|
||||
@ -1925,6 +1931,12 @@ int spider_parse_connect_info(
|
||||
SPIDER_PARAM_LONGLONG("dol", direct_order_limit, 0);
|
||||
SPIDER_PARAM_INT_WITH_MAX("erm", error_read_mode, 0, 1);
|
||||
SPIDER_PARAM_INT_WITH_MAX("ewm", error_write_mode, 0, 1);
|
||||
#ifdef HA_CAN_FORCE_BULK_DELETE
|
||||
SPIDER_PARAM_INT_WITH_MAX("fbd", force_bulk_delete, 0, 1);
|
||||
#endif
|
||||
#ifdef HA_CAN_FORCE_BULK_UPDATE
|
||||
SPIDER_PARAM_INT_WITH_MAX("fbu", force_bulk_update, 0, 1);
|
||||
#endif
|
||||
SPIDER_PARAM_LONGLONG("frd", first_read, 0);
|
||||
#if defined(HS_HAS_SQLCOM) && defined(HAVE_HANDLERSOCKET)
|
||||
SPIDER_PARAM_LONGLONG("hrf", hs_result_free_size, 0);
|
||||
@ -2205,6 +2217,14 @@ int spider_parse_connect_info(
|
||||
SPIDER_PARAM_INT("active_link_count", active_link_count, 1);
|
||||
SPIDER_PARAM_LONG_LIST_WITH_MAX("net_write_timeout",
|
||||
net_write_timeouts, 0, 2147483647);
|
||||
#ifdef HA_CAN_FORCE_BULK_DELETE
|
||||
SPIDER_PARAM_INT_WITH_MAX(
|
||||
"force_bulk_delete", force_bulk_delete, 0, 1);
|
||||
#endif
|
||||
#ifdef HA_CAN_FORCE_BULK_UPDATE
|
||||
SPIDER_PARAM_INT_WITH_MAX(
|
||||
"force_bulk_update", force_bulk_update, 0, 1);
|
||||
#endif
|
||||
error_num = ER_SPIDER_INVALID_CONNECT_INFO_NUM;
|
||||
my_printf_error(error_num, ER_SPIDER_INVALID_CONNECT_INFO_STR,
|
||||
MYF(0), tmp_ptr);
|
||||
@ -3404,6 +3424,14 @@ int spider_set_connect_info_default(
|
||||
#ifdef HA_CAN_BULK_ACCESS
|
||||
if (share->bulk_access_free == -1)
|
||||
share->bulk_access_free = 0;
|
||||
#endif
|
||||
#ifdef HA_CAN_FORCE_BULK_UPDATE
|
||||
if (share->force_bulk_update == -1)
|
||||
share->force_bulk_update = 0;
|
||||
#endif
|
||||
#ifdef HA_CAN_FORCE_BULK_DELETE
|
||||
if (share->force_bulk_delete == -1)
|
||||
share->force_bulk_delete = 0;
|
||||
#endif
|
||||
if (share->bka_mode == -1)
|
||||
share->bka_mode = 1;
|
||||
@ -7398,6 +7426,77 @@ longlong spider_split_read_param(
|
||||
spider_get_select_limit(spider, &select_lex, &select_limit, &offset_limit);
|
||||
if (!result_list->set_split_read)
|
||||
{
|
||||
int bulk_update_mode = spider_param_bulk_update_mode(thd,
|
||||
share->bulk_update_mode);
|
||||
DBUG_PRINT("info",("spider sql_command=%u", spider->sql_command));
|
||||
DBUG_PRINT("info",("spider bulk_update_mode=%d", bulk_update_mode));
|
||||
DBUG_PRINT("info",("spider support_bulk_update_sql=%s",
|
||||
spider->support_bulk_update_sql() ? "TRUE" : "FALSE"));
|
||||
bool updating =
|
||||
(
|
||||
#ifdef HS_HAS_SQLCOM
|
||||
spider->sql_command == SQLCOM_HS_UPDATE ||
|
||||
#endif
|
||||
spider->sql_command == SQLCOM_UPDATE ||
|
||||
spider->sql_command == SQLCOM_UPDATE_MULTI
|
||||
);
|
||||
bool deleting =
|
||||
(
|
||||
#ifdef HS_HAS_SQLCOM
|
||||
spider->sql_command == SQLCOM_HS_DELETE ||
|
||||
#endif
|
||||
spider->sql_command == SQLCOM_DELETE ||
|
||||
spider->sql_command == SQLCOM_DELETE_MULTI
|
||||
);
|
||||
bool replacing =
|
||||
(
|
||||
spider->sql_command == SQLCOM_REPLACE ||
|
||||
spider->sql_command == SQLCOM_REPLACE_SELECT
|
||||
);
|
||||
DBUG_PRINT("info",("spider updating=%s", updating ? "TRUE" : "FALSE"));
|
||||
DBUG_PRINT("info",("spider deleting=%s", deleting ? "TRUE" : "FALSE"));
|
||||
DBUG_PRINT("info",("spider replacing=%s", replacing ? "TRUE" : "FALSE"));
|
||||
TABLE *table = spider->get_table();
|
||||
if (
|
||||
replacing ||
|
||||
(
|
||||
(
|
||||
updating ||
|
||||
deleting
|
||||
) &&
|
||||
(
|
||||
bulk_update_mode != 2 ||
|
||||
!spider->support_bulk_update_sql() ||
|
||||
(
|
||||
updating &&
|
||||
table->triggers &&
|
||||
#ifdef HA_CAN_FORCE_BULK_UPDATE
|
||||
!(table->file->ha_table_flags() & HA_CAN_FORCE_BULK_UPDATE) &&
|
||||
#endif
|
||||
table->triggers->has_triggers(TRG_EVENT_UPDATE, TRG_ACTION_AFTER)
|
||||
) ||
|
||||
(
|
||||
deleting &&
|
||||
table->triggers &&
|
||||
#ifdef HA_CAN_FORCE_BULK_DELETE
|
||||
!(table->file->ha_table_flags() & HA_CAN_FORCE_BULK_DELETE) &&
|
||||
#endif
|
||||
table->triggers->has_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER)
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
/* This case must select by one shot */
|
||||
DBUG_PRINT("info",("spider cancel split read"));
|
||||
result_list->split_read_base = 9223372036854775807LL;
|
||||
result_list->semi_split_read = 9223372036854775807LL;
|
||||
result_list->semi_split_read_limit = 9223372036854775807LL;
|
||||
result_list->first_read = 9223372036854775807LL;
|
||||
result_list->second_read = 9223372036854775807LL;
|
||||
result_list->semi_split_read_base = 0;
|
||||
result_list->set_split_read = TRUE;
|
||||
DBUG_RETURN(9223372036854775807LL);
|
||||
}
|
||||
result_list->split_read_base =
|
||||
spider_param_split_read(thd, share->split_read);
|
||||
result_list->semi_split_read =
|
||||
|
@ -2429,14 +2429,18 @@ int spider_initinal_xa_recover(
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (!thd)
|
||||
{
|
||||
*/
|
||||
if (!(thd = spider_create_tmp_thd()))
|
||||
{
|
||||
error_num = HA_ERR_OUT_OF_MEM;
|
||||
goto error_create_thd;
|
||||
}
|
||||
/*
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
select
|
||||
@ -2465,8 +2469,10 @@ int spider_initinal_xa_recover(
|
||||
}
|
||||
free_root(&mem_root, MYF(0));
|
||||
|
||||
/*
|
||||
if (cnt < (int) len)
|
||||
{
|
||||
*/
|
||||
end_read_record(read_record);
|
||||
spider_close_sys_table(thd, table_xa, open_tables_backup, TRUE);
|
||||
table_xa = NULL;
|
||||
@ -2476,7 +2482,9 @@ int spider_initinal_xa_recover(
|
||||
read_record = NULL;
|
||||
delete open_tables_backup;
|
||||
open_tables_backup = NULL;
|
||||
/*
|
||||
}
|
||||
*/
|
||||
DBUG_RETURN(cnt);
|
||||
|
||||
/*
|
||||
@ -3749,6 +3757,11 @@ void spider_free_tmp_thd(
|
||||
THD *thd
|
||||
) {
|
||||
DBUG_ENTER("spider_free_tmp_thd");
|
||||
#if defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 100000
|
||||
thd->reset_globals();
|
||||
#else
|
||||
thd->restore_globals();
|
||||
#endif
|
||||
thd->cleanup();
|
||||
delete thd;
|
||||
DBUG_VOID_RETURN;
|
||||
@ -3966,7 +3979,7 @@ int spider_trx_check_link_idx_failed(
|
||||
uint *conn_link_idx = spider->conn_link_idx;
|
||||
int link_count = share->link_count;
|
||||
uchar *conn_can_fo = spider->conn_can_fo;
|
||||
DBUG_ENTER("spider_trx_set_link_idx_for_all");
|
||||
DBUG_ENTER("spider_trx_check_link_idx_failed");
|
||||
for (roop_count = 0; roop_count < link_count; roop_count++)
|
||||
{
|
||||
if (
|
||||
|
Loading…
x
Reference in New Issue
Block a user