diff --git a/include/ma_common.h b/include/ma_common.h index 4c4e76a2..d73ad58b 100644 --- a/include/ma_common.h +++ b/include/ma_common.h @@ -26,7 +26,8 @@ enum enum_multi_status { COM_MULTI_OFF= 0, COM_MULTI_CANCEL, - COM_MULTI_PROGRESS, + COM_MULTI_ENABLED, + COM_MULTI_DISABLED, COM_MULTI_END }; diff --git a/include/mariadb_stmt.h b/include/mariadb_stmt.h index ed8500c0..4189c37f 100644 --- a/include/mariadb_stmt.h +++ b/include/mariadb_stmt.h @@ -222,6 +222,7 @@ struct st_mysql_stmt struct st_mysqlnd_stmt_methods *m; unsigned int array_size; size_t row_size; + unsigned int prebind_params; }; typedef void (*ps_field_fetch_func)(MYSQL_BIND *r_param, const MYSQL_FIELD * field, unsigned char **row); diff --git a/include/mysql.h b/include/mysql.h index d8de55b6..3cf28cdf 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -160,12 +160,6 @@ extern unsigned int mariadb_deinitialize_ssl; void *extension; } MYSQL_DATA; - enum mariadb_com_multi { - MARIADB_COM_MULTI_END, - MARIADB_COM_MULTI_BEGIN, - MARIADB_COM_MULTI_CANCEL - }; - enum mysql_option { MYSQL_OPT_CONNECT_TIMEOUT, diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c index 4b920525..e05aa13f 100644 --- a/libmariadb/mariadb_lib.c +++ b/libmariadb/mariadb_lib.c @@ -382,7 +382,7 @@ mthd_my_send_cmd(MYSQL *mysql,enum enum_server_command command, const char *arg, if (!arg) arg=""; - if (net->extension->multi_status== COM_MULTI_PROGRESS) + if (net->extension->multi_status== COM_MULTI_ENABLED) { return net_add_multi_command(net, command, (const uchar *)arg, length); } @@ -435,12 +435,17 @@ int ma_multi_command(MYSQL *mysql, enum enum_multi_status status) ma_net_clear(net); net->extension->multi_status= status; return 0; - case COM_MULTI_PROGRESS: - if (net->extension->multi_status > COM_MULTI_OFF) + case COM_MULTI_ENABLED: + if (net->extension->multi_status > COM_MULTI_DISABLED) return 1; ma_net_clear(net); net->extension->multi_status= status; return 0; + case COM_MULTI_DISABLED: + /* Opposite to COM_MULTI_OFF we don't clear net buffer, + next command or com_nulti_end will flush entire buffer */ + net->extension->multi_status= status; + return 0; case COM_MULTI_END: { size_t len= net->write_pos - net->buff - NET_HEADER_SIZE; diff --git a/libmariadb/mariadb_stmt.c b/libmariadb/mariadb_stmt.c index 28a39dac..7c575721 100644 --- a/libmariadb/mariadb_stmt.c +++ b/libmariadb/mariadb_stmt.c @@ -835,7 +835,7 @@ my_bool STDCALL mysql_stmt_attr_get(MYSQL_STMT *stmt, enum enum_stmt_attr_type a *(unsigned long *)value= stmt->prefetch_rows; break; case STMT_ATTR_PREBIND_PARAMS: - *(unsigned int *)value= stmt->param_count; + *(unsigned int *)value= stmt->prebind_params; break; case STMT_ATTR_ARRAY_SIZE: *(unsigned int *)value= stmt->array_size; @@ -870,7 +870,7 @@ my_bool STDCALL mysql_stmt_attr_set(MYSQL_STMT *stmt, enum enum_stmt_attr_type a stmt->prefetch_rows= *(long *)value; break; case STMT_ATTR_PREBIND_PARAMS: - stmt->param_count= *(unsigned int *)value; + stmt->prebind_params= *(unsigned int *)value; break; case STMT_ATTR_ARRAY_SIZE: stmt->array_size= *(unsigned int *)value; @@ -899,17 +899,18 @@ my_bool STDCALL mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND *bind) them, e.g. for mariadb_stmt_execute_direct() */ if ((stmt->state < MYSQL_STMT_PREPARED || stmt->state >= MYSQL_STMT_EXECUTED) && - !(mysql->server_capabilities & CLIENT_MYSQL)) + stmt->prebind_params > 0) { - if (!stmt->params && stmt->param_count) + if (!stmt->params && stmt->prebind_params) { - if (!(stmt->params= (MYSQL_BIND *)ma_alloc_root(&stmt->mem_root, stmt->param_count * sizeof(MYSQL_BIND)))) + if (!(stmt->params= (MYSQL_BIND *)ma_alloc_root(&stmt->mem_root, stmt->prebind_params * sizeof(MYSQL_BIND)))) { SET_CLIENT_STMT_ERROR(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); return(1); } - memset(stmt->params, '\0', stmt->param_count * sizeof(MYSQL_BIND)); + memset(stmt->params, '\0', stmt->prebind_params * sizeof(MYSQL_BIND)); } + stmt->param_count= stmt->prebind_params; } else if (stmt->state < MYSQL_STMT_PREPARED) { SET_CLIENT_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, SQLSTATE_UNKNOWN, 0); @@ -1350,6 +1351,7 @@ int STDCALL mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, size_t lengt { MYSQL *mysql= stmt->mysql; int rc= 1; + my_bool is_multi= 0; if (!stmt->mysql) { @@ -1368,10 +1370,14 @@ int STDCALL mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, size_t lengt /* check if we have to clear results */ if (stmt->state > MYSQL_STMT_INITTED) { + char stmt_id[STMT_ID_LENGTH]; + is_multi= (mysql->net.extension->multi_status > COM_MULTI_OFF); /* We need to semi-close the prepared statement: reset stmt and free all buffers and close the statement on server side. Statment handle will get a new stmt_id */ - char stmt_id[STMT_ID_LENGTH]; + + if (!is_multi) + ma_multi_command(mysql, COM_MULTI_ENABLED); if (mysql_stmt_internal_reset(stmt, 1)) goto fail; @@ -1391,6 +1397,9 @@ int STDCALL mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, size_t lengt if (mysql->methods->db_command(mysql, COM_STMT_PREPARE, query, length, 1, stmt)) goto fail; + if (!is_multi && mysql->net.extension->multi_status == COM_MULTI_ENABLED) + ma_multi_command(mysql, COM_MULTI_END); + if (mysql->net.extension->multi_status > COM_MULTI_OFF) return 0; @@ -1414,12 +1423,21 @@ int STDCALL mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, size_t lengt } if (stmt->param_count) { - if (!(stmt->params= (MYSQL_BIND *)ma_alloc_root(&stmt->mem_root, stmt->param_count * sizeof(MYSQL_BIND)))) + if (stmt->prebind_params) { - SET_CLIENT_STMT_ERROR(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); - goto fail; + if (stmt->prebind_params != stmt->param_count) + { + SET_CLIENT_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, SQLSTATE_UNKNOWN, 0); + goto fail; + } + } else { + if (!(stmt->params= (MYSQL_BIND *)ma_alloc_root(&stmt->mem_root, stmt->param_count * sizeof(MYSQL_BIND)))) + { + SET_CLIENT_STMT_ERROR(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + goto fail; + } + memset(stmt->params, '\0', stmt->param_count * sizeof(MYSQL_BIND)); } - memset(stmt->params, '\0', stmt->param_count * sizeof(MYSQL_BIND)); } /* allocated bind buffer for result */ if (stmt->field_count) @@ -2087,12 +2105,14 @@ int STDCALL mariadb_stmt_execute_direct(MYSQL_STMT *stmt, { int rc; + /* avoid sending close + prepare in 2 packets */ + if ((rc= mysql_stmt_prepare(stmt, stmt_str, length))) return rc; return mysql_stmt_execute(stmt); } - if (ma_multi_command(mysql, COM_MULTI_PROGRESS)) + if (ma_multi_command(mysql, COM_MULTI_ENABLED)) { SET_CLIENT_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); goto fail; diff --git a/unittest/libmariadb/bulk1.c b/unittest/libmariadb/bulk1.c index 33437ed0..14dc8612 100644 --- a/unittest/libmariadb/bulk1.c +++ b/unittest/libmariadb/bulk1.c @@ -19,6 +19,8 @@ #define TEST_ARRAY_SIZE 1024 +static my_bool bulk_enabled= 0; + char *rand_str(size_t length) { const char charset[] = "0123456789" "abcdefghijklmnopqrstuvwxyz" @@ -32,6 +34,14 @@ char *rand_str(size_t length) { return p; } +static int check_bulk(MYSQL *mysql) +{ + bulk_enabled= (!(mysql->server_capabilities & CLIENT_MYSQL) && + (mysql->extension->mariadb_server_capabilities & MARIADB_CLIENT_STMT_BULK_OPERATIONS >> 32)); + diag("bulk %ssupported", bulk_enabled ? "" : "not "); + return OK; +} + static int bulk1(MYSQL *mysql) { MYSQL_STMT *stmt= mysql_stmt_init(mysql); @@ -47,6 +57,9 @@ static int bulk1(MYSQL *mysql) MYSQL_ROW row; unsigned int intval; + if (!bulk_enabled) + return SKIP; + rc= mysql_query(mysql, "DROP TABLE IF EXISTS bulk1"); check_mysql_rc(rc, mysql); @@ -133,6 +146,9 @@ static int bulk2(MYSQL *mysql) unsigned int array_size=1024; char indicator[1024]; long lval[1024]; + + if (!bulk_enabled) + return SKIP; rc= mysql_query(mysql, "DROP TABLE IF EXISTS bulk2"); check_mysql_rc(rc, mysql); @@ -187,6 +203,8 @@ static int bulk3(MYSQL *mysql) int array_size= 3; ulong length= -1; + if (!bulk_enabled) + return SKIP; rc= mysql_query(mysql, "DROP TABLE IF EXISTS bulk3"); check_mysql_rc(rc,mysql); rc= mysql_query(mysql, "CREATE TABLE bulk3 (name varchar(20), row int)"); @@ -218,6 +236,7 @@ static int bulk3(MYSQL *mysql) } struct my_tests_st my_tests[] = { + {"check_bulk", check_bulk, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"bulk1", bulk1, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"bulk2", bulk2, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"bulk3", bulk3, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, diff --git a/unittest/libmariadb/features-10_2.c b/unittest/libmariadb/features-10_2.c index 699330f4..4465fc99 100644 --- a/unittest/libmariadb/features-10_2.c +++ b/unittest/libmariadb/features-10_2.c @@ -100,7 +100,66 @@ static int execute_direct_example(MYSQL *mysql) return OK; } +static int conc_213(MYSQL *mysql) +{ + MYSQL_BIND bind; + unsigned int param_count= 1; + long id= 1234; + MYSQL_STMT *stmt; + + stmt = mysql_stmt_init(mysql); + + memset(&bind, '\0', sizeof(bind)); + + bind.buffer_type = MYSQL_TYPE_LONG; + bind.buffer = (void *)&id; + bind.buffer_length = sizeof(long); +/* bind.is_null = &is_null; + bind.length = &length; + bind.error = &error; */ + + mysql_stmt_attr_set(stmt, STMT_ATTR_PREBIND_PARAMS, ¶m_count); + check_stmt_rc(mysql_stmt_bind_param(stmt, &bind), stmt); + check_stmt_rc(mariadb_stmt_execute_direct(stmt, "SELECT ?", -1), stmt); + check_stmt_rc(mysql_stmt_store_result(stmt), stmt); + check_stmt_rc(mysql_stmt_free_result(stmt), stmt); + + mysql_stmt_close(stmt); + + return OK; +} + +static int conc_212(MYSQL *mysql) +{ + MYSQL_STMT *stmt= mysql_stmt_init(mysql); + int rc; + + rc= mariadb_stmt_execute_direct(stmt, "SELECT 1, 2", -1); + check_stmt_rc(rc, stmt); + mysql_stmt_store_result(stmt); + mysql_stmt_free_result(stmt); + + sleep(100); + rc= mariadb_stmt_execute_direct(stmt, "SELECT 1, 2", -1); + check_stmt_rc(rc, stmt); + mysql_stmt_store_result(stmt); + mysql_stmt_free_result(stmt); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc,mysql); + + rc= mysql_query(mysql, "CREATE TABLE t1(a int)"); + check_mysql_rc(rc,mysql); + + rc= mysql_stmt_close(stmt); + + return OK; +} + + struct my_tests_st my_tests[] = { + {"conc_212", conc_212, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"conc_213", conc_213, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"execute_direct", execute_direct, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"execute_direct_example", execute_direct_example, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {NULL, NULL, 0, 0, NULL, NULL}