#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#define DBIXS_VERSION 93
#define DBI_MAGIC '~'
#define DBISTATE_PERLNAME "DBI::_dbistate"
#define DBISTATE_ADDRSV (perl_get_sv (DBISTATE_PERLNAME, 0x05))
#define DBIS_PUBLISHED_LVALUE (*(INT2PTR(dbistate_t**, &SvIVX(DBISTATE_ADDRSV))))
static
SV *sql_varchar, *sql_integer, *sql_double;
static
SV *tmp_iv;
struct
dbistate_st {
#define DBISTATE_VERSION 94 /* Must change whenever dbistate_t does */
void
(*check_version) _((
const
char
*name,
int
dbis_cv,
int
dbis_cs,
int
need_dbixs_cv,
int
drc_s,
int
dbc_s,
int
stc_s,
int
fdc_s));
U16 version;
U16 size;
U16 xs_version;
U16 spare_pad;
};
typedef
struct
dbistate_st dbistate_t;
#define DBIcf_ACTIVE 0x000004 /* needs finish/disconnect before clear */
typedef
U32 imp_sth;
static
imp_sth *
sth_get_imp (SV *sth)
{
MAGIC *mg = mg_find (SvRV (sth), PERL_MAGIC_tied);
sth = mg->mg_obj;
mg = mg_find (SvRV (sth), DBI_MAGIC);
return
(imp_sth *)SvPVX (mg->mg_obj);
}
#define DBI_STH_ACTIVE(imp) (*(imp) & DBIcf_ACTIVE)
#if (PERL_VERSION < 5) || ((PERL_VERSION == 5) && (PERL_SUBVERSION <= 6))
# define get_sv perl_get_sv
# define call_method perl_call_method
# define call_sv perl_call_sv
#endif
#if (PERL_VERSION > 5) || ((PERL_VERSION == 5) && (PERL_SUBVERSION >= 6))
# define CAN_UTF8 1
#endif
#define MAX_CACHED_STATEMENT_SIZE 2048
static
SV *
sql_upgrade_utf8 (SV *sv)
{
#if CAN_UTF8
if
(SvPOKp (sv))
sv_utf8_upgrade (sv);
#endif
return
sv;
}
static
SV *
mortalcopy_and_maybe_force_utf8(
int
utf8, SV *sv)
{
sv = sv_mortalcopy (sv);
#if CAN_UTF8
if
(utf8 && SvPOKp (sv))
SvUTF8_on (sv);
#endif
return
sv;
}
#define maybe_upgrade_utf8(utf8,sv) ((utf8) ? sql_upgrade_utf8 (sv) : (sv))
#define is_dbh(sv) ((sv) && sv_isobject (sv) && sv_derived_from ((sv), "DBI::db"))
typedef
struct
mc_node
{
struct
mc_node *next;
HV *stash;
U32 gen;
SV *prepare;
SV *execute;
SV *bind_param;
SV *bind_columns;
SV *fetchrow_arrayref;
SV *fetchall_arrayref;
SV *finish;
} mc_node;
static
mc_node *first;
static
mc_node *
mc_find (HV *stash)
{
mc_node *mc;
U32 gen = PL_sub_generation;
#ifdef HvMROMETA
gen += HvMROMETA (stash)->cache_gen;
#endif
for
(mc = first; mc; mc = mc->next)
if
(mc->stash == stash && mc->gen == gen)
return
mc;
if
(!mc)
{
Newz (0, mc, 1, mc_node);
mc->stash = stash;
mc->next = first;
first = mc;
}
else
{
mc->execute =
mc->bind_param =
mc->bind_columns =
mc->fetchrow_arrayref =
mc->fetchall_arrayref =
mc->finish = 0;
}
mc->gen = gen;
return
mc;
}
static
void
mc_cache (mc_node *mc, SV **method,
const
char
*name)
{
*method = (SV *)gv_fetchmethod_autoload (mc->stash, name, 0);
if
(!method)
croak (
"%s: method not found in stash, please report."
, name);
}
#define mc_cache(mc, method) mc_cache ((mc), &((mc)->method), # method)
typedef
struct
lru_node
{
struct
lru_node *next;
struct
lru_node *prev;
U32 hash;
SV *dbh;
SV *sql;
SV *sth;
imp_sth *sth_imp;
mc_node *mc;
} lru_node;
static
lru_node lru_list;
static
int
lru_size;
static
int
lru_maxsize;
#define lru_init() lru_list.next = &lru_list; lru_list.prev = &lru_list /* other fields are zero */
static
U32
lru_hash (SV *dbh, SV *sql)
{
STRLEN i, l;
char
*b = SvPV (sql, l);
U32 hash = 2166136261U;
hash = (hash ^ (U32)dbh) * 16777619U;
hash = (hash ^ l) * 16777619U;
for
(i = 7; i < l; i += i >> 2)
hash = (hash ^ b [i]) * 16777619U;
return
hash;
}
static
lru_node *
lru_fetch (SV *dbh, SV *sql)
{
lru_node *n;
U32 hash;
dbh = SvRV (dbh);
hash = lru_hash (dbh, sql);
n = &lru_list;
do
{
n = n->next;
if
(!n->hash)
return
0;
}
while
(n->hash != hash
|| DBI_STH_ACTIVE (n->sth_imp)
|| !sv_eq (n->sql, sql)
|| n->dbh != dbh);
n->prev->next = n->next;
n->next->prev = n->prev;
n->next = lru_list.next;
n->prev = &lru_list;
lru_list.next->prev = n;
lru_list.next = n;
return
n;
}
static
void
lru_trim (
void
)
{
while
(lru_size > lru_maxsize)
{
lru_node *n = lru_list.prev;
n = lru_list.prev;
lru_list.prev = n->prev;
n->prev->next = &lru_list;
SvREFCNT_dec (n->dbh);
SvREFCNT_dec (n->sql);
SvREFCNT_dec (n->sth);
Safefree (n);
lru_size--;
}
}
static
void
lru_store (SV *dbh, SV *sql, SV *sth, mc_node *mc)
{
lru_node *n;
U32 hash;
if
(!lru_maxsize)
return
;
dbh = SvRV (dbh);
hash = lru_hash (dbh, sql);
lru_size++;
lru_trim ();
New (0, n, 1, lru_node);
n->hash = hash;
n->dbh = dbh; SvREFCNT_inc (dbh);
n->sql = newSVsv (sql);
n->sth = sth; SvREFCNT_inc (sth);
n->sth_imp = sth_get_imp (sth);
n->mc = mc;
n->next = lru_list.next;
n->prev = &lru_list;
lru_list.next->prev = n;
lru_list.next = n;
}
static
void
lru_cachesize (
int
size)
{
if
(size >= 0)
{
lru_maxsize = size;
lru_trim ();
}
}
static
GV *sql_exec;
static
GV *DBH;
#define newconstpv(str) newSVpvn ((str), sizeof (str))
MODULE = PApp::SQL PACKAGE = PApp::SQL
PROTOTYPES: DISABLE
BOOT:
{
struct
dbistate_st *dbis = DBIS_PUBLISHED_LVALUE;
assert
(dbis->version == DBISTATE_VERSION);
assert
(dbis->xs_version == DBIXS_VERSION);
tmp_iv = newSViv (0);
sql_exec = gv_fetchpv (
"PApp::SQL::sql_exec"
, TRUE, SVt_PV);
DBH = gv_fetchpv (
"PApp::SQL::DBH"
, TRUE, SVt_PV);
if
(lru_size)
lru_cachesize (0);
lru_init ();
lru_cachesize (100);
}
void
boot2 (SV *t_str, SV *t_int, SV *t_dbl)
CODE:
sql_varchar = newSVsv (t_str);
sql_integer = newSVsv (t_int);
sql_double = newSVsv (t_dbl);
int
cachesize(size = -1)
int
size
CODE:
RETVAL = lru_maxsize;
lru_cachesize (size);
OUTPUT:
RETVAL
void
sql_exec(...)
ALIAS:
sql_uexec = 1
sql_fetch = 2
sql_ufetch = 3
sql_fetchall = 4
sql_ufetchall = 5
sql_exists = 6
sql_uexists = 7
PPCODE:
{
if
(items == 0)
croak (
"Usage: sql_exec [database-handle,] [bind-var-refs,... ] \"sql-statement\", [arguments, ...]"
);
else
{
int
i;
int
arg = 0;
int
bind_first, bind_last;
int
count;
lru_node *lru;
SV *dbh = ST(0);
SV *sth;
SV *sql;
SV *execute;
mc_node *mc;
STRLEN dc, dd;
I32 orig_stack = SP - PL_stack_base;
SP += items;
if
(!is_dbh (dbh))
{
dbh = GvSV (DBH);
if
(!is_dbh (dbh))
croak (
"sql_exec: no $DBH argument and no fallback in $PApp::SQL::DBH"
);
}
else
arg++;
dbh = sv_2mortal (newSVsv (dbh));
bind_first = arg;
while
(items > arg && SvROK (ST(arg)))
arg++;
bind_last = arg;
if
(items <= arg)
croak (
"sql_exec: required argument \"sql-statement\" missing"
);
if
(!SvPOK (ST(arg)))
croak (
"sql_exec: sql-statement must be a string"
);
sql = ST(arg); arg++;
if
((ix & ~1) == 6)
{
SV *neu = sv_2mortal (newSVpv (
"select count(*) > 0 from "
, 0));
sv_catsv (neu, sql);
sv_catpv (neu,
" limit 1"
);
sql = neu;
ix -= 4;
}
for
(i = arg; i < items; ++i)
{
SV *sv = ST (i);
if
(SvMAGICAL (sv))
ST (i) = sv = sv_mortalcopy (sv);
if
((ix & 1) && SvPOKp (sv) && !SvUTF8 (sv))
{
ST (i) = sv = sv_mortalcopy (sv);
sv_utf8_upgrade (sv);
}
}
lru = SvCUR (sql) <= MAX_CACHED_STATEMENT_SIZE
? lru_fetch (dbh, sql)
: 0;
if
(!lru)
{
mc = mc_find (SvSTASH (SvRV (dbh)));
if
(!mc->prepare)
mc_cache (mc, prepare);
PUSHMARK (SP);
EXTEND (SP, 2);
PUSHs (dbh);
PUSHs (sql);
PUTBACK;
count = call_sv (mc->prepare, G_SCALAR);
SPAGAIN;
if
(count != 1)
croak (
"sql_exec: unable to prepare() statement '%s': %s"
,
SvPV (sql, dc),
SvPV (get_sv (
"DBI::errstr"
, TRUE), dd));
sth = POPs;
if
(!SvROK (sth))
croak (
"sql_exec: buggy DBD driver, prepare returned non-reference for '%s': %s"
,
SvPV (sql, dc),
SvPV (get_sv (
"DBI::errstr"
, TRUE), dd));
mc = mc_find (SvSTASH (SvRV (sth)));
if
(!mc->bind_param)
{
mc_cache (mc, bind_param);
mc_cache (mc, execute);
mc_cache (mc, finish);
}
if
(SvCUR (sql) <= MAX_CACHED_STATEMENT_SIZE)
lru_store (dbh, sql, sth, mc);
SvIV_set (tmp_iv, 0);
while
(items > arg)
{
SV *sv = ST (arg);
PUSHMARK (SP);
EXTEND (SP, 4);
PUSHs (sth);
SvIVX (tmp_iv)++;
SvIOK_only (tmp_iv);
PUSHs (tmp_iv);
PUSHs (sv);
PUSHs (
SvPOKp (sv) ? sql_varchar
: SvNOKp (sv) ? sql_double
: SvIOKp (sv) ? sql_integer
: sql_varchar
);
PUTBACK;
call_sv (mc->bind_param, G_VOID);
SPAGAIN;
arg++;
}
PUSHMARK (SP);
EXTEND (SP, 1);
PUSHs (sth);
}
else
{
sth = sv_2mortal (SvREFCNT_inc (lru->sth));
mc = lru->mc;
PUSHMARK (SP);
EXTEND (SP, items - arg + 1);
PUSHs (sth);
while
(items > arg)
{
SV *sv = ST (arg);
PUSHs (sv);
arg++;
}
}
PUTBACK;
count = call_sv (mc->execute, G_SCALAR);
SPAGAIN;
if
(count != 1)
croak (
"sql_exec: execute() didn't return any value ('%s'): %s"
,
SvPV (sql, dc),
SvPV (get_sv (
"DBI::errstr"
, TRUE), dd));
execute = POPs;
if
(!SvTRUE (execute))
croak (
"sql_exec: unable to execute statement '%s' (%s)"
,
SvPV (sql, dc),
SvPV (get_sv (
"DBI::errstr"
, TRUE), dd));
sv_setsv (GvSV (sql_exec), execute);
if
(bind_first != bind_last)
{
PUSHMARK (SP);
EXTEND (SP, bind_last - bind_first + 2);
PUSHs (sth);
do
{
#if CAN_UTF8
if
(ix & 1)
SvUTF8_on (SvRV(ST(bind_first)));
#endif
PUSHs (ST(bind_first));
bind_first++;
}
while
(bind_first != bind_last);
PUTBACK;
if
(!mc->bind_columns)
mc_cache (mc, bind_columns);
count = call_sv (mc->bind_columns, G_SCALAR);
SPAGAIN;
if
(count != 1)
croak (
"sql_exec: bind_columns() didn't return any value ('%s'): %s"
,
SvPV (sql, dc),
SvPV (get_sv (
"DBI::errstr"
, TRUE), dd));
if
(!SvOK (TOPs))
croak (
"sql_exec: bind_columns() didn't return a true ('%s'): %s"
,
SvPV (sql, dc),
SvPV (get_sv (
"DBI::errstr"
, TRUE), dd));
POPs;
}
if
((ix & ~1) == 2)
{
SV *row;
PUSHMARK (SP);
XPUSHs (sth);
PUTBACK;
if
(!mc->fetchrow_arrayref)
mc_cache (mc, fetchrow_arrayref);
count = call_sv (mc->fetchrow_arrayref, G_SCALAR);
SPAGAIN;
if
(count != 1)
abort
();
row = POPs;
SP = PL_stack_base + orig_stack;
if
(SvROK (row))
{
AV *av;
switch
(GIMME_V)
{
case
G_VOID:
break
;
case
G_SCALAR:
XPUSHs (mortalcopy_and_maybe_force_utf8 (ix & 1, AvARRAY ((AV *)SvRV (row))[0]));
count = 1;
break
;
case
G_ARRAY:
av = (AV *)SvRV (row);
count = AvFILL (av) + 1;
EXTEND (SP, count);
for
(arg = 0; arg < count; arg++)
PUSHs (mortalcopy_and_maybe_force_utf8 (ix & 1, AvARRAY (av)[arg]));
break
;
default
:
abort
();
}
}
}
else
if
((ix & ~1) == 4)
{
SV *rows;
PUSHMARK (SP);
XPUSHs (sth);
PUTBACK;
if
(!mc->fetchall_arrayref)
mc_cache (mc, fetchall_arrayref);
count = call_sv (mc->fetchall_arrayref, G_SCALAR);
SPAGAIN;
if
(count != 1)
abort
();
rows = POPs;
SP = PL_stack_base + orig_stack;
if
(SvROK (rows))
{
AV *av = (AV *)SvRV (rows);
count = AvFILL (av) + 1;
if
(count)
{
int
columns = AvFILL ((AV *) SvRV (AvARRAY (av)[0])) + 1;
EXTEND (SP, count);
if
(columns == 1)
for
(arg = 0; arg < count; arg++)
PUSHs (mortalcopy_and_maybe_force_utf8 (ix & 1, AvARRAY ((AV *)SvRV (AvARRAY (av)[arg]))[0]));
else
for
(arg = 0; arg < count; arg++)
PUSHs (mortalcopy_and_maybe_force_utf8 (ix & 1, AvARRAY (av)[arg]));
}
}
}
else
{
SP = PL_stack_base + orig_stack;
XPUSHs (sth);
}
if
(ix > 1 || GIMME_V == G_VOID)
{
orig_stack = SP - PL_stack_base;
PUSHMARK (SP);
XPUSHs (sth);
PUTBACK;
if
(!mc->finish)
mc_cache (mc, finish);
call_sv (mc->finish, G_DISCARD);
SPAGAIN;
SP = PL_stack_base + orig_stack;
}
}
}