Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SNOW-710476 bulk update: batch execution for DML queries with arrayBindSupported=false #800

Merged
merged 31 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
39d1036
SNOW-692945: multiple statements support (#727)
SimbaGithub Oct 17, 2024
1ee5015
Merge branch 'master' into master-2.0.0
sfc-gh-ext-simba-hx Oct 17, 2024
6bc1b92
SNOW-692945: refactoring for resultset C wrapper (#724)
SimbaGithub Oct 18, 2024
e3e1b88
SNOW-1524269: support put/get for GCP (#738)
sfc-gh-ext-simba-hx Oct 22, 2024
4b2cc16
SNOW-1524269 native put get support (#745)
sfc-gh-ext-simba-hx Oct 23, 2024
81bf6fb
Merge branch 'master' into master-2.0.0
sfc-gh-ext-simba-hx Oct 24, 2024
7055be5
Merge branch 'master' into master-2.0.0
sfc-gh-ext-simba-hx Nov 20, 2024
271dab6
Merge branch 'master' into master-2.0.0
sfc-gh-ext-simba-hx Dec 3, 2024
17fc0d5
SNOW-710476: bulk update essential (#776)
sfc-gh-ext-simba-hx Dec 3, 2024
0f9f288
SNOW-1821508: OCSP deprecation plan steps (#789)
sfc-gh-ext-simba-hx Dec 4, 2024
8ff0b38
SNOW-1852178 Remove the deprecated part of API (#794)
sfc-gh-mmishchenko Dec 13, 2024
314c16f
Merge branch 'master' into master-2.0.0
sfc-gh-ext-simba-hx Dec 16, 2024
d14c285
batch execution for DML queries with arrayBindSupported=false
sfc-gh-ext-simba-hx Dec 18, 2024
f57e7a1
fix build/test failures
sfc-gh-ext-simba-hx Dec 19, 2024
c15c602
Merge branch 'master' into master-2.0.0
sfc-gh-mmishchenko Dec 19, 2024
d5ba080
Merge branch 'master-2.0.0' into SNOW-710476-bulk-update-more-features
sfc-gh-ext-simba-hx Dec 19, 2024
e107644
SNOW-1732752: Set version to 2.0.0 (#804)
sfc-gh-dprzybysz Dec 20, 2024
bd88fe5
Merge branch 'master-2.0.0' into SNOW-710476-bulk-update-more-features
sfc-gh-ext-simba-hx Dec 23, 2024
b0e8734
Merge branch 'master' into master-2.0.0
sfc-gh-ext-simba-hx Jan 6, 2025
29c38a8
Merge branch 'master-2.0.0' into SNOW-710476-bulk-update-more-features
sfc-gh-ext-simba-hx Jan 6, 2025
ac951f3
Merge branch 'master' into master-2.0.0
sfc-gh-ext-simba-hx Jan 7, 2025
21b235d
Merge branch 'master-2.0.0' into SNOW-710476-bulk-update-more-features
sfc-gh-ext-simba-hx Jan 7, 2025
630bd0c
Merge branch 'master' into SNOW-710476-bulk-update-more-features
sfc-gh-ext-simba-hx Jan 9, 2025
766499f
fix for review comments
sfc-gh-ext-simba-hx Jan 10, 2025
0231c8f
fix for test failure and review comments
sfc-gh-ext-simba-hx Jan 10, 2025
6c0a5f6
Merge branch 'master' into SNOW-710476-bulk-update-more-features
sfc-gh-ext-simba-hx Jan 13, 2025
99eeb9e
comment with jira ID to track server issue
sfc-gh-ext-simba-hx Jan 13, 2025
d232eb4
unused params
sfc-gh-ext-simba-hx Jan 15, 2025
98fd4a5
debug
sfc-gh-ext-simba-hx Jan 15, 2025
fc8197f
Merge branch 'master' into SNOW-710476-bulk-update-more-features
sfc-gh-mkubik Jan 27, 2025
92cb5fc
Merge branch 'master' into SNOW-710476-bulk-update-more-features
sfc-gh-dprzybysz Jan 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/snowflake/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,8 @@ typedef struct SF_STMT {
void* multi_stmt_result_ids;
int64 multi_stmt_count;
int64 paramset_size;
sf_bool array_bind_supported;
int64 affected_rows;

/**
* User realloc function used in snowflake_fetch
Expand Down
279 changes: 203 additions & 76 deletions lib/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -1501,6 +1501,9 @@
} else {
sfstmt->is_dml = detect_stmt_type(stmt_type_id);
}

json_copy_bool(&sfstmt->array_bind_supported, data, "arrayBindSupported");

cJSON* rowtype = snowflake_cJSON_GetObjectItem(data, "rowtype");
if (snowflake_cJSON_IsArray(rowtype)) {
_snowflake_stmt_desc_reset(sfstmt);
Expand Down Expand Up @@ -1949,18 +1952,69 @@
}
}

#define SF_BIND_ALL -1
/**
* @param index The index for array binding. -1 for single binding.
* @return single parameter binding value in cJSON.
*/
static cJSON* get_single_binding_value_json(SF_BIND_INPUT* input, int64 index)
{
char* value = NULL;
cJSON* single_val = NULL;
int64 val_len = 0;
size_t step = 0;
void* val_ptr = NULL;

if (index < 0)
{
value = value_to_string(input->value, input->len, input->c_type);
single_val = snowflake_cJSON_CreateString(value);
SF_FREE(value);
}
else
{
val_len = (int64)input->len;
step = _snowflake_get_binding_value_size(input);
val_ptr = (char*)(input->value) + step * index;
if (input->len_ind)
{
val_len = input->len_ind[index];
}

if (SF_BIND_LEN_NULL == val_len)
{
single_val = snowflake_cJSON_CreateNull();
}
else
{
if ((SF_C_TYPE_STRING == input->c_type) &&
(SF_BIND_LEN_NTS == val_len))
{
val_len = (int64)strlen((char*)val_ptr);

Check warning on line 1993 in lib/client.c

View check run for this annotation

Codecov / codecov/patch

lib/client.c#L1993

Added line #L1993 was not covered by tests
}

value = value_to_string(val_ptr, val_len, input->c_type);
single_val = snowflake_cJSON_CreateString(value);
SF_FREE(value);
}
}

return single_val;
}

/**
* @param sfstmt SNOWFLAKE_STMT context.
* @param index The parameter set index (for batch execution), -1 to return all
* parameter sets (non-batch execution)
* @return parameter bindings in cJSON.
*/
cJSON* STDCALL _snowflake_get_binding_json(SF_STMT* sfstmt)
cJSON* STDCALL _snowflake_get_binding_json(SF_STMT* sfstmt, int64 index)
{
size_t i;
SF_BIND_INPUT* input;
const char* type;
char name_buf[SF_PARAM_NAME_BUF_LEN];
char* name = NULL;
char* value = NULL;
cJSON* bindings = NULL;

if (_snowflake_get_current_param_style(sfstmt) == INVALID_PARAM_TYPE)
Expand All @@ -1970,7 +2024,9 @@
bindings = snowflake_cJSON_CreateObject();
for (i = 0; i < sfstmt->params_len; i++)
{
cJSON* binding;
cJSON* binding = NULL;
cJSON* single_val = NULL;

input = _snowflake_get_binding_by_index(sfstmt, i, &name,
name_buf, SF_PARAM_NAME_BUF_LEN);
if (input == NULL)
Expand All @@ -1981,50 +2037,22 @@
binding = snowflake_cJSON_CreateObject();
type = snowflake_type_to_string(
c_type_to_snowflake(input->c_type, SF_DB_TYPE_TIMESTAMP_NTZ));
if (sfstmt->paramset_size > 1)
// json array for all parameter sets
if ((sfstmt->paramset_size > 1) && (index < 0))
{
cJSON* val_array = snowflake_cJSON_CreateArray();
size_t step = _snowflake_get_binding_value_size(input);
void* val_ptr = input->value;
int64 val_len;
cJSON* single_val = NULL;
for (int64 j = 0; j < sfstmt->paramset_size; j++, val_ptr = (char*)val_ptr + step)
// get all parameter sets, use array
for (int64 j = 0; j < sfstmt->paramset_size; j++)
{
val_len = input->len;
if (input->len_ind)
{
val_len = input->len_ind[j];
}

if (SF_BIND_LEN_NULL == val_len)
{
single_val = snowflake_cJSON_CreateNull();
snowflake_cJSON_AddItemToArray(val_array, single_val);
continue;
}

if ((SF_C_TYPE_STRING == input->c_type) &&
(SF_BIND_LEN_NTS == val_len))
{
val_len = strlen((char*)val_ptr);
}

value = value_to_string(val_ptr, val_len, input->c_type);
single_val = snowflake_cJSON_CreateString(value);
single_val = get_single_binding_value_json(input, j);
snowflake_cJSON_AddItemToArray(val_array, single_val);
if (value) {
SF_FREE(value);
}
}
snowflake_cJSON_AddItemToObject(binding, "value", val_array);
}
else // paramset_size = 1, single value binding
else // single value binding
{
value = value_to_string(input->value, input->len, input->c_type);
snowflake_cJSON_AddStringToObject(binding, "value", value);
if (value) {
SF_FREE(value);
}
single_val = get_single_binding_value_json(input, index);
snowflake_cJSON_AddItemToObject(binding, "value", single_val);
}
snowflake_cJSON_AddStringToObject(binding, "type", type);
snowflake_cJSON_AddItemToObject(bindings, name, binding);
Expand All @@ -2038,7 +2066,7 @@
if (!sfstmt || !sfstmt->connection ||
(_snowflake_get_current_param_style(sfstmt) == INVALID_PARAM_TYPE) ||
sfstmt->connection->stage_binding_disabled ||
sfstmt->paramset_size <= 1)
sfstmt->paramset_size <= 1 || !sfstmt->array_bind_supported)
{
return SF_BOOLEAN_FALSE;
}
Expand Down Expand Up @@ -2091,6 +2119,8 @@
sfstmt->params = NULL;
sfstmt->params_len = 0;
sfstmt->name_list = NULL;
sfstmt->array_bind_supported = SF_BOOLEAN_FALSE;
sfstmt->affected_rows = -1;

_snowflake_stmt_desc_reset(sfstmt);

Expand Down Expand Up @@ -2172,6 +2202,7 @@
sfstmt->multi_stmt_count = SF_MULTI_STMT_COUNT_UNSET;
// single value binding by default
sfstmt->paramset_size = 1;
sfstmt->affected_rows = -1;
}
return sfstmt;
}
Expand Down Expand Up @@ -2524,6 +2555,11 @@
}

if (sfstmt->is_dml == SF_BOOLEAN_TRUE) {
if (sfstmt->affected_rows >= 0)
{
// use the value initialized previously from batch execution
return sfstmt->affected_rows;
}
if (SF_STATUS_SUCCESS != snowflake_fetch(sfstmt))
{
return -1;
Expand Down Expand Up @@ -2588,22 +2624,13 @@
return _snowflake_execute_ex(sfstmt, _is_put_get_command(sfstmt->sql_text), SF_BOOLEAN_TRUE, result_capture, SF_BOOLEAN_FALSE);
}

SF_STATUS STDCALL _snowflake_execute_ex(SF_STMT *sfstmt,
sf_bool is_put_get_command,
sf_bool is_native_put_get,
SF_QUERY_RESULT_CAPTURE* result_capture,
sf_bool is_describe_only) {
if (!sfstmt) {
return SF_STATUS_ERROR_STATEMENT_NOT_EXIST;
}

if (is_put_get_command && is_native_put_get && !is_describe_only)
{
_snowflake_stmt_desc_reset(sfstmt);
return _snowflake_execute_put_get_native(sfstmt, NULL, 0, result_capture);
}

clear_snowflake_error(&sfstmt->error);
static SF_STATUS _snowflake_execute_with_binds_ex(SF_STMT* sfstmt,
sf_bool is_put_get_command,
SF_QUERY_RESULT_CAPTURE* result_capture,
sf_bool is_describe_only,
char* bind_stage,
cJSON* bindings)
{
SF_STATUS ret = SF_STATUS_ERROR_GENERAL;
SF_JSON_ERROR json_error;
const char *error_msg;
Expand All @@ -2617,25 +2644,6 @@
URL_KEY_VALUE url_params[] = {
{.key="requestId=", .value=sfstmt->request_id, .formatted_key=NULL, .formatted_value=NULL, .key_size=0, .value_size=0}
};
size_t i;
cJSON *bindings = NULL;
char* bind_stage = NULL;
SF_BIND_INPUT *input;
const char *type;
char *value;

_mutex_lock(&sfstmt->connection->mutex_sequence_counter);
sfstmt->sequence_counter = ++sfstmt->connection->sequence_counter;
_mutex_unlock(&sfstmt->connection->mutex_sequence_counter);

if (_snowflake_needs_stage_binding(sfstmt))
{
bind_stage = _snowflake_stage_bind_upload(sfstmt);
}
if (!bind_stage)
{
bindings = _snowflake_get_binding_json(sfstmt);
}

if (is_string_empty(sfstmt->connection->directURL) &&
(is_string_empty(sfstmt->connection->master_token) ||
Expand Down Expand Up @@ -2808,7 +2816,7 @@
sfstmt->is_multi_stmt = (_SF_STMT_TYPE_MULTI_STMT == stmt_type_id);
}

if (sfstmt->is_multi_stmt)
if ((sfstmt->is_multi_stmt) && (!is_describe_only))
{
if (!setup_multi_stmt_result(sfstmt, data))
{
Expand Down Expand Up @@ -2879,6 +2887,125 @@
return ret;
}

static SF_STATUS _batch_dml_execute(SF_STMT* sfstmt,
SF_QUERY_RESULT_CAPTURE* result_capture)
{
SF_STATUS ret = SF_STATUS_ERROR_GENERAL;
cJSON* bindings = NULL;
int64 affected_rows = 0;

// impossible but just in case
if (!sfstmt->is_dml)
{
SET_SNOWFLAKE_STMT_ERROR(&sfstmt->error,

Check warning on line 2900 in lib/client.c

View check run for this annotation

Codecov / codecov/patch

lib/client.c#L2900

Added line #L2900 was not covered by tests
SF_STATUS_ERROR_GENERAL,
"Internal error. _batch_dml_execute is called for non-dml query.",
SF_SQLSTATE_GENERAL_ERROR,
sfstmt->sfqid);
return SF_STATUS_ERROR_GENERAL;

Check warning on line 2905 in lib/client.c

View check run for this annotation

Codecov / codecov/patch

lib/client.c#L2905

Added line #L2905 was not covered by tests
}
sfstmt->affected_rows = -1;
for (int64 i = 0; i < sfstmt->paramset_size; i++)
{
bindings = _snowflake_get_binding_json(sfstmt, i);
ret = _snowflake_execute_with_binds_ex(sfstmt,
SF_BOOLEAN_FALSE,
result_capture,
SF_BOOLEAN_FALSE,
NULL,
bindings);
if (ret != SF_STATUS_SUCCESS)
{
return ret;

Check warning on line 2919 in lib/client.c

View check run for this annotation

Codecov / codecov/patch

lib/client.c#L2919

Added line #L2919 was not covered by tests
}
affected_rows += snowflake_affected_rows(sfstmt);
}

sfstmt->affected_rows = affected_rows;
return SF_STATUS_SUCCESS;
}

SF_STATUS STDCALL _snowflake_execute_ex(SF_STMT *sfstmt,
sf_bool is_put_get_command,
sf_bool is_native_put_get,
SF_QUERY_RESULT_CAPTURE* result_capture,
sf_bool is_describe_only) {
SF_STATUS ret = SF_STATUS_ERROR_GENERAL;
cJSON* bindings = NULL;
char* bind_stage = NULL;

if (!sfstmt) {
return SF_STATUS_ERROR_STATEMENT_NOT_EXIST;

Check warning on line 2938 in lib/client.c

View check run for this annotation

Codecov / codecov/patch

lib/client.c#L2938

Added line #L2938 was not covered by tests
}

if (is_put_get_command && is_native_put_get && !is_describe_only)
{
_snowflake_stmt_desc_reset(sfstmt);
return _snowflake_execute_put_get_native(sfstmt, NULL, 0, result_capture);
}

clear_snowflake_error(&sfstmt->error);

_mutex_lock(&sfstmt->connection->mutex_sequence_counter);
sfstmt->sequence_counter = ++sfstmt->connection->sequence_counter;
_mutex_unlock(&sfstmt->connection->mutex_sequence_counter);

// batch execution needed when application using array binding for DML
// queries and in some cases that's not supported by server:
// paramset_size > 1 && arrayBindSupported = false
// fallback to execute the query with each parmeter set in such case
sf_bool need_batch_exec = SF_BOOLEAN_FALSE;

// describe request only returns metadata and doesn't need bindings
if (!is_describe_only)
{
// for parameter array bindings, send describe request first to
// check whether that's supported by server (by checking arrayBindSupported)
if (sfstmt->paramset_size > 1)
{
ret = _snowflake_execute_with_binds_ex(sfstmt,
is_put_get_command,
result_capture,
SF_BOOLEAN_TRUE,
NULL, NULL);
if (ret != SF_STATUS_SUCCESS)
{
return ret;

Check warning on line 2973 in lib/client.c

View check run for this annotation

Codecov / codecov/patch

lib/client.c#L2973

Added line #L2973 was not covered by tests
}
if ((sfstmt->is_dml) && (!sfstmt->array_bind_supported))
{
log_debug("Array bind is not supported - each parameter set entry "
"will be executed as a single request for query: %s",
sfstmt->sql_text);
need_batch_exec = SF_BOOLEAN_TRUE;
}
}
if (!need_batch_exec)
{
if (_snowflake_needs_stage_binding(sfstmt))
{
bind_stage = _snowflake_stage_bind_upload(sfstmt);
}
if (!bind_stage)
{
bindings = _snowflake_get_binding_json(sfstmt, SF_BIND_ALL);
}
}
}

if (need_batch_exec)
{
return _batch_dml_execute(sfstmt, result_capture);
}

return _snowflake_execute_with_binds_ex(sfstmt,
is_put_get_command,
result_capture,
is_describe_only,
bind_stage,
bindings);
}

SF_ERROR_STRUCT *STDCALL snowflake_error(SF_CONNECT *sf) {
if (!sf) {
return NULL;
Expand Down
Loading
Loading