Skip to content

Commit c1c5b52

Browse files
authored
Add catalog support (#297)
This adds support for multiple catalogs ("databases"). The client application can now set the catalog at connection level - with SQLSetConnectAttr - to a pattern matching a local or remote cluster name and ES/SQL will execute the query on the respective cluster/s. A default catalog can also be set through the connection string, through the "Catalog" DSN attribute. In case the catalog is not set, or set to an empty or null string, the local catalog will be chosen for execution. The driver will simply add a "catalog" request attribute containing the value received from the application, or skip adding the attribute if this is empty.
1 parent 7becfd9 commit c1c5b52

File tree

8 files changed

+216
-91
lines changed

8 files changed

+216
-91
lines changed

driver/catalogue.c

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -87,35 +87,52 @@ SQLRETURN EsSQLStatisticsW(
8787
return fake_answer(hstmt, &statistics);
8888
}
8989

90-
BOOL TEST_API set_current_catalog(esodbc_dbc_st *dbc, wstr_st *catalog)
90+
void free_current_catalog(esodbc_dbc_st *dbc)
9191
{
92-
if (dbc->catalog.cnt) {
93-
DBGH(dbc, "catalog already set to `" LWPDL "`.", LWSTR(&dbc->catalog));
94-
if (! EQ_WSTR(&dbc->catalog, catalog)) {
95-
/* this should never happen, as cluster's name is not updateable
96-
* on the fly. */
97-
ERRH(dbc, "overwriting previously set catalog value!");
98-
free(dbc->catalog.str);
99-
dbc->catalog.str = NULL;
100-
dbc->catalog.cnt = 0;
101-
} else {
102-
return FALSE;
92+
if (dbc->catalog.w.str) {
93+
free(dbc->catalog.w.str);
94+
dbc->catalog.w.str = NULL;
95+
dbc->catalog.w.cnt = 0;
96+
}
97+
if (dbc->catalog.c.str) {
98+
free(dbc->catalog.c.str);
99+
dbc->catalog.c.str = NULL;
100+
dbc->catalog.c.cnt = 0;
101+
}
102+
}
103+
104+
SQLRETURN set_current_catalog(esodbc_dbc_st *dbc, wstr_st *catalog)
105+
{
106+
if (dbc->catalog.w.cnt) {
107+
DBGH(dbc, "catalog previously set to `" LWPDL "`.",
108+
LWSTR(&dbc->catalog.w));
109+
if (! EQ_WSTR(&dbc->catalog.w, catalog)) {
110+
free_current_catalog(dbc);
103111
}
104112
}
105-
if (! catalog->cnt) {
106-
WARNH(dbc, "attempting to set catalog name to empty value.");
107-
return FALSE;
113+
if (! catalog->cnt || ! catalog->str) {
114+
WARNH(dbc, "catalog name set to empty value.");
115+
return SQL_SUCCESS;
108116
}
109-
if (! (dbc->catalog.str = malloc((catalog->cnt + 1) * sizeof(SQLWCHAR)))) {
117+
dbc->catalog.w.str = malloc((catalog->cnt + 1) * sizeof(SQLWCHAR));
118+
if (! dbc->catalog.w.str) {
110119
ERRNH(dbc, "OOM for %zu wchars.", catalog->cnt + 1);
111-
return FALSE;
120+
RET_HDIAGS(dbc, SQL_STATE_HY001);
112121
}
113-
wmemcpy(dbc->catalog.str, catalog->str, catalog->cnt);
114-
dbc->catalog.str[catalog->cnt] = L'\0';
115-
dbc->catalog.cnt = catalog->cnt;
116-
INFOH(dbc, "current catalog name: `" LWPDL "`.", LWSTR(&dbc->catalog));
122+
wmemcpy(dbc->catalog.w.str, catalog->str, catalog->cnt);
123+
dbc->catalog.w.str[catalog->cnt] = L'\0';
124+
dbc->catalog.w.cnt = catalog->cnt;
117125

118-
return TRUE;
126+
if (! wstr_to_utf8(catalog, &dbc->catalog.c)) {
127+
goto err;
128+
}
129+
130+
INFOH(dbc, "current catalog name: `" LWPDL "`.", LWSTR(&dbc->catalog.w));
131+
return SQL_SUCCESS;
132+
133+
err:
134+
free_current_catalog(dbc);
135+
RET_HDIAG(dbc, SQL_STATE_HY000, "Saving current catalog failed", 0);
119136
}
120137

121138
/* writes into 'dest', of size 'room', the current requested attr. of 'dbc'.
@@ -198,9 +215,6 @@ SQLSMALLINT fetch_server_attr(esodbc_dbc_st *dbc, SQLINTEGER attr_id,
198215
/* 0-term room left out when binding */
199216
buff[attr_val.cnt] = L'\0'; /* write_wstr() expects the 0-term */
200217
}
201-
if (attr_id == SQL_ATTR_CURRENT_CATALOG) {
202-
set_current_catalog(dbc, &attr_val);
203-
}
204218
}
205219
DBGH(dbc, "attribute %ld value: `" LWPDL "`.", attr_id, LWSTR(&attr_val));
206220

driver/catalogue.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ enum {
3535

3636
SQLSMALLINT fetch_server_attr(esodbc_dbc_st *dbc, SQLINTEGER attr_id,
3737
SQLWCHAR *dest, SQLSMALLINT room);
38-
BOOL TEST_API set_current_catalog(esodbc_dbc_st *dbc, wstr_st *catalog);
38+
SQLRETURN set_current_catalog(esodbc_dbc_st *dbc, wstr_st *catalog);
39+
void free_current_catalog(esodbc_dbc_st *dbc);
3940
SQLRETURN TEST_API update_varchar_defs(esodbc_stmt_st *stmt);
4041

4142

driver/connect.c

Lines changed: 28 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,6 +1501,11 @@ SQLRETURN config_dbc(esodbc_dbc_st *dbc, esodbc_dsn_attrs_st *attrs)
15011501
/* early execution */
15021502
dbc->early_exec = wstr2bool(&attrs->early_exec);
15031503
INFOH(dbc, "early execution: %s.", dbc->early_exec ? "true" : "false");
1504+
/* default current catalog */
1505+
if (attrs->catalog.cnt &&
1506+
(! SQL_SUCCEEDED(set_current_catalog(dbc, &attrs->catalog)))) {
1507+
goto err;
1508+
}
15041509

15051510
/* how to print the floats? */
15061511
assert(1 <= attrs->sci_floats.cnt); /* default should apply */
@@ -1657,13 +1662,9 @@ void cleanup_dbc(esodbc_dbc_st *dbc)
16571662
dbc->srv_ver.str = NULL;
16581663
dbc->srv_ver.cnt = 0;
16591664
}
1660-
if (dbc->catalog.str) {
1661-
free(dbc->catalog.str);
1662-
dbc->catalog.str = NULL;
1663-
dbc->catalog.cnt = 0;
1664-
} else {
1665-
assert(dbc->catalog.cnt == 0);
1666-
}
1665+
free_current_catalog(dbc);
1666+
assert(dbc->catalog.w.cnt == 0);
1667+
assert(dbc->catalog.c.cnt == 0);
16671668
if (dbc->varchar_limit_str.str) {
16681669
free(dbc->varchar_limit_str.str);
16691670
dbc->varchar_limit_str.str = NULL;
@@ -3266,38 +3267,6 @@ SQLRETURN EsSQLDisconnect(SQLHDBC ConnectionHandle)
32663267
return SQL_SUCCESS;
32673268
}
32683269

3269-
/* ES/SQL doesn't support catalogs (yet). This function checks that a
3270-
* previously retrieved (and cached) catalog value is the same with what the
3271-
* app currently tries to set it to.
3272-
* Ideally, the app provided value would be cached here too (as per the spec:
3273-
* "SQL_ATTR_CURRENT_CATALOG can be set before or after connecting"), in case
3274-
* there's no connection "established" yet and checked at "establishment"
3275-
* time. But there's no client reported yet setting a catalog value before
3276-
* connecting. */
3277-
static SQLRETURN check_catalog_name(esodbc_dbc_st *dbc, SQLWCHAR *name,
3278-
SQLINTEGER len)
3279-
{
3280-
wstr_st catalog;
3281-
catalog.str = name;
3282-
if (len < 0) {
3283-
catalog.cnt = wcslen(name);
3284-
} else {
3285-
catalog.cnt = ((size_t)len)/sizeof(SQLWCHAR);
3286-
}
3287-
if (! EQ_WSTR(&dbc->catalog, &catalog)) {
3288-
if (! dbc->catalog.cnt) {
3289-
/* this will happen if the app tries to set a value that it
3290-
* discovered over a different connection.
3291-
* TODO on a first reported issue. */
3292-
WARNH(dbc, "connection's current catalog not yet set!");
3293-
}
3294-
ERRH(dbc, "setting catalog name not supported.");
3295-
RET_HDIAGS(dbc, SQL_STATE_HYC00);
3296-
}
3297-
WARNH(dbc, "ignoring attempt to set the current catalog.");
3298-
return SQL_SUCCESS;
3299-
}
3300-
33013270
/*
33023271
* https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/unicode-drivers :
33033272
* """
@@ -3325,6 +3294,7 @@ SQLRETURN EsSQLSetConnectAttrW(
33253294
_In_reads_bytes_opt_(StringLength) SQLPOINTER Value,
33263295
SQLINTEGER StringLength)
33273296
{
3297+
wstr_st catalog;
33283298
esodbc_dbc_st *dbc = DBCH(ConnectionHandle);
33293299

33303300
switch(Attribute) {
@@ -3434,11 +3404,13 @@ SQLRETURN EsSQLSetConnectAttrW(
34343404
RET_HDIAGS(dbc, SQL_STATE_IM009);
34353405

34363406
case SQL_ATTR_CURRENT_CATALOG:
3437-
INFOH(dbc, "setting current catalog to: `" LWPDL "`.",
3438-
/* string should be 0-term'd */
3439-
0 <= StringLength ? StringLength/sizeof(SQLWCHAR) : SHRT_MAX,
3440-
(SQLWCHAR *)Value);
3441-
return check_catalog_name(dbc, (SQLWCHAR *)Value, StringLength);
3407+
if (StringLength < 0) {
3408+
ERRH(dbc, "invalid catalog name lenght: %ld", StringLength);
3409+
RET_HDIAGS(dbc, SQL_STATE_HY090);
3410+
}
3411+
catalog.str = (SQLWCHAR *)Value;
3412+
catalog.cnt = StringLength/sizeof(SQLWCHAR);
3413+
return set_current_catalog(dbc, &catalog);
34423414

34433415
case SQL_ATTR_TRACE:
34443416
case SQL_ATTR_TRACEFILE: /* DM-only */
@@ -3504,7 +3476,16 @@ SQLRETURN EsSQLGetConnectAttrW(
35043476
ERRH(dbc, "no connection active.");
35053477
RET_HDIAGS(dbc, SQL_STATE_08003);
35063478
}
3507-
if ((used = fetch_server_attr(dbc, SQL_ATTR_CURRENT_CATALOG,
3479+
if (dbc->catalog.w.cnt) {
3480+
if (! SQL_SUCCEEDED(write_wstr(dbc, (SQLWCHAR *)ValuePtr,
3481+
&dbc->catalog.w, (SQLSMALLINT)BufferLength,
3482+
&used))) {
3483+
ERRH(dbc, "failed to copy current catalog out.");
3484+
RET_STATE(dbc->hdr.diag.state);
3485+
}
3486+
used = SHRT_MAX <= dbc->catalog.w.cnt ? SHRT_MAX :
3487+
(SQLSMALLINT)dbc->catalog.w.cnt;
3488+
} else if ((used = fetch_server_attr(dbc, SQL_ATTR_CURRENT_CATALOG,
35083489
(SQLWCHAR *)ValuePtr,
35093490
(SQLSMALLINT)BufferLength)) < 0) {
35103491
ERRH(dbc, "failed to get current catalog.");
@@ -3583,14 +3564,14 @@ SQLRETURN EsSQLGetConnectAttrW(
35833564
/* MS Access/Jet proprietary info type */
35843565
case 30002:
35853566
ERRH(dbc, "unsupported info type.");
3586-
RET_HDIAGS(DBCH(ConnectionHandle), SQL_STATE_HY092);
3567+
RET_HDIAGS(dbc, SQL_STATE_HY092);
35873568
#endif
35883569

35893570
default:
35903571
ERRH(dbc, "unknown Attribute type %ld.", Attribute);
35913572
// FIXME: add the other attributes
35923573
FIXME;
3593-
RET_HDIAGS(DBCH(ConnectionHandle), SQL_STATE_HY092);
3574+
RET_HDIAGS(dbc, SQL_STATE_HY092);
35943575
}
35953576

35963577
return SQL_SUCCESS;

driver/handles.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ typedef struct struct_dbc {
131131

132132
wstr_st dsn; /* data source name SQLGetInfo(SQL_DATA_SOURCE_NAME) */
133133
wstr_st server; /* ~ name; requested with SQLGetInfo(SQL_SERVER_NAME) */
134-
wstr_st catalog; /* cached value; checked against if app setting it */
135134
wstr_st srv_ver; /* server version: SQLGetInfo(SQL_DBMS_VER) */
136135

137136
cstr_st proxy_url;
@@ -166,6 +165,10 @@ typedef struct struct_dbc {
166165
ESODBC_CMPSS_AUTO,
167166
} compression;
168167
BOOL apply_tz; /* should the times be converted from UTC to local TZ? */
168+
struct {
169+
wstr_st w; /* NB: w.str and c.str are co-allocated */
170+
cstr_st c;
171+
} catalog; /* current ~ */
169172
BOOL early_exec; /* should prepared, non-param queries be exec'd early? */
170173
enum {
171174
ESODBC_FLTS_DEFAULT = 0,
@@ -559,7 +562,7 @@ SQLRETURN EsSQLSetDescRec(
559562
/* return the code associated with the given state (and debug-log) */
560563
#define RET_STATE(_s) \
561564
do { \
562-
assert(_s < SQL_STATE_MAX); \
565+
assert(SQL_STATE_00000 <= _s && _s < SQL_STATE_MAX); \
563566
return esodbc_errors[_s].retcode; \
564567
} while (0)
565568

driver/queries.c

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3052,28 +3052,40 @@ static SQLRETURN statement_len_cbor(esodbc_stmt_st *stmt, size_t *enc_len,
30523052
/* "field_multi_value_leniency": true/false */
30533053
bodylen += cbor_str_obj_len(sizeof(REQ_KEY_MULTIVAL) - 1);
30543054
bodylen += CBOR_OBJ_BOOL_LEN;
3055+
(*keys) ++;
30553056
/* "index_include_frozen": true/false */
30563057
bodylen += cbor_str_obj_len(sizeof(REQ_KEY_IDX_FROZEN) - 1);
30573058
bodylen += CBOR_OBJ_BOOL_LEN;
3059+
(*keys) ++;
30583060
/* "time_zone": "-05:45" */
30593061
bodylen += cbor_str_obj_len(sizeof(REQ_KEY_TIMEZONE) - 1);
30603062
bodylen += cbor_str_obj_len(tz_param.cnt); /* lax len */
3063+
(*keys) ++;
3064+
/* "catalog": "my_cluster" */
3065+
if (dbc->catalog.c.cnt) {
3066+
bodylen += cbor_str_obj_len(sizeof(REQ_KEY_CATALOG) - 1);
3067+
bodylen += cbor_str_obj_len(dbc->catalog.c.cnt);
3068+
(*keys) ++;
3069+
}
30613070
bodylen += cbor_str_obj_len(sizeof(REQ_KEY_VERSION) - 1);
30623071
bodylen += cbor_str_obj_len(version.cnt);
3063-
*keys += 4; /* field_m._val., idx._inc._frozen, time_zone, version */
3072+
(*keys) ++;
30643073
}
30653074
/* mode */
30663075
bodylen += cbor_str_obj_len(sizeof(REQ_KEY_MODE) - 1);
30673076
bodylen += cbor_str_obj_len(sizeof(REQ_VAL_MODE) - 1);
3077+
(*keys) ++;
30683078
/* client_id */
30693079
bodylen += cbor_str_obj_len(sizeof(REQ_KEY_CLT_ID) - 1);
30703080
bodylen += cbor_str_obj_len(sizeof(REQ_VAL_CLT_ID) - 1);
3081+
(*keys) ++;
30713082
/* binary_format */
30723083
bodylen += cbor_str_obj_len(sizeof(REQ_KEY_BINARY_FMT) - 1);
30733084
bodylen += CBOR_OBJ_BOOL_LEN;
3074-
*keys += 3; /* mode, client_id, binary_format */
3085+
(*keys) ++;
30753086
/* TODO: request_/page_timeout */
30763087

3088+
assert(*keys <= REST_REQ_KEY_COUNT);
30773089
*enc_len = bodylen;
30783090
return SQL_SUCCESS;
30793091
}
@@ -3125,6 +3137,12 @@ static SQLRETURN statement_len_json(esodbc_stmt_st *stmt, size_t *outlen)
31253137
/* "time_zone": "-05:45" */
31263138
bodylen += sizeof(JSON_KEY_TIMEZONE) - 1;
31273139
bodylen += tz_param.cnt;
3140+
/* "catalog": "my_cluster" */
3141+
if (dbc->catalog.c.cnt) {
3142+
bodylen += sizeof(JSON_KEY_CATALOG) - 1;
3143+
bodylen += dbc->catalog.c.cnt;
3144+
bodylen += /* 2x `"` */2;
3145+
}
31283146
/* "version": */
31293147
bodylen += sizeof(JSON_KEY_VERSION) - 1;
31303148
bodylen += version.cnt + /* 2x`"` */2;
@@ -3407,6 +3425,14 @@ static SQLRETURN serialize_to_cbor(esodbc_stmt_st *stmt, cstr_st *dest,
34073425
}
34083426
err = cbor_encode_text_string(&map, tz.str, tz.cnt);
34093427
FAIL_ON_CBOR_ERR(stmt, err);
3428+
if (dbc->catalog.c.cnt) {
3429+
err = cbor_encode_text_string(&map, REQ_KEY_CATALOG,
3430+
sizeof(REQ_KEY_CATALOG) - 1);
3431+
FAIL_ON_CBOR_ERR(stmt, err);
3432+
err = cbor_encode_text_string(&map, dbc->catalog.c.str,
3433+
dbc->catalog.c.cnt);
3434+
FAIL_ON_CBOR_ERR(stmt, err);
3435+
}
34103436
/* version */
34113437
err = cbor_encode_text_string(&map, REQ_KEY_VERSION,
34123438
sizeof(REQ_KEY_VERSION) - 1);
@@ -3535,6 +3561,15 @@ static SQLRETURN serialize_to_json(esodbc_stmt_st *stmt, cstr_st *dest)
35353561
sizeof(JSON_VAL_TIMEZONE_Z) - 1);
35363562
pos += sizeof(JSON_VAL_TIMEZONE_Z) - 1;
35373563
}
3564+
if (dbc->catalog.c.cnt) {
3565+
/* "catalog": "my_cluster" */
3566+
memcpy(body + pos, JSON_KEY_CATALOG, sizeof(JSON_KEY_CATALOG) - 1);
3567+
pos += sizeof(JSON_KEY_CATALOG) - 1;
3568+
body[pos ++] = '"';
3569+
memcpy(body + pos, dbc->catalog.c.str, dbc->catalog.c.cnt);
3570+
pos += dbc->catalog.c.cnt;
3571+
body[pos ++] = '"';
3572+
}
35383573
/* "version": ... */
35393574
memcpy(body + pos, JSON_KEY_VERSION, sizeof(JSON_KEY_VERSION) - 1);
35403575
pos += sizeof(JSON_KEY_VERSION) - 1;

driver/queries.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,15 @@ SQLRETURN EsSQLRowCount(_In_ SQLHSTMT StatementHandle, _Out_ SQLLEN *RowCount);
142142
#define REQ_KEY_MULTIVAL "field_multi_value_leniency"
143143
#define REQ_KEY_IDX_FROZEN "index_include_frozen"
144144
#define REQ_KEY_TIMEZONE "time_zone"
145+
#define REQ_KEY_CATALOG "catalog"
145146
#define REQ_KEY_BINARY_FMT "binary_format"
147+
148+
#define REST_REQ_KEY_COUNT 13 /* "query" / "cursor" count as one */
149+
146150
/* keys for the "params" argument */
147151
#define REQ_KEY_PARAM_TYPE "type"
148152
#define REQ_KEY_PARAM_VAL "value"
149153

150-
#define REST_REQ_KEY_COUNT 11 /* "query" or "cursor" */
151154

152155
#ifdef _WIN64
153156
# define REQ_VAL_CLT_ID "odbc64"
@@ -172,6 +175,7 @@ SQLRETURN EsSQLRowCount(_In_ SQLHSTMT StatementHandle, _Out_ SQLLEN *RowCount);
172175
#define JSON_KEY_MULTIVAL ", \"" REQ_KEY_MULTIVAL "\": " /* n-th */
173176
#define JSON_KEY_IDX_FROZEN ", \"" REQ_KEY_IDX_FROZEN "\": " /* n-th */
174177
#define JSON_KEY_TIMEZONE ", \"" REQ_KEY_TIMEZONE "\": " /* n-th key */
178+
#define JSON_KEY_CATALOG ", \"" REQ_KEY_CATALOG "\": " /* n-th key */
175179
#define JSON_KEY_BINARY_FMT ", \"" REQ_KEY_BINARY_FMT "\": " /* n-th key */
176180

177181
#define JSON_VAL_TIMEZONE_Z "\"" REQ_VAL_TIMEZONE_Z "\""

0 commit comments

Comments
 (0)