Skip to content

Commit 616a641

Browse files
committed
CBOR support for parameters (#175)
* CBOR support for parameters This commit adds the support for sending the paramters encapsulated as CBOR. The implementation makes use of the already available paramter conversion functionality, which prepares the parameters as string values. Consequently, except NULL values (and type), the values will be sent as strings and have the server do a final conversion to the signaled SQL type (which is actually also the case with JSON). * split CBOR param value encoding by meta type - for SQL_VARCHAR targets, the JSON parameter serialization adds the quotes, which need to be stripped before CBOR-packing the result. This commit also change the integer types of two vars, to avoid signed/unsigned comparision warnings. * address PR review note - correct comment phrasing. (cherry picked from commit 758b8e2)
1 parent 8f5cb03 commit 616a641

File tree

3 files changed

+222
-42
lines changed

3 files changed

+222
-42
lines changed

driver/queries.c

Lines changed: 217 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
#define CHK_RES(_hnd, _fmt, ...) \
3737
JUMP_ON_CBOR_ERR(res, err, _hnd, _fmt, __VA_ARGS__)
3838

39+
/* fwd decl */
40+
static SQLRETURN statement_params_len_cbor(esodbc_stmt_st *stmt,
41+
size_t *enc_len, size_t *conv_len);
42+
3943
static thread_local cstr_st tz_param;
4044

4145
static BOOL print_tz_param(long tz_dst_offt)
@@ -677,6 +681,9 @@ static SQLRETURN attach_answer_cbor(esodbc_stmt_st *stmt)
677681
"answer: `%s`.", cstr_hex_dump(&stmt->rset.body));
678682
goto err;
679683
}
684+
/* save the object, as it might be required by EsSQLRowCount() */
685+
stmt->rset.pack.cbor.rows_obj = rows_obj;
686+
680687
/* ES uses indefinite-length arrays -- meh. */
681688
res = cbor_container_is_empty(rows_obj, &empty);
682689
CHK_RES(stmt, "failed to check if '" PACK_PARAM_ROWS "' array is empty");
@@ -691,9 +698,6 @@ static SQLRETURN attach_answer_cbor(esodbc_stmt_st *stmt)
691698
INFOH(stmt, "rows received in current (#%zu) result set: %zu.",
692699
stmt->nset + 1, nrows);
693700
# endif /* NDEBUG */
694-
/* save the object, as it might be required by EsSQLRowCount() */
695-
stmt->rset.pack.cbor.rows_obj = rows_obj;
696-
697701
/* prepare iterator for EsSQLFetch(); recursing object and iterator
698702
* can be the same, since there's no need to "leave" the container. */
699703
res = cbor_value_enter_container(&rows_obj, &rows_obj);
@@ -2832,7 +2836,6 @@ static SQLRETURN convert_param_val(esodbc_rec_st *arec, esodbc_rec_st *irec,
28322836
ERRH(stmt, "conversion to SQL BINARY not implemented.");
28332837
RET_HDIAG(stmt, SQL_STATE_HYC00, "conversion to SQL BINARY "
28342838
"not yet supported", 0);
2835-
break;
28362839

28372840
default:
28382841
BUGH(arec->desc->hdr.stmt, "unexpected ES/SQL type %hd.",
@@ -2842,6 +2845,7 @@ static SQLRETURN convert_param_val(esodbc_rec_st *arec, esodbc_rec_st *irec,
28422845
}
28432846
/*INDENT-ON*/
28442847

2848+
assert(0);
28452849
return SQL_SUCCESS;
28462850
}
28472851

@@ -2851,9 +2855,8 @@ static SQLRETURN serialize_params_json(esodbc_stmt_st *stmt, char *dest,
28512855
size_t *len)
28522856
{
28532857
/* JSON keys for building one parameter object */
2854-
# define JSON_KEY_TYPE "{\"type\": \""
2855-
# define JSON_KEY_VALUE "\", \"value\": "
2856-
2858+
const static cstr_st j_type = CSTR_INIT("{\"" REQ_KEY_PARAM_TYPE "\": \"");
2859+
const static cstr_st j_val = CSTR_INIT("\", \"" REQ_KEY_PARAM_VAL "\": ");
28572860
esodbc_rec_st *arec, *irec;
28582861
SQLRETURN ret;
28592862
SQLSMALLINT i;
@@ -2875,10 +2878,9 @@ static SQLRETURN serialize_params_json(esodbc_stmt_st *stmt, char *dest,
28752878
memcpy(dest + pos, ", ", 2);
28762879
}
28772880
/* copy 'type' JSON key name */
2878-
memcpy(dest + pos + 2 * !!i, JSON_KEY_TYPE,
2879-
sizeof(JSON_KEY_TYPE) - 1);
2881+
memcpy(dest + pos + 2 * !!i, j_type.str, j_type.cnt);
28802882
}
2881-
pos += 2 * !!i + sizeof(JSON_KEY_TYPE) - 1;
2883+
pos += 2 * !!i + j_type.cnt;
28822884

28832885
/* copy/eval ES/SQL type name */
28842886
pos += json_escape(irec->es_type->type_name_c.str,
@@ -2887,9 +2889,9 @@ static SQLRETURN serialize_params_json(esodbc_stmt_st *stmt, char *dest,
28872889

28882890
if (dest) {
28892891
/* copy 'value' JSON key name */
2890-
memcpy(dest + pos, JSON_KEY_VALUE, sizeof(JSON_KEY_VALUE) - 1);
2892+
memcpy(dest + pos, j_val.str, j_val.cnt);
28912893
}
2892-
pos += sizeof(JSON_KEY_VALUE) - 1;
2894+
pos += j_val.cnt;
28932895

28942896
/* copy converted parameter value */
28952897
ret = convert_param_val(arec, irec, /*params array pos*/0LLU,
@@ -2914,14 +2916,10 @@ static SQLRETURN serialize_params_json(esodbc_stmt_st *stmt, char *dest,
29142916

29152917
*len = pos;
29162918
return SQL_SUCCESS;
2917-
2918-
# undef JSON_KEY_TYPE
2919-
# undef JSON_KEY_VALUE
29202919
}
29212920

2922-
2923-
static SQLRETURN statement_len_cbor(esodbc_stmt_st *stmt, size_t *outlen,
2924-
size_t *keys)
2921+
static SQLRETURN statement_len_cbor(esodbc_stmt_st *stmt, size_t *enc_len,
2922+
size_t *conv_len, size_t *keys)
29252923
{
29262924
SQLRETURN ret;
29272925
size_t bodylen, len, curslen;
@@ -2951,12 +2949,8 @@ static SQLRETURN statement_len_cbor(esodbc_stmt_st *stmt, size_t *outlen,
29512949
/* does the statement have any parameters? */
29522950
if (stmt->apd->count) {
29532951
bodylen += cbor_str_obj_len(sizeof(REQ_KEY_PARAMS) - 1);
2954-
// TODO:
2955-
// ret = serialize_params_cbor(stmt, /* no copy, just eval */NULL,
2956-
// &len);
2957-
ret = SQL_ERROR;
2958-
len = 0;
2959-
FIXME; // TODO
2952+
2953+
ret = statement_params_len_cbor(stmt, &len, conv_len);
29602954
if (! SQL_SUCCEEDED(ret)) {
29612955
ERRH(stmt, "failed to eval parameters length");
29622956
return ret;
@@ -2989,7 +2983,7 @@ static SQLRETURN statement_len_cbor(esodbc_stmt_st *stmt, size_t *outlen,
29892983
*keys += 2; /* mode, client_id */
29902984
/* TODO: request_/page_timeout */
29912985

2992-
*outlen = bodylen;
2986+
*enc_len = bodylen;
29932987
return SQL_SUCCESS;
29942988
}
29952989

@@ -3052,23 +3046,199 @@ static SQLRETURN statement_len_json(esodbc_stmt_st *stmt, size_t *outlen)
30523046

30533047
#define FAIL_ON_CBOR_ERR(_hnd, _cbor_err) \
30543048
do { \
3055-
if (err != CborNoError) { \
3049+
if (_cbor_err != CborNoError) { \
30563050
ERRH(_hnd, "CBOR: %s.", cbor_error_string(_cbor_err)); \
30573051
RET_HDIAG(_hnd, SQL_STATE_HY000, "CBOR serialization error", \
30583052
_cbor_err); \
30593053
} \
30603054
} while (0)
30613055

3056+
static SQLRETURN statement_params_len_cbor(esodbc_stmt_st *stmt,
3057+
size_t *enc_len, size_t *conv_len)
3058+
{
3059+
SQLSMALLINT i;
3060+
size_t len, l, max;
3061+
esodbc_rec_st *arec, *irec;
3062+
SQLRETURN ret;
3063+
3064+
/* Initial all-encompassing array preamble. */
3065+
/* ~ [] */
3066+
len = cbor_nn_hdr_len(stmt->apd->count);
3067+
3068+
max = 0;
3069+
for (i = 0; i < stmt->apd->count; i ++) {
3070+
assert(stmt->ipd->count == stmt->apd->count);
3071+
arec = &stmt->apd->recs[i];
3072+
irec = &stmt->ipd->recs[i];
3073+
3074+
/* ~ {} */
3075+
len = cbor_nn_hdr_len(stmt->apd->count);
3076+
3077+
/* ~ "type": "..." */
3078+
len += cbor_str_obj_len(sizeof(REQ_KEY_PARAM_TYPE) - 1);
3079+
len += cbor_str_obj_len(irec->es_type->type_name.cnt);
3080+
3081+
/* ~ "value": "..." */
3082+
len += cbor_str_obj_len(sizeof(REQ_KEY_PARAM_VAL) - 1);
3083+
assert(irec->es_type);
3084+
3085+
/* assume quick maxes */
3086+
ret = convert_param_val(arec, irec, /*params array pos*/0,
3087+
/*dest: calc length*/NULL, &l);
3088+
if (! SQL_SUCCEEDED(ret)) {
3089+
return ret;
3090+
}
3091+
/* keep maximum space required for storing the converted obj */
3092+
if (max < l) {
3093+
max = l;
3094+
}
3095+
/* the values are going to be sent as strings
3096+
* (see serialize_param_cbor() note) */
3097+
len += cbor_str_obj_len(l);
3098+
}
3099+
3100+
*conv_len = max;
3101+
*enc_len = len;
3102+
return SQL_SUCCESS;
3103+
}
3104+
3105+
/* Note: this implementation will encode numeric SQL types as strings (as it's
3106+
* reusing the JSON converters). This is somewhat negating CBOR's intentions,
3107+
* but: (1) it's a simplified and tested implementation; (2) the overall
3108+
* performance impact is negligible with this driver's currently intended
3109+
* usage pattern (SELECTs only, fetching data volume far outweighing that of
3110+
* the queries); (3) the server will convert the received value according to
3111+
* the correctly indicated type. XXX */
3112+
static SQLRETURN serialize_param_cbor(esodbc_rec_st *arec,
3113+
esodbc_rec_st *irec, CborEncoder *pmap, size_t conv_len)
3114+
{
3115+
SQLRETURN ret;
3116+
CborError res;
3117+
size_t len;
3118+
SQLLEN *ind_ptr;
3119+
size_t skip_quote;
3120+
static SQLULEN param_array_pos = 0; /* parames array not yet supported */
3121+
esodbc_stmt_st *stmt = HDRH(arec->desc)->stmt;
3122+
3123+
ind_ptr = deferred_address(SQL_DESC_INDICATOR_PTR, param_array_pos, arec);
3124+
if (ind_ptr && *ind_ptr == SQL_NULL_DATA) {
3125+
res = cbor_encode_null(pmap);
3126+
FAIL_ON_CBOR_ERR(stmt, res);
3127+
return SQL_SUCCESS;
3128+
}
3129+
/* from here on, "input parameter value[ is] non-NULL" */
3130+
assert(deferred_address(SQL_DESC_DATA_PTR, param_array_pos, arec));
3131+
3132+
/* the pmap->end is the start of the conversion buffer */
3133+
ret = convert_param_val(arec, irec, param_array_pos, (char *)pmap->end,
3134+
&len);
3135+
if (! SQL_SUCCEEDED(ret)) {
3136+
return ret;
3137+
}
3138+
assert(len <= conv_len);
3139+
3140+
/* need to skip the leading and trailing `"`? */
3141+
switch (irec->es_type->meta_type) {
3142+
case METATYPE_EXACT_NUMERIC:
3143+
case METATYPE_FLOAT_NUMERIC:
3144+
case METATYPE_BIT:
3145+
case METATYPE_BIN:
3146+
skip_quote = 0;
3147+
break;
3148+
3149+
case METATYPE_STRING:
3150+
case METATYPE_DATE_TIME:
3151+
case METATYPE_INTERVAL_WSEC:
3152+
case METATYPE_INTERVAL_WOSEC:
3153+
case METATYPE_UID:
3154+
skip_quote = 1;
3155+
break;
3156+
3157+
case METATYPE_MAX: /*DEFAULT, NULL*/
3158+
case METATYPE_UNKNOWN:
3159+
default:
3160+
BUGH(stmt, "unexpected SQL meta %d / type %d.",
3161+
irec->es_type->meta_type, irec->es_type->data_type);
3162+
RET_HDIAGS(stmt, SQL_STATE_HY000);
3163+
}
3164+
res = cbor_encode_text_string(pmap, pmap->end + skip_quote,
3165+
len - 2 * skip_quote);
3166+
FAIL_ON_CBOR_ERR(stmt, res);
3167+
3168+
return SQL_SUCCESS;
3169+
}
3170+
3171+
static SQLRETURN serialize_params_cbor(esodbc_stmt_st *stmt, CborEncoder *map,
3172+
size_t conv_len)
3173+
{
3174+
const static cstr_st p_type = CSTR_INIT(REQ_KEY_PARAM_TYPE);
3175+
const static cstr_st p_val = CSTR_INIT(REQ_KEY_PARAM_VAL);
3176+
SQLSMALLINT i;
3177+
CborError res;
3178+
SQLRETURN ret;
3179+
CborEncoder array; /* array for all params */
3180+
CborEncoder pmap; /* map for one param */
3181+
esodbc_rec_st *arec, *irec;
3182+
3183+
/* ~ [ */
3184+
res = cbor_encoder_create_array(map, &array, stmt->apd->count);
3185+
FAIL_ON_CBOR_ERR(stmt, res);
3186+
3187+
for (i = 0; i < stmt->apd->count; i ++) {
3188+
assert(stmt->ipd->count == stmt->apd->count);
3189+
arec = &stmt->apd->recs[i];
3190+
irec = &stmt->ipd->recs[i];
3191+
3192+
/* ~ { */
3193+
res = cbor_encoder_create_map(&array, &pmap, /* type + value = */2);
3194+
FAIL_ON_CBOR_ERR(stmt, res);
3195+
3196+
/*
3197+
* ~ "type": "..."
3198+
*/
3199+
res = cbor_encode_text_string(&pmap, p_type.str, p_type.cnt);
3200+
FAIL_ON_CBOR_ERR(stmt, res);
3201+
3202+
assert(irec->es_type);
3203+
res = cbor_encode_text_string(&pmap, irec->es_type->type_name_c.str,
3204+
irec->es_type->type_name_c.cnt);
3205+
FAIL_ON_CBOR_ERR(stmt, res);
3206+
3207+
/*
3208+
* ~ "value": "..."
3209+
*/
3210+
res = cbor_encode_text_string(&pmap, p_val.str, p_val.cnt);
3211+
FAIL_ON_CBOR_ERR(stmt, res);
3212+
3213+
ret = serialize_param_cbor(arec, irec, &pmap, conv_len);
3214+
if (! SQL_SUCCEEDED(ret)) {
3215+
ERRH(stmt, "converting parameter #%hd failed.", i + 1);
3216+
return ret;
3217+
}
3218+
3219+
/* ~ } */
3220+
res = cbor_encoder_close_container(&array, &pmap);
3221+
FAIL_ON_CBOR_ERR(stmt, res);
3222+
}
3223+
3224+
/* ~ ] */
3225+
res = cbor_encoder_close_container(map, &array);
3226+
FAIL_ON_CBOR_ERR(stmt, res);
3227+
3228+
return SQL_SUCCESS;
3229+
}
3230+
30623231
static SQLRETURN serialize_to_cbor(esodbc_stmt_st *stmt, cstr_st *dest,
3063-
size_t keys)
3232+
size_t conv_len, size_t keys)
30643233
{
30653234
CborEncoder encoder, map;
30663235
CborError err;
30673236
cstr_st tz, curs;
30683237
esodbc_dbc_st *dbc = HDRH(stmt)->dbc;
30693238
size_t dest_cnt;
30703239

3071-
cbor_encoder_init(&encoder, dest->str, dest->cnt, /*flags*/0);
3240+
assert(conv_len < dest->cnt);
3241+
cbor_encoder_init(&encoder, dest->str, dest->cnt - conv_len, /*flags*/0);
30723242
err = cbor_encoder_create_map(&encoder, &map, keys);
30733243
FAIL_ON_CBOR_ERR(stmt, err);
30743244

@@ -3101,8 +3271,11 @@ static SQLRETURN serialize_to_cbor(esodbc_stmt_st *stmt, cstr_st *dest,
31013271

31023272
/* does the statement have any parameters? */
31033273
if (stmt->apd->count) {
3104-
FIXME; // TODO
3105-
return SQL_ERROR;
3274+
err = cbor_encode_text_string(&map, REQ_KEY_PARAMS,
3275+
sizeof(REQ_KEY_PARAMS) - 1);
3276+
FAIL_ON_CBOR_ERR(stmt, err);
3277+
err = serialize_params_cbor(stmt, &map, conv_len);
3278+
FAIL_ON_CBOR_ERR(stmt, err);
31063279
}
31073280
/* does the statement have any fetch_size? */
31083281
if (dbc->fetch.slen) {
@@ -3157,7 +3330,7 @@ static SQLRETURN serialize_to_cbor(esodbc_stmt_st *stmt, cstr_st *dest,
31573330
dest_cnt = cbor_encoder_get_buffer_size(&encoder, dest->str);
31583331
assert(dest_cnt <= dest->cnt); /* tinycbor should check this, but still */
31593332
dest->cnt = dest_cnt;
3160-
DBGH(stmt, "request serialized to CBOR: [%zd] `0x%s`.", dest->cnt,
3333+
DBGH(stmt, "request serialized to CBOR: [%zd] `%s`.", dest->cnt,
31613334
cstr_hex_dump(dest));
31623335

31633336
return SQL_SUCCESS;
@@ -3278,7 +3451,7 @@ static SQLRETURN serialize_to_json(esodbc_stmt_st *stmt, cstr_st *dest)
32783451
SQLRETURN TEST_API serialize_statement(esodbc_stmt_st *stmt, cstr_st *dest)
32793452
{
32803453
SQLRETURN ret;
3281-
size_t len, keys;
3454+
size_t enc_len, conv_len, alloc_len, keys;
32823455
esodbc_dbc_st *dbc = HDRH(stmt)->dbc;
32833456

32843457
/* enforced in EsSQLSetDescFieldW(SQL_DESC_ARRAY_SIZE) */
@@ -3289,27 +3462,30 @@ SQLRETURN TEST_API serialize_statement(esodbc_stmt_st *stmt, cstr_st *dest)
32893462
"Failed to update the timezone parameter", 0);
32903463
}
32913464

3292-
ret = dbc->pack_json ? statement_len_json(stmt, &len) :
3293-
statement_len_cbor(stmt, &len, &keys);
3465+
conv_len = 0;
3466+
ret = dbc->pack_json ? statement_len_json(stmt, &enc_len) :
3467+
statement_len_cbor(stmt, &enc_len, &conv_len, &keys);
32943468
if (! SQL_SUCCEEDED(ret)) {
32953469
return ret;
3470+
} else {
3471+
alloc_len = enc_len + conv_len;
32963472
}
32973473

32983474
/* allocate memory for the stringified statement, if needed */
3299-
if (dest->cnt < len) {
3300-
INFOH(dbc, "local buffer too small (%zd), need %zdB; will alloc.",
3301-
dest->cnt, len);
3475+
if (dest->cnt < alloc_len) {
3476+
INFOH(dbc, "local buffer too small (%zu), need %zuB; will alloc.",
3477+
dest->cnt, alloc_len);
33023478
DBGH(dbc, "local buffer too small, SQL: `" LCPDL "`.",
33033479
LCSTR(&stmt->u8sql));
3304-
if (! (dest->str = malloc(len))) {
3305-
ERRNH(stmt, "failed to alloc %zdB.", len);
3480+
if (! (dest->str = malloc(alloc_len))) {
3481+
ERRNH(stmt, "failed to alloc %zdB.", alloc_len);
33063482
RET_HDIAGS(stmt, SQL_STATE_HY001);
33073483
}
3308-
dest->cnt = len;
3484+
dest->cnt = alloc_len;
33093485
}
33103486

33113487
return dbc->pack_json ? serialize_to_json(stmt, dest) :
3312-
serialize_to_cbor(stmt, dest, keys);
3488+
serialize_to_cbor(stmt, dest, conv_len, keys);
33133489
}
33143490

33153491

0 commit comments

Comments
 (0)