driver/handles.h (402 lines of code) (raw):

/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ #ifndef __HANDLES_H__ #define __HANDLES_H__ #include <curl/curl.h> #include <ujdecode.h> #include "error.h" #include "defs.h" #include "log.h" #include "tinycbor.h" /* forward declarations */ struct struct_env; struct struct_dbc; struct struct_stmt; struct struct_desc; /* * header structure, embedded in all API handles * Note: must remain the first declared member. */ typedef struct struct_hheader { /* handle header */ /* SQL_HANDLE_ENV / _DBC / _STMT / _DESC */ SQLSMALLINT type; /* diagnostic/state keeping */ esodbc_diag_st diag; /* ODBC API multi-threading exclusive lock */ esodbc_mutex_lt mutex; /* back reference to "parent" structure (in type hierarchy) */ union { struct struct_env *env; struct struct_dbc *dbc; struct struct_stmt *stmt; void *parent; }; /* logging helpers */ wstr_st typew; /* ENV/DBC/STMT/DESC as w-string */ esodbc_filelog_st *log; /* logger: owned by a DBC; ENV uses global */ } esodbc_hhdr_st; /* * https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/environment-handles : * """ * The environment's state * The current environment-level diagnostics * The handles of connections currently allocated on the environment * The current settings of each environment attribute * """ */ typedef struct struct_env { esodbc_hhdr_st hdr; SQLUINTEGER version; /* versions defined as UL (see SQL_OV_ODBC3) */ } esodbc_env_st; /* meta data types (same for both SQL_C_<t> and SQL_<t> types) */ typedef enum { METATYPE_UNKNOWN = 0, METATYPE_EXACT_NUMERIC, METATYPE_FLOAT_NUMERIC, METATYPE_STRING, METATYPE_BIN, METATYPE_DATE_TIME, METATYPE_INTERVAL_WSEC, METATYPE_INTERVAL_WOSEC, METATYPE_BIT, METATYPE_UID, METATYPE_MAX // SQL_C_DEFAULT, ESODBC_SQL_NULL } esodbc_metatype_et; /* Structure mapping one ES/SQL data type. */ typedef struct elasticsearch_type { /* fields of one row returned in response to 'SYS TYPES' query */ wstr_st type_name; SQLSMALLINT data_type; /* maps to rec's .concise_type member */ SQLINTEGER column_size; wstr_st literal_prefix; wstr_st literal_suffix; wstr_st create_params; SQLSMALLINT nullable; SQLSMALLINT case_sensitive; SQLSMALLINT searchable; SQLSMALLINT unsigned_attribute; SQLSMALLINT fixed_prec_scale; SQLSMALLINT auto_unique_value; wstr_st local_type_name; SQLSMALLINT minimum_scale; SQLSMALLINT maximum_scale; SQLSMALLINT sql_data_type; /* :-> rec's .type member */ SQLSMALLINT sql_datetime_sub; /* :-> rec's .datetime_interval_code */ SQLINTEGER num_prec_radix; SQLSMALLINT interval_precision; /* number of SYS TYPES result columns mapped over the above members */ #define ESODBC_TYPES_COLUMNS 19 /* SQL C type driver mapping of ES' data_type; this is derived from * .type_name, rathern than .(sql_)data_type (sice the name is the * "unique" key and sole identifier in general queries results). */ SQLSMALLINT c_concise_type; /* There should be no need for a supplemental 'sql_c_type': if * rec.datetime_interval_code == 0, then this member would equal the * concise one (above); else, rec.type will contain the right value * already (i.e. they'd be the same for SQL and SQL C data types). */ /* helper member, to characterize the type */ esodbc_metatype_et meta_type; SQLLEN display_size; /* convenience C-string conversion of type_name */ cstr_st type_name_c; } esodbc_estype_st; /* * https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/connection-handles : * """ * The state of the connection * The current connection-level diagnostics * The handles of statements and descriptors currently allocated on the * connection * The current settings of each connection attribute * """ */ typedef struct struct_dbc { esodbc_hhdr_st hdr; wstr_st dsn; /* data source name SQLGetInfo(SQL_DATA_SOURCE_NAME) */ wstr_st server; /* ~ name; requested with SQLGetInfo(SQL_SERVER_NAME) */ wstr_st srv_ver; /* server version: SQLGetInfo(SQL_DBMS_VER) */ cstr_st proxy_url; cstr_st proxy_uid; cstr_st proxy_pwd; cstr_st url; /* SQL URL (posts) */ cstr_st close_url; /* SQL close URL (posts) */ cstr_st root_url; /* root URL (gets) */ enum { ESODBC_SEC_NONE = 0, ESODBC_SEC_USE_SSL, /* 1 */ ESODBC_SEC_CHECK_CA, ESODBC_SEC_CHECK_HOST, ESODBC_SEC_CHECK_REVOKE, /* 4 */ ESODBC_SEC_MAX /* meta */ } secure; cstr_st ca_path; cstr_st uid; union { cstr_st pwd; /* when dbc configuring, pwd is only set if uid is */ cstr_st api_key; }; SQLUINTEGER timeout; BOOL follow; struct { size_t max; /* max fetch size */ char *str; /* as string */ char slen; /* string's length (w/o terminator) */ } fetch; BOOL pack_json; /* should JSON be used in REST bodies? (vs. CBOR) */ enum { ESODBC_CMPSS_OFF = 0, ESODBC_CMPSS_ON, ESODBC_CMPSS_AUTO, } compression; BOOL apply_tz; /* should the times be converted from UTC to local TZ? */ struct { wstr_st w; /* NB: w.str and c.str are co-allocated */ cstr_st c; } catalog; /* current ~ */ BOOL early_exec; /* should prepared, non-param queries be exec'd early? */ enum { ESODBC_FLTS_DEFAULT = 0, ESODBC_FLTS_SCIENTIFIC, ESODBC_FLTS_AUTO, } sci_floats; /* floats printing on conversion */ BOOL mfield_lenient; /* 'field_multi_value_leniency' request param */ BOOL idx_inc_frozen; /* 'field_multi_value_leniency' request param */ BOOL auto_esc_pva; /* auto-escape PVA args in catalog functions */ esodbc_estype_st *es_types; /* array with ES types */ SQLULEN no_types; /* number of types in array */ /* maximum precision/length of types using same SQL data type ID */ esodbc_estype_st *max_varchar_type; /* pointer to TEXT type */ esodbc_estype_st *max_float_type; /* pointer to DOUBLE type */ /* configuration imposed lengths for the ES/SQL string types */ SQLUINTEGER varchar_limit; wstr_st varchar_limit_str; /* convenience w-string of varchar limit */ esodbc_estype_st *ulong; /* the UNSIGNED_LONG type in es_types array */ esodbc_estype_st *lgst_name; /* type with longest name */ CURL *curl; /* cURL handle */ CURLcode curl_err; char curl_err_buff[CURL_ERROR_SIZE]; enum { ESODBC_CURL_NONE = 0, /* init value */ ESODBC_CURL_QUERY, ESODBC_CURL_CLOSE, ESODBC_CURL_ROOT } crr_url; /* curl is set to 'url', 'close_url' or 'root_url' (above) */ char *abuff; /* buffer holding the answer */ size_t alen; /* size of abuff */ size_t apos; /* current write position in the abuff */ size_t amax; /* maximum length (bytes) that abuff can grow to */ esodbc_mutex_lt curl_mux; /* mutex for above 'networking' members */ struct curl_slist *curl_hdrs; /* HTTP headers list */ /* window handler */ HWND hwin; /* options */ SQLULEN metadata_id; // default: SQL_FALSE } esodbc_dbc_st; typedef struct desc_rec { /* back ref to owning descriptor */ struct struct_desc *desc; /* helper member, to characterize the type */ esodbc_metatype_et meta_type; /* pointer to the ES/SQL type in DBC array * need to be set for records in IxD descriptors */ esodbc_estype_st *es_type; /* IRD reference copy of respective protocol value */ union { UJObject json; CborValue cbor; } i_val; /* * record fields */ /* following record fields have been moved into es_type: * display_size, literal_prefix, literal_suffix, local_type_name, * type_name, auto_unique_value, case_sensitive, fixed_prec_scale, * nullable, searchable, usigned */ /* record types (SQL_<t> for IxD, or SQL_C_<t> for AxD) */ SQLSMALLINT concise_type; SQLSMALLINT type; SQLSMALLINT datetime_interval_code; SQLPOINTER data_ptr; /* array, if .array_size > 1 */ wstr_st base_column_name; /* read-only */ wstr_st base_table_name; /* r/o */ wstr_st catalog_name; /* r/o */ wstr_st label; /* r/o */ //alias? wstr_st name; wstr_st schema_name; /* r/o */ wstr_st table_name; /* r/o */ SQLLEN *indicator_ptr; /* array, if .array_size > 1 */ SQLLEN *octet_length_ptr; /* array, if .array_size > 1 */ SQLLEN octet_length; SQLULEN length; SQLINTEGER datetime_interval_precision; /*TODO: -> es_type? */ SQLINTEGER num_prec_radix; /*TODO: -> es_type? */ SQLSMALLINT parameter_type; /* "number of digits for an exact numeric type, the number of bits in the * mantissa (binary precision) for an approximate numeric type, or the * numbers of digits in the fractional seconds component "*/ /* Intervals: "the number of decimal digits allowed in the fractional part * of the seconds value" */ SQLSMALLINT precision; SQLSMALLINT rowver; SQLSMALLINT scale; SQLSMALLINT unnamed; SQLSMALLINT updatable; } esodbc_rec_st; typedef enum { DESC_TYPE_ANON, /* SQLAllocHandle()'ed */ DESC_TYPE_ARD, DESC_TYPE_IRD, DESC_TYPE_APD, DESC_TYPE_IPD, } desc_type_et; /* type is for an application descriptor */ #define DESC_TYPE_IS_APPLICATION(_dtype) \ (_dtype == DESC_TYPE_ARD || _dtype == DESC_TYPE_APD) /* type is for an implementation descriptor */ #define DESC_TYPE_IS_IMPLEMENTATION(_dtype) \ (_dtype == DESC_TYPE_IRD || _dtype == DESC_TYPE_IPD) /* type is for a record descriptor */ #define DESC_TYPE_IS_RECORD(_dtype) \ (_dtype == DESC_TYPE_ARD || _dtype == DESC_TYPE_IRD) /* type is for a parameter descriptor */ #define DESC_TYPE_IS_PARAMETER(_dtype) \ (_dtype == DESC_TYPE_APD || _dtype == DESC_TYPE_IPD) typedef struct struct_desc { esodbc_hhdr_st hdr; desc_type_et type; /* APD, IPD, ARD, IRD */ /* header fields */ /* descriptor was allocated automatically by the driver or explicitly by * the application */ SQLSMALLINT alloc_type; /* ARDs: the number of rows returned by each call to SQLFetch* = the rowset * APDs: the number of values for each parameter. */ SQLULEN array_size; SQLUSMALLINT *array_status_ptr; SQLLEN *bind_offset_ptr; SQLINTEGER bind_type; /* row/col */ SQLSMALLINT count; /* of recs */ SQLULEN *rows_processed_ptr; /* /header fields */ /* array of records of .count cardinality */ esodbc_rec_st *recs; } esodbc_desc_st; /* the ES/SQL type must be set for implementation descriptor records */ #define ASSERT_IXD_HAS_ES_TYPE(_rec) \ assert(DESC_TYPE_IS_IMPLEMENTATION(_rec->desc->type) && _rec->es_type) struct resultset_cbor { cstr_st curs; /* ES'es cursor */ BOOL curs_allocd; /* curs.str is allocated (and reassembled) */ CborValue rows_obj; /* top object rows container (EsSQLRowCount()) */ CborValue rows_iter; /* iterator over received rows; refs req's body */ wstr_st cols_buff /* columns descriptions; refs allocated chunk */; }; struct resultset_json { wstr_st curs; /* ES'es cursor; refs UJSON4C 'state' */ void *state; /* top UJSON decoder state */ UJObject rows_obj; /* top object rows container (EsSQLRowCount()) */ void *rows_iter; /* UJSON iterator with the rows in result set */ UJObject row_array; /* UJSON object for current row */ }; typedef struct struct_resultset { long code; /* HTTP code of last response */ cstr_st body; /* HTTP body of last answer to a statement */ BOOL pack_json; /* the server could send a JSON answer for a CBOR req. */ union { struct resultset_cbor cbor; struct resultset_json json; } pack; size_t vrows; /* (count of) visited rows in current result set */ } resultset_st; #define STMT_HAS_CURSOR(_stmt) \ ((_stmt)->rset.pack_json ? \ (_stmt)->rset.pack.json.curs.cnt : \ (_stmt)->rset.pack.cbor.curs.cnt) /* * "The fields of an IRD have a default value only after the statement has * been prepared or executed and the IRD has been populated, not when the * statement handle or descriptor has been allocated. Until the IRD has been * populated, any attempt to gain access to a field of an IRD will return an * error." */ typedef struct struct_stmt { esodbc_hhdr_st hdr; /* cache UTF8 JSON serialized SQL: can be (re)used with varying params */ cstr_st u8sql; /* pointers to the current descriptors */ esodbc_desc_st *ard; esodbc_desc_st *ird; esodbc_desc_st *apd; esodbc_desc_st *ipd; /* initial implicit descriptors allocated with the statement */ esodbc_desc_st i_ard; esodbc_desc_st i_ird; esodbc_desc_st i_apd; esodbc_desc_st i_ipd; /* options */ SQLULEN bookmarks; //default: SQL_UB_OFF SQLULEN metadata_id; // default: copied from connection /* "the maximum amount of data that the driver returns from a character or * binary column" */ SQLULEN max_length; /* "number of seconds to wait for an SQL statement to execute before * returning to the application." */ SQLULEN query_timeout; /* [current] result set (= one page from ES/SQL; can contain a cursor) */ resultset_st rset; /* count of result sets fetched */ size_t nset; /* total visited rows (SUM(resultset.vrows)) <=> SQL_ATTR_ROW_NUMBER */ size_t tv_rows; /* SQL data types conversion to SQL C compatibility (IRD.SQL -> ARD.C) */ enum { CONVERSION_VIOLATION = -2, /* specs disallowed */ CONVERSION_UNSUPPORTED, /* ES/driver not supported */ CONVERSION_UNCHECKED, /* 0 */ CONVERSION_SUPPORTED, CONVERSION_SKIPPED, /* used with driver's meta queries */ } sql2c_conversion; /* early execution */ BOOL early_executed; /* SQLGetData state members */ SQLINTEGER gd_col; /* current column to get from, if positive */ SQLINTEGER gd_ctype; /* current target type */ SQLLEN gd_offt; /* position in source buffer */ } esodbc_stmt_st; /* reset statment's result set count and number of visited rows */ #define STMT_ROW_CNT_RESET(_stmt) \ do { \ (_stmt)->nset = 0; \ (_stmt)->tv_rows = 0; \ } while (0) /* SQLGetData() state reset */ #define STMT_GD_RESET(_stmt) \ do { \ _stmt->gd_col = -1; \ _stmt->gd_ctype = 0; \ _stmt->gd_offt = 0; \ } while (0) /* is currently a SQLGetData() call being serviced? */ #define STMT_GD_CALLING(_stmt) (0 <= _stmt->gd_col) SQLRETURN update_rec_count(esodbc_desc_st *desc, SQLSMALLINT new_count); SQLSMALLINT count_bound(esodbc_desc_st *desc); esodbc_rec_st *get_record(esodbc_desc_st *desc, SQLSMALLINT rec_no, BOOL grow); void dump_record(esodbc_rec_st *rec); void init_dbc(esodbc_dbc_st *dbc, SQLHANDLE InputHandle); esodbc_desc_st *getdata_set_ard(esodbc_stmt_st *stmt, esodbc_desc_st *gd_ard, SQLUSMALLINT colno, esodbc_rec_st *recs, SQLUSMALLINT count); void getdata_reset_ard(esodbc_stmt_st *stmt, esodbc_desc_st *ard, SQLUSMALLINT colno, esodbc_rec_st *recs, SQLUSMALLINT count); void concise_to_type_code(SQLSMALLINT concise, SQLSMALLINT *type, SQLSMALLINT *code); esodbc_metatype_et concise_to_meta(SQLSMALLINT concise_type, desc_type_et desc_type); SQLRETURN EsSQLAllocHandle(SQLSMALLINT HandleType, SQLHANDLE InputHandle, _Out_ SQLHANDLE *OutputHandle); SQLRETURN EsSQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle); SQLRETURN EsSQLFreeStmt(SQLHSTMT StatementHandle, SQLUSMALLINT Option); SQLRETURN EsSQLSetEnvAttr(SQLHENV EnvironmentHandle, SQLINTEGER Attribute, _In_reads_bytes_opt_(StringLength) SQLPOINTER Value, SQLINTEGER StringLength); SQLRETURN EsSQLGetEnvAttr(SQLHENV EnvironmentHandle, SQLINTEGER Attribute, _Out_writes_(_Inexpressible_(BufferLength)) SQLPOINTER Value, SQLINTEGER BufferLength, _Out_opt_ SQLINTEGER *StringLength); SQLRETURN EsSQLSetStmtAttrW( SQLHSTMT StatementHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER BufferLength); SQLRETURN EsSQLGetStmtAttrW( SQLHSTMT StatementHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER BufferLength, SQLINTEGER *StringLengthPtr); SQLRETURN EsSQLGetDescFieldW( SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, SQLSMALLINT FieldIdentifier, _Out_writes_opt_(_Inexpressible_(BufferLength)) SQLPOINTER ValuePtr, SQLINTEGER BufferLength, SQLINTEGER *StringLengthPtr); SQLRETURN EsSQLGetDescRecW( SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, _Out_writes_opt_(BufferLength) SQLWCHAR *Name, _Out_opt_ SQLSMALLINT BufferLength, _Out_opt_ SQLSMALLINT *StringLengthPtr, _Out_opt_ SQLSMALLINT *TypePtr, _Out_opt_ SQLSMALLINT *SubTypePtr, _Out_opt_ SQLLEN *LengthPtr, _Out_opt_ SQLSMALLINT *PrecisionPtr, _Out_opt_ SQLSMALLINT *ScalePtr, _Out_opt_ SQLSMALLINT *NullablePtr); /* use with RecNumber for header fields */ #define NO_REC_NR -1 SQLRETURN EsSQLSetDescFieldW( SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, SQLSMALLINT FieldIdentifier, SQLPOINTER Value, SQLINTEGER BufferLength); SQLRETURN EsSQLSetDescRec( SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, SQLSMALLINT Type, SQLSMALLINT SubType, SQLLEN Length, SQLSMALLINT Precision, SQLSMALLINT Scale, _Inout_updates_bytes_opt_(Length) SQLPOINTER Data, _Inout_opt_ SQLLEN *StringLength, _Inout_opt_ SQLLEN *Indicator); /* * Macros to convert ODBC API generic handles into implementation types. */ #define ENVH(_h) ((esodbc_env_st *)(_h)) #define DBCH(_h) ((esodbc_dbc_st *)(_h)) #define STMH(_h) ((esodbc_stmt_st *)(_h)) #define DSCH(_h) ((esodbc_desc_st *)(_h)) /* this will only work if member stays first in handles (see struct decl). */ #define HDRH(_h) ((esodbc_hhdr_st *)(_h)) /* * Locking macros for ODBC API calls. */ #define HND_LOCK(_h) ESODBC_MUX_LOCK(&HDRH(_h)->mutex) #define HND_TRYLOCK(_h) ESODBC_MUX_TRYLOCK(&HDRH(_h)->mutex) #define HND_UNLOCK(_h) ESODBC_MUX_UNLOCK(&HDRH(_h)->mutex) /* post state into the diagnostic and return state's return code */ #define RET_HDIAG(_hp/*handle ptr*/, _s/*tate*/, _t/*char text*/, _c/*ode*/) \ return post_diagnostic(_hp, _s, MK_WPTR(_t), _c) /* similar to RET_HDIAG, but only post the state */ #define RET_HDIAGS(_hp/*handle ptr*/, _s/*tate*/) \ return post_diagnostic(_hp, _s, NULL, 0) /* copy the diagnostics from one handle to the other */ #define HDIAG_COPY(_s, _d) (_d)->hdr.diag = (_s)->hdr.diag /* set a diagnostic to a(ny) handle */ #define SET_HDIAG(_hp/*handle ptr*/, _s/*tate*/, _t/*char text*/, _c/*ode*/) \ post_diagnostic(_hp, _s, MK_WPTR(_t), _c) /* reset handle diagnostic state */ #define RESET_HDIAG(_hp/*handle ptr*/) \ init_diagnostic(&HDRH(dbc)->diag) /* return the code associated with the given state (and debug-log) */ #define RET_STATE(_s) \ do { \ assert(SQL_STATE_00000 <= _s && _s < SQL_STATE_MAX); \ return esodbc_errors[_s].retcode; \ } while (0) #define STMT_HAS_RESULTSET(stmt) ((stmt)->rset.body.str != NULL) #define STMT_FORCE_NODATA(stmt) (stmt)->rset.body.cnt = (size_t)-1 #define STMT_NODATA_FORCED(stmt) ((stmt)->rset.body.cnt == (size_t)-1) /* "An application can unbind the data buffer for a column but still have a * length/indicator buffer bound for the column" */ #define REC_IS_BOUND(rec) ( \ (rec)->data_ptr != NULL || \ (rec)->indicator_ptr != NULL || \ (rec)->octet_length_ptr != NULL) /* * Logging with handle */ #define LOGH(hnd, lvl, werrn, fmt, ...) \ _LOG(HDRH(hnd)->log, lvl, werrn, "[" LWPDL "@0x%p] " fmt, \ LWSTR(&HDRH(hnd)->typew), hnd, __VA_ARGS__) #define ERRNH(hnd, fmt, ...) LOGH(hnd, LOG_LEVEL_ERR, 1, fmt, __VA_ARGS__) #define ERRH(hnd, fmt, ...) LOGH(hnd, LOG_LEVEL_ERR, 0, fmt, __VA_ARGS__) #define WARNH(hnd, fmt, ...) LOGH(hnd, LOG_LEVEL_WARN, 0, fmt, __VA_ARGS__) #define INFOH(hnd, fmt, ...) LOGH(hnd, LOG_LEVEL_INFO, 0, fmt, __VA_ARGS__) #define DBGH(hnd, fmt, ...) LOGH(hnd, LOG_LEVEL_DBG, 0, fmt, __VA_ARGS__) #define BUGH(hnd, fmt, ...) \ do { \ ERRH(hnd, "[BUG] " fmt, __VA_ARGS__); \ assert(0); \ } while (0) #endif /* __HANDLES_H__ */ /* vim: set noet fenc=utf-8 ff=dos sts=0 sw=4 ts=4 : */