master
c 675 lines 20.3 KB
Raw
1 #include "bridge.h"
2 #include <sql.h>
3 #include <sqlext.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <stdio.h>
7
8 #define ODBC_DEFAULT_BUFFER_SIZE 4096
9 #define ODBC_MAX_BUFFER_SIZE (16 * 1024 * 1024)
10
11 // Connection structure with optimizations
12 typedef struct {
13 SQLHENV env;
14 SQLHDBC dbc;
15 SQLHSTMT stmt;
16 int connected;
17 bool stmt_prepared;
18 bool cursor_open;
19 char last_error[SQL_MAX_MESSAGE_LENGTH];
20 char last_sqlstate[6];
21
22 // Optimization: reuse buffers
23 SQLLEN* indicators;
24 void** column_buffers;
25 int column_count;
26 size_t* buffer_sizes;
27 } odbc_connection;
28
29 // Helper function to extract ODBC errors
30 static void extract_error(const char* fn, SQLHANDLE handle, SQLSMALLINT type,
31 char* error_buf, int buf_size, char* sqlstate) {
32 SQLINTEGER i = 0;
33 SQLINTEGER native;
34 SQLCHAR state[7];
35 SQLCHAR text[SQL_MAX_MESSAGE_LENGTH];
36 SQLSMALLINT len;
37 SQLRETURN ret;
38
39 error_buf[0] = '\0';
40 if (sqlstate) sqlstate[0] = '\0';
41
42 snprintf(error_buf, buf_size, "%s: ", fn);
43 int offset = strlen(error_buf);
44
45 do {
46 ret = SQLGetDiagRec(type, handle, ++i, state, &native, text,
47 sizeof(text), &len);
48 if (SQL_SUCCEEDED(ret)) {
49 if (i == 1 && sqlstate) {
50 strcpy(sqlstate, (char*)state);
51 }
52 snprintf(error_buf + offset, buf_size - offset,
53 "%s:%ld:%ld:%s ", state, (long)i, (long)native, text);
54 offset = strlen(error_buf);
55 if (offset >= buf_size - 1) break;
56 }
57 } while (ret == SQL_SUCCESS);
58 }
59
60 // Convert SQL type to our type enum
61 static odbc_data_type_t sql_type_to_odbc_type(SQLSMALLINT sql_type) {
62 switch (sql_type) {
63 case SQL_SMALLINT:
64 case SQL_INTEGER:
65 case SQL_BIGINT:
66 case SQL_TINYINT:
67 return ODBC_TYPE_INT64;
68
69 case SQL_FLOAT:
70 case SQL_REAL:
71 case SQL_DOUBLE:
72 case SQL_DECIMAL:
73 case SQL_NUMERIC:
74 return ODBC_TYPE_DOUBLE;
75
76 case SQL_CHAR:
77 case SQL_VARCHAR:
78 case SQL_LONGVARCHAR:
79 case SQL_WCHAR:
80 case SQL_WVARCHAR:
81 case SQL_WLONGVARCHAR:
82 return ODBC_TYPE_STRING;
83
84 case SQL_BINARY:
85 case SQL_VARBINARY:
86 case SQL_LONGVARBINARY:
87 return ODBC_TYPE_BINARY;
88
89 default:
90 return ODBC_TYPE_STRING; // Default to string
91 }
92 }
93
94 // Connect to database
95 odbc_conn_t odbc_connect(const char* dsn, char* error_buf, int error_buf_size) {
96 odbc_connection* conn = (odbc_connection*)calloc(1, sizeof(odbc_connection));
97 if (!conn) {
98 snprintf(error_buf, error_buf_size, "Memory allocation failed");
99 return NULL;
100 }
101
102 SQLRETURN ret;
103
104 // Allocate environment handle
105 ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &conn->env);
106 if (!SQL_SUCCEEDED(ret)) {
107 snprintf(error_buf, error_buf_size, "Failed to allocate environment handle");
108 free(conn);
109 return NULL;
110 }
111
112 // Set ODBC version
113 ret = SQLSetEnvAttr(conn->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
114 if (!SQL_SUCCEEDED(ret)) {
115 extract_error("SQLSetEnvAttr", conn->env, SQL_HANDLE_ENV, error_buf, error_buf_size, NULL);
116 SQLFreeHandle(SQL_HANDLE_ENV, conn->env);
117 free(conn);
118 return NULL;
119 }
120
121 // Allocate connection handle
122 ret = SQLAllocHandle(SQL_HANDLE_DBC, conn->env, &conn->dbc);
123 if (!SQL_SUCCEEDED(ret)) {
124 extract_error("SQLAllocHandle(DBC)", conn->env, SQL_HANDLE_ENV, error_buf, error_buf_size, NULL);
125 SQLFreeHandle(SQL_HANDLE_ENV, conn->env);
126 free(conn);
127 return NULL;
128 }
129
130 // Set connection attributes for better performance
131 SQLSetConnectAttr(conn->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0);
132
133 // Connect
134 SQLCHAR outstr[1024];
135 SQLSMALLINT outstrlen;
136 ret = SQLDriverConnect(conn->dbc, NULL, (SQLCHAR*)dsn, SQL_NTS,
137 outstr, sizeof(outstr), &outstrlen,
138 SQL_DRIVER_NOPROMPT);
139
140 if (!SQL_SUCCEEDED(ret)) {
141 extract_error("SQLDriverConnect", conn->dbc, SQL_HANDLE_DBC, error_buf, error_buf_size, NULL);
142 SQLFreeHandle(SQL_HANDLE_DBC, conn->dbc);
143 SQLFreeHandle(SQL_HANDLE_ENV, conn->env);
144 free(conn);
145 return NULL;
146 }
147
148 // Pre-allocate statement handle for reuse
149 ret = SQLAllocHandle(SQL_HANDLE_STMT, conn->dbc, &conn->stmt);
150 if (!SQL_SUCCEEDED(ret)) {
151 extract_error("SQLAllocHandle(STMT)", conn->dbc, SQL_HANDLE_DBC, error_buf, error_buf_size, NULL);
152 SQLDisconnect(conn->dbc);
153 SQLFreeHandle(SQL_HANDLE_DBC, conn->dbc);
154 SQLFreeHandle(SQL_HANDLE_ENV, conn->env);
155 free(conn);
156 return NULL;
157 }
158
159 conn->connected = 1;
160 return (odbc_conn_t)conn;
161 }
162
163 // Prepare a statement for execution
164 int odbc_prepare(odbc_conn_t conn_handle, const char* query, char* error_buf, int error_buf_size) {
165 if (!conn_handle) return ODBC_ERROR_CONNECT;
166
167 odbc_connection* conn = (odbc_connection*)conn_handle;
168
169 // Reset statement if needed
170 if (conn->stmt_prepared || conn->cursor_open) {
171 odbc_reset_statement(conn_handle);
172 }
173
174 SQLRETURN ret = SQLPrepare(conn->stmt, (SQLCHAR*)query, SQL_NTS);
175 if (!SQL_SUCCEEDED(ret)) {
176 extract_error("SQLPrepare", conn->stmt, SQL_HANDLE_STMT, error_buf, error_buf_size, conn->last_sqlstate);
177 return ODBC_ERROR_QUERY;
178 }
179
180 conn->stmt_prepared = true;
181 return ODBC_SUCCESS;
182 }
183
184 // Execute a prepared statement
185 int odbc_execute(odbc_conn_t conn_handle, char* error_buf, int error_buf_size) {
186 if (!conn_handle) return ODBC_ERROR_CONNECT;
187
188 odbc_connection* conn = (odbc_connection*)conn_handle;
189
190 if (!conn->stmt_prepared) {
191 snprintf(error_buf, error_buf_size, "No statement prepared");
192 return ODBC_ERROR_QUERY;
193 }
194
195 SQLRETURN ret = SQLExecute(conn->stmt);
196 if (!SQL_SUCCEEDED(ret)) {
197 extract_error("SQLExecute", conn->stmt, SQL_HANDLE_STMT, error_buf, error_buf_size, conn->last_sqlstate);
198 // Reset on error to prevent SQL0519
199 odbc_reset_statement(conn_handle);
200 return ODBC_ERROR_QUERY;
201 }
202
203 conn->cursor_open = true;
204 return ODBC_SUCCESS;
205 }
206
207 // Execute query directly (no prepare)
208 int odbc_execute_direct(odbc_conn_t conn_handle, const char* query, char* error_buf, int error_buf_size) {
209 if (!conn_handle) return ODBC_ERROR_CONNECT;
210
211 odbc_connection* conn = (odbc_connection*)conn_handle;
212
213 // Reset statement if needed
214 if (conn->stmt_prepared || conn->cursor_open) {
215 odbc_reset_statement(conn_handle);
216 }
217
218 SQLRETURN ret = SQLExecDirect(conn->stmt, (SQLCHAR*)query, SQL_NTS);
219 if (!SQL_SUCCEEDED(ret)) {
220 extract_error("SQLExecDirect", conn->stmt, SQL_HANDLE_STMT, error_buf, error_buf_size, conn->last_sqlstate);
221 // Reset on error to prevent SQL0519
222 odbc_reset_statement(conn_handle);
223 return ODBC_ERROR_QUERY;
224 }
225
226 conn->cursor_open = true;
227 return ODBC_SUCCESS;
228 }
229
230 // Get row count - can be negative on AS400!
231 int64_t odbc_get_row_count(odbc_conn_t conn_handle) {
232 if (!conn_handle) return 0;
233
234 odbc_connection* conn = (odbc_connection*)conn_handle;
235 SQLLEN row_count = 0;
236
237 SQLRETURN ret = SQLRowCount(conn->stmt, &row_count);
238 if (!SQL_SUCCEEDED(ret)) {
239 return 0;
240 }
241
242 // AS400 can return negative row counts!
243 return (int64_t)row_count;
244 }
245
246 // Get column count
247 int odbc_get_column_count(odbc_conn_t conn_handle) {
248 if (!conn_handle) return 0;
249
250 odbc_connection* conn = (odbc_connection*)conn_handle;
251 SQLSMALLINT column_count = 0;
252
253 SQLRETURN ret = SQLNumResultCols(conn->stmt, &column_count);
254 if (!SQL_SUCCEEDED(ret)) {
255 return 0;
256 }
257
258 // Allocate column buffers if needed
259 if (column_count > 0 && column_count != conn->column_count) {
260 // Free old buffers
261 if (conn->column_buffers) {
262 for (int i = 0; i < conn->column_count; i++) {
263 free(conn->column_buffers[i]);
264 }
265 free(conn->column_buffers);
266 free(conn->indicators);
267 free(conn->buffer_sizes);
268 }
269
270 // Allocate new buffers
271 conn->column_count = column_count;
272 conn->column_buffers = calloc(column_count, sizeof(void*));
273 conn->indicators = calloc(column_count, sizeof(SQLLEN));
274 conn->buffer_sizes = calloc(column_count, sizeof(size_t));
275 }
276
277 return column_count;
278 }
279
280 // Get column metadata
281 int odbc_get_column_info(odbc_conn_t conn_handle, int column_index, odbc_column_info_t* info) {
282 if (!conn_handle || !info) return ODBC_ERROR;
283
284 odbc_connection* conn = (odbc_connection*)conn_handle;
285
286 SQLSMALLINT name_length;
287 SQLSMALLINT data_type;
288 SQLULEN column_size;
289 SQLSMALLINT decimal_digits;
290 SQLSMALLINT nullable;
291
292 SQLRETURN ret = SQLDescribeCol(conn->stmt, column_index + 1,
293 (SQLCHAR*)info->name, sizeof(info->name),
294 &name_length, &data_type, &column_size,
295 &decimal_digits, &nullable);
296
297 if (!SQL_SUCCEEDED(ret)) {
298 return ODBC_ERROR;
299 }
300
301 info->sql_type = data_type;
302 info->type = sql_type_to_odbc_type(data_type);
303 info->size = column_size;
304 info->scale = decimal_digits;
305 info->nullable = (nullable == SQL_NULLABLE);
306
307 // For numeric types, get precision
308 if (info->type == ODBC_TYPE_DOUBLE) {
309 info->precision = column_size;
310 }
311
312 return ODBC_SUCCESS;
313 }
314
315 // Fetch next row
316 int odbc_fetch_row(odbc_conn_t conn_handle) {
317 if (!conn_handle) return ODBC_ERROR;
318
319 odbc_connection* conn = (odbc_connection*)conn_handle;
320 if (!conn->cursor_open) return ODBC_ERROR;
321
322 SQLRETURN ret = SQLFetch(conn->stmt);
323 if (ret == SQL_NO_DATA) {
324 return ODBC_NO_DATA;
325 } else if (SQL_SUCCEEDED(ret)) {
326 return ODBC_SUCCESS;
327 } else {
328 extract_error("SQLFetch", conn->stmt, SQL_HANDLE_STMT,
329 conn->last_error, sizeof(conn->last_error), NULL);
330 return ODBC_ERROR_FETCH;
331 }
332 }
333
334 // Get value with proper type handling
335 int odbc_get_value(odbc_conn_t conn_handle, int column_index, odbc_value_t* value) {
336 if (!conn_handle || !value) return ODBC_ERROR;
337
338 odbc_connection* conn = (odbc_connection*)conn_handle;
339
340 // First get column info to know the type
341 odbc_column_info_t info;
342 if (odbc_get_column_info(conn_handle, column_index, &info) != ODBC_SUCCESS) {
343 return ODBC_ERROR;
344 }
345
346 SQLLEN indicator;
347 SQLRETURN ret;
348
349 // Handle different data types
350 switch (info.type) {
351 case ODBC_TYPE_INT64: {
352 SQLBIGINT int_val = 0;
353 ret = SQLGetData(conn->stmt, column_index + 1, SQL_C_SBIGINT,
354 &int_val, sizeof(int_val), &indicator);
355 if (SQL_SUCCEEDED(ret)) {
356 if (indicator == SQL_NULL_DATA) {
357 value->is_null = true;
358 } else {
359 value->type = ODBC_TYPE_INT64;
360 value->is_null = false;
361 value->data.int_val = int_val;
362 }
363 return ODBC_SUCCESS;
364 }
365 break;
366 }
367
368 case ODBC_TYPE_DOUBLE: {
369 double double_val = 0.0;
370 ret = SQLGetData(conn->stmt, column_index + 1, SQL_C_DOUBLE,
371 &double_val, sizeof(double_val), &indicator);
372 if (SQL_SUCCEEDED(ret)) {
373 if (indicator == SQL_NULL_DATA) {
374 value->is_null = true;
375 } else {
376 value->type = ODBC_TYPE_DOUBLE;
377 value->is_null = false;
378 value->data.double_val = double_val;
379 }
380 return ODBC_SUCCESS;
381 }
382 break;
383 }
384
385 case ODBC_TYPE_BINARY: {
386 // Determine required buffer size
387 char dummy[1];
388 SQLLEN binary_len = 0;
389
390 ret = SQLGetData(conn->stmt, column_index + 1, SQL_C_BINARY,
391 dummy, 0, &binary_len);
392
393 if (!SQL_SUCCEEDED(ret) && ret != SQL_SUCCESS_WITH_INFO) {
394 return ODBC_ERROR;
395 }
396
397 if (binary_len == SQL_NULL_DATA) {
398 value->is_null = true;
399 return ODBC_SUCCESS;
400 }
401
402 size_t buffer_size = ODBC_DEFAULT_BUFFER_SIZE;
403 if (binary_len > 0 && binary_len != SQL_NO_TOTAL) {
404 size_t required = (size_t)binary_len;
405 if (required > buffer_size) {
406 buffer_size = required;
407 }
408 }
409
410 if (buffer_size > ODBC_MAX_BUFFER_SIZE) {
411 buffer_size = ODBC_MAX_BUFFER_SIZE;
412 }
413
414 if (buffer_size == 0) {
415 buffer_size = ODBC_DEFAULT_BUFFER_SIZE;
416 }
417
418 void* buffer = malloc(buffer_size);
419 if (!buffer) {
420 return ODBC_ERROR;
421 }
422
423 ret = SQLGetData(conn->stmt, column_index + 1, SQL_C_BINARY,
424 buffer, buffer_size, &indicator);
425
426 if (SQL_SUCCEEDED(ret) || ret == SQL_SUCCESS_WITH_INFO) {
427 if (indicator == SQL_NULL_DATA) {
428 value->is_null = true;
429 free(buffer);
430 } else {
431 value->type = ODBC_TYPE_BINARY;
432 value->is_null = false;
433 if (indicator >= 0 && indicator <= (SQLLEN)buffer_size) {
434 value->data.binary_val.len = (size_t)indicator;
435 } else if (indicator == SQL_NO_TOTAL || indicator > (SQLLEN)buffer_size) {
436 value->data.binary_val.len = buffer_size;
437 } else {
438 value->data.binary_val.len = 0;
439 }
440 value->data.binary_val.data = buffer;
441 }
442 return ODBC_SUCCESS;
443 }
444
445 free(buffer);
446 break;
447 }
448
449 case ODBC_TYPE_STRING:
450 default: {
451 // First call to get the required buffer size
452 char dummy[1];
453 SQLLEN str_len_or_ind = 0;
454
455 ret = SQLGetData(conn->stmt, column_index + 1, SQL_C_CHAR,
456 dummy, 0, &str_len_or_ind);
457
458 if (!SQL_SUCCEEDED(ret) && ret != SQL_SUCCESS_WITH_INFO) {
459 return ODBC_ERROR;
460 }
461
462 if (str_len_or_ind == SQL_NULL_DATA) {
463 value->is_null = true;
464 return ODBC_SUCCESS;
465 }
466
467 size_t buffer_size = ODBC_DEFAULT_BUFFER_SIZE;
468 if (str_len_or_ind > 0 && str_len_or_ind != SQL_NO_TOTAL) {
469 size_t required = (size_t)str_len_or_ind + 1;
470 if (required > buffer_size) {
471 buffer_size = required;
472 }
473 }
474
475 if (buffer_size > ODBC_MAX_BUFFER_SIZE) {
476 buffer_size = ODBC_MAX_BUFFER_SIZE;
477 }
478
479 if (buffer_size == 0) {
480 buffer_size = ODBC_DEFAULT_BUFFER_SIZE;
481 }
482
483 char* buffer = malloc(buffer_size);
484 if (!buffer) {
485 return ODBC_ERROR;
486 }
487
488 ret = SQLGetData(conn->stmt, column_index + 1, SQL_C_CHAR,
489 buffer, buffer_size, &indicator);
490
491 if (SQL_SUCCEEDED(ret) || ret == SQL_SUCCESS_WITH_INFO) {
492 if (indicator == SQL_NULL_DATA) {
493 value->is_null = true;
494 free(buffer);
495 } else {
496 value->type = ODBC_TYPE_STRING;
497 value->is_null = false;
498 value->data.string_val = buffer;
499 }
500 return ODBC_SUCCESS;
501 }
502
503 free(buffer);
504 break;
505 }
506 }
507
508 return ODBC_ERROR;
509 }
510
511 // Free allocated value
512 void odbc_free_value(odbc_value_t* value) {
513 if (!value) return;
514
515 if (!value->is_null) {
516 switch (value->type) {
517 case ODBC_TYPE_STRING:
518 if (value->data.string_val) {
519 free(value->data.string_val);
520 value->data.string_val = NULL;
521 }
522 break;
523 case ODBC_TYPE_BINARY:
524 if (value->data.binary_val.data) {
525 free(value->data.binary_val.data);
526 value->data.binary_val.data = NULL;
527 }
528 break;
529 default:
530 break;
531 }
532 }
533 }
534
535 // Reset statement for reuse
536 int odbc_reset_statement(odbc_conn_t conn_handle) {
537 if (!conn_handle) return ODBC_ERROR;
538
539 odbc_connection* conn = (odbc_connection*)conn_handle;
540
541 // Close cursor if open
542 if (conn->cursor_open) {
543 SQLCloseCursor(conn->stmt);
544 conn->cursor_open = false;
545 }
546
547 // Reset parameters if prepared
548 if (conn->stmt_prepared) {
549 SQLFreeStmt(conn->stmt, SQL_RESET_PARAMS);
550 SQLFreeStmt(conn->stmt, SQL_UNBIND);
551 conn->stmt_prepared = false;
552 }
553
554 return ODBC_SUCCESS;
555 }
556
557 // Close cursor only
558 int odbc_close_cursor(odbc_conn_t conn_handle) {
559 if (!conn_handle) return ODBC_ERROR;
560
561 odbc_connection* conn = (odbc_connection*)conn_handle;
562
563 if (conn->cursor_open) {
564 SQLRETURN ret = SQLCloseCursor(conn->stmt);
565 if (SQL_SUCCEEDED(ret)) {
566 conn->cursor_open = false;
567 return ODBC_SUCCESS;
568 }
569 }
570
571 return ODBC_SUCCESS;
572 }
573
574 // Free statement completely
575 int odbc_free_statement(odbc_conn_t conn_handle) {
576 if (!conn_handle) return ODBC_ERROR;
577
578 odbc_connection* conn = (odbc_connection*)conn_handle;
579
580 if (conn->stmt) {
581 SQLFreeHandle(SQL_HANDLE_STMT, conn->stmt);
582 conn->stmt = NULL;
583 conn->stmt_prepared = false;
584 conn->cursor_open = false;
585
586 // Allocate new statement for next use
587 SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_STMT, conn->dbc, &conn->stmt);
588 if (!SQL_SUCCEEDED(ret)) {
589 return ODBC_ERROR;
590 }
591 }
592
593 return ODBC_SUCCESS;
594 }
595
596 // Disconnect and cleanup
597 void odbc_disconnect(odbc_conn_t conn_handle) {
598 if (!conn_handle) return;
599
600 odbc_connection* conn = (odbc_connection*)conn_handle;
601
602 // Free column buffers
603 if (conn->column_buffers) {
604 for (int i = 0; i < conn->column_count; i++) {
605 free(conn->column_buffers[i]);
606 }
607 free(conn->column_buffers);
608 free(conn->indicators);
609 free(conn->buffer_sizes);
610 }
611
612 if (conn->stmt) {
613 SQLFreeHandle(SQL_HANDLE_STMT, conn->stmt);
614 }
615
616 if (conn->connected && conn->dbc) {
617 SQLDisconnect(conn->dbc);
618 }
619
620 if (conn->dbc) {
621 SQLFreeHandle(SQL_HANDLE_DBC, conn->dbc);
622 }
623
624 if (conn->env) {
625 SQLFreeHandle(SQL_HANDLE_ENV, conn->env);
626 }
627
628 free(conn);
629 }
630
631 int64_t odbc_value_get_int64(const odbc_value_t* value) {
632 if (!value || value->is_null || value->type != ODBC_TYPE_INT64) {
633 return 0;
634 }
635 return value->data.int_val;
636 }
637
638 double odbc_value_get_double(const odbc_value_t* value) {
639 if (!value || value->is_null || value->type != ODBC_TYPE_DOUBLE) {
640 return 0.0;
641 }
642 return value->data.double_val;
643 }
644
645 const char* odbc_value_get_string(const odbc_value_t* value) {
646 if (!value || value->is_null || value->type != ODBC_TYPE_STRING) {
647 return NULL;
648 }
649 return value->data.string_val;
650 }
651
652 // Check connection status
653 int odbc_is_connected(odbc_conn_t conn_handle) {
654 if (!conn_handle) return 0;
655 odbc_connection* conn = (odbc_connection*)conn_handle;
656 return conn->connected;
657 }
658
659 // Get last error
660 const char* odbc_get_last_error(odbc_conn_t conn_handle) {
661 if (!conn_handle) return "Invalid connection handle";
662 odbc_connection* conn = (odbc_connection*)conn_handle;
663 return conn->last_error;
664 }
665
666 // Get SQLSTATE
667 int odbc_get_sqlstate(odbc_conn_t conn_handle, char* state, size_t state_size) {
668 if (!conn_handle || !state) return ODBC_ERROR;
669
670 odbc_connection* conn = (odbc_connection*)conn_handle;
671 strncpy(state, conn->last_sqlstate, state_size - 1);
672 state[state_size - 1] = '\0';
673
674 return ODBC_SUCCESS;
675 }