Skip to content

Commit c5cb9cb

Browse files
committed
CBOR support for result sets (#172)
* cbor: add source files individually to the project - this ends up to simply the spec file a bit and removes some unnecessary code in the library. * add CBOR support for result sets This commit extends the CBOR support with the ability to read the CBOR-encapsulated result sets. The commit also makes fetching data more efficient with the SQLGetData() (the alternative to the generally more efficient SQLBindCol()). The implementation still uses punctual column binding and unbinding, but SQLFetch() will now cache the source JSON/CBOR object into IRD's records and will no longer walk the entire list of instantied ARD records all the way to the ad-hoc bound column. Counting the total number of rows returned for a query has been changed to cope with ES's use of indefinite-size arrays, to avoid iterating twice over the rows in a page. * addressing PR review comments - slight code simplification (cherry picked from commit 4fc9197)
1 parent 17d54a7 commit c5cb9cb

File tree

10 files changed

+781
-277
lines changed

10 files changed

+781
-277
lines changed

CMakeLists.txt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -292,10 +292,8 @@ add_custom_target(curlclean
292292
#
293293
set(TINYCBOR_PATH_SRC ${CMAKE_SOURCE_DIR}/libs/tinycbor CACHE PATH
294294
"Lib tinycbor source path")
295-
aux_source_directory(${TINYCBOR_PATH_SRC}/src DRV_SRC)
296-
list(FILTER DRV_SRC EXCLUDE REGEX .*open_memstream.c$) # Win-unsupported
297-
list(FILTER DRV_SRC EXCLUDE REGEX .*cborparser.c$) # to be patched
298-
file(COPY ${TINYCBOR_PATH_SRC}/src/cborparser.c DESTINATION ${CMAKE_BINARY_DIR})
295+
file(COPY ${TINYCBOR_PATH_SRC}/src/cborparser.c DESTINATION
296+
${CMAKE_BINARY_DIR})
299297
# tinycbor doesn't expose (yet? #125) the text/binary string pointer, since the
300298
# string can span multiple stream chunks. However, in our case the CBOR object
301299
# is available entirely, so access to it can safely be had; this saves a
@@ -308,9 +306,13 @@ CborError cbor_value_get_string_chunk(CborValue *it,
308306
CborError err = get_string_chunk(it, bufferptr, len);
309307
return err != CborNoError ? err : preparse_next_value(it);
310308
}")
311-
aux_source_directory(${CMAKE_BINARY_DIR} DRV_SRC)
309+
list(APPEND DRV_SRC ${CMAKE_BINARY_DIR}/cborparser.c)
310+
list(APPEND DRV_SRC ${TINYCBOR_PATH_SRC}/src/cborvalidation.c)
311+
list(APPEND DRV_SRC ${TINYCBOR_PATH_SRC}/src/cborerrorstrings.c)
312+
list(APPEND DRV_SRC ${TINYCBOR_PATH_SRC}/src/cborencoder.c)
313+
list(APPEND DRV_SRC
314+
${TINYCBOR_PATH_SRC}/src/cborencoder_close_container_checked.c)
312315
set(TINYCBOR_INC ${TINYCBOR_PATH_SRC}/src)
313-
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DWITHOUT_OPEN_MEMSTREAM")
314316
# limit how deep the parser will recurse (current need: 3)
315317
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DCBOR_PARSER_MAX_RECURSIONS=16")
316318

driver/connect.c

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,20 @@ static int debug_callback(CURL *handle, curl_infotype type, char *data,
235235
/*
236236
* "ptr points to the delivered data, and the size of that data is size
237237
* multiplied with nmemb."
238+
*
239+
* Note: Elasticsearch supports (atm.) no streaming API and ES/SQL doesn't
240+
* either. This function will keep realloc'ing (if needed) until the entire
241+
* page sent by ES/SQL is received. The alternative is to stream-parse.
242+
* However, with text & binary data, the stream parsing libraries will ask the
243+
* client to provide a buffer to copy the data into, out of potentially
244+
* multiple received data chunks in the stream. Which could require an extra
245+
* allocation and will always involve an extra copy (or more, for UTF-8
246+
* decoding). With current design (= reply object in contiguous chunk) at
247+
* least the copy is skipped, since the text/binary data is contiguous and
248+
* ready to be read from the receive buffer directly.
249+
*
250+
* TODO: initial chunk size and incremental sizes for the reallocation should
251+
* be better "calibrated" (/ follow some max/hysteretic curve).
238252
*/
239253
static size_t write_callback(char *ptr, size_t size, size_t nmemb,
240254
void *userdata)
@@ -740,10 +754,10 @@ SQLRETURN curl_post(esodbc_stmt_st *stmt, int url_type,
740754
BOOL is_json;
741755

742756
if (dbc->pack_json) {
743-
DBGH(stmt, "POSTing JSON type %d: [%zu] `" LCPDL "`.", url_type,
757+
DBGH(stmt, "POSTing JSON to URL type %d: [%zu] `" LCPDL "`.", url_type,
744758
req_body->cnt, LCSTR(req_body));
745759
} else {
746-
DBGH(stmt, "POSTing CBOR type %d: [%zu] `%s`.", url_type,
760+
DBGH(stmt, "POSTing CBOR to URL type %d: [%zu] `%s`.", url_type,
747761
req_body->cnt, cstr_hex_dump(req_body));
748762
}
749763

@@ -1514,7 +1528,7 @@ static BOOL parse_es_version_cbor(esodbc_dbc_st *dbc, cstr_st *rsp_body,
15141528
/* the _init() doesn't actually validate the object */
15151529
res = cbor_value_validate(&top_obj, ES_CBOR_PARSE_FLAGS);
15161530
CHK_RES(stmt, "failed to validate CBOR object: [%zu] `%s`",
1517-
stmt->rset.body.cnt, cstr_hex_dump(&stmt->rset.body));
1531+
rsp_body->cnt, cstr_hex_dump(rsp_body));
15181532
# endif /*0*/
15191533
# endif /* !NDEBUG */
15201534

driver/convert.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3495,12 +3495,12 @@ static inline BOOL conv_implemented(SQLSMALLINT sqltype, SQLSMALLINT ctype)
34953495
}
34963496

34973497

3498-
/* Check if data types in returned columns are compabile with buffer types
3499-
* bound for those columns OR if parameter data conversion is allowed.
3498+
/* Check (1) if data types in returned columns are compabile with buffer types
3499+
* bound for those columns OR (2) if parameter data conversion is allowed.
35003500
* idx:
35013501
* if > 0: parameter number for parameter binding;
3502-
* if < 0: indicator for bound columns check.
3503-
* */
3502+
* if < 0: negated column number to check OR indicator to check all bound
3503+
* columns (CONV_CHECK_ALL_COLS). */
35043504
SQLRETURN convertability_check(esodbc_stmt_st *stmt, SQLINTEGER idx,
35053505
int *conv_code)
35063506
{
@@ -3520,7 +3520,9 @@ SQLRETURN convertability_check(esodbc_stmt_st *stmt, SQLINTEGER idx,
35203520
axd = stmt->ard;
35213521
ixd = stmt->ird;
35223522

3523-
start = 0;
3523+
/* if this is a SQLGetData() call, only check the one bound column */
3524+
assert(idx == CONV_CHECK_ALL_COLS || STMT_GD_CALLING(stmt));
3525+
start = (idx == CONV_CHECK_ALL_COLS) ? 0 : -idx - 1;
35243526
stop = axd->count < ixd->count ? axd->count : ixd->count;
35253527
} else {
35263528
/*

driver/convert.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ SQLULEN get_param_size(esodbc_rec_st *irec);
2121
inline void *deferred_address(SQLSMALLINT field_id, size_t pos,
2222
esodbc_rec_st *rec);
2323

24+
25+
/* column and parameters are all SQLUSMALLINT (unsigned short) */
26+
#define CONV_CHECK_ALL_COLS (- ((SQLINTEGER)USHRT_MAX + 1))
27+
/* Check (1) if data types in returned columns are compabile with buffer types
28+
* bound for those columns OR (2) if parameter data conversion is allowed.
29+
* idx:
30+
* if > 0: parameter number for parameter binding;
31+
* if < 0: negated column number to check OR indicator to check all bound
32+
* columns (CONV_CHECK_ALL_COLS). */
2433
SQLRETURN convertability_check(esodbc_stmt_st *stmt, SQLINTEGER idx,
2534
int *conv_code);
2635
BOOL update_crr_date(struct tm *now);

driver/handles.c

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,7 @@ SQLRETURN EsSQLGetStmtAttrW(
953953

954954
/* "determine the number of the current row in the result set" */
955955
case SQL_ATTR_ROW_NUMBER:
956-
*(SQLULEN *)ValuePtr = (SQLULEN)STMT_CRR_ROW_NUMBER(stmt);
956+
*(SQLULEN *)ValuePtr = (SQLULEN)stmt->tv_rows;
957957
DBGH(stmt, "getting row number: %llu", *(SQLULEN *)ValuePtr);
958958
break;
959959

@@ -1499,7 +1499,6 @@ esodbc_desc_st *getdata_set_ard(esodbc_stmt_st *stmt, esodbc_desc_st *gd_ard,
14991499
SQLUSMALLINT colno, esodbc_rec_st *recs, SQLUSMALLINT count)
15001500
{
15011501
SQLRETURN ret;
1502-
SQLUSMALLINT i;
15031502
esodbc_desc_st *ard = stmt->ard;
15041503

15051504
init_desc(gd_ard, stmt, DESC_TYPE_ARD, SQL_DESC_ALLOC_USER);
@@ -1511,13 +1510,10 @@ esodbc_desc_st *getdata_set_ard(esodbc_stmt_st *stmt, esodbc_desc_st *gd_ard,
15111510
}
15121511

15131512
if (colno < count) { /* can the static recs be used? */
1514-
/* need to init all records, not only the single one that will be
1515-
* bound, since data covert. check will run against all bound recs. */
1516-
for (i = 0; i < count; i ++) {
1517-
init_rec(&recs[i], gd_ard);
1518-
}
1513+
assert(0 < colno);
1514+
init_rec(&recs[colno - 1], gd_ard);
15191515

1520-
gd_ard->count = count;
1516+
gd_ard->count = colno;
15211517
gd_ard->recs = recs;
15221518
}
15231519
/* else: recs will be alloc'd later when binding the column */
@@ -1996,7 +1992,8 @@ static void set_defaults_from_meta_type(esodbc_rec_st *rec)
19961992
rec->concise_type == SQL_C_DEFAULT) ||
19971993
(DESC_TYPE_IS_IMPLEMENTATION(rec->desc->type) &&
19981994
rec->concise_type == ESODBC_SQL_NULL));
1999-
WARNH(rec->desc, "max meta type: can't set defaults");
1995+
DBGH(rec->desc, "max meta type (C default / SQL NULL): "
1996+
"can't set defaults");
20001997
break;
20011998
}
20021999
}

driver/handles.h

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,12 @@ typedef struct desc_rec {
206206
* need to be set for records in IxD descriptors */
207207
esodbc_estype_st *es_type;
208208

209+
/* IRD reference copy of respective protocol value */
210+
union {
211+
UJObject json;
212+
CborValue cbor;
213+
} i_val;
214+
209215
/*
210216
* record fields
211217
*/
@@ -302,13 +308,15 @@ typedef struct struct_desc {
302308

303309
struct resultset_cbor {
304310
cstr_st curs; /* ES'es cursor; refs req's body */
311+
CborValue rows_obj; /* top object rows container (EsSQLRowCount()) */
305312
CborValue rows_iter; /* iterator over received rows; refs req's body */
306313
wstr_st cols_buff /* columns descriptions; refs allocated chunk */;
307314
};
308315

309316
struct resultset_json {
310317
wstr_st curs; /* ES'es cursor; refs UJSON4C 'state' */
311318
void *state; /* top UJSON decoder state */
319+
UJObject rows_obj; /* top object rows container (EsSQLRowCount()) */
312320
void *rows_iter; /* UJSON iterator with the rows in result set */
313321
UJObject row_array; /* UJSON object for current row */
314322
};
@@ -317,17 +325,17 @@ typedef struct struct_resultset {
317325
long code; /* HTTP code of last response */
318326
cstr_st body; /* HTTP body of last answer to a statement */
319327

328+
BOOL pack_json; /* the server could send a JSON answer for a CBOR req. */
320329
union {
321330
struct resultset_cbor cbor;
322331
struct resultset_json json;
323332
} pack;
324333

325-
size_t nrows; /* (count of) rows in current result set */
326334
size_t vrows; /* (count of) visited rows in current result set */
327335
} resultset_st;
328336

329337
#define STMT_HAS_CURSOR(_stmt) \
330-
(HDRH(_stmt)->dbc->pack_json ? \
338+
((_stmt)->rset.pack_json ? \
331339
(_stmt)->rset.pack.json.curs.cnt : \
332340
(_stmt)->rset.pack.cbor.curs.cnt)
333341

@@ -370,8 +378,8 @@ typedef struct struct_stmt {
370378
resultset_st rset;
371379
/* count of result sets fetched */
372380
size_t nset;
373-
/* total count of fetched rows for one statement (sum(resultset.nrows)) */
374-
size_t tf_rows;
381+
/* total visited rows (SUM(resultset.vrows)) <=> SQL_ATTR_ROW_NUMBER */
382+
size_t tv_rows;
375383
/* SQL data types conversion to SQL C compatibility (IRD.SQL -> ARD.C) */
376384
enum {
377385
CONVERSION_VIOLATION = -2, /* specs disallowed */
@@ -388,17 +396,13 @@ typedef struct struct_stmt {
388396

389397
} esodbc_stmt_st;
390398

391-
/* reset total number of fetched rows for a statement */
392-
#define STMT_TFROWS_RESET(_stmt) \
399+
/* reset statment's result set count and number of visited rows */
400+
#define STMT_ROW_CNT_RESET(_stmt) \
393401
do { \
394-
(_stmt)->tf_rows = 0; \
402+
(_stmt)->nset = 0; \
403+
(_stmt)->tv_rows = 0; \
395404
} while (0)
396405

397-
/* 1-based current row number */
398-
#define STMT_CRR_ROW_NUMBER(_stmt) \
399-
((_stmt)->tf_rows - (_stmt)->rset.nrows + \
400-
(_stmt)->rset.vrows + /*1-based*/1)
401-
402406
/* SQLGetData() state reset */
403407
#define STMT_GD_RESET(_stmt) \
404408
do { \

0 commit comments

Comments
 (0)