Skip to content

Commit 22fa668

Browse files
committed
Params: ignore unbound parameters (#223)
Unbound parameters are allowed by the standard, although there's a mechanism that allows the application to signal the driver which parameters to ignore. There are appliacations however that set certain attributes of a first parameter (despite the query containing no markers), that however don't use this mechanism. This commit will have the driver ignore parameters with attributes set by the application, but not bound, as long as these unbound ones don't show up before the contiguous list of bound ones. (cherry picked from commit c39cb37)
1 parent cf54ab2 commit 22fa668

File tree

5 files changed

+218
-28
lines changed

5 files changed

+218
-28
lines changed

driver/handles.c

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,8 +1138,8 @@ static esodbc_state_et check_buff(SQLSMALLINT field_id, SQLPOINTER buff,
11381138
/* pointer to a value other than string or binary string */
11391139
case SQL_DESC_DATA_PTR:
11401140
if ((buff_len != SQL_IS_POINTER) && (buff_len < 0)) {
1141-
/* spec says the length "should" be it's size => this check
1142-
* might be too strict? */
1141+
/* according to the spec the length "should" be its size =>
1142+
* this check might be too strict? */
11431143
ERR("buffer is for pointer, but its length indicator "
11441144
"doesn't match (%d).", buff_len);
11451145
return SQL_STATE_HY090;
@@ -1489,11 +1489,12 @@ esodbc_rec_st *get_record(esodbc_desc_st *desc, SQLSMALLINT rec_no, BOOL grow)
14891489
return &desc->recs[rec_no - 1];
14901490
}
14911491

1492-
static SQLSMALLINT recount_bound(esodbc_desc_st *desc)
1492+
SQLSMALLINT count_bound(esodbc_desc_st *desc)
14931493
{
14941494
SQLSMALLINT i;
1495-
for (i = desc->count; 0 < i && REC_IS_BOUND(&desc->recs[i-1]); i--)
1495+
for (i = desc->count; 0 < i && !REC_IS_BOUND(&desc->recs[i - 1]); i --) {
14961496
;
1497+
}
14971498
return i;
14981499
}
14991500

@@ -2555,8 +2556,9 @@ SQLRETURN EsSQLSetDescFieldW(
25552556
* buffer(s), so the above "binding" definition is incomplete.
25562557
*/
25572558
if (FieldIdentifier != SQL_DESC_DATA_PTR) {
2558-
DBGH(desc, "attribute to set is different than %d => unbinding data "
2559-
"buffer (was 0x%p).", rec->data_ptr);
2559+
DBGH(desc, "attribute to set is different than data ptr (%d) => "
2560+
"unbinding data buffer (was 0x%p).", SQL_DESC_DATA_PTR,
2561+
rec->data_ptr);
25602562
rec->data_ptr = NULL;
25612563
}
25622564

@@ -2654,10 +2656,10 @@ SQLRETURN EsSQLSetDescFieldW(
26542656
DBGH(desc, "rec 0x%p of desc type %d unbound.", rec,
26552657
desc->type);
26562658
if (RecNumber == desc->count) {
2657-
count = recount_bound(desc);
2658-
/* worst case: trying to unbound a not-yet-bound rec */
2659+
count = count_bound(desc);
2660+
/* worst case: trying to unbind a not-yet-bound rec */
26592661
if (count != desc->count) {
2660-
DBGH(desc, "adjusting rec count from %d to %d.",
2662+
DBGH(desc, "adjusting rec count from %hd to %hd.",
26612663
desc->count, count);
26622664
return update_rec_count(desc, count);
26632665
}

driver/handles.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ typedef struct struct_stmt {
417417
#define STMT_GD_CALLING(_stmt) (0 <= _stmt->gd_col)
418418

419419
SQLRETURN update_rec_count(esodbc_desc_st *desc, SQLSMALLINT new_count);
420+
SQLSMALLINT count_bound(esodbc_desc_st *desc);
420421
esodbc_rec_st *get_record(esodbc_desc_st *desc, SQLSMALLINT rec_no, BOOL grow);
421422
void dump_record(esodbc_rec_st *rec);
422423

driver/queries.c

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2857,6 +2857,31 @@ static SQLRETURN convert_param_val(esodbc_rec_st *arec, esodbc_rec_st *irec,
28572857
return SQL_SUCCESS;
28582858
}
28592859

2860+
static SQLRETURN get_param_recs(esodbc_stmt_st *stmt, SQLSMALLINT no,
2861+
esodbc_rec_st **arec, esodbc_rec_st **irec)
2862+
{
2863+
esodbc_rec_st *a, *i;
2864+
2865+
a = get_record(stmt->apd, no, /*grow?*/FALSE);
2866+
if (a && REC_IS_BOUND(a)) {
2867+
*arec = a;
2868+
} else {
2869+
ERRH(stmt, "APD record #%hd @0x%p not bound", no, a);
2870+
RET_HDIAG(stmt, SQL_STATE_07002, "Invalid parameter configuration", 0);
2871+
}
2872+
2873+
i = get_record(stmt->ipd, no, /*grow?*/FALSE);
2874+
if (i && i->es_type) {
2875+
*irec = i;
2876+
} else {
2877+
ERRH(stmt, "IPD record #%hd @0x%p not configured", no, i);
2878+
/* possibly "manually" configured param (i.e. no SQLBindParam use) */
2879+
RET_HDIAG(stmt, SQL_STATE_07002, "Invalid parameter configuration", 0);
2880+
}
2881+
2882+
return SQL_SUCCESS;
2883+
}
2884+
28602885
/* Forms the JSON array with params:
28612886
* [{"type": "<ES/SQL type name>", "value": <param value>}(,etc)*] */
28622887
static SQLRETURN serialize_params_json(esodbc_stmt_st *stmt, char *dest,
@@ -2867,7 +2892,7 @@ static SQLRETURN serialize_params_json(esodbc_stmt_st *stmt, char *dest,
28672892
const static cstr_st j_val = CSTR_INIT("\", \"" REQ_KEY_PARAM_VAL "\": ");
28682893
esodbc_rec_st *arec, *irec;
28692894
SQLRETURN ret;
2870-
SQLSMALLINT i;
2895+
SQLSMALLINT i, count;
28712896
size_t l, pos;
28722897

28732898
pos = 0;
@@ -2876,10 +2901,17 @@ static SQLRETURN serialize_params_json(esodbc_stmt_st *stmt, char *dest,
28762901
}
28772902
pos ++;
28782903

2879-
for (i = 0; i < stmt->apd->count; i ++) {
2880-
arec = get_record(stmt->apd, i + 1, /*grow?*/FALSE);
2881-
irec = get_record(stmt->ipd, i + 1, /*grow?*/FALSE);
2882-
assert(arec && irec && irec->es_type);
2904+
/* some apps set/reset various parameter record attributes (maybe to
2905+
* workaround for some driver issues? like Linked Servers), which increaes
2906+
* the record count, but (1) do not bind the parameter and (2) do not use
2907+
* the standard mechanism (through SQL_DESC_ARRAY_STATUS_PTR) to signal
2908+
* params to ignore. */
2909+
count = count_bound(stmt->apd);
2910+
for (i = 0; i < count; i ++) {
2911+
ret = get_param_recs(stmt, i + 1, &arec, &irec);
2912+
if (! SQL_SUCCEEDED(ret)) {
2913+
return ret;
2914+
}
28832915

28842916
if (dest) {
28852917
if (i) {
@@ -2954,8 +2986,8 @@ static SQLRETURN statement_len_cbor(esodbc_stmt_st *stmt, size_t *enc_len,
29542986
bodylen += cbor_str_obj_len(sizeof(REQ_KEY_QUERY) - 1);
29552987
bodylen += cbor_str_obj_len(stmt->u8sql.cnt);
29562988

2957-
/* does the statement have any parameters? */
2958-
if (stmt->apd->count) {
2989+
/* does the statement have any bound parameters? */
2990+
if (count_bound(stmt->apd)) {
29592991
bodylen += cbor_str_obj_len(sizeof(REQ_KEY_PARAMS) - 1);
29602992

29612993
ret = statement_params_len_cbor(stmt, &len, conv_len);
@@ -3023,8 +3055,8 @@ static SQLRETURN statement_len_json(esodbc_stmt_st *stmt, size_t *outlen)
30233055
bodylen += json_escape(stmt->u8sql.str, stmt->u8sql.cnt, NULL, 0);
30243056
bodylen += 2; /* 2x `"` for query value */
30253057

3026-
/* does the statement have any parameters? */
3027-
if (stmt->apd->count) {
3058+
/* does the statement have any bound parameters? */
3059+
if (count_bound(stmt->apd)) {
30283060
bodylen += sizeof(JSON_KEY_PARAMS) - 1;
30293061
/* serialize_params_json will count/copy array delims (`[`, `]`) */
30303062
ret = serialize_params_json(stmt, /* no copy, just eval */NULL,
@@ -3193,7 +3225,7 @@ static SQLRETURN serialize_params_cbor(esodbc_stmt_st *stmt, CborEncoder *map,
31933225
{
31943226
const static cstr_st p_type = CSTR_INIT(REQ_KEY_PARAM_TYPE);
31953227
const static cstr_st p_val = CSTR_INIT(REQ_KEY_PARAM_VAL);
3196-
SQLSMALLINT i;
3228+
SQLSMALLINT i, count;
31973229
CborError res;
31983230
SQLRETURN ret;
31993231
CborEncoder array; /* array for all params */
@@ -3204,10 +3236,13 @@ static SQLRETURN serialize_params_cbor(esodbc_stmt_st *stmt, CborEncoder *map,
32043236
res = cbor_encoder_create_array(map, &array, stmt->apd->count);
32053237
FAIL_ON_CBOR_ERR(stmt, res);
32063238

3207-
for (i = 0; i < stmt->apd->count; i ++) {
3208-
assert(stmt->ipd->count == stmt->apd->count);
3209-
arec = &stmt->apd->recs[i];
3210-
irec = &stmt->ipd->recs[i];
3239+
/* see note in serialize_params_json() */
3240+
count = count_bound(stmt->apd);
3241+
for (i = 0; i < count; i ++) {
3242+
ret = get_param_recs(stmt, i + 1, &arec, &irec);
3243+
if (! SQL_SUCCEEDED(ret)) {
3244+
return ret;
3245+
}
32113246

32123247
/* ~ { */
32133248
res = cbor_encoder_create_map(&array, &pmap, /* type + value = */2);
@@ -3289,8 +3324,8 @@ static SQLRETURN serialize_to_cbor(esodbc_stmt_st *stmt, cstr_st *dest,
32893324
err = cbor_encode_text_string(&map, stmt->u8sql.str, stmt->u8sql.cnt);
32903325
FAIL_ON_CBOR_ERR(stmt, err);
32913326

3292-
/* does the statement have any parameters? */
3293-
if (stmt->apd->count) {
3327+
/* does the statement have any bound parameters? */
3328+
if (count_bound(stmt->apd)) {
32943329
err = cbor_encode_text_string(&map, REQ_KEY_PARAMS,
32953330
sizeof(REQ_KEY_PARAMS) - 1);
32963331
FAIL_ON_CBOR_ERR(stmt, err);
@@ -3418,7 +3453,7 @@ static SQLRETURN serialize_to_json(esodbc_stmt_st *stmt, cstr_st *dest)
34183453
body[pos ++] = '"';
34193454

34203455
/* does the statement have any parameters? */
3421-
if (stmt->apd->count) {
3456+
if (count_bound(stmt->apd)) {
34223457
memcpy(body + pos, JSON_KEY_PARAMS, sizeof(JSON_KEY_PARAMS) - 1);
34233458
pos += sizeof(JSON_KEY_PARAMS) - 1;
34243459
/* serialize_params_json will count/copy array delims (`[`, `]`) */

test/connected_dbc.cc

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ void ConnectedDBC::assertState(const SQLWCHAR *state)
104104

105105
void ConnectedDBC::assertRequest(const char *params, const char *tz)
106106
{
107+
const static char *answ_templ_no_params = "{"
108+
JSON_KEY_QUERY "\"%s\""
109+
/* params */ "%s"
110+
JSON_KEY_MULTIVAL ESODBC_DEF_MFIELD_LENIENT
111+
JSON_KEY_IDX_FROZEN ESODBC_DEF_IDX_INC_FROZEN
112+
JSON_KEY_TIMEZONE "%s%s%s"
113+
JSON_KEY_VERSION "\"%s\""
114+
JSON_KEY_VAL_MODE
115+
JSON_KEY_CLT_ID
116+
JSON_KEY_BINARY_FMT "false"
117+
"}";
107118
const static char *answ_templ = "{"
108119
JSON_KEY_QUERY "\"%s\""
109120
JSON_KEY_PARAMS "%s"
@@ -115,18 +126,26 @@ void ConnectedDBC::assertRequest(const char *params, const char *tz)
115126
JSON_KEY_CLT_ID
116127
JSON_KEY_BINARY_FMT "false"
117128
"}";
129+
const char *templ;
118130
char expect[1024];
119131
int n;
120132

121133
cstr_st actual = {NULL, 0};
122134
ret = serialize_statement((esodbc_stmt_st *)stmt, &actual);
123135
ASSERT_TRUE(SQL_SUCCEEDED(ret));
124136

137+
if (params) {
138+
templ = answ_templ;
139+
} else {
140+
templ = answ_templ_no_params;
141+
params = "";
142+
}
143+
125144
if (tz) {
126-
n = snprintf(expect, sizeof(expect), answ_templ, test_name, params,
145+
n = snprintf(expect, sizeof(expect), templ, test_name, params,
127146
"\"", tz, "\"", version);
128147
} else {
129-
n = snprintf(expect, sizeof(expect), answ_templ, test_name, params,
148+
n = snprintf(expect, sizeof(expect), templ, test_name, params,
130149
"", JSON_VAL_TIMEZONE_Z, "", version);
131150
}
132151
ASSERT_LT(actual.cnt, sizeof(expect));

test/test_bindparam.cc

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
#include <gtest/gtest.h>
8+
#include "connected_dbc.h"
9+
10+
#include <string.h>
11+
12+
13+
namespace test {
14+
15+
class BindParam : public ::testing::Test, public ConnectedDBC {
16+
};
17+
18+
19+
TEST_F(BindParam, UnbindColapse) {
20+
prepareStatement();
21+
22+
SQLSMALLINT val = 1;
23+
for (int i = 1; i <= 3; i ++) {
24+
ret = SQLBindParameter(stmt, /*param nr*/i, SQL_PARAM_INPUT, SQL_C_SSHORT,
25+
ESODBC_SQL_BOOLEAN, /*size*/0, /*decdigits*/0, &val, sizeof(val),
26+
/*IndLen*/NULL);
27+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
28+
}
29+
30+
SQLHDESC apd;
31+
ret = SQLGetStmtAttr(stmt, SQL_ATTR_APP_PARAM_DESC, &apd, SQL_IS_POINTER,
32+
/*str-len-ptr*/NULL);
33+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
34+
35+
for (int i = 2; i <= 3; i ++) {
36+
/* unbind 2nd and 3rd param (by setting smth else than the data ptr) */
37+
ret = SQLSetDescField(apd, /*rec no*/i, SQL_DESC_DATA_PTR,
38+
/*val*/NULL, SQL_IS_POINTER);
39+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
40+
}
41+
42+
/* param count should have now be updated to 1 */
43+
SQLSMALLINT count;
44+
ret = SQLGetDescField(apd, /*rec no, ignored*/0, SQL_DESC_COUNT, &count,
45+
SQL_IS_SMALLINT, /*str-len-ptr*/NULL);
46+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
47+
48+
ASSERT_EQ(count, 1);
49+
}
50+
51+
/* Linked Servers behavior */
52+
TEST_F(BindParam, NoBind) {
53+
prepareStatement();
54+
55+
SQLHDESC apd;
56+
ret = SQLGetStmtAttr(stmt, SQL_ATTR_APP_PARAM_DESC, &apd, SQL_IS_POINTER,
57+
/*str-len-ptr*/NULL);
58+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
59+
60+
ret = SQLSetStmtAttr(stmt, SQL_ATTR_PARAM_BIND_TYPE, (SQLPOINTER)0x1,
61+
SQL_IS_INTEGER);
62+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
63+
ret = SQLSetStmtAttr(stmt, SQL_ATTR_PARAM_BIND_TYPE, NULL, SQL_IS_INTEGER);
64+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
65+
66+
ret = SQLSetStmtAttr(stmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, (SQLPOINTER)0x1,
67+
SQL_IS_INTEGER);
68+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
69+
ret = SQLSetStmtAttr(stmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, NULL,
70+
SQL_IS_INTEGER);
71+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
72+
73+
ret = SQLSetDescField(apd, /*rec no*/1, SQL_DESC_OCTET_LENGTH_PTR,
74+
(SQLPOINTER)0x1, SQL_IS_POINTER);
75+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
76+
ret = SQLSetDescField(apd, /*rec no*/1, SQL_DESC_OCTET_LENGTH_PTR, NULL,
77+
SQL_IS_POINTER);
78+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
79+
80+
ret = SQLSetStmtAttr(stmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)1,
81+
SQL_IS_INTEGER);
82+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
83+
84+
assertRequest(/* no params */NULL);
85+
}
86+
87+
TEST_F(BindParam, InvalidBind) {
88+
prepareStatement();
89+
90+
SQLHDESC apd;
91+
ret = SQLGetStmtAttr(stmt, SQL_ATTR_APP_PARAM_DESC, &apd, SQL_IS_POINTER,
92+
/*str-len-ptr*/NULL);
93+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
94+
95+
ret = SQLSetDescField(apd, /*rec no*/1, SQL_DESC_OCTET_LENGTH_PTR,
96+
(SQLPOINTER)0x1, SQL_IS_POINTER);
97+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
98+
ret = SQLSetDescField(apd, /*rec no*/1, SQL_DESC_OCTET_LENGTH_PTR, NULL,
99+
SQL_IS_POINTER);
100+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
101+
102+
SQLSMALLINT val = 1;
103+
ret = SQLBindParameter(stmt, /*param nr*/2, SQL_PARAM_INPUT, SQL_C_SSHORT,
104+
ESODBC_SQL_BOOLEAN, /*size*/0, /*decdigits*/0, &val, sizeof(val),
105+
/*IndLen*/NULL);
106+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
107+
108+
cstr_st actual = {NULL, 0};
109+
ret = serialize_statement((esodbc_stmt_st *)stmt, &actual);
110+
ASSERT_FALSE(SQL_SUCCEEDED(ret));
111+
}
112+
113+
TEST_F(BindParam, BindTwo) {
114+
prepareStatement();
115+
116+
SQLSMALLINT v1 = 1;
117+
ret = SQLBindParameter(stmt, /*param nr*/1, SQL_PARAM_INPUT, SQL_C_SSHORT,
118+
ESODBC_SQL_BOOLEAN, /*size*/0, /*decdigits*/0, &v1, sizeof(v1),
119+
/*IndLen*/NULL);
120+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
121+
122+
SQLSMALLINT v0 = 0;
123+
ret = SQLBindParameter(stmt, /*param nr*/2, SQL_PARAM_INPUT, SQL_C_SSHORT,
124+
ESODBC_SQL_BOOLEAN, /*size*/0, /*decdigits*/0, &v0, sizeof(v0),
125+
/*IndLen*/NULL);
126+
ASSERT_TRUE(SQL_SUCCEEDED(ret));
127+
128+
assertRequest("[{\"type\": \"BOOLEAN\", \"value\": true}, "
129+
"{\"type\": \"BOOLEAN\", \"value\": false}]");
130+
}
131+
132+
} // test namespace
133+

0 commit comments

Comments
 (0)