1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "apu.h"
18
19#if APU_HAVE_MYSQL
20
21#include "apu_version.h"
22#include "apu_config.h"
23
24#include <ctype.h>
25#include <stdlib.h>
26
27#if defined(HAVE_MYSQL_MYSQL_H)
28#if defined(HAVE_MYSQL_MY_GLOBAL_H)
29#include <mysql/my_global.h>
30#if defined(HAVE_MYSQL_MY_SYS_H)
31#include <mysql/my_sys.h>
32#endif
33#endif
34#include <mysql/mysql.h>
35#include <mysql/errmsg.h>
36#else /* !defined(HAVE_MYSQL_MYSQL_H) */
37#if defined(HAVE_MY_GLOBAL_H)
38#include <my_global.h>
39#if defined(HAVE_MY_SYS_H)
40#include <my_sys.h>
41#endif
42#endif
43#include <mysql.h>
44#include <errmsg.h>
45#endif
46
47#include "apr_strings.h"
48#include "apr_lib.h"
49#include "apr_buckets.h"
50
51#include "apr_dbd_internal.h"
52
53/* default maximum field size 1 MB */
54#define FIELDSIZE 1048575
55
56struct apr_dbd_prepared_t {
57    MYSQL_STMT* stmt;
58    int nargs;
59    int nvals;
60    apr_dbd_type_e *types;
61};
62
63struct apr_dbd_transaction_t {
64    int mode;
65    int errnum;
66    apr_dbd_t *handle;
67};
68
69struct apr_dbd_t {
70    MYSQL* conn ;
71    apr_dbd_transaction_t* trans ;
72    unsigned long fldsz;
73};
74
75struct apr_dbd_results_t {
76    int random;
77    MYSQL_RES *res;
78    MYSQL_STMT *statement;
79    MYSQL_BIND *bind;
80    apr_pool_t *pool;
81};
82struct apr_dbd_row_t {
83    MYSQL_ROW row;
84    apr_dbd_results_t *res;
85    unsigned long *len;
86};
87
88/* MySQL specific bucket for BLOB types */
89typedef struct apr_bucket_lob apr_bucket_lob;
90/**
91 * A bucket referring to a MySQL BLOB
92 */
93struct apr_bucket_lob {
94    /** Number of buckets using this memory */
95    apr_bucket_refcount  refcount;
96    /** The row this bucket refers to */
97    const apr_dbd_row_t *row;
98    /** The column this bucket refers to */
99    int col;
100    /** The pool into which any needed structures should
101     *  be created while reading from this bucket */
102    apr_pool_t *readpool;
103};
104
105static void lob_bucket_destroy(void *data);
106static apr_status_t lob_bucket_read(apr_bucket *e, const char **str,
107                                    apr_size_t *len, apr_read_type_e block);
108static apr_bucket *apr_bucket_lob_make(apr_bucket *b,
109                                       const apr_dbd_row_t *row, int col,
110                                       apr_off_t offset, apr_size_t len,
111                                       apr_pool_t *p);
112static apr_bucket *apr_bucket_lob_create(const apr_dbd_row_t *row, int col,
113                                         apr_off_t offset,
114                                         apr_size_t len, apr_pool_t *p,
115                                         apr_bucket_alloc_t *list);
116static int dbd_mysql_num_cols(apr_dbd_results_t *res);
117
118static const apr_bucket_type_t apr_bucket_type_lob = {
119    "LOB", 5, APR_BUCKET_DATA,
120    lob_bucket_destroy,
121    lob_bucket_read,
122    apr_bucket_setaside_notimpl,
123    apr_bucket_shared_split,
124    apr_bucket_shared_copy
125};
126
127static void lob_bucket_destroy(void *data)
128{
129    apr_bucket_lob *f = data;
130
131    if (apr_bucket_shared_destroy(f)) {
132        /* no need to destroy database objects here; it will get
133         * done automatically when the pool gets cleaned up */
134        apr_bucket_free(f);
135    }
136}
137
138static apr_status_t lob_bucket_read(apr_bucket *e, const char **str,
139                                    apr_size_t *len, apr_read_type_e block)
140{
141    apr_bucket_lob *a = e->data;
142    const apr_dbd_row_t *row = a->row;
143    apr_dbd_results_t *res = row->res;
144    int col = a->col;
145    apr_bucket *b = NULL;
146    int rv;
147    apr_size_t blength = e->length;  /* bytes remaining in file past offset */
148    apr_off_t boffset = e->start;
149    MYSQL_BIND *bind = &res->bind[col];
150
151    *str = NULL;  /* in case we die prematurely */
152
153    /* fetch from offset if not at the beginning */
154    if (boffset > 0) {
155        rv = mysql_stmt_fetch_column(res->statement, bind, col,
156                                     (unsigned long) boffset);
157        if (rv != 0) {
158            return APR_EGENERAL;
159        }
160    }
161    blength -= blength > bind->buffer_length ? bind->buffer_length : blength;
162    *len = e->length - blength;
163    *str = bind->buffer;
164
165    /* allocate new buffer, since we used this one for the bucket */
166    bind->buffer = apr_palloc(res->pool, bind->buffer_length);
167
168    /*
169     * Change the current bucket to refer to what we read,
170     * even if we read nothing because we hit EOF.
171     */
172    apr_bucket_pool_make(e, *str, *len, res->pool);
173
174    /* If we have more to read from the field, then create another bucket */
175    if (blength > 0) {
176        /* for efficiency, we can just build a new apr_bucket struct
177         * to wrap around the existing LOB bucket */
178        b = apr_bucket_alloc(sizeof(*b), e->list);
179        b->start  = boffset + *len;
180        b->length = blength;
181        b->data   = a;
182        b->type   = &apr_bucket_type_lob;
183        b->free   = apr_bucket_free;
184        b->list   = e->list;
185        APR_BUCKET_INSERT_AFTER(e, b);
186    }
187    else {
188        lob_bucket_destroy(a);
189    }
190
191    return APR_SUCCESS;
192}
193
194static apr_bucket *apr_bucket_lob_make(apr_bucket *b,
195                                       const apr_dbd_row_t *row, int col,
196                                       apr_off_t offset, apr_size_t len,
197                                       apr_pool_t *p)
198{
199    apr_bucket_lob *f;
200
201    f = apr_bucket_alloc(sizeof(*f), b->list);
202    f->row = row;
203    f->col = col;
204    f->readpool = p;
205
206    b = apr_bucket_shared_make(b, f, offset, len);
207    b->type = &apr_bucket_type_lob;
208
209    return b;
210}
211
212static apr_bucket *apr_bucket_lob_create(const apr_dbd_row_t *row, int col,
213                                         apr_off_t offset,
214                                         apr_size_t len, apr_pool_t *p,
215                                         apr_bucket_alloc_t *list)
216{
217    apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
218
219    APR_BUCKET_INIT(b);
220    b->free = apr_bucket_free;
221    b->list = list;
222    return apr_bucket_lob_make(b, row, col, offset, len, p);
223}
224
225static apr_status_t free_result(void *data)
226{
227    mysql_free_result(data);
228    return APR_SUCCESS;
229}
230
231static int dbd_mysql_select(apr_pool_t *pool, apr_dbd_t *sql,
232                            apr_dbd_results_t **results,
233                            const char *query, int seek)
234{
235    int sz;
236    int ret;
237    if (sql->trans && sql->trans->errnum) {
238        return sql->trans->errnum;
239    }
240    ret = mysql_query(sql->conn, query);
241    if (!ret) {
242        if (sz = mysql_field_count(sql->conn), sz > 0) {
243            if (!*results) {
244                *results = apr_palloc(pool, sizeof(apr_dbd_results_t));
245            }
246            (*results)->random = seek;
247            (*results)->statement = NULL;
248            (*results)->pool = pool;
249            if (seek) {
250                (*results)->res = mysql_store_result(sql->conn);
251            }
252            else {
253                (*results)->res = mysql_use_result(sql->conn);
254            }
255            apr_pool_cleanup_register(pool, (*results)->res,
256                                      free_result,apr_pool_cleanup_null);
257        }
258    } else {
259        ret = mysql_errno(sql->conn);
260    }
261
262    if (TXN_NOTICE_ERRORS(sql->trans)) {
263        sql->trans->errnum = ret;
264    }
265    return ret;
266}
267
268static const char *dbd_mysql_get_name(const apr_dbd_results_t *res, int n)
269{
270    if ((n < 0) || (n >= (int) mysql_num_fields(res->res))) {
271        return NULL;
272    }
273
274    return mysql_fetch_fields(res->res)[n].name;
275}
276
277static int dbd_mysql_get_row(apr_pool_t *pool, apr_dbd_results_t *res,
278                             apr_dbd_row_t **row, int rownum)
279{
280    MYSQL_ROW r = NULL;
281    int ret = 0;
282
283    if (res->statement) {
284        if (res->random) {
285            if (rownum > 0) {
286                mysql_stmt_data_seek(res->statement, (my_ulonglong) --rownum);
287            }
288            else {
289                return -1; /* invalid row */
290            }
291        }
292        ret = mysql_stmt_fetch(res->statement);
293        switch (ret) {
294        case 1:
295            ret = mysql_stmt_errno(res->statement);
296            break;
297        case MYSQL_NO_DATA:
298            ret = -1;
299            break;
300        default:
301            ret = 0; /* bad luck - get_entry will deal with this */
302            break;
303        }
304    }
305    else {
306        if (res->random) {
307            if (rownum > 0) {
308                mysql_data_seek(res->res, (my_ulonglong) --rownum);
309            }
310            else {
311                return -1; /* invalid row */
312            }
313        }
314        r = mysql_fetch_row(res->res);
315        if (r == NULL) {
316            ret = -1;
317        }
318    }
319    if (ret == 0) {
320        if (!*row) {
321            *row = apr_palloc(pool, sizeof(apr_dbd_row_t));
322        }
323        (*row)->row = r;
324        (*row)->res = res;
325        (*row)->len = mysql_fetch_lengths(res->res);
326    }
327    else {
328        apr_pool_cleanup_run(res->pool, res->res, free_result);
329    }
330    return ret;
331}
332#if 0
333/* An improved API that was proposed but not followed up */
334static int dbd_mysql_get_entry(const apr_dbd_row_t *row, int n,
335                               apr_dbd_datum_t *val)
336{
337    MYSQL_BIND *bind;
338    if (dbd_mysql_num_cols(row->res) <= n) {
339    	return NULL;
340    }
341    if (row->res->statement) {
342        bind = &row->res->bind[n];
343        if (mysql_stmt_fetch_column(row->res->statement, bind, n, 0) != 0) {
344            val->type = APR_DBD_VALUE_NULL;
345            return -1;
346        }
347        if (*bind->is_null) {
348            val->type = APR_DBD_VALUE_NULL;
349            return -1;
350        }
351        else {
352            val->type = APR_DBD_VALUE_STRING;
353            val->value.stringval = bind->buffer;
354        }
355    }
356    else {
357        val->type = APR_DBD_VALUE_STRING;
358        val->value.stringval = row->row[n];
359    }
360    return 0;
361}
362#else
363
364static const char *dbd_mysql_get_entry(const apr_dbd_row_t *row, int n)
365{
366    MYSQL_BIND *bind;
367    if (dbd_mysql_num_cols(row->res) <= n) {
368    	return NULL;
369    }
370    if (row->res->statement) {
371        bind = &row->res->bind[n];
372        if (mysql_stmt_fetch_column(row->res->statement, bind, n, 0) != 0) {
373            return NULL;
374        }
375        if (*bind->is_null) {
376            return NULL;
377        }
378        else {
379            return bind->buffer;
380        }
381    }
382    else {
383        return row->row[n];
384    }
385    return NULL;
386}
387#endif
388
389static apr_status_t dbd_mysql_datum_get(const apr_dbd_row_t *row, int n,
390                                        apr_dbd_type_e type, void *data)
391{
392    if (row->res->statement) {
393        MYSQL_BIND *bind = &row->res->bind[n];
394        unsigned long len = *bind->length;
395
396        if (mysql_stmt_fetch_column(row->res->statement, bind, n, 0) != 0) {
397            return APR_EGENERAL;
398        }
399
400        if (*bind->is_null) {
401            return APR_ENOENT;
402        }
403
404        switch (type) {
405        case APR_DBD_TYPE_TINY:
406            *(char*)data = atoi(bind->buffer);
407            break;
408        case APR_DBD_TYPE_UTINY:
409            *(unsigned char*)data = atoi(bind->buffer);
410            break;
411        case APR_DBD_TYPE_SHORT:
412            *(short*)data = atoi(bind->buffer);
413            break;
414        case APR_DBD_TYPE_USHORT:
415            *(unsigned short*)data = atoi(bind->buffer);
416            break;
417        case APR_DBD_TYPE_INT:
418            *(int*)data = atoi(bind->buffer);
419            break;
420        case APR_DBD_TYPE_UINT:
421            *(unsigned int*)data = atoi(bind->buffer);
422            break;
423        case APR_DBD_TYPE_LONG:
424            *(long*)data = atol(bind->buffer);
425            break;
426        case APR_DBD_TYPE_ULONG:
427            *(unsigned long*)data = atol(bind->buffer);
428            break;
429        case APR_DBD_TYPE_LONGLONG:
430            *(apr_int64_t*)data = apr_atoi64(bind->buffer);
431            break;
432        case APR_DBD_TYPE_ULONGLONG:
433            *(apr_uint64_t*)data = apr_atoi64(bind->buffer);
434            break;
435        case APR_DBD_TYPE_FLOAT:
436            *(float*)data = (float) atof(bind->buffer);
437            break;
438        case APR_DBD_TYPE_DOUBLE:
439            *(double*)data = atof(bind->buffer);
440            break;
441        case APR_DBD_TYPE_STRING:
442        case APR_DBD_TYPE_TEXT:
443        case APR_DBD_TYPE_TIME:
444        case APR_DBD_TYPE_DATE:
445        case APR_DBD_TYPE_DATETIME:
446        case APR_DBD_TYPE_TIMESTAMP:
447        case APR_DBD_TYPE_ZTIMESTAMP:
448            *((char*)bind->buffer+bind->buffer_length-1) = '\0';
449            *(char**)data = bind->buffer;
450            break;
451        case APR_DBD_TYPE_BLOB:
452        case APR_DBD_TYPE_CLOB:
453            {
454            apr_bucket *e;
455            apr_bucket_brigade *b = (apr_bucket_brigade*)data;
456
457            e = apr_bucket_lob_create(row, n, 0, len,
458                                      row->res->pool, b->bucket_alloc);
459            APR_BRIGADE_INSERT_TAIL(b, e);
460            }
461            break;
462        case APR_DBD_TYPE_NULL:
463            *(void**)data = NULL;
464            break;
465        default:
466            return APR_EGENERAL;
467        }
468    }
469    else {
470        if (row->row[n] == NULL) {
471            return APR_ENOENT;
472        }
473
474        switch (type) {
475        case APR_DBD_TYPE_TINY:
476            *(char*)data = atoi(row->row[n]);
477            break;
478        case APR_DBD_TYPE_UTINY:
479            *(unsigned char*)data = atoi(row->row[n]);
480            break;
481        case APR_DBD_TYPE_SHORT:
482            *(short*)data = atoi(row->row[n]);
483            break;
484        case APR_DBD_TYPE_USHORT:
485            *(unsigned short*)data = atoi(row->row[n]);
486            break;
487        case APR_DBD_TYPE_INT:
488            *(int*)data = atoi(row->row[n]);
489            break;
490        case APR_DBD_TYPE_UINT:
491            *(unsigned int*)data = atoi(row->row[n]);
492            break;
493        case APR_DBD_TYPE_LONG:
494            *(long*)data = atol(row->row[n]);
495            break;
496        case APR_DBD_TYPE_ULONG:
497            *(unsigned long*)data = atol(row->row[n]);
498            break;
499        case APR_DBD_TYPE_LONGLONG:
500            *(apr_int64_t*)data = apr_atoi64(row->row[n]);
501            break;
502        case APR_DBD_TYPE_ULONGLONG:
503            *(apr_uint64_t*)data = apr_atoi64(row->row[n]);
504            break;
505        case APR_DBD_TYPE_FLOAT:
506            *(float*)data = (float) atof(row->row[n]);
507            break;
508        case APR_DBD_TYPE_DOUBLE:
509            *(double*)data = atof(row->row[n]);
510            break;
511        case APR_DBD_TYPE_STRING:
512        case APR_DBD_TYPE_TEXT:
513        case APR_DBD_TYPE_TIME:
514        case APR_DBD_TYPE_DATE:
515        case APR_DBD_TYPE_DATETIME:
516        case APR_DBD_TYPE_TIMESTAMP:
517        case APR_DBD_TYPE_ZTIMESTAMP:
518            *(char**)data = row->row[n];
519            break;
520        case APR_DBD_TYPE_BLOB:
521        case APR_DBD_TYPE_CLOB:
522            {
523            apr_bucket *e;
524            apr_bucket_brigade *b = (apr_bucket_brigade*)data;
525
526            e = apr_bucket_pool_create(row->row[n], row->len[n],
527                                       row->res->pool, b->bucket_alloc);
528            APR_BRIGADE_INSERT_TAIL(b, e);
529            }
530            break;
531        case APR_DBD_TYPE_NULL:
532            *(void**)data = NULL;
533            break;
534        default:
535            return APR_EGENERAL;
536        }
537    }
538    return 0;
539}
540
541static const char *dbd_mysql_error(apr_dbd_t *sql, int n)
542{
543    return mysql_error(sql->conn);
544}
545
546static int dbd_mysql_query(apr_dbd_t *sql, int *nrows, const char *query)
547{
548    int ret;
549    if (sql->trans && sql->trans->errnum) {
550        return sql->trans->errnum;
551    }
552    ret = mysql_query(sql->conn, query);
553    if (ret != 0) {
554        ret = mysql_errno(sql->conn);
555    }
556    *nrows = (int) mysql_affected_rows(sql->conn);
557    if (TXN_NOTICE_ERRORS(sql->trans)) {
558        sql->trans->errnum = ret;
559    }
560    return ret;
561}
562
563static const char *dbd_mysql_escape(apr_pool_t *pool, const char *arg,
564                                    apr_dbd_t *sql)
565{
566    unsigned long len = strlen(arg);
567    char *ret = apr_palloc(pool, 2*len + 1);
568    mysql_real_escape_string(sql->conn, ret, arg, len);
569    return ret;
570}
571
572static apr_status_t stmt_close(void *data)
573{
574    mysql_stmt_close(data);
575    return APR_SUCCESS;
576}
577
578static int dbd_mysql_prepare(apr_pool_t *pool, apr_dbd_t *sql,
579                             const char *query, const char *label,
580                             int nargs, int nvals, apr_dbd_type_e *types,
581                             apr_dbd_prepared_t **statement)
582{
583    /* Translate from apr_dbd to native query format */
584    int ret;
585
586    if (!*statement) {
587        *statement = apr_palloc(pool, sizeof(apr_dbd_prepared_t));
588    }
589    (*statement)->stmt = mysql_stmt_init(sql->conn);
590
591    if ((*statement)->stmt) {
592        apr_pool_cleanup_register(pool, (*statement)->stmt,
593                                  stmt_close, apr_pool_cleanup_null);
594        ret = mysql_stmt_prepare((*statement)->stmt, query, strlen(query));
595
596        if (ret != 0) {
597            ret = mysql_stmt_errno((*statement)->stmt);
598        }
599
600        (*statement)->nargs = nargs;
601        (*statement)->nvals = nvals;
602        (*statement)->types = types;
603
604        return ret;
605    }
606
607    return CR_OUT_OF_MEMORY;
608}
609
610static void dbd_mysql_bind(apr_dbd_prepared_t *statement,
611                           const char **values, MYSQL_BIND *bind)
612{
613    int i, j;
614
615    for (i = 0, j = 0; i < statement->nargs; i++, j++) {
616        bind[i].length = &bind[i].buffer_length;
617        bind[i].is_unsigned = 0;
618        bind[i].is_null = NULL;
619
620        if (values[j] == NULL) {
621            bind[i].buffer_type = MYSQL_TYPE_NULL;
622        }
623        else {
624            switch (statement->types[i]) {
625            case APR_DBD_TYPE_BLOB:
626            case APR_DBD_TYPE_CLOB:
627                bind[i].buffer_type = MYSQL_TYPE_LONG_BLOB;
628                bind[i].buffer = (void*)values[j];
629                bind[i].buffer_length = atol(values[++j]);
630
631                /* skip table and column */
632                j += 2;
633                break;
634            default:
635                bind[i].buffer_type = MYSQL_TYPE_VAR_STRING;
636                bind[i].buffer = (void*)values[j];
637                bind[i].buffer_length = strlen(values[j]);
638                break;
639            }
640        }
641    }
642
643    return;
644}
645
646static int dbd_mysql_pquery_internal(apr_pool_t *pool, apr_dbd_t *sql,
647                                     int *nrows, apr_dbd_prepared_t *statement,
648                                     MYSQL_BIND *bind)
649{
650    int ret;
651
652    ret = mysql_stmt_bind_param(statement->stmt, bind);
653    if (ret != 0) {
654        *nrows = 0;
655        ret = mysql_stmt_errno(statement->stmt);
656    }
657    else {
658        ret = mysql_stmt_execute(statement->stmt);
659        if (ret != 0) {
660            ret = mysql_stmt_errno(statement->stmt);
661        }
662        *nrows = (int) mysql_stmt_affected_rows(statement->stmt);
663    }
664
665    return ret;
666}
667
668static int dbd_mysql_pquery(apr_pool_t *pool, apr_dbd_t *sql,
669                            int *nrows, apr_dbd_prepared_t *statement,
670                            const char **values)
671{
672    MYSQL_BIND *bind;
673    int ret;
674
675    if (sql->trans && sql->trans->errnum) {
676        return sql->trans->errnum;
677    }
678
679    bind = apr_palloc(pool, statement->nargs * sizeof(MYSQL_BIND));
680
681    dbd_mysql_bind(statement, values, bind);
682
683    ret = dbd_mysql_pquery_internal(pool, sql, nrows, statement, bind);
684
685    if (TXN_NOTICE_ERRORS(sql->trans)) {
686        sql->trans->errnum = ret;
687    }
688    return ret;
689}
690
691static int dbd_mysql_pvquery(apr_pool_t *pool, apr_dbd_t *sql, int *nrows,
692                             apr_dbd_prepared_t *statement, va_list args)
693{
694    const char **values;
695    int i;
696
697    if (sql->trans && sql->trans->errnum) {
698        return sql->trans->errnum;
699    }
700
701    values = apr_palloc(pool, sizeof(*values) * statement->nvals);
702
703    for (i = 0; i < statement->nvals; i++) {
704        values[i] = va_arg(args, const char*);
705    }
706
707    return dbd_mysql_pquery(pool, sql, nrows, statement, values);
708}
709
710static int dbd_mysql_pselect_internal(apr_pool_t *pool, apr_dbd_t *sql,
711                                      apr_dbd_results_t **res,
712                                      apr_dbd_prepared_t *statement,
713                                      int random, MYSQL_BIND *bind)
714{
715    int nfields, i;
716    my_bool *is_nullr;
717#if MYSQL_VERSION_ID >= 50000
718    my_bool *error;
719#endif
720    int ret;
721    unsigned long *length, maxlen;
722
723    ret = mysql_stmt_bind_param(statement->stmt, bind);
724    if (ret == 0) {
725        ret = mysql_stmt_execute(statement->stmt);
726        if (!ret) {
727            if (!*res) {
728                *res = apr_pcalloc(pool, sizeof(apr_dbd_results_t));
729            }
730            (*res)->random = random;
731            (*res)->statement = statement->stmt;
732            (*res)->res = mysql_stmt_result_metadata(statement->stmt);
733            (*res)->pool = pool;
734            apr_pool_cleanup_register(pool, (*res)->res,
735                                      free_result, apr_pool_cleanup_null);
736            nfields = mysql_num_fields((*res)->res);
737            if (!(*res)->bind) {
738                (*res)->bind = apr_palloc(pool, nfields*sizeof(MYSQL_BIND));
739                length = apr_pcalloc(pool, nfields*sizeof(unsigned long));
740#if MYSQL_VERSION_ID >= 50000
741                error = apr_palloc(pool, nfields*sizeof(my_bool));
742#endif
743                is_nullr = apr_pcalloc(pool, nfields*sizeof(my_bool));
744                for ( i = 0; i < nfields; ++i ) {
745                    maxlen = ((*res)->res->fields[i].length < sql->fldsz ?
746                              (*res)->res->fields[i].length : sql->fldsz) + 1;
747                    if ((*res)->res->fields[i].type == MYSQL_TYPE_BLOB) {
748                        (*res)->bind[i].buffer_type = MYSQL_TYPE_LONG_BLOB;
749                    }
750                    else {
751                        (*res)->bind[i].buffer_type = MYSQL_TYPE_VAR_STRING;
752                    }
753                    (*res)->bind[i].buffer_length = maxlen;
754                    (*res)->bind[i].length = &length[i];
755                    (*res)->bind[i].buffer = apr_palloc(pool, maxlen);
756                    (*res)->bind[i].is_null = is_nullr+i;
757#if MYSQL_VERSION_ID >= 50000
758                    (*res)->bind[i].error = error+i;
759#endif
760                }
761            }
762            ret = mysql_stmt_bind_result(statement->stmt, (*res)->bind);
763            if (!ret) {
764                ret = mysql_stmt_store_result(statement->stmt);
765            }
766        }
767    }
768    if (ret != 0) {
769        ret = mysql_stmt_errno(statement->stmt);
770    }
771
772    return ret;
773}
774
775static int dbd_mysql_pselect(apr_pool_t *pool, apr_dbd_t *sql,
776                             apr_dbd_results_t **res,
777                             apr_dbd_prepared_t *statement, int random,
778                             const char **args)
779{
780    int ret;
781    MYSQL_BIND *bind;
782
783    if (sql->trans && sql->trans->errnum) {
784        return sql->trans->errnum;
785    }
786
787    bind = apr_palloc(pool, statement->nargs * sizeof(MYSQL_BIND));
788
789    dbd_mysql_bind(statement, args, bind);
790
791    ret = dbd_mysql_pselect_internal(pool, sql,  res, statement, random, bind);
792
793    if (TXN_NOTICE_ERRORS(sql->trans)) {
794        sql->trans->errnum = ret;
795    }
796    return ret;
797}
798
799static int dbd_mysql_pvselect(apr_pool_t *pool, apr_dbd_t *sql,
800                              apr_dbd_results_t **res,
801                              apr_dbd_prepared_t *statement, int random,
802                              va_list args)
803{
804    const char **values;
805    int i;
806
807    if (sql->trans && sql->trans->errnum) {
808        return sql->trans->errnum;
809    }
810
811    values = apr_palloc(pool, sizeof(*values) * statement->nvals);
812
813    for (i = 0; i < statement->nvals; i++) {
814        values[i] = va_arg(args, const char*);
815    }
816
817    return dbd_mysql_pselect(pool, sql, res, statement, random, values);
818}
819
820static void dbd_mysql_bbind(apr_pool_t *pool, apr_dbd_prepared_t *statement,
821                            const void **values, MYSQL_BIND *bind)
822{
823    void *arg;
824    int i, j;
825    apr_dbd_type_e type;
826
827    for (i = 0, j = 0; i < statement->nargs; i++, j++) {
828        arg = (void *)values[j];
829
830        bind[i].length = &bind[i].buffer_length;
831        bind[i].is_null = NULL;
832
833        type = (arg == NULL ? APR_DBD_TYPE_NULL : statement->types[i]);
834        switch (type) {
835        case APR_DBD_TYPE_TINY:
836            bind[i].buffer = arg;
837            bind[i].buffer_type = MYSQL_TYPE_TINY;
838            bind[i].is_unsigned = 0;
839            break;
840        case APR_DBD_TYPE_UTINY:
841            bind[i].buffer = arg;
842            bind[i].buffer_type = MYSQL_TYPE_TINY;
843            bind[i].is_unsigned = 1;
844            break;
845        case APR_DBD_TYPE_SHORT:
846            bind[i].buffer = arg;
847            bind[i].buffer_type = MYSQL_TYPE_SHORT;
848            bind[i].is_unsigned = 0;
849            break;
850        case APR_DBD_TYPE_USHORT:
851            bind[i].buffer = arg;
852            bind[i].buffer_type = MYSQL_TYPE_SHORT;
853            bind[i].is_unsigned = 1;
854            break;
855        case APR_DBD_TYPE_INT:
856            bind[i].buffer = arg;
857            bind[i].buffer_type = MYSQL_TYPE_LONG;
858            bind[i].is_unsigned = 0;
859            break;
860        case APR_DBD_TYPE_UINT:
861            bind[i].buffer = arg;
862            bind[i].buffer_type = MYSQL_TYPE_LONG;
863            bind[i].is_unsigned = 1;
864            break;
865        case APR_DBD_TYPE_LONG:
866            if (sizeof(int) == sizeof(long)) {
867                bind[i].buffer = arg;
868            }
869            else {
870                bind[i].buffer = apr_palloc(pool, sizeof(int));
871                *(int*)bind[i].buffer = *(long*)arg;
872            }
873            bind[i].buffer_type = MYSQL_TYPE_LONG;
874            bind[i].is_unsigned = 0;
875            break;
876        case APR_DBD_TYPE_ULONG:
877            if (sizeof(unsigned int) == sizeof(unsigned long)) {
878                bind[i].buffer = arg;
879            }
880            else {
881                bind[i].buffer = apr_palloc(pool, sizeof(unsigned int));
882                *(unsigned int*)bind[i].buffer = *(unsigned long*)arg;
883            }
884            bind[i].buffer_type = MYSQL_TYPE_LONG;
885            bind[i].is_unsigned = 1;
886            break;
887        case APR_DBD_TYPE_LONGLONG:
888            if (sizeof(my_ulonglong) == sizeof(apr_int64_t)) {
889                bind[i].buffer = arg;
890                bind[i].buffer_type = MYSQL_TYPE_LONGLONG;
891            }
892            else { /* have to downsize, long long is not portable */
893                bind[i].buffer = apr_palloc(pool, sizeof(long));
894                *(long*)bind[i].buffer = (long) *(apr_int64_t*)arg;
895                bind[i].buffer_type = MYSQL_TYPE_LONG;
896            }
897            bind[i].is_unsigned = 0;
898            break;
899        case APR_DBD_TYPE_ULONGLONG:
900            if (sizeof(my_ulonglong) == sizeof(apr_uint64_t)) {
901                bind[i].buffer = arg;
902                bind[i].buffer_type = MYSQL_TYPE_LONGLONG;
903            }
904            else { /* have to downsize, long long is not portable */
905                bind[i].buffer = apr_palloc(pool, sizeof(long));
906                *(unsigned long*)bind[i].buffer =
907                    (unsigned long) *(apr_uint64_t*)arg;
908                bind[i].buffer_type = MYSQL_TYPE_LONG;
909            }
910            bind[i].is_unsigned = 1;
911            break;
912        case APR_DBD_TYPE_FLOAT:
913            bind[i].buffer = arg;
914            bind[i].buffer_type = MYSQL_TYPE_FLOAT;
915            bind[i].is_unsigned = 0;
916            break;
917        case APR_DBD_TYPE_DOUBLE:
918            bind[i].buffer = arg;
919            bind[i].buffer_type = MYSQL_TYPE_DOUBLE;
920            bind[i].is_unsigned = 0;
921            break;
922        case APR_DBD_TYPE_STRING:
923        case APR_DBD_TYPE_TEXT:
924        case APR_DBD_TYPE_TIME:
925        case APR_DBD_TYPE_DATE:
926        case APR_DBD_TYPE_DATETIME:
927        case APR_DBD_TYPE_TIMESTAMP:
928        case APR_DBD_TYPE_ZTIMESTAMP:
929            bind[i].buffer = arg;
930            bind[i].buffer_type = MYSQL_TYPE_VAR_STRING;
931            bind[i].is_unsigned = 0;
932            bind[i].buffer_length = strlen((const char *)arg);
933            break;
934        case APR_DBD_TYPE_BLOB:
935        case APR_DBD_TYPE_CLOB:
936            bind[i].buffer = (void *)arg;
937            bind[i].buffer_type = MYSQL_TYPE_LONG_BLOB;
938            bind[i].is_unsigned = 0;
939            bind[i].buffer_length = *(apr_size_t*)values[++j];
940
941            /* skip table and column */
942            j += 2;
943            break;
944        case APR_DBD_TYPE_NULL:
945        default:
946            bind[i].buffer_type = MYSQL_TYPE_NULL;
947            break;
948        }
949    }
950
951    return;
952}
953
954static int dbd_mysql_pbquery(apr_pool_t *pool, apr_dbd_t *sql,
955                             int *nrows, apr_dbd_prepared_t *statement,
956                             const void **values)
957{
958    MYSQL_BIND *bind;
959    int ret;
960
961    if (sql->trans && sql->trans->errnum) {
962        return sql->trans->errnum;
963    }
964
965    bind = apr_palloc(pool, statement->nargs * sizeof(MYSQL_BIND));
966
967    dbd_mysql_bbind(pool, statement, values, bind);
968
969    ret = dbd_mysql_pquery_internal(pool, sql, nrows, statement, bind);
970
971    if (TXN_NOTICE_ERRORS(sql->trans)) {
972        sql->trans->errnum = ret;
973    }
974    return ret;
975}
976
977static int dbd_mysql_pvbquery(apr_pool_t *pool, apr_dbd_t *sql, int *nrows,
978                              apr_dbd_prepared_t *statement, va_list args)
979{
980    const void **values;
981    int i;
982
983    if (sql->trans && sql->trans->errnum) {
984        return sql->trans->errnum;
985    }
986
987    values = apr_palloc(pool, sizeof(*values) * statement->nvals);
988
989    for (i = 0; i < statement->nvals; i++) {
990        values[i] = va_arg(args, const void*);
991    }
992
993    return dbd_mysql_pbquery(pool, sql, nrows, statement, values);
994}
995
996static int dbd_mysql_pbselect(apr_pool_t *pool, apr_dbd_t *sql,
997                              apr_dbd_results_t **res,
998                              apr_dbd_prepared_t *statement, int random,
999                              const void **args)
1000{
1001    int ret;
1002    MYSQL_BIND *bind;
1003
1004    if (sql->trans && sql->trans->errnum) {
1005        return sql->trans->errnum;
1006    }
1007
1008    bind = apr_palloc(pool, statement->nargs * sizeof(MYSQL_BIND));
1009
1010    dbd_mysql_bbind(pool, statement, args, bind);
1011
1012    ret = dbd_mysql_pselect_internal(pool, sql,  res, statement, random, bind);
1013
1014    if (TXN_NOTICE_ERRORS(sql->trans)) {
1015        sql->trans->errnum = ret;
1016    }
1017    return ret;
1018}
1019
1020static int dbd_mysql_pvbselect(apr_pool_t *pool, apr_dbd_t *sql,
1021                               apr_dbd_results_t **res,
1022                               apr_dbd_prepared_t *statement, int random,
1023                               va_list args)
1024{
1025    const void **values;
1026    int i;
1027
1028    if (sql->trans && sql->trans->errnum) {
1029        return sql->trans->errnum;
1030    }
1031
1032    values = apr_palloc(pool, sizeof(*values) * statement->nvals);
1033
1034    for (i = 0; i < statement->nvals; i++) {
1035        values[i] = va_arg(args, const void*);
1036    }
1037
1038    return dbd_mysql_pbselect(pool, sql, res, statement, random, values);
1039}
1040
1041static int dbd_mysql_end_transaction(apr_dbd_transaction_t *trans)
1042{
1043    int ret = -1;
1044    if (trans) {
1045        /* rollback on error or explicit rollback request */
1046        if (trans->errnum || TXN_DO_ROLLBACK(trans)) {
1047            trans->errnum = 0;
1048            ret = mysql_rollback(trans->handle->conn);
1049        }
1050        else {
1051            ret = mysql_commit(trans->handle->conn);
1052        }
1053        ret |= mysql_autocommit(trans->handle->conn, 1);
1054        trans->handle->trans = NULL;
1055    }
1056    return ret;
1057}
1058/* Whether or not transactions work depends on whether the
1059 * underlying DB supports them within MySQL.  Unfortunately
1060 * it fails silently with the default InnoDB.
1061 */
1062
1063static int dbd_mysql_transaction(apr_pool_t *pool, apr_dbd_t *handle,
1064                                 apr_dbd_transaction_t **trans)
1065{
1066    /* Don't try recursive transactions here */
1067    if (handle->trans) {
1068        dbd_mysql_end_transaction(handle->trans) ;
1069    }
1070    if (!*trans) {
1071        *trans = apr_pcalloc(pool, sizeof(apr_dbd_transaction_t));
1072    }
1073    (*trans)->errnum = mysql_autocommit(handle->conn, 0);
1074    (*trans)->handle = handle;
1075    handle->trans = *trans;
1076    return (*trans)->errnum;
1077}
1078
1079static int dbd_mysql_transaction_mode_get(apr_dbd_transaction_t *trans)
1080{
1081    if (!trans)
1082        return APR_DBD_TRANSACTION_COMMIT;
1083
1084    return trans->mode;
1085}
1086
1087static int dbd_mysql_transaction_mode_set(apr_dbd_transaction_t *trans,
1088                                          int mode)
1089{
1090    if (!trans)
1091        return APR_DBD_TRANSACTION_COMMIT;
1092
1093    return trans->mode = (mode & TXN_MODE_BITS);
1094}
1095
1096static apr_dbd_t *dbd_mysql_open(apr_pool_t *pool, const char *params,
1097                                 const char **error)
1098{
1099    static const char *const delims = " \r\n\t;|,";
1100    const char *ptr;
1101    int i;
1102    const char *key;
1103    size_t klen;
1104    const char *value;
1105    size_t vlen;
1106#if MYSQL_VERSION_ID >= 50013
1107    my_bool do_reconnect = 1;
1108#endif
1109    MYSQL *real_conn;
1110    unsigned long flags = 0;
1111
1112    struct {
1113        const char *field;
1114        const char *value;
1115    } fields[] = {
1116        {"host", NULL},
1117        {"user", NULL},
1118        {"pass", NULL},
1119        {"dbname", NULL},
1120        {"port", NULL},
1121        {"sock", NULL},
1122        {"flags", NULL},
1123        {"fldsz", NULL},
1124        {"group", NULL},
1125        {"reconnect", NULL},
1126        {NULL, NULL}
1127    };
1128    unsigned int port = 0;
1129    apr_dbd_t *sql = apr_pcalloc(pool, sizeof(apr_dbd_t));
1130    sql->fldsz = FIELDSIZE;
1131    sql->conn = mysql_init(sql->conn);
1132    if ( sql->conn == NULL ) {
1133        return NULL;
1134    }
1135    for (ptr = strchr(params, '='); ptr; ptr = strchr(ptr, '=')) {
1136        /* don't dereference memory that may not belong to us */
1137        if (ptr == params) {
1138            ++ptr;
1139            continue;
1140        }
1141        for (key = ptr-1; apr_isspace(*key); --key);
1142        klen = 0;
1143        while (apr_isalpha(*key)) {
1144            /* don't parse backwards off the start of the string */
1145            if (key == params) {
1146                --key;
1147                ++klen;
1148                break;
1149            }
1150            --key;
1151            ++klen;
1152        }
1153        ++key;
1154        for (value = ptr+1; apr_isspace(*value); ++value);
1155        vlen = strcspn(value, delims);
1156        for (i = 0; fields[i].field != NULL; i++) {
1157            if (!strncasecmp(fields[i].field, key, klen)) {
1158                fields[i].value = apr_pstrndup(pool, value, vlen);
1159                break;
1160            }
1161        }
1162        ptr = value+vlen;
1163    }
1164    if (fields[4].value != NULL) {
1165        port = atoi(fields[4].value);
1166    }
1167    if (fields[6].value != NULL &&
1168        !strcmp(fields[6].value, "CLIENT_FOUND_ROWS")) {
1169        flags |= CLIENT_FOUND_ROWS; /* only option we know */
1170    }
1171    if (fields[7].value != NULL) {
1172        sql->fldsz = atol(fields[7].value);
1173    }
1174    if (fields[8].value != NULL) {
1175         mysql_options(sql->conn, MYSQL_READ_DEFAULT_GROUP, fields[8].value);
1176    }
1177#if MYSQL_VERSION_ID >= 50013
1178    if (fields[9].value != NULL) {
1179         do_reconnect = atoi(fields[9].value) ? 1 : 0;
1180    }
1181#endif
1182
1183#if MYSQL_VERSION_ID >= 50013
1184    /* the MySQL manual says this should be BEFORE mysql_real_connect */
1185    mysql_options(sql->conn, MYSQL_OPT_RECONNECT, &do_reconnect);
1186#endif
1187
1188    real_conn = mysql_real_connect(sql->conn, fields[0].value,
1189                                   fields[1].value, fields[2].value,
1190                                   fields[3].value, port,
1191                                   fields[5].value, flags);
1192
1193    if(real_conn == NULL) {
1194        if (error) {
1195            *error = apr_pstrdup(pool, mysql_error(sql->conn));
1196        }
1197        mysql_close(sql->conn);
1198        return NULL;
1199    }
1200
1201#if MYSQL_VERSION_ID >= 50013
1202    /* Some say this should be AFTER mysql_real_connect */
1203    mysql_options(sql->conn, MYSQL_OPT_RECONNECT, &do_reconnect);
1204#endif
1205
1206    return sql;
1207}
1208
1209static apr_status_t dbd_mysql_close(apr_dbd_t *handle)
1210{
1211    mysql_close(handle->conn);
1212    return APR_SUCCESS;
1213}
1214
1215static apr_status_t dbd_mysql_check_conn(apr_pool_t *pool,
1216                                         apr_dbd_t *handle)
1217{
1218    return mysql_ping(handle->conn) ? APR_EGENERAL : APR_SUCCESS;
1219}
1220
1221static int dbd_mysql_select_db(apr_pool_t *pool, apr_dbd_t* handle,
1222                               const char* name)
1223{
1224    return mysql_select_db(handle->conn, name);
1225}
1226
1227static void *dbd_mysql_native(apr_dbd_t *handle)
1228{
1229    return handle->conn;
1230}
1231
1232static int dbd_mysql_num_cols(apr_dbd_results_t *res)
1233{
1234    if (res->statement) {
1235        return mysql_stmt_field_count(res->statement);
1236    }
1237    else {
1238        return mysql_num_fields(res->res);
1239    }
1240}
1241
1242static int dbd_mysql_num_tuples(apr_dbd_results_t *res)
1243{
1244    if (res->random) {
1245        if (res->statement) {
1246            return (int) mysql_stmt_num_rows(res->statement);
1247        }
1248        else {
1249            return (int) mysql_num_rows(res->res);
1250        }
1251    }
1252    else {
1253        return -1;
1254    }
1255}
1256
1257static apr_status_t thread_end(void *data)
1258{
1259    mysql_thread_end();
1260    return APR_SUCCESS;
1261}
1262
1263static void dbd_mysql_init(apr_pool_t *pool)
1264{
1265    my_init();
1266    mysql_thread_init();
1267
1268    /* FIXME: this is a guess; find out what it really does */
1269    apr_pool_cleanup_register(pool, NULL, thread_end, apr_pool_cleanup_null);
1270}
1271APU_MODULE_DECLARE_DATA const apr_dbd_driver_t apr_dbd_mysql_driver = {
1272    "mysql",
1273    dbd_mysql_init,
1274    dbd_mysql_native,
1275    dbd_mysql_open,
1276    dbd_mysql_check_conn,
1277    dbd_mysql_close,
1278    dbd_mysql_select_db,
1279    dbd_mysql_transaction,
1280    dbd_mysql_end_transaction,
1281    dbd_mysql_query,
1282    dbd_mysql_select,
1283    dbd_mysql_num_cols,
1284    dbd_mysql_num_tuples,
1285    dbd_mysql_get_row,
1286    dbd_mysql_get_entry,
1287    dbd_mysql_error,
1288    dbd_mysql_escape,
1289    dbd_mysql_prepare,
1290    dbd_mysql_pvquery,
1291    dbd_mysql_pvselect,
1292    dbd_mysql_pquery,
1293    dbd_mysql_pselect,
1294    dbd_mysql_get_name,
1295    dbd_mysql_transaction_mode_get,
1296    dbd_mysql_transaction_mode_set,
1297    "?",
1298    dbd_mysql_pvbquery,
1299    dbd_mysql_pvbselect,
1300    dbd_mysql_pbquery,
1301    dbd_mysql_pbselect,
1302    dbd_mysql_datum_get
1303};
1304
1305#endif
1306