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