The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#define PERL_NO_GET_CONTEXT

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <mysql/mysql.h>

#define CHUNK_SIZE 10

static MYSQL*
get_mysql_handler(pTHX_ SV* self) {
    HV *hv=(HV*)SvRV(self);
    SV **sql=hv_fetch(hv,"sql",3,0);

    if(!sql)
        return NULL;

    return (MYSQL*)SvIV(*sql);
}

static char*
build_query(pTHX_ MYSQL* mysql, SV* sv_qtemplate, SV* sv_values, STRLEN* ql) {
    static char *query=NULL;
    static unsigned long query_size=0;

    STRLEN qt_len;
    char const *qt;

    AV* values=(AV*)SvRV(sv_values);
    I32 value_index=0;

    char *query_ptr;

    /* Template might be double wrapped if we got it from the prepare
    */
    if(SvROK(sv_qtemplate)) {
        SV* sv=SvRV(sv_qtemplate);
        qt=SvPV(sv,qt_len);
    }
    else {
        qt=SvPV(sv_qtemplate,qt_len);
    }

    if(!query) {
        New(0,query,CHUNK_SIZE,char);
        query_size=CHUNK_SIZE;
    }
    query_ptr=query;

    for(; qt_len; qt_len--, qt++) {
        if(*qt!='?') {
            if(query_ptr-query+2>=query_size) {
                char *q=query;
                query_size+=CHUNK_SIZE;
                Renew(query,query_size,char);
                query_ptr=query+(query_ptr-q);
            }
            *query_ptr++=*qt;
        }
        else {
            SV** value_ptr=av_fetch(values,value_index++,0);
            SV* value;
            if(value_ptr) {
                value=*value_ptr;
                if(!SvIOK(value) && !SvNOK(value) &&
                   !SvPOK(value) && !SvROK(value))
                    value_ptr=NULL;
            }
            if(!value_ptr || value==&PL_sv_undef) {
                if(query_ptr-query+5>=query_size) {
                    char *q=query;
                    query_size+=CHUNK_SIZE;
                    Renew(query,query_size,char);
                    query_ptr=query+(query_ptr-q);
                }
                Copy("NULL",query_ptr,4,char);
                query_ptr+=4;
            }
            else {
                STRLEN v_len=0;
                char *v=SvPV(*value_ptr,v_len);
                if(query_ptr-query+v_len*2+3>=query_size) {
                    char *q=query;
                    query_size+=v_len*2+CHUNK_SIZE;
                    Renew(query,query_size,char);
                    query_ptr=query+(query_ptr-q);
                }
                *query_ptr++='\'';
                v_len=mysql_real_escape_string(mysql,query_ptr,v,v_len);
                query_ptr+=v_len;
                *query_ptr++='\'';
            }
        }
    }

    *query_ptr=0;

    *ql=query_ptr-query;

    return query;
}

MODULE = XAO::DO::FS::Glue::MySQL		PACKAGE = XAO::DO::FS::Glue::MySQL		

void
sql_print_refcnt (sv)
        SV*     sv;
    CODE:
        printf("sql_pr_ref=%u\n",SvREFCNT(sv));

void
sql_disconnect(self)
        SV*     self;
    CODE:
        MYSQL *mysql=get_mysql_handler(aTHX_ self);

        if(mysql) {
            HV *hv_self=(HV*)SvRV(self);
            hv_delete(hv_self,"sql",3,G_DISCARD);
            mysql_close(mysql);
        }

SV*
sql_error_text(self)
        SV*     self;
    CODE:
        MYSQL *mysql=get_mysql_handler(aTHX_ self);
        char const *error=mysql_error(mysql);
        RETVAL=newSVpv(error,0);
    OUTPUT:
        RETVAL

SV*
sql_real_connect(hostname,user,password,dbname)
        SV*     hostname;
        SV*     user;
        SV*     password;
        SV*     dbname;
    CODE:
        MYSQL *mysql=mysql_init(NULL);
        char *sh=(hostname == &PL_sv_undef) ? NULL : SvPV_nolen(hostname);
        char *su=(user == &PL_sv_undef) ? NULL : SvPV_nolen(user);
        char *sp=(password == &PL_sv_undef) ? NULL : SvPV_nolen(password);
        char *sd=(dbname == &PL_sv_undef) ? NULL : SvPV_nolen(dbname);
        if(mysql_real_connect(mysql,sh,su,sp,sd,0,NULL,0)) {
            RETVAL=newSViv((IV)mysql);
        }
        else {
            RETVAL=&PL_sv_undef;
        }
    OUTPUT:
        RETVAL

int
sql_real_do(self,qtemplate,values)
        SV*     self;
        SV*     qtemplate;
        SV*     values;
    CODE:
        MYSQL *mysql=get_mysql_handler(aTHX_ self);
        STRLEN query_len;
        char *query=build_query(aTHX_ mysql,qtemplate,values,&query_len);
        RETVAL=mysql_real_query(mysql,query,query_len);
        if(!RETVAL) {
            MYSQL_RES* mres=mysql_store_result(mysql);
            if(mres)
                mysql_free_result(mres);
        }
    OUTPUT:
        RETVAL

SV*
sql_real_execute(self,qtemplate,values)
        SV*     self;
        SV*     qtemplate;
        SV*     values;
    CODE:
        MYSQL *mysql=get_mysql_handler(aTHX_ self);
        STRLEN query_len;
        char *query=build_query(aTHX_ mysql,qtemplate,values,&query_len);
        if(mysql_real_query(mysql,query,query_len)) {
            RETVAL=&PL_sv_undef;
        }
        else {
            MYSQL_RES* mres=mysql_store_result(mysql);
            RETVAL=newSViv((IV)mres);
        }
    OUTPUT:
        RETVAL

SV*
sql_fetch_row (self,qr)
        SV*     self;
        SV*     qr;
    CODE:
        MYSQL_RES* mres=(MYSQL_RES*)SvIV(qr);
        MYSQL_ROW row;
        if(!mres || (row=mysql_fetch_row(mres))==NULL) {
            RETVAL=&PL_sv_undef;
        }
        else {
            AV *av=newAV();
            I32 num=mysql_num_fields(mres);
            unsigned long *row_l=mysql_fetch_lengths(mres);
            I32 i;
            for(i=0; i!=num; i++, row_l++) {
                char const *f=row[i];
                av_push(av,f ? newSVpv(row[i],*row_l) : &PL_sv_undef);
            }
            RETVAL=newRV_noinc((SV*)av);
        }
    OUTPUT:
        RETVAL

void
sql_finish (self,qr)
        SV*     self;
        SV*     qr;
    CODE:
        MYSQL_RES* mres=(MYSQL_RES*)SvIV(qr);
        mysql_free_result(mres);

SV*
sql_first_column (self,qr)
        SV*     self;
        SV*     qr;
    CODE:
        MYSQL_RES* mres=(MYSQL_RES*)SvIV(qr);
        AV* av=newAV();
        if(mres) {
            unsigned long *row_l;
            while(1) {
                MYSQL_ROW row=mysql_fetch_row(mres);
                if(!row)
                    break;
                row_l=mysql_fetch_lengths(mres);
                av_push(av,*row ? newSVpv(*row,*row_l) : &PL_sv_undef);
            }
            mysql_free_result(mres);
        }
        RETVAL=newRV_noinc((SV*)av);
    OUTPUT:
        RETVAL

SV*
sql_first_row (self,qr)
        SV*     self;
        SV*     qr;
    CODE:
        MYSQL_RES* mres=(MYSQL_RES*)SvIV(qr);
        MYSQL_ROW row;
        if(!mres || (row=mysql_fetch_row(mres))==NULL) {
            if(mres)
                mysql_free_result(mres);
            RETVAL=&PL_sv_undef;
        }
        else {
            AV *av=newAV();
            I32 num=mysql_num_fields(mres);
            unsigned long *row_l=mysql_fetch_lengths(mres);
            I32 i;
            for(i=0; i!=num; i++, row_l++) {
                char const *f=row[i];
                av_push(av,f ? newSVpv(row[i],*row_l) : &PL_sv_undef);
            }
            mysql_free_result(mres);
            RETVAL=newRV_noinc((SV*)av);
        }
    OUTPUT:
        RETVAL

SV*
sql_prepare (self,qtemplate)
        SV*     self;
        SV*     qtemplate;
    CODE:
        RETVAL=newRV_noinc(newSVsv(qtemplate));
    OUTPUT:
        RETVAL