File: simplesql-mysql.c

package info (click to toggle)
guile-simplesql 2.3.2-1
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 1,672 kB
  • ctags: 97
  • sloc: sh: 8,413; ansic: 834; lisp: 42; makefile: 23
file content (384 lines) | stat: -rw-r--r-- 11,766 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
/* sqmysql.c: MySQL backend for Guile SimpleSQL

   Copyright (C) 2004 David J. Lambert.
   Copyright (C) 1999 forcer <forcer@mindless.com>
   Copyright (C) 1996-98 Hal Roberts

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
   USA  */

#include "guile-simplesql.h"

#include <libguile.h>
#include <stdio.h>
#include <string.h>
#include <mysql/mysql.h>
#include <mysql/errmsg.h>

static void *sqmysql_open(const char *dbname, const char *host, int port, const char *,
                          const char *user, const char *pass, SCM *err);
static SCM sqmysql_query(void *db, const char *query, int querylen);
static void sqmysql_close(void *db);
static SCM sqmysql_convert_query_result(MYSQL *db, MYSQL_RES *res);
static SCM sqmysql_mysql_sql2scm(MYSQL_RES *res, char *field, unsigned long len, int pos);
static SCM sqmysql_convert_floatingpoint(char *arg, unsigned long len);
static SCM sqmysql_convert_integer(char *arg, unsigned long len);
static SCM sqmysql_convert_time(struct tm *bd_time);
static SCM sqmysql_convert_string(char *arg, unsigned long len);
static SCM sqmysql_convert_set(char *arg, unsigned long len);
static SCM sqmysql_convert_blob(char *arg, unsigned long len);

#ifdef HAVE_STRPTIME
static SCM sqmysql_convert_timestamp(char *s, unsigned long len);
#endif

/* Init function for the MySQL API.  */
void
sq_mysql_init(void)
{
        sql_register_api("MySQL", sqmysql_open, sqmysql_query, sqmysql_close);
}

/*
 * Open a connection to the MySQL database.
 * The params might be NULL if none were given, but MySQL handles that
 * exactly like we want it.
 * Returns a mysql object to be used lateron
 */
static void *
sqmysql_open (const char *dbname, const char *host, int port, const char * socket,
	      const char *user, const char *pass, SCM *err)
{
    MYSQL *mysql = mysql_init (NULL);

    if (! mysql_real_connect (mysql, host, user, pass, dbname, port, socket, 0))
    {
	*err = scm_makfrom0str (mysql_error (mysql));
	mysql_close(mysql);
	return NULL;
    }
    return mysql;
}

/*
 * db is our MYSQL struct
 * query is a string containing the query (not null terminated)
 * querylen is the length of the query
 * The return value is the list of vectors
 * Send the query, and convert the result set at once
 */
static SCM
sqmysql_query(void *db, const char *query, int querylen)
{
        int err;
        MYSQL_RES *res;
        SCM scm_result;

        err = mysql_real_query((MYSQL*)db, query, querylen);
        if(err){
                scm_misc_error("sql-query", "~A",
                               scm_cons (scm_makfrom0str (mysql_error((MYSQL*)db)),
					 SCM_EOL));
        }

        res = mysql_store_result((MYSQL*)db);
        if(res){
                scm_result = sqmysql_convert_query_result((MYSQL*)db, res);
                mysql_free_result(res);
        } else {
                scm_result = scm_int2num (mysql_affected_rows((MYSQL*)db));
        }
        return scm_result;
}

/*
 * Close the connection again.
 */
static void
sqmysql_close(void *db)
{
        mysql_close((MYSQL*)db);
}

/*
 * Walk through the MYSQL_RES result. Build a list of vectors,
 * the first containing the result tables' headers, the others
 * containing the result values
 */
static SCM
sqmysql_convert_query_result(MYSQL *db, MYSQL_RES *res)
{
        unsigned long *lens;
        long f, r, num_fields, num_rows;
        char *field_name;
        char **row;
        SCM scm_fields, scm_rows, scm_data, scm_result;

        num_fields = mysql_num_fields(res);
        num_rows = mysql_num_rows(res);

        scm_fields = scm_make_vector(scm_int2num (num_fields),
				     scm_int2num (0));
        for (f = 0; f < num_fields; f++) {
                field_name = mysql_fetch_field(res)->name;
                scm_vector_set_x (scm_fields, scm_int2num (f),
				  scm_makfrom0str (field_name));
        }

        scm_result = scm_rows = scm_cons(scm_fields, SCM_EOL);
        for (r = 0; r < num_rows; r++) {
                row = mysql_fetch_row(res);
                lens = mysql_fetch_lengths(res);
                
                scm_data = scm_make_vector(scm_int2num (num_fields),
					   scm_int2num (0));
                for (f = 0; f < num_fields; f++) {
                        scm_vector_set_x (scm_data, scm_int2num (f),
					  sqmysql_mysql_sql2scm(res,
								row[f],
								lens[f],
								f));
                }
                SCM_SETCDR(scm_rows,
                           scm_cons(scm_data,
                                    SCM_CDR(scm_rows)));
                scm_rows = SCM_CDR(scm_rows);
        }
        return scm_result;
}

/* The main conversion routine for MySQL types.

   Converts the @var{len} bytes at @var{field} to the Scheme
   representation.  */
static SCM
sqmysql_mysql_sql2scm(MYSQL_RES *res, char *field, unsigned long len, int pos)
{
        struct tm tm;
        int type;

        /* If it's a null value, return a SCM `null'. */
	if (!field)
	    return SCM_EOL;

        /* set the cursor to the correct field */
        mysql_field_seek(res, pos);

        /* memset(&tm, -1, sizeof(struct tm)); %) */
        tm.tm_sec = tm.tm_min = tm.tm_hour = tm.tm_mday
                = tm.tm_mon = tm.tm_year = tm.tm_wday
                = tm.tm_yday = tm.tm_isdst = -1;

        /* else, figure out what scm type it should be */
        type = (mysql_fetch_field(res))->type;
        switch (type) {
            case FIELD_TYPE_NULL:
                return SCM_EOL;
                break;

            case FIELD_TYPE_TINY:
            case FIELD_TYPE_SHORT:
            case FIELD_TYPE_INT24:
            case FIELD_TYPE_LONG:
            case FIELD_TYPE_LONGLONG:
            case FIELD_TYPE_DECIMAL:
                return sqmysql_convert_integer (field, len);
                break;

            case FIELD_TYPE_FLOAT:
            case FIELD_TYPE_DOUBLE:
                return sqmysql_convert_floatingpoint (field, len);
                break;

            case FIELD_TYPE_STRING:
            case FIELD_TYPE_ENUM:
            case FIELD_TYPE_VAR_STRING:
                return sqmysql_convert_string(field, len);
                break;

            case FIELD_TYPE_SET:
                return sqmysql_convert_set(field, len);
                break;

            case FIELD_TYPE_BLOB:
                return sqmysql_convert_blob(field, len);
                break;

                /* the following time fields use strptime to
                 * read the ascii time into a struct tm
                 * structure and then convert that one
                 */
#ifdef HAVE_STRPTIME
            case FIELD_TYPE_DATE:
                strptime(field, "%Y-%m-%d", &tm);
                return sqmysql_convert_time(&tm);
                break;
            case FIELD_TYPE_TIME:
                /* FIXME: This can actually be a duration, with hours > 100
                   and < 0 */
                strptime(field, "%T", &tm);
                return sqmysql_convert_time(&tm);
                break;
            case FIELD_TYPE_DATETIME:
                strptime(field, "%Y-%m-%d %T", &tm);
                return sqmysql_convert_time(&tm);
                break;
            case FIELD_TYPE_TIMESTAMP:
                return sqmysql_convert_timestamp(field, len);
                break;
            case FIELD_TYPE_YEAR:
                strptime(field, "%Y", &tm);
                return sqmysql_convert_time(&tm);
                break;
#else
            case FIELD_TYPE_DATE:
            case FIELD_TYPE_TIME:
            case FIELD_TYPE_DATETIME:
            case FIELD_TYPE_TIMESTAMP:
            case FIELD_TYPE_YEAR:
                return scm_makfrom0str (field);
                break;
#endif
            default:
                scm_misc_error("sql-query",
                               "Unknown field type ~S in result set",
                               scm_cons(SCM_MAKINUM(type), SCM_EOL));
                return SCM_BOOL_F;
                break;
        }
}

/* This function just creates the time representation we chose.  */
static SCM
sqmysql_convert_time (struct tm *bd_time)
{
    return _simplesql_filltime (bd_time, 0, "GMT");
}


#ifdef HAVE_STRPTIME

/*
 * Timestamps are pretty messy.
 * len format
 *  14 YYYYMMDDHHMMSS
 *  12 YYMMDDHHMMSS
 *   8 YYYYMMDD
 *   6 YYMMDD
 *
 * strptime doesn't like the date string with no breaks, so
 * let's make it look like the datetime string
 * YYYY-MM-DD HH:MM:SS
 *
 * Datestrings with 2-field years will be Y2K converted:
 * Years below 70 are 20xx, above 70 are 19xx
 *
 * We use sprintf not snprintf because we control the length explicitly
 */
static SCM
sqmysql_convert_timestamp(char *s, unsigned long len)
{
        char buf[32];
        struct tm tm;

        switch(len) {
            case 14: /* YYYYMMDDHHMMSS */
                sprintf(buf,
                        "%.4s-%.2s-%.2s %.2s:%.2s:%.2s",
                        s, s + 4, s + 6, s + 8, s + 10, s + 12);
                break;
            case 12: /* YYMMDDHHMMSS */
                sprintf(buf,
                        "%i%.2s-%.2s-%.2s %.2s:%.2s:%.2s",
                        s[0] < '7' ? 20 : 19,
                        s, s + 2, s + 4, s + 6, s + 8, s + 10);
                break;
            case  8: /* YYYYMMDD */
                sprintf(buf,
                        "%.4s-%.2s-%.2s 00:00:00",
                        s, s + 4, s + 6);
                break;
            case  6: /* YYMMDD */
                sprintf(buf,
                        "%i%.2s-%.2s-%.2s 00:00:00",
                        s[0] < '7' ? 20 : 19,
                        s, s + 2, s + 4);
                break;
        }
        strptime(buf, "%Y-%m-%d %T", &tm);
        return sqmysql_convert_time(&tm);
}

#endif


/* Convert the string representation of an integer to a Scheme
   number.  */
static SCM
sqmysql_convert_integer (char *arg, unsigned long len)
{
    return scm_i_mem2number (arg, len, 10);
}


/* Convert the string representation of a floating-point number to a
   Scheme number.  */
static SCM
sqmysql_convert_floatingpoint (char *arg, unsigned long len)
{
    return scm_i_mem2number (arg, len, 10);
}

/*
 * Convert the string to a Guile string
 */
static SCM
sqmysql_convert_string(char *arg, unsigned long len)
{
        return sq_latin1_string(arg, len);
}

/*
 * Convert a SET to a SCM list
 * the SET is a comma-seperated list of strings (which are required not
 * to contain commas)
 */
static SCM
sqmysql_convert_set(char *arg, unsigned long len)
{
        char *end;
        int i;
        SCM rv = SCM_EOL;

        end = arg + len;
        for(i=len; i >= 0; i--){
                if(arg[i] == ','){
                        rv = scm_cons(sq_latin1_string(arg+i+1,
                                                       end-(arg+i+1)),
                                      rv);
                        end = arg+i;
                }
        }
        return scm_cons(sq_latin1_string(arg, end-arg), rv);
}

/*
 * Convert a BLOB (Binary Large OBject) to a SCM
 */
static SCM
sqmysql_convert_blob(char *arg, unsigned long len)
{
        return sq_binary_vector(arg, len);
}