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#include "apu_config.h"
19
20/* COMPILE_STUBS: compile stubs for unimplemented functions.
21 *
22 * This is required to compile in /trunk/, but can be
23 * undefined to compile a driver for httpd-2.2 and other
24 * APR-1.2 applications
25 */
26#define COMPILE_STUBS
27
28#if APU_HAVE_FREETDS
29
30#include <ctype.h>
31#include <stdlib.h>
32
33#include "apr_strings.h"
34#include "apr_lib.h"
35
36#include "apr_pools.h"
37#include "apr_dbd_internal.h"
38
39#ifdef HAVE_FREETDS_SYBDB_H
40#include <freetds/sybdb.h>
41#endif
42#ifdef HAVE_SYBDB_H
43#include <sybdb.h>
44#endif
45
46#include <stdio.h>
47#include <sys/types.h>
48#include <regex.h>
49
50/* This probably needs to change for different applications */
51#define MAX_COL_LEN 256
52
53typedef struct freetds_cell_t {
54    int type;
55    DBINT len;
56    BYTE *data;
57} freetds_cell_t;
58
59struct apr_dbd_transaction_t {
60    int mode;
61    int errnum;
62    apr_dbd_t *handle;
63};
64
65struct apr_dbd_t {
66    DBPROCESS *proc;
67    apr_dbd_transaction_t *trans;
68    apr_pool_t *pool;
69    const char *params;
70    RETCODE err;
71};
72
73struct apr_dbd_results_t {
74    int random;
75    size_t ntuples;
76    size_t sz;
77    apr_pool_t *pool;
78    DBPROCESS *proc;
79};
80
81struct apr_dbd_row_t {
82    apr_dbd_results_t *res;
83    BYTE buf[MAX_COL_LEN];
84};
85
86struct apr_dbd_prepared_t {
87    int nargs;
88    regex_t **taint;
89    int *sz;
90    char *fmt;
91};
92
93#define dbd_freetds_is_success(x) (x == SUCCEED)
94
95static int labelnum = 0; /* FIXME */
96static regex_t dbd_freetds_find_arg;
97
98/* execute a query that doesn't return a result set, mop up,
99 * and return and APR-flavoured status
100 */
101static RETCODE freetds_exec(DBPROCESS *proc, const char *query,
102                            int want_results, int *nrows)
103{
104    /* TBD */
105    RETCODE rv = dbcmd(proc, query);
106    if (rv != SUCCEED) {
107        return rv;
108    }
109    rv = dbsqlexec(proc);
110    if (rv != SUCCEED) {
111        return rv;
112    }
113    if (!want_results) {
114        while (dbresults(proc) != NO_MORE_RESULTS) {
115            ++*nrows;
116        }
117    }
118    return SUCCEED;
119}
120static apr_status_t clear_result(void *data)
121{
122    /* clear cursor */
123    return (dbcanquery((DBPROCESS*)data) == SUCCEED)
124            ? APR_SUCCESS
125            : APR_EGENERAL;
126}
127
128static int dbd_freetds_select(apr_pool_t *pool, apr_dbd_t *sql,
129                              apr_dbd_results_t **results,
130                              const char *query, int seek)
131{
132    apr_dbd_results_t *res;
133    if (sql->trans && (sql->trans->errnum != SUCCEED)) {
134        return 1;
135    }
136    /* the core of this is
137     * dbcmd(proc, query);
138     * dbsqlexec(proc);
139     * while (dbnextrow(dbproc) != NO_MORE_ROWS) {
140     *     do things
141     * }
142     *
143     * Ignore seek
144     */
145
146    sql->err = freetds_exec(sql->proc, query, 1, NULL);
147    if (!dbd_freetds_is_success(sql->err)) {
148        if (sql->trans) {
149            sql->trans->errnum = sql->err;
150        }
151        return 1;
152    }
153
154    sql->err = dbresults(sql->proc);
155    if (sql->err != SUCCEED) {
156        if (sql->trans) {
157            sql->trans->errnum = sql->err;
158        }
159        return 1;
160    }
161
162    if (!*results) {
163        *results = apr_pcalloc(pool, sizeof(apr_dbd_results_t));
164    }
165    res = *results;
166    res->proc = sql->proc;
167    res->random = seek;
168    res->pool = pool;
169    res->ntuples = dblastrow(sql->proc);
170    res->sz = dbnumcols(sql->proc);
171    apr_pool_cleanup_register(pool, sql->proc, clear_result,
172                              apr_pool_cleanup_null);
173
174#if 0
175    /* Now we have a result set.  We need to bind to its vars */
176    res->vars = apr_palloc(pool, res->sz * sizeof(freetds_cell_t*));
177    for (i=1; i <= res->sz; ++i) {
178        freetds_cell_t *cell = &res->vars[i-1];
179        cell->type = dbcoltype(sql->proc, i);
180        cell->len = dbcollen(sql->proc, i);
181        cell->data = apr_palloc(pool, cell->len);
182        sql->err = dbbind(sql->proc, i, /*cell->type */ STRINGBIND, cell->len, cell->data);
183        if (sql->err != SUCCEED) {
184            fprintf(stderr, "dbbind error: %d, %d, %d", i, cell->type, cell->len);
185        }
186        if ((sql->err != SUCCEED) && (sql->trans != NULL)) {
187            sql->trans->errnum = sql->err;
188        }
189    }
190#endif
191    return (sql->err == SUCCEED) ? 0 : 1;
192}
193static const char *dbd_untaint(apr_pool_t *pool, regex_t *rx, const char *val)
194{
195    regmatch_t match[1];
196    if (rx == NULL) {
197        /* no untaint expression */
198        return val;
199    }
200    if (regexec(rx, val, 1, match, 0) == 0) {
201        return apr_pstrndup(pool, val+match[0].rm_so,
202                            match[0].rm_eo - match[0].rm_so);
203    }
204    return "";
205}
206static const char *dbd_statement(apr_pool_t *pool,
207                                 apr_dbd_prepared_t *stmt,
208                                 int nargs, const char **args)
209{
210    int i;
211    int len;
212    const char *var;
213    char *ret;
214    const char *p_in;
215    char *p_out;
216    char *q;
217
218    /* compute upper bound on length (since untaint shrinks) */
219    len  = strlen(stmt->fmt) +1;
220    for (i=0; i<nargs; ++i) {
221        len += strlen(args[i]) - 2;
222    }
223    i = 0;
224    p_in = stmt->fmt;
225    p_out = ret = apr_palloc(pool, len);
226    /* FIXME silly bug - this'll catch %%s */
227    while (q = strstr(p_in, "%s"), q != NULL) {
228        len = q-p_in;
229        strncpy(p_out, p_in, len);
230        p_in += len;
231        p_out += len;
232        var = dbd_untaint(pool, stmt->taint[i], args[i]);
233        len = strlen(var);
234        strncpy(p_out, var, len);
235        p_in += 2;
236        p_out += len;
237        ++i;
238    }
239    strcpy(p_out, p_in);
240    return ret;
241}
242static int dbd_freetds_pselect(apr_pool_t *pool, apr_dbd_t *sql,
243                               apr_dbd_results_t **results,
244                               apr_dbd_prepared_t *statement,
245                               int seek, const char **values)
246{
247    const char *query = dbd_statement(pool, statement,
248                                      statement->nargs, values);
249    return dbd_freetds_select(pool, sql, results, query, seek);
250}
251static int dbd_freetds_pvselect(apr_pool_t *pool, apr_dbd_t *sql,
252                                apr_dbd_results_t **results,
253                                apr_dbd_prepared_t *statement,
254                                int seek, va_list args)
255{
256    const char **values;
257    int i;
258
259    if (sql->trans && sql->trans->errnum) {
260        return sql->trans->errnum;
261    }
262
263    values = apr_palloc(pool, sizeof(*values) * statement->nargs);
264
265    for (i = 0; i < statement->nargs; i++) {
266        values[i] = va_arg(args, const char*);
267    }
268
269    return dbd_freetds_pselect(pool, sql, results, statement, seek, values);
270}
271static int dbd_freetds_query(apr_dbd_t *sql, int *nrows, const char *query);
272static int dbd_freetds_pquery(apr_pool_t *pool, apr_dbd_t *sql,
273                              int *nrows, apr_dbd_prepared_t *statement,
274                              const char **values)
275{
276    const char *query = dbd_statement(pool, statement,
277                                      statement->nargs, values);
278    return dbd_freetds_query(sql, nrows, query);
279}
280static int dbd_freetds_pvquery(apr_pool_t *pool, apr_dbd_t *sql, int *nrows,
281                               apr_dbd_prepared_t *statement, va_list args)
282{
283    const char **values;
284    int i;
285
286    if (sql->trans && sql->trans->errnum) {
287        return sql->trans->errnum;
288    }
289
290    values = apr_palloc(pool, sizeof(*values) * statement->nargs);
291
292    for (i = 0; i < statement->nargs; i++) {
293        values[i] = va_arg(args, const char*);
294    }
295    return dbd_freetds_pquery(pool, sql, nrows, statement, values);
296}
297
298static int dbd_freetds_get_row(apr_pool_t *pool, apr_dbd_results_t *res,
299                               apr_dbd_row_t **rowp, int rownum)
300{
301    RETCODE rv = 0;
302    apr_dbd_row_t *row = *rowp;
303    int sequential = ((rownum >= 0) && res->random) ? 0 : 1;
304
305    if (row == NULL) {
306        row = apr_palloc(pool, sizeof(apr_dbd_row_t));
307        *rowp = row;
308        row->res = res;
309    }
310    /*
311    else {
312        if ( sequential ) {
313            ++row->n;
314        }
315        else {
316            row->n = rownum;
317        }
318    }
319    */
320    if (sequential) {
321        rv = dbnextrow(res->proc);
322    }
323    else {
324        rv = (rownum >= 0) ? dbgetrow(res->proc, rownum) : NO_MORE_ROWS;
325    }
326    switch (rv) {
327    case SUCCEED: return 0;
328    case REG_ROW: return 0;
329    case NO_MORE_ROWS:
330        apr_pool_cleanup_run(res->pool, res->proc, clear_result);
331        *rowp = NULL;
332        return -1;
333    case FAIL: return 1;
334    case BUF_FULL: return 2; /* FIXME */
335    default: return 3;
336    }
337
338    return 0;
339}
340
341static const char *dbd_freetds_get_entry(const apr_dbd_row_t *row, int n)
342{
343    /* FIXME: support different data types */
344    /* this fails - bind gets some vars but not others
345    return (const char*)row->res->vars[n].data;
346     */
347    DBPROCESS* proc = row->res->proc;
348    BYTE *ptr = dbdata(proc, n+1);
349    int t = dbcoltype(proc, n+1);
350    int l = dbcollen(proc, n+1);
351    if (dbwillconvert(t, SYBCHAR)) {
352      dbconvert(proc, t, ptr, l, SYBCHAR, (BYTE *)row->buf, -1);
353      return (const char*)row->buf;
354    }
355    return (char*)ptr;
356}
357
358static const char *dbd_freetds_error(apr_dbd_t *sql, int n)
359{
360    /* XXX this doesn't seem to exist in the API ??? */
361    return apr_psprintf(sql->pool, "Error %d", sql->err);
362}
363
364static int dbd_freetds_query(apr_dbd_t *sql, int *nrows, const char *query)
365{
366    if (sql->trans && sql->trans->errnum) {
367        return sql->trans->errnum;
368    }
369    *nrows = 0;
370    sql->err = freetds_exec(sql->proc, query, 0, nrows);
371
372    if (sql->err != SUCCEED) {
373        if (sql->trans) {
374            sql->trans->errnum = sql->err;
375        }
376        return 1;
377    }
378    return 0;
379}
380
381static const char *dbd_freetds_escape(apr_pool_t *pool, const char *arg,
382                                      apr_dbd_t *sql)
383{
384    return arg;
385}
386
387static apr_status_t freetds_regfree(void *rx)
388{
389    regfree((regex_t*)rx);
390    return APR_SUCCESS;
391}
392static int recurse_args(apr_pool_t *pool, int n, const char *query,
393                        apr_dbd_prepared_t *stmt, int offs)
394{
395
396    /* we only support %s arguments for now */
397    int ret;
398    char arg[256];
399    regmatch_t matches[3];
400    if (regexec(&dbd_freetds_find_arg, query, 3, matches, 0) != 0) {
401        /* No more args */
402        stmt->nargs = n;
403        stmt->taint = apr_palloc(pool, n*sizeof(regex_t*));
404        stmt->sz = apr_palloc(pool, n*sizeof(int));
405        ret = 0;
406    }
407    else {
408        int i;
409        int sz = 0;
410        int len = matches[1].rm_eo - matches[1].rm_so - 2;
411        if (len > 255) {
412            return 9999;
413        }
414
415        ret = recurse_args(pool, n+1, query+matches[0].rm_eo,
416                           stmt, offs+matches[0].rm_eo);
417
418        memmove(stmt->fmt + offs + matches[1].rm_so,
419                stmt->fmt + offs + matches[0].rm_eo-1,
420                strlen(stmt->fmt+offs+matches[0].rm_eo)+2);
421
422        /* compile untaint to a regex if found */
423        if (matches[1].rm_so == -1) {
424            stmt->taint[n] = NULL;
425        }
426        else {
427            strncpy(arg, query+matches[1].rm_so+1,
428                    matches[1].rm_eo - matches[1].rm_so - 2);
429            arg[matches[1].rm_eo - matches[1].rm_so - 2] = '\0';
430            stmt->taint[n] = apr_palloc(pool, sizeof(regex_t));
431            if (regcomp(stmt->taint[n], arg, REG_ICASE|REG_EXTENDED) != 0) {
432                ++ret;
433            }
434            else {
435                apr_pool_cleanup_register(pool, stmt->taint[n], freetds_regfree,
436                                          apr_pool_cleanup_null);
437            }
438        }
439
440        /* record length if specified */
441        for (i=matches[2].rm_so; i<matches[2].rm_eo; ++i) {
442            sz = 10*sz + (query[i]-'\0');
443        }
444    }
445    return ret;
446}
447
448static int dbd_freetds_prepare(apr_pool_t *pool, apr_dbd_t *sql,
449                             const char *query, const char *label,
450                             int nargs, int nvals, apr_dbd_type_e *types,
451                             apr_dbd_prepared_t **statement)
452{
453    apr_dbd_prepared_t *stmt;
454
455    if (label == NULL) {
456        label = apr_psprintf(pool, "%d", labelnum++);
457    }
458
459    if (!*statement) {
460        *statement = apr_palloc(pool, sizeof(apr_dbd_prepared_t));
461    }
462    stmt = *statement;
463
464#if 0
465    /* count args */
466    stmt->fmt = apr_pstrdup(pool, query);
467    stmt->fmt = recurse_args(pool, 0, query, stmt, stmt->fmt);
468
469    /* overestimate by a byte or two to simplify */
470    len = strlen("CREATE PROC apr.")
471            + strlen(label)
472            + stmt->nargs * strlen(" @arg1 varchar(len1),")
473            + strlen(" AS begin ")
474            + strlen(stmt->fmt)
475            + strlen(" end "); /* extra byte for terminator */
476
477    pquery = apr_pcalloc(pool, len);
478    sprintf(pquery, "CREATE PROC apr.%s", label);
479    for (i=0; i<stmt->nargs; ++i) {
480        sprintf(pquery+strlen(pquery), " @arg%d varchar(%d)", i, stmt->sz[i]);
481        if (i < stmt->nargs-1) {
482            pquery[strlen(pquery)] = ',';
483        }
484    }
485    strcat(pquery, " AS BEGIN ");
486    strcat(pquery, stmt->fmt);
487    strcat(pquery, " END");
488
489    return (freetds_exec(sql->proc, pquery, 0, &i) == SUCCEED) ? 0 : 1;
490#else
491    stmt->fmt = apr_pstrdup(pool, query);
492    return recurse_args(pool, 0, query, stmt, 0);
493#endif
494
495}
496
497static int dbd_freetds_start_transaction(apr_pool_t *pool, apr_dbd_t *handle,
498                                         apr_dbd_transaction_t **trans)
499{
500    int dummy;
501
502    /* XXX handle recursive transactions here */
503
504    handle->err = freetds_exec(handle->proc, "BEGIN TRANSACTION", 0, &dummy);
505
506    if (dbd_freetds_is_success(handle->err)) {
507        if (!*trans) {
508            *trans = apr_pcalloc(pool, sizeof(apr_dbd_transaction_t));
509        }
510        (*trans)->handle = handle;
511        handle->trans = *trans;
512        return 0;
513    }
514
515    return 1;
516}
517
518static int dbd_freetds_end_transaction(apr_dbd_transaction_t *trans)
519{
520    int dummy;
521    if (trans) {
522        /* rollback on error or explicit rollback request */
523        if (trans->errnum) {
524            trans->errnum = 0;
525            trans->handle->err = freetds_exec(trans->handle->proc,
526                                              "ROLLBACK", 0, &dummy);
527        }
528        else {
529            trans->handle->err = freetds_exec(trans->handle->proc,
530                                              "COMMIT", 0, &dummy);
531        }
532        trans->handle->trans = NULL;
533    }
534    return (trans->handle->err == SUCCEED) ? 0 : 1;
535}
536
537static DBPROCESS *freetds_open(apr_pool_t *pool, const char *params,
538                               const char **error)
539{
540    char *server = NULL;
541    DBPROCESS *process;
542    LOGINREC *login;
543    static const char *delims = " \r\n\t;|,";
544    char *ptr;
545    char *key;
546    char *value;
547    int vlen;
548    int klen;
549    char *buf;
550    char *databaseName = NULL;
551
552    /* FIXME - this uses malloc */
553    /* FIXME - pass error message back to the caller in case of failure */
554    login = dblogin();
555    if (login == NULL) {
556        return NULL;
557    }
558    /* now set login properties */
559    for (ptr = strchr(params, '='); ptr; ptr = strchr(ptr, '=')) {
560        /* don't dereference memory that may not belong to us */
561        if (ptr == params) {
562            ++ptr;
563            continue;
564        }
565        for (key = ptr-1; apr_isspace(*key); --key);
566        klen = 0;
567        while (apr_isalpha(*key)) {
568            --key;
569            ++klen;
570        }
571        ++key;
572        for (value = ptr+1; apr_isspace(*value); ++value);
573
574        vlen = strcspn(value, delims);
575        buf = apr_pstrndup(pool, value, vlen);        /* NULL-terminated copy */
576
577        if (!strncasecmp(key, "username", klen)) {
578            DBSETLUSER(login, buf);
579        }
580        else if (!strncasecmp(key, "password", klen)) {
581            DBSETLPWD(login, buf);
582        }
583        else if (!strncasecmp(key, "appname", klen)) {
584            DBSETLAPP(login, buf);
585        }
586        else if (!strncasecmp(key, "dbname", klen)) {
587            databaseName = buf;
588        }
589        else if (!strncasecmp(key, "host", klen)) {
590            DBSETLHOST(login, buf);
591        }
592        else if (!strncasecmp(key, "charset", klen)) {
593            DBSETLCHARSET(login, buf);
594        }
595        else if (!strncasecmp(key, "lang", klen)) {
596            DBSETLNATLANG(login, buf);
597        }
598        else if (!strncasecmp(key, "server", klen)) {
599            server = buf;
600        }
601        else {
602            /* unknown param */
603        }
604        ptr = value+vlen;
605    }
606
607    process = dbopen(login, server);
608
609    if (process != NULL && databaseName != NULL)
610    {
611        dbuse(process, databaseName);
612    }
613
614    dbloginfree(login);
615    if (process == NULL) {
616        return NULL;
617    }
618
619    return process;
620}
621static apr_dbd_t *dbd_freetds_open(apr_pool_t *pool, const char *params,
622                                   const char **error)
623{
624    apr_dbd_t *sql;
625    /* FIXME - pass error message back to the caller in case of failure */
626    DBPROCESS *process = freetds_open(pool, params, error);
627    if (process == NULL) {
628        return NULL;
629    }
630    sql = apr_pcalloc(pool, sizeof (apr_dbd_t));
631    sql->pool = pool;
632    sql->proc = process;
633    sql->params = params;
634    return sql;
635}
636
637static apr_status_t dbd_freetds_close(apr_dbd_t *handle)
638{
639    dbclose(handle->proc);
640    return APR_SUCCESS;
641}
642
643static apr_status_t dbd_freetds_check_conn(apr_pool_t *pool,
644                                           apr_dbd_t *handle)
645{
646    if (dbdead(handle->proc)) {
647        /* try again */
648        dbclose(handle->proc);
649        handle->proc = freetds_open(handle->pool, handle->params, NULL);
650        if (!handle->proc || dbdead(handle->proc)) {
651            return APR_EGENERAL;
652        }
653    }
654    /* clear it, in case this is called in error handling */
655    dbcancel(handle->proc);
656    return APR_SUCCESS;
657}
658
659static int dbd_freetds_select_db(apr_pool_t *pool, apr_dbd_t *handle,
660                               const char *name)
661{
662    /* ouch, it's declared int.  But we can use APR 0/nonzero */
663    return (dbuse(handle->proc, (char*)name) == SUCCEED) ? APR_SUCCESS : APR_EGENERAL;
664}
665
666static void *dbd_freetds_native(apr_dbd_t *handle)
667{
668    return handle->proc;
669}
670
671static int dbd_freetds_num_cols(apr_dbd_results_t* res)
672{
673    return res->sz;
674}
675
676static int dbd_freetds_num_tuples(apr_dbd_results_t* res)
677{
678    if (res->random) {
679        return res->ntuples;
680    }
681    else {
682        return -1;
683    }
684}
685
686static apr_status_t freetds_term(void *dummy)
687{
688    dbexit();
689    regfree(&dbd_freetds_find_arg);
690    return APR_SUCCESS;
691}
692static int freetds_err_handler(DBPROCESS *dbproc, int severity, int dberr,
693                               int oserr, char *dberrstr, char *oserrstr)
694{
695    return INT_CANCEL; /* never exit */
696}
697static void dbd_freetds_init(apr_pool_t *pool)
698{
699    int rv = regcomp(&dbd_freetds_find_arg,
700                     "%(\\{[^}]*\\})?([0-9]*)[A-Za-z]", REG_EXTENDED);
701    if (rv != 0) {
702        char errmsg[256];
703        regerror(rv, &dbd_freetds_find_arg, errmsg, 256);
704        fprintf(stderr, "regcomp failed: %s\n", errmsg);
705    }
706    dbinit();
707    dberrhandle(freetds_err_handler);
708    apr_pool_cleanup_register(pool, NULL, freetds_term, apr_pool_cleanup_null);
709}
710
711#ifdef COMPILE_STUBS
712/* get_name is the only one of these that is implemented */
713static const char *dbd_freetds_get_name(const apr_dbd_results_t *res, int n)
714{
715    return (const char*) dbcolname(res->proc, n+1); /* numbering starts at 1 */
716}
717
718/* These are stubs: transaction modes not implemented here */
719#define DBD_NOTIMPL APR_ENOTIMPL;
720static int dbd_freetds_transaction_mode_get(apr_dbd_transaction_t *trans)
721{
722    return trans ? trans->mode : APR_DBD_TRANSACTION_COMMIT;
723}
724
725static int dbd_freetds_transaction_mode_set(apr_dbd_transaction_t *trans,
726                                            int mode)
727{
728    if (trans) {
729        trans->mode = mode & TXN_MODE_BITS;
730        return trans->mode;
731    }
732    return APR_DBD_TRANSACTION_COMMIT;
733}
734static int dbd_freetds_pvbquery(apr_pool_t *pool, apr_dbd_t *sql, int *nrows,
735                                apr_dbd_prepared_t *statement, va_list args)
736{
737    return DBD_NOTIMPL;
738}
739static int dbd_freetds_pbquery(apr_pool_t *pool, apr_dbd_t *sql, int *nrows,
740                               apr_dbd_prepared_t * statement,
741                               const void **values)
742{
743    return DBD_NOTIMPL;
744}
745
746static int dbd_freetds_pvbselect(apr_pool_t *pool, apr_dbd_t *sql,
747                                 apr_dbd_results_t **results,
748                                 apr_dbd_prepared_t *statement,
749                                 int seek, va_list args)
750{
751    return DBD_NOTIMPL;
752}
753static int dbd_freetds_pbselect(apr_pool_t *pool, apr_dbd_t *sql,
754                                apr_dbd_results_t **results,
755                                apr_dbd_prepared_t *statement,
756                                int seek, const void **values)
757{
758    return DBD_NOTIMPL;
759}
760static apr_status_t dbd_freetds_datum_get(const apr_dbd_row_t *row, int n,
761                                          apr_dbd_type_e type, void *data)
762{
763    return APR_ENOTIMPL;
764}
765#endif
766
767APU_MODULE_DECLARE_DATA const apr_dbd_driver_t apr_dbd_freetds_driver = {
768    "freetds",
769    dbd_freetds_init,
770    dbd_freetds_native,
771    dbd_freetds_open,
772    dbd_freetds_check_conn,
773    dbd_freetds_close,
774    dbd_freetds_select_db,
775    dbd_freetds_start_transaction,
776    dbd_freetds_end_transaction,
777    dbd_freetds_query,
778    dbd_freetds_select,
779    dbd_freetds_num_cols,
780    dbd_freetds_num_tuples,
781    dbd_freetds_get_row,
782    dbd_freetds_get_entry,
783    dbd_freetds_error,
784    dbd_freetds_escape,
785    dbd_freetds_prepare,
786    dbd_freetds_pvquery,
787    dbd_freetds_pvselect,
788    dbd_freetds_pquery,
789    dbd_freetds_pselect,
790    /* this is only implemented to support httpd/2.2 standard usage,
791     * as in the original DBD implementation.  Everything else is NOTIMPL.
792     */
793#ifdef COMPILE_STUBS
794    dbd_freetds_get_name,
795    dbd_freetds_transaction_mode_get,
796    dbd_freetds_transaction_mode_set,
797    "",
798    dbd_freetds_pvbquery,
799    dbd_freetds_pvbselect,
800    dbd_freetds_pbquery,
801    dbd_freetds_pbselect,
802    dbd_freetds_datum_get
803#endif
804};
805#endif
806