2 * ODBC database backend
4 * Part of Gammu project
6 * Copyright (C) 2011 Michal Čihař
8 * Licensed under GNU GPL version 2 or later
16 #pragma comment(lib, "libodbc32.lib")
28 static void SMSDODBC_LogError(GSM_SMSDConfig
* Config
, SQLRETURN origret
, SQLSMALLINT handle_type
, SQLHANDLE handle
, const char *message
)
37 SMSD_Log(DEBUG_ERROR
, Config
, "%s, Code = %d, ODBC diagnostics:", message
, (int)origret
);
40 ret
= SQLGetDiagRec(handle_type
, handle
, ++i
, state
, &native
, text
, sizeof(text
), &len
);
41 if (SQL_SUCCEEDED(ret
)) {
42 SMSD_Log(DEBUG_ERROR
, Config
, "%s:%ld:%ld:%s\n", state
, (long)i
, (long)native
, text
);
44 } while (ret
== SQL_SUCCESS
);
47 long long SMSDODBC_GetNumber(GSM_SMSDConfig
* Config
, SQL_result
*res
, unsigned int field
)
52 ret
= SQLGetData(res
->odbc
, field
+ 1, SQL_C_SLONG
, &value
, 0, NULL
);
53 if (!SQL_SUCCEEDED(ret
)) {
54 SMSDODBC_LogError(Config
, ret
, SQL_HANDLE_STMT
, res
->odbc
, "SQLGetData(long) failed");
60 time_t SMSDODBC_GetDate(GSM_SMSDConfig
* Config
, SQL_result
*res
, unsigned int field
)
62 SQL_TIMESTAMP_STRUCT sqltime
;
66 ret
= SQLGetData(res
->odbc
, field
+ 1, SQL_C_TYPE_TIMESTAMP
, &sqltime
, 0, NULL
);
67 if (!SQL_SUCCEEDED(ret
)) {
68 SMSDODBC_LogError(Config
, ret
, SQL_HANDLE_STMT
, res
->odbc
, "SQLGetData(timestamp) failed");
72 DT
.Year
= sqltime
.year
;
73 DT
.Month
= sqltime
.month
;
75 DT
.Hour
= sqltime
.hour
;
76 DT
.Minute
= sqltime
.minute
;
77 DT
.Second
= sqltime
.second
;
79 return Fill_Time_T(DT
);
82 const char *SMSDODBC_GetString(GSM_SMSDConfig
* Config
, SQL_result
*res
, unsigned int field
)
89 if (field
> SMSD_ODBC_MAX_RETURN_STRINGS
) {
90 SMSD_Log(DEBUG_ERROR
, Config
, "Field %d returning NULL, too many fields!", field
);
94 /* Figure out string length */
95 ret
= SQLGetData(res
->odbc
, field
+ 1, SQL_C_CHAR
, shortbuffer
, 0, &sqllen
);
96 if (!SQL_SUCCEEDED(ret
)) {
97 SMSDODBC_LogError(Config
, ret
, SQL_HANDLE_STMT
, res
->odbc
, "SQLGetData(string,0) failed");
102 * This hack seems to be needed to avoid type breakage on Win64, don't ask me why.
104 * Might be actually bug in MinGW compiler, but when using SQLLEN type bellow
105 * anything fails (it does not match to SQL_NULL_DATA and realloc always fails).
109 /* Did not we get NULL? */
110 if (size
== SQL_NULL_DATA
) {
111 SMSD_Log(DEBUG_SQL
, Config
, "Field %d returning NULL", field
);
115 /* Allocate string */
116 Config
->conn
.odbc
.retstr
[field
] = realloc(Config
->conn
.odbc
.retstr
[field
], size
+ 1);
117 if (Config
->conn
.odbc
.retstr
[field
] == NULL
) {
118 SMSD_Log(DEBUG_ERROR
, Config
, "Field %d returning NULL, failed to allocate %d bytes of memory", field
, size
+ 1);
122 /* Actually grab result from database */
123 ret
= SQLGetData(res
->odbc
, field
+ 1, SQL_C_CHAR
, Config
->conn
.odbc
.retstr
[field
], size
+ 1, &sqllen
);
124 if (!SQL_SUCCEEDED(ret
)) {
125 SMSDODBC_LogError(Config
, ret
, SQL_HANDLE_STMT
, res
->odbc
, "SQLGetData(string) failed");
129 SMSD_Log(DEBUG_SQL
, Config
, "Field %d returning string \"%s\"", field
, Config
->conn
.odbc
.retstr
[field
]);
131 return Config
->conn
.odbc
.retstr
[field
];
134 gboolean
SMSDODBC_GetBool(GSM_SMSDConfig
* Config
, SQL_result
*res
, unsigned int field
)
137 const char * charval
;
139 /* Try to get numeric value first */
140 intval
= SMSDODBC_GetNumber(Config
, res
, field
);
142 /* If that fails, fall back to string and parse it */
143 charval
= SMSDODBC_GetString(Config
, res
, field
);
144 return GSM_StringToBool(charval
);
146 return intval
? TRUE
: FALSE
;
149 /* Disconnects from a database */
150 void SMSDODBC_Free(GSM_SMSDConfig
* Config
)
154 SQLDisconnect(Config
->conn
.odbc
.dbc
);
155 SQLFreeHandle(SQL_HANDLE_ENV
, Config
->conn
.odbc
.env
);
157 for (field
= 0; field
< SMSD_ODBC_MAX_RETURN_STRINGS
; field
++) {
158 if (Config
->conn
.odbc
.retstr
[field
] != NULL
) {
159 free(Config
->conn
.odbc
.retstr
[field
]);
160 Config
->conn
.odbc
.retstr
[field
] = NULL
;
165 /* Connects to database */
166 static SQL_Error
SMSDODBC_Connect(GSM_SMSDConfig
* Config
)
170 char driver_name
[1000];
173 for (field
= 0; field
< SMSD_ODBC_MAX_RETURN_STRINGS
; field
++) {
174 Config
->conn
.odbc
.retstr
[field
] = NULL
;
177 ret
= SQLAllocHandle (SQL_HANDLE_ENV
, SQL_NULL_HANDLE
, &Config
->conn
.odbc
.env
);
178 if (!SQL_SUCCEEDED(ret
)) {
179 SMSDODBC_LogError(Config
, ret
, SQL_HANDLE_ENV
, Config
->conn
.odbc
.env
, "SQLAllocHandle(ENV) failed");
183 ret
= SQLSetEnvAttr (Config
->conn
.odbc
.env
, SQL_ATTR_ODBC_VERSION
, (void*)SQL_OV_ODBC3
, 0);
184 if (!SQL_SUCCEEDED(ret
)) {
185 SMSDODBC_LogError(Config
, ret
, SQL_HANDLE_ENV
, Config
->conn
.odbc
.env
, "SQLSetEnvAttr failed");
189 ret
= SQLAllocHandle (SQL_HANDLE_DBC
, Config
->conn
.odbc
.env
, &Config
->conn
.odbc
.dbc
);
190 if (!SQL_SUCCEEDED(ret
)) {
191 SMSDODBC_LogError(Config
, ret
, SQL_HANDLE_ENV
, Config
->conn
.odbc
.env
, "SQLAllocHandle(DBC) failed");
195 ret
= SQLConnect(Config
->conn
.odbc
.dbc
,
196 (SQLCHAR
*)Config
->host
, SQL_NTS
,
197 (SQLCHAR
*)Config
->user
, SQL_NTS
,
198 (SQLCHAR
*)Config
->password
, SQL_NTS
);
199 if (!SQL_SUCCEEDED(ret
)) {
200 SMSDODBC_LogError(Config
, ret
, SQL_HANDLE_DBC
, Config
->conn
.odbc
.dbc
, "SQLConnect failed");
204 ret
= SQLGetInfo(Config
->conn
.odbc
.dbc
, SQL_DRIVER_NAME
, driver_name
, sizeof(driver_name
), &len
);
205 if (!SQL_SUCCEEDED(ret
)) {
206 SMSDODBC_LogError(Config
, ret
, SQL_HANDLE_DBC
, Config
->conn
.odbc
.dbc
, "SQLGetInfo failed");
209 SMSD_Log(DEBUG_NOTICE
, Config
, "Connected to driver %s", driver_name
);
216 static SQL_Error
SMSDODBC_Query(GSM_SMSDConfig
* Config
, const char *query
, SQL_result
* res
)
220 ret
= SQLAllocHandle(SQL_HANDLE_STMT
, Config
->conn
.odbc
.dbc
, &res
->odbc
);
221 if (!SQL_SUCCEEDED(ret
)) {
225 ret
= SQLExecDirect (res
->odbc
, (SQLCHAR
*)query
, SQL_NTS
);
227 * If SQLExecDirect executes a searched update, insert, or delete
228 * statement that does not affect any rows at the data source, the call
229 * to SQLExecDirect returns SQL_NO_DATA.
231 if (SQL_SUCCEEDED(ret
) || ret
== SQL_NO_DATA
) {
235 SMSDODBC_LogError(Config
, ret
, SQL_HANDLE_STMT
, res
->odbc
, "SQLExecDirect failed");
239 /* free sql results */
240 void SMSDODBC_FreeResult(GSM_SMSDConfig
* Config
, SQL_result
*res
)
242 SQLFreeHandle (SQL_HANDLE_STMT
, res
->odbc
);
245 /* set pointer to next row */
246 int SMSDODBC_NextRow(GSM_SMSDConfig
* Config
, SQL_result
*res
)
250 ret
= SQLFetch(res
->odbc
);
252 if (!SQL_SUCCEEDED(ret
)) {
253 if (ret
!= SQL_NO_DATA
) {
254 SMSDODBC_LogError(Config
, ret
, SQL_HANDLE_STMT
, res
->odbc
, "SQLFetch failed");
262 char * SMSDODBC_QuoteString(GSM_SMSDConfig
* Config
, const char *string
)
264 char *encoded_text
= NULL
;
265 size_t i
, len
, pos
= 0;
268 const char *driver_name
;
270 if (Config
->sql
!= NULL
) {
271 driver_name
= Config
->sql
;
273 driver_name
= Config
->driver
;
276 if (strcasecmp(driver_name
, "access") == 0) {
280 len
= strlen(string
);
282 encoded_text
= (char *)malloc((len
* 2) + 3);
283 encoded_text
[pos
++] = quote
;
284 for (i
= 0; i
< len
; i
++) {
285 if (string
[i
] == quote
|| string
[i
] == '\\') {
286 encoded_text
[pos
++] = '\\';
288 encoded_text
[pos
++] = string
[i
];
290 encoded_text
[pos
++] = quote
;
291 encoded_text
[pos
] = '\0';
296 unsigned long long SMSDODBC_SeqID(GSM_SMSDConfig
* Config
, const char *id
)
302 ret
= SQLAllocHandle(SQL_HANDLE_STMT
, Config
->conn
.odbc
.dbc
, &stmt
);
303 if (!SQL_SUCCEEDED(ret
)) {
307 ret
= SQLExecDirect (stmt
, (SQLCHAR
*)"SELECT @@IDENTITY", SQL_NTS
);
308 if (!SQL_SUCCEEDED(ret
)) {
309 SQLFreeHandle (SQL_HANDLE_STMT
, stmt
);
313 ret
= SQLFetch(stmt
);
314 if (!SQL_SUCCEEDED(ret
)) {
315 SQLFreeHandle (SQL_HANDLE_STMT
, stmt
);
319 ret
= SQLGetData(stmt
, 1, SQL_C_SLONG
, &value
, 0, NULL
);
320 if (!SQL_SUCCEEDED(ret
)) {
321 SQLFreeHandle (SQL_HANDLE_STMT
, stmt
);
324 SQLFreeHandle (SQL_HANDLE_STMT
, stmt
);
329 unsigned long SMSDODBC_AffectedRows(GSM_SMSDConfig
* Config
, SQL_result
*res
)
334 ret
= SQLRowCount (res
->odbc
, &count
);
335 if (!SQL_SUCCEEDED(ret
)) {
336 SMSDODBC_LogError(Config
, ret
, SQL_HANDLE_DBC
, Config
->conn
.odbc
.dbc
, "SQLRowCount failed");
342 struct GSM_SMSDdbobj SMSDODBC
= {
349 SMSDODBC_AffectedRows
,
354 SMSDODBC_QuoteString
,
357 /* How should editor hadle tabs in this file? Add editor commands here.
358 * vim: noexpandtab sw=8 ts=8 sts=8: