#define PERL_NO_GET_CONTEXT 1
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include <mysql.h>
#ifndef hv_deletes
# define hv_deletes(hv, key, flags) \
hv_delete((hv), (
""
key
""
), (
sizeof
(key)-1), (flags))
#endif
#define prepare_new_result_accumulator(maria) STMT_START { \
maria->query_results ? SvREFCNT_dec(maria->query_results) : NULL; \
maria->query_results = MUTABLE_SV(newAV()); \
} STMT_END
#ifndef MARIADB_PACKAGE_VERSION_ID
# if defined SERVER_STATUS_IN_TRANS_READONLY
# define MARIADB_PACKAGE_VERSION_ID 30004
# elif defined MARIADB_BASE_VERSION
# define MARIADB_PACKAGE_VERSION_ID 30003
# elif defined TLS_LIBRARY_VERSION
# define MARIADB_PACKAGE_VERSION_ID 30002
# elif defined MYSQL_CLIENT_reserved22
# define MARIADB_PACKAGE_VERSION_ID 30000
# endif
#endif
#ifdef MARIADB_PACKAGE_VERSION_ID
# if MARIADB_PACKAGE_VERSION_ID >= 30002
# define HAVE_SSL_ENFORCE
# endif
#endif
typedef
struct
sql_config {
const
char
* username;
const
char
* hostname;
const
char
* unix_socket;
const
char
* database;
unsigned
int
port;
unsigned
long
client_opts;
SV* password_temp;
const
char
* charset_name;
const
char
* ssl_key;
const
char
* ssl_cert;
const
char
* ssl_ca;
const
char
* ssl_capath;
const
char
* ssl_cipher;
} sql_config;
typedef
struct
MariaDB_client {
MYSQL* mysql;
MYSQL_RES* res;
sql_config* config;
SV* query_results;
SV* query_sv;
int
socket_fd;
unsigned
long
thread_id;
bool
is_cont;
bool
run_query;
int
current_state;
int
last_status;
bool
store_query_result;
bool
want_hashrefs;
bool
destroyed;
} MariaDB_client;
#define dMARIA MariaDB_client* maria; STMT_START { \
{\
MAGIC *mg;\
if
(sv_isobject(self) && SvTYPE(SvRV(self)) == SVt_PVHV && \
(mg = mg_findext(SvRV(self), PERL_MAGIC_ext, &maria_vtable)) != 0) \
{ maria = (MariaDB_client*) mg->mg_ptr; }\
else
{ \
croak(
"%"
SVf
" is not a valid Maria::NonBlocking object"
, self); \
} \
} \
} STMT_END
#define NAMES C(DISCONNECTED)C(STANDBY)C(CONNECT)C(QUERY)C(ROW_FETCH)C(FREE_RESULT)C(PING)C(STORE_RESULT)
#define C(x) STATE_##x,
enum
color { NAMES STATE_END };
#undef C
#define C(x) #x,
const
char
*
const
state_to_name[] = { NAMES };
#undef C
const
char
*
const
cont_to_name[] = {
"cont"
,
"run_query_cont"
,
"ping_cont"
,
"connect_cont"
};
void
THX_free_our_config_items(pTHX_ sql_config* config)
#define free_our_config_items(c) THX_free_our_config_items(aTHX_ c)
{
Safefree(config->username);
Safefree(config->hostname);
Safefree(config->unix_socket);
Safefree(config->database);
Safefree(config->charset_name);
Safefree(config->ssl_key);
Safefree(config->ssl_cert);
Safefree(config->ssl_ca);
Safefree(config->ssl_capath);
Safefree(config->ssl_cipher);
return
;
}
void
THX_disconnect_generic(pTHX_ MariaDB_client* maria)
#define disconnect_generic(maria) THX_disconnect_generic(aTHX_ maria)
{
if
( maria->run_query ) {
SvREFCNT_dec(maria->query_sv);
maria->query_sv = NULL;
maria->run_query = FALSE;
}
if
( maria->res ) {
int
status = mysql_free_result_start(maria->res);
maria->res = NULL;
if
( status ) {
croak(
"Could not free statement handle without blocking."
);
}
}
if
( maria->mysql ) {
mysql_close(maria->mysql);
Safefree(maria->mysql);
maria->mysql = NULL;
}
maria->is_cont = FALSE;
maria->current_state = STATE_DISCONNECTED;
maria->socket_fd = -1;
return
;
}
static
int
free_mariadb_handle(pTHX_ SV* sv, MAGIC *mg)
{
MariaDB_client* maria = (MariaDB_client*)mg->mg_ptr;
sql_config *config;
if
( !maria || maria->destroyed ) {
return
0;
}
config = maria->config;
maria->destroyed = 1;
disconnect_generic(maria);
free_our_config_items(config);
Safefree(maria->config);
maria->config = NULL;
Safefree(maria);
mg->mg_ptr = NULL;
return
0;
}
static
MGVTBL maria_vtable = { .svt_free = free_mariadb_handle };
const
char
*
THX_fetch_pv_try_not_to_copy_buffer(pTHX_ SV* password_sv)
#define fetch_pv_try_not_to_copy_buffer(a) \
THX_fetch_pv_try_not_to_copy_buffer(aTHX_ a)
{
const
char
* password = NULL;
if
( !password_sv || !SvOK(password_sv) ) {
return
NULL;
}
if
( SvPOK(password_sv) && !SvGMAGICAL(password_sv) ) {
password = SvPVX_const(password_sv);
}
else
{
password = SvPV_nolen(password_sv);
}
return
password;
}
const
char
*
THX_fetch_password_try_not_to_copy_buffer(pTHX_ MariaDB_client *maria)
#define fetch_password_try_not_to_copy_buffer(a) \
THX_fetch_password_try_not_to_copy_buffer(aTHX_ a)
{
return
fetch_pv_try_not_to_copy_buffer(maria->config->password_temp);
}
enum
perl_type {
PERL_TYPE_UNDEF,
PERL_TYPE_INTEGER,
PERL_TYPE_NUMERIC,
PERL_TYPE_BINARY,
PERL_TYPE_STRING
};
static
enum
perl_type mysql_to_perl_type(
enum
enum_field_types type)
{
switch
(type) {
case
MYSQL_TYPE_NULL:
return
PERL_TYPE_UNDEF;
case
MYSQL_TYPE_TINY:
case
MYSQL_TYPE_SHORT:
case
MYSQL_TYPE_INT24:
case
MYSQL_TYPE_LONG:
#if IVSIZE >= 8
case
MYSQL_TYPE_LONGLONG:
#endif
case
MYSQL_TYPE_YEAR:
return
PERL_TYPE_INTEGER;
case
MYSQL_TYPE_FLOAT:
#if NVSIZE >= 8
case
MYSQL_TYPE_DOUBLE:
#endif
return
PERL_TYPE_NUMERIC;
#if MYSQL_VERSION_ID > NEW_DATATYPE_VERSION
case
MYSQL_TYPE_BIT:
#endif
#if MYSQL_VERSION_ID > GEO_DATATYPE_VERSION
case
MYSQL_TYPE_GEOMETRY:
#endif
case
MYSQL_TYPE_TINY_BLOB:
case
MYSQL_TYPE_BLOB:
case
MYSQL_TYPE_MEDIUM_BLOB:
case
MYSQL_TYPE_LONG_BLOB:
return
PERL_TYPE_BINARY;
default
:
return
PERL_TYPE_STRING;
}
}
int
THX_add_row_to_results(pTHX_ MariaDB_client *maria, MYSQL_ROW row,
bool
want_hashref,
int
field_count, unsigned
long
*lengths, MYSQL_FIELD *fields)
#define add_row_to_results(a,b,c) THX_add_row_to_results(aTHX_ a,b,c,0,NULL,NULL)
#define add_row_to_results_heavy(a,b,c,d,e,f) THX_add_row_to_results(aTHX_ a,b,c,d,e,f)
{
AV *query_results;
SV *row_results;
SSize_t i = 0;
if
(!maria->query_results) {
maria->query_results = MUTABLE_SV(newAV());
}
query_results = MUTABLE_AV(maria->query_results);
if
( !field_count )
field_count = mysql_field_count(maria->mysql);
if
( !fields )
fields = mysql_fetch_fields(maria->res);
if
( !lengths )
lengths = mysql_fetch_lengths(maria->res);
if
( want_hashref ) {
row_results = MUTABLE_SV(newHV());
}
else
{
row_results = MUTABLE_SV(newAV());
av_extend(MUTABLE_AV(row_results), field_count);
}
av_push(query_results, newRV_noinc(row_results));
for
( i = 0; i < field_count; i++ ) {
SV *col_data;
if
( !row[i] ) {
if
( !want_hashref ) {
continue
;
}
else
{
hv_store(
MUTABLE_HV(row_results),
fields[i].name,
fields[i].name_length,
newSVsv(&PL_sv_undef),
0
);
continue
;
}
}
col_data = newSVpvn(row[i], (STRLEN)lengths[i]);
switch
(mysql_to_perl_type(fields[i].type)) {
case
PERL_TYPE_NUMERIC:
if
(!(fields[i].flags & ZEROFILL_FLAG)) {
(
void
)SvNV(col_data);
SvNOK_only(col_data);
}
break
;
case
PERL_TYPE_INTEGER:
if
(!(fields[i].flags & ZEROFILL_FLAG)) {
if
(fields[i].flags & UNSIGNED_FLAG) {
(
void
)SvUV(col_data);
SvIOK_only_UV(col_data);
}
else
{
(
void
)SvIV(col_data);
SvIOK_only(col_data);
}
}
break
;
case
PERL_TYPE_UNDEF:
(
void
)SvOK_off(col_data);
break
;
default
:
break
;
}
if
( want_hashref ) {
hv_store(
MUTABLE_HV(row_results),
fields[i].name,
fields[i].name_length,
col_data,
0
);
}
else
{
av_store(MUTABLE_AV(row_results), i, col_data);
}
}
return
1;
}
#define store_in_self(pv, sv) hv_stores(MUTABLE_HV(SvRV(self)),pv,sv)
void
THX_usual_post_connect_shenanigans(pTHX_ SV* self)
#define usual_post_connect_shenanigans(s) \
THX_usual_post_connect_shenanigans(aTHX_ s)
{
dMARIA;
maria->is_cont = FALSE;
maria->current_state = STATE_STANDBY;
maria->thread_id = mysql_thread_id(maria->mysql);
(
void
)store_in_self(
"mysql_thread_id"
,
newSVnv(maria->thread_id)
);
return
;
}
#define maybe_init_mysql_connection(mysql) STMT_START { \
if
( !mysql ) { \
my_bool reconnect = 0; \
Newxz(mysql, 1, MYSQL); \
mysql_init(mysql); \
mysql_options(mysql, MYSQL_OPT_NONBLOCK, 0); \
mysql_options(mysql, MYSQL_OPT_RECONNECT, &reconnect); \
} \
} STMT_END
int
THX_do_work(pTHX_ SV* self, IV event)
#define do_work(self, event) THX_do_work(aTHX_ self, event)
{
dMARIA;
int
err = 0;
int
status = 0;
int
state = maria->current_state;
int
state_for_error = STATE_STANDBY;
sql_config *config = maria->config;
const
char
* errstring = NULL;
#define have_password_in_memory(maria) (maria && maria->config && maria->config->password_temp && SvOK(maria->config->password_temp))
while
( 1 ) {
if
( err )
break
;
if
( status )
break
;
if
( state == STATE_STANDBY && !maria->run_query )
break
;
if
( state == STATE_DISCONNECTED && !have_password_in_memory(maria) ) {
break
;
}
switch
( state ) {
case
STATE_STANDBY:
if
( maria->run_query ) {
state = STATE_QUERY;
}
break
;
case
STATE_DISCONNECTED:
if
( have_password_in_memory(maria) ) {
state = STATE_CONNECT;
}
else
{
if
( maria->run_query ) {
err = 1;
errstring =
"Disconnected and no password in memory to reconnect, nothing to do!"
;
}
}
break
;
case
STATE_CONNECT:
{
MYSQL *mysql_ret = 0;
if
( !maria->is_cont ) {
const
char
*password =
fetch_password_try_not_to_copy_buffer(maria);
if
( !password ) {
err = 1;
errstring =
"No password in memory during ->connect! If check that the password was passed in and was defined."
;
break
;
}
status = mysql_real_connect_start(
&mysql_ret,
maria->mysql,
maria->config->hostname,
maria->config->username,
password,
maria->config->database,
maria->config->port,
maria->config->unix_socket,
CLIENT_REMEMBER_OPTIONS
|
maria->config->client_opts
);
maria->socket_fd = mysql_get_socket(maria->mysql);
(
void
)store_in_self(
"mysql_socket_fd"
,
newSViv(maria->socket_fd)
);
}
else
{
status = mysql_real_connect_cont(
&mysql_ret,
maria->mysql,
event
);
event = 0;
}
if
(mysql_errno(maria->mysql) > 0) {
maria->is_cont = FALSE;
err = 1;
errstring = mysql_error(maria->mysql);
state_for_error = STATE_DISCONNECTED;
break
;
}
if
( status ) {
maria->is_cont = TRUE;
}
else
{
usual_post_connect_shenanigans(self);
state = STATE_STANDBY;
}
break
;
}
case
STATE_QUERY:
if
( maria->is_cont ) {
status = mysql_real_query_cont(&err, maria->mysql, event);
event = 0;
}
else
{
STRLEN query_len;
char
* query_pv = SvPV(maria->query_sv, query_len);
status = mysql_real_query_start(
&err,
maria->mysql,
query_pv,
query_len
);
}
if
( err ) {
maria->is_cont = FALSE;
state = STATE_STANDBY;
maria->run_query = NULL;
errstring = mysql_error(maria->mysql);
}
else
if
( status ) {
maria->is_cont = TRUE;
}
else
{
maria->is_cont = FALSE;
maria->run_query = NULL;
if
( maria->store_query_result ) {
state = STATE_STORE_RESULT;
}
else
{
maria->res = mysql_use_result(maria->mysql);
if
( maria->res ) {
state = STATE_ROW_FETCH;
}
else
if
( mysql_field_count(maria->mysql) == 0 ) {
state = STATE_STANDBY;
}
else
{
err = 1;
errstring = mysql_error(maria->mysql);
state = STATE_STANDBY;
}
}
}
break
;
case
STATE_STORE_RESULT:
if
( maria->is_cont ) {
status = mysql_store_result_cont(&(maria->res), maria->mysql, event);
event = 0;
}
else
{
status = mysql_store_result_start(&(maria->res), maria->mysql);
}
if
( status ) {
maria->is_cont = TRUE;
}
else
{
maria->is_cont = FALSE;
if
( mysql_errno(maria->mysql) > 0 ) {
err = 1;
errstring = mysql_error(maria->mysql);
}
else
{
if
( !maria->res ) {
UV affected_rows = mysql_affected_rows(maria->mysql);
if
( affected_rows ) {
if
(!maria->query_results) {
maria->query_results = MUTABLE_SV(newAV());
}
av_push(MUTABLE_AV(maria->query_results), newSVuv( affected_rows ));
}
}
else
if
( mysql_field_count(maria->mysql) == 0 ) {
}
else
{
MYSQL_FIELD *fields
= mysql_fetch_fields(maria->res);
int
field_count
= mysql_field_count(maria->mysql);
unsigned
long
*lengths
= mysql_fetch_lengths(maria->res);
while
(1) {
MYSQL_ROW row = mysql_fetch_row(maria->res);
if
( !row )
break
;
add_row_to_results_heavy(
maria,
row,
maria->want_hashrefs,
field_count,
lengths,
fields
);
}
}
}
maria->want_hashrefs = FALSE;
state = STATE_FREE_RESULT;
}
break
;
case
STATE_FREE_RESULT:
if
( maria->is_cont ) {
status = mysql_free_result_cont(maria->res, event);
event = 0;
}
else
{
status = mysql_free_result_start(maria->res);
}
if
(!status) {
maria->res = NULL;
maria->is_cont = FALSE;
state = STATE_STANDBY;
}
else
{
maria->is_cont = TRUE;
}
break
;
case
STATE_ROW_FETCH:
{
MYSQL_ROW row = 0;
if
( maria->is_cont ) {
status = mysql_fetch_row_cont(&row, maria->res, event);
event = 0;
if
(!status) {
if
( row ) {
add_row_to_results(maria, row, maria->want_hashrefs);
}
else
{
if
( mysql_errno(maria->mysql) > 0 ) {
err = 1;
errstring = mysql_error(maria->mysql);
}
state = STATE_FREE_RESULT;
}
maria->is_cont = FALSE;
}
}
else
{
do
{
row = 0;
status = mysql_fetch_row_start(&row, maria->res);
if
(!status) {
if
( row ) {
add_row_to_results(maria, row, maria->want_hashrefs);
}
else
if
( mysql_errno(maria->mysql) > 0 ) {
err = 1;
errstring = mysql_error(maria->mysql);
state = STATE_FREE_RESULT;
}
}
}
while
(!status && row);
if
( status ) {
maria->is_cont = TRUE;
}
else
if
(!row) {
maria->want_hashrefs = FALSE;
state = STATE_FREE_RESULT;
}
}
break
;
}
case
STATE_PING:
{
int
ret;
if
( maria->is_cont ) {
status = mysql_ping_cont(&ret, maria->mysql, event);
event = 0;
}
else
{
status = mysql_ping_start(&ret, maria->mysql);
}
if
( status ) {
maria->is_cont = TRUE;
}
else
{
maria->is_cont = FALSE;
state = STATE_STANDBY;
if
( ret ) {
const
char
* ping_error = mysql_error(maria->mysql);
maria->query_results = newSVpvn( ping_error,
strlen
(ping_error) );
}
else
{
maria->query_results = &PL_sv_no;
}
}
break
;
}
default
:
err = 1;
errstring =
"Should never happen! Invalid state"
;
}
}
if
( err ) {
maria->is_cont = FALSE;
maria->current_state = state_for_error;
maria->last_status = 0;
if
( maria->run_query ) {
maria->run_query = NULL;
}
if
(!errstring)
errstring =
"Unknown MySQL error"
;
croak(
"%s"
, errstring);
}
maria->current_state = state;
maria->last_status = status;
return
status;
}
SV*
THX_quote_sv(pTHX_ MariaDB_client* maria, SV* to_be_quoted)
#define quote_sv(to_be_quoted) THX_quote_sv(aTHX_ maria, to_be_quoted)
{
UV new_length;
STRLEN new_len_needed;
STRLEN to_be_quoted_len;
char
* escaped_buffer;
const
char
* to_be_quoted_pv;
SV *quoted_sv;
if
( !SvOK(to_be_quoted) ) {
return
newSVpvs(
"NULL"
);
}
to_be_quoted_pv = SvPV(to_be_quoted, to_be_quoted_len);
if
( to_be_quoted_len == 0 ) {
return
newSVpvs(
"\"\""
);
}
if
( (to_be_quoted_len+3) > (MEM_SIZE_MAX/2) ) {
croak(
"Cannot quote absurdly long string, would cause an overflow"
);
}
new_len_needed = to_be_quoted_len * 2 + 3;
quoted_sv = newSV(new_len_needed);
escaped_buffer = SvPVX_mutable(quoted_sv);
escaped_buffer[0] =
'\''
;
SvPOK_on(quoted_sv);
new_length = mysql_real_escape_string(
maria->mysql,
escaped_buffer+1,
to_be_quoted_pv,
to_be_quoted_len
);
escaped_buffer[new_length+1] =
'\''
;
escaped_buffer[new_length+2] =
'\0'
;
SvCUR_set(quoted_sv, (STRLEN)new_length + 2);
if
( SvUTF8(to_be_quoted) )
SvUTF8_on(quoted_sv);
return
quoted_sv;
}
const
char
*
THX_easy_arg_fetch(pTHX_ HV *hv,
char
* pv, STRLEN pv_len,
bool
required)
#define easy_arg_fetch(hv, s, b) THX_easy_arg_fetch(aTHX_ hv, s, sizeof(s)-1, b)
{
SV** svp;
const
char
*res = NULL;
if
((svp = hv_fetch(hv, pv, pv_len, FALSE))) {
if
( SvOK(*svp) ) {
STRLEN len;
res = SvPV_const(*svp, len);
}
}
else
if
( required ) {
croak(
"No %s given to ->connect / ->connect_start!"
, pv);
}
return
res;
}
void
THX_mysql_opt_integer(pTHX_ MariaDB_client* maria, HV* hv,
const
char
* str, STRLEN str_len,
enum
mysql_option option )
#define mysql_opt_integer(m, h, s, o) \
THX_mysql_opt_integer(aTHX_ m, h, s,
sizeof
(s)-1, o)
{
int
value;
SV** svp = hv_fetch(hv, str, str_len, FALSE);
if
( !svp || !*svp || !SvOK(*svp) )
return
;
value = SvIV(*svp);
if
( value )
mysql_options(
maria->mysql,
option,
(
const
char
*)&value
);
return
;
}
void
THX_unpack_config_from_hashref(pTHX_ SV* self, HV* args)
#define unpack_config_from_hashref(a,b) THX_unpack_config_from_hashref(aTHX_ a,b)
{
SV** svp;
dMARIA;
sql_config *config = maria->config;
free_our_config_items(config);
config->hostname = savepv(easy_arg_fetch(args,
"host"
, TRUE));
config->username = savepv(easy_arg_fetch(args,
"user"
, TRUE));
config->database = savepv(easy_arg_fetch(args,
"database"
, FALSE));
config->unix_socket = savepv(easy_arg_fetch(args,
"unix_socket"
, FALSE));
config->charset_name = savepv(easy_arg_fetch(args,
"charset"
, FALSE));
#define FETCH_FROM_HV(s) \
((svp = hv_fetch(args, s,
sizeof
(s)-1, FALSE)) && *svp && SvOK(*svp))
config->port = FETCH_FROM_HV(
"port"
) ? SvIV(*svp) : 0;
maria->store_query_result = FETCH_FROM_HV(
"mysql_use_results"
)
? cBOOL(SvTRUE(*svp) ? FALSE : TRUE)
: TRUE;
if
( config->charset_name &&
strlen
(config->charset_name) )
mysql_options(
maria->mysql,
MYSQL_SET_CHARSET_NAME,
config->charset_name
);
if
( config->unix_socket &&
strlen
(config->unix_socket) ) {
const
int
xxx = MYSQL_PROTOCOL_SOCKET;
mysql_options(
maria->mysql,
MYSQL_OPT_PROTOCOL,
&xxx
);
}
if
( FETCH_FROM_HV(
"mysql_init_command"
) && SvTRUE(*svp) ) {
const
char
* init_command = SvPV_nolen_const(*svp);
mysql_options(
maria->mysql,
MYSQL_INIT_COMMAND,
init_command
);
}
if
( FETCH_FROM_HV(
"mysql_compression"
) ) {
mysql_options(
maria->mysql,
MYSQL_OPT_COMPRESS,
NULL
);
}
#define my_mysql_opt_integer(a,b) mysql_opt_integer(maria, args, a, b)
my_mysql_opt_integer(
"mysql_connect_timeout"
, MYSQL_OPT_CONNECT_TIMEOUT);
my_mysql_opt_integer(
"mysql_write_timeout"
, MYSQL_OPT_WRITE_TIMEOUT);
my_mysql_opt_integer(
"mysql_read_timeout"
, MYSQL_OPT_READ_TIMEOUT);
#undef my_mysql_opt_integer
if
( FETCH_FROM_HV(
"ssl"
) ) {
my_bool ssl_verify = 0;
my_bool ssl_enforce = 1;
bool
use_default_ciphers = TRUE;
HV *ssl;
if
( !SvROK(*svp) || SvTYPE(SvRV(*svp)) != SVt_PVHV ) {
croak(
"ssl argument should point to a hashref"
);
}
else
{
ssl = MUTABLE_HV(SvRV(*svp));
}
#define ssl_config_set(s) STMT_START { \
const
char
*tmp = easy_arg_fetch(ssl, #s, FALSE); \
config->ssl_##s = tmp ? savepv(tmp) : NULL; \
} STMT_END
ssl_config_set(key);
ssl_config_set(cert);
ssl_config_set(ca);
ssl_config_set(capath);
ssl_config_set(cipher);
if
( config->ssl_cipher &&
strlen
(config->ssl_cipher) ) {
use_default_ciphers = FALSE;
}
if
( (svp = hv_fetchs(ssl,
"optional"
, FALSE)) && *svp )
ssl_enforce = !SvTRUE(*svp);
if
((svp = hv_fetchs(ssl,
"verify_server_cert"
, FALSE)) && *svp) {
ssl_verify = SvTRUE(*svp);
}
#ifdef HAVE_SSL_ENFORCE
if
(mysql_options(maria->mysql, MYSQL_OPT_SSL_ENFORCE, &ssl_enforce) != 0) {
croak(
"Enforcing SSL encryption is not supported"
);
}
#else
ssl_verify = 1;
#endif
if
( ssl_verify && mysql_options(maria->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &ssl_verify) != 0 ) {
croak(
"verify_server_cert=1 (or optional=0 in a version without MYSQL_OPT_SSL_ENFORCE) is not supported"
);
}
mysql_ssl_set(
maria->mysql,
config->ssl_key,
config->ssl_cert,
config->ssl_ca,
config->ssl_capath,
use_default_ciphers ? NULL : config->ssl_cipher
);
}
#undef FETCH_FROM_HV
}
MODULE = MariaDB::NonBlocking PACKAGE = MariaDB::NonBlocking
PROTOTYPES: DISABLE
BOOT:
{
HV *stash = gv_stashpvs(
"MariaDB::NonBlocking"
, GV_ADD);
CV *wait_read_cv = newCONSTSUB(stash,
"MYSQL_WAIT_READ"
, newSViv(MYSQL_WAIT_READ));
CV *wait_write_cv = newCONSTSUB(stash,
"MYSQL_WAIT_WRITE"
, newSViv(MYSQL_WAIT_WRITE));
CV *wait_except_cv = newCONSTSUB(stash,
"MYSQL_WAIT_EXCEPT"
, newSViv(MYSQL_WAIT_EXCEPT));
CV *wait_timeout_cv = newCONSTSUB(stash,
"MYSQL_WAIT_TIMEOUT"
, newSViv(MYSQL_WAIT_TIMEOUT));
PERL_UNUSED_VAR(wait_read_cv);
PERL_UNUSED_VAR(wait_write_cv);
PERL_UNUSED_VAR(wait_except_cv);
PERL_UNUSED_VAR(wait_timeout_cv);
}
SV*
init(SV* classname)
CODE:
{
SV* self;
MariaDB_client *maria;
sql_config *config;
Newxz(maria, 1, MariaDB_client);
Newxz(config, 1, sql_config);
maria->config = config;
maria->query_results = NULL;
maria->current_state = STATE_DISCONNECTED;
maria->socket_fd = -1;
maria->thread_id = -1;
maria->store_query_result = TRUE;
maria->query_sv = newSVpvs(
""
);
maybe_init_mysql_connection(maria->mysql);
RETVAL = newRV_noinc(MUTABLE_SV(newHV()));
sv_bless(RETVAL, gv_stashsv(classname, GV_ADD));
sv_magicext(
SvRV(RETVAL),
SvRV(RETVAL),
PERL_MAGIC_ext,
&maria_vtable,
(
char
*) maria,
0
);
}
OUTPUT: RETVAL
int
mysql_socket_fd(SV* self)
CODE:
dMARIA;
RETVAL = maria->socket_fd;
OUTPUT: RETVAL
IV
mysql_warning_count(SV* self)
CODE:
dMARIA;
RETVAL = mysql_warning_count(maria->mysql);
OUTPUT: RETVAL
IV
connect_start(SV* self, HV* args)
CODE:
{
HV *inner_self = MUTABLE_HV(SvRV(self));
SV **svp = hv_fetchs(args,
"password"
, FALSE);
dMARIA;
if
(!svp || !*svp)
croak(
"No password given to ->connect_start"
);
if
( maria->current_state != STATE_DISCONNECTED )
croak(
"Cannot call connect_start because the current internal state says we are on %s but we need to be in %s"
, state_to_name[maria->current_state], state_to_name[STATE_DISCONNECTED]);
maybe_init_mysql_connection(maria->mysql);
unpack_config_from_hashref(self, args);
SAVEGENERICSV(maria->config->password_temp);
maria->config->password_temp = SvREFCNT_inc(*svp);
RETVAL = do_work(self, 0);
}
OUTPUT: RETVAL
IV
ping_start(SV* self)
CODE:
{
dMARIA;
RETVAL = 0;
if
( maria->current_state == STATE_DISCONNECTED ) {
maria->query_results = newSVpvs(
"Not connected to MySQL, automatically failed ping"
);
}
else
if
( maria->current_state != STATE_STANDBY || maria->is_cont ) {
croak(
"Cannot ping an active connection!!"
);
}
else
if
( maria->run_query ) {
croak(
"Cannot ping when we have a query queued to be run"
);
}
else
{
maria->current_state = STATE_PING;
RETVAL = do_work(self, 0);
}
}
OUTPUT: RETVAL
SV*
ping_result(SV* self)
CODE:
{
dMARIA;
if
( maria->is_cont )
croak(
"Cannot get the results of the ping, because we are still waiting on the server to respond!"
);
RETVAL = maria->query_results;
maria->query_results = NULL;
}
OUTPUT: RETVAL
IV
run_query_start(SV* self, SV * query, ...)
CODE:
{
dMARIA;
if
( maria->run_query )
croak(
"Attempted to start a query when this connection already has a query in flight"
);
if
( !SvOK(query) )
croak(
"Query was empty"
);
if
( maria->current_state == STATE_QUERY ) {
croak(
"Cannot start running a second query while we are still completing the first!"
);
}
if
(
maria->current_state == STATE_DISCONNECTED
&&
!have_password_in_memory(maria)
) {
croak(
"Cannot start query; not connected"
);
}
maria->want_hashrefs = FALSE;
if
( items > 2 && SvOK(ST(2)) ) {
SV *arg = ST(2);
SV **svp;
if
( !SvROK(arg) || SvTYPE(SvRV(ST(2))) != SVt_PVHV ) {
croak(
"Invalid (non-hash, non-undef) argument given to run_query"
);
}
svp = hv_fetchs(MUTABLE_HV(SvRV(arg)),
"want_hashrefs"
, FALSE);
if
( svp && *svp ) {
maria->want_hashrefs = cBOOL(SvTRUE(*svp));
}
}
if
( items > 3 && SvOK(ST(3)) ) {
SV* bind = ST(3);
AV* bind_av;
bool
escaped;
SV* query_with_params = maria->query_sv;
bool
need_utf8_on = cBOOL(SvUTF8(query));
STRLEN max_size_of_query_string;
const
char
* query_pv = SvPV(query, max_size_of_query_string);
char
*d = NULL;
IV num_bind_params;
if
( max_size_of_query_string == 0 )
croak(
"Query was empty"
);
if
( !SvROK(bind) || SvTYPE(SvRV(bind)) != SVt_PVAV ) {
croak(
"Query bind values should be passed in an arrayref!"
);
}
bind_av = MUTABLE_AV(SvRV(bind));
num_bind_params = av_len(bind_av) + 1;
IV i = 0;
for
( ; i < num_bind_params; i++ ) {
SV* query_param = *av_fetch(bind_av, i, FALSE);
if
( !SvOK(query_param) ) {
max_size_of_query_string += 4;
continue
;
}
if
( SvGMAGICAL(query_param) )
mg_get(query_param);
if
( SvUTF8(query_param) )
need_utf8_on = TRUE;
max_size_of_query_string += sv_len(query_param)*2+1;
}
SvGROW(query_with_params, max_size_of_query_string);
if
( need_utf8_on ) {
SvUTF8_on(query_with_params);
}
else
{
SvUTF8_off(query_with_params);
}
d = SvPVX(query_with_params);
i = 0;
while
( *query_pv ) {
if
( *query_pv ==
'?'
&& !escaped ) {
UV new_length = 0;
STRLEN to_be_quoted_len;
const
char
* to_be_quoted_pv;
SV *to_be_quoted;
bool
upgraded = FALSE;
query_pv++;
if
( i >= num_bind_params )
croak(
"Not enough bind params given to run_query"
);
to_be_quoted = *av_fetch(bind_av, i++, FALSE);
if
( !SvOK(to_be_quoted) ) {
*d++ =
'N'
;
*d++ =
'U'
;
*d++ =
'L'
;
*d++ =
'L'
;
continue
;
}
if
( need_utf8_on && !SvUTF8(to_be_quoted) ) {
sv_utf8_upgrade_nomg(to_be_quoted);
upgraded = TRUE;
}
to_be_quoted_pv = SvPV(to_be_quoted, to_be_quoted_len);
if
( to_be_quoted_len == 0 ) {
*d++ =
'"'
;
*d++ =
'"'
;
continue
;
}
*d++ =
'\''
;
new_length = mysql_real_escape_string(
maria->mysql,
d,
to_be_quoted_pv,
to_be_quoted_len
);
d += new_length;
*d++ =
'\''
;
if
( upgraded ) {
sv_utf8_downgrade(to_be_quoted, 1);
}
}
else
if
( *query_pv ==
'\\'
&& !escaped ) {
escaped = TRUE;
query_pv++;
}
else
{
escaped = FALSE;
*d++ = *query_pv++;
}
}
SvCUR_set(
query_with_params,
(STRLEN)(d - SvPVX(query_with_params))
);
*d++ =
'\0'
;
if
( i != num_bind_params ) {
croak(
"Too many bind params given for query! Got %"
IVdf
", query needed %"
IVdf, num_bind_params, i);
}
}
else
{
sv_setsv(maria->query_sv, query);
}
maria->run_query = TRUE;
if
( maria->is_cont ) {
RETVAL = maria->last_status;
}
else
{
RETVAL = do_work(self, 0);
}
}
OUTPUT: RETVAL
const
char
*
current_state(SV* self)
CODE:
{
dMARIA;
RETVAL = state_to_name[maria->current_state];
}
OUTPUT: RETVAL
IV
cont(SV* self, ...)
ALIAS:
run_query_cont = 1
ping_cont = 2
connect_cont = 3
CODE:
{
dMARIA;
IV event = 0;
if
( !maria->is_cont ) {
croak(
"Calling ->%s, but we are not currently waiting for the server. Current state is %s"
, cont_to_name[ix], state_to_name[maria->current_state]);
}
if
( items > 1 ) {
if
( ST(1) == self )
croak(
"Called $self->%s($self), that is just wrong"
, cont_to_name[ix]);
event = SvIV(ST(1));
}
RETVAL = do_work(self, event);
}
OUTPUT: RETVAL
SV*
query_results(SV* self)
CODE:
dMARIA;
RETVAL = newRV_noinc(maria->query_results ? maria->query_results : MUTABLE_SV(newAV()));
maria->query_results = NULL;
OUTPUT: RETVAL
UV
get_timeout_value_ms(SV* self)
CODE:
dMARIA;
RETVAL = mysql_get_timeout_value_ms(maria->mysql);
OUTPUT: RETVAL
NV
insert_id(SV* self)
CODE:
dMARIA;
RETVAL = mysql_insert_id(maria->mysql);
OUTPUT: RETVAL
SV*
quote(SV* self, SV* to_be_quoted)
CODE:
{
dMARIA;
RETVAL = quote_sv(to_be_quoted);
}
OUTPUT: RETVAL
#define SQL_IDENTIFIER_QUOTE_CHAR '`'
SV*
quote_identifier(SV *self, ...)
CODE:
{
IV utf8_sv_count = 0;
STRLEN retval_actual_len = 0;
STRLEN max_retval_len = 0;
char
*d = NULL;
IV i = 0;
IV items_start = 1;
IV items_end = items;
if
( items > 3 && SvROK(ST(items)) ) {
items_end--;
}
for
(i = items_start; i < items_end; i++ ) {
SV *identifier = ST(i);
if
( SvGMAGICAL(identifier) )
mg_get(identifier);
if
( SvOK(identifier) ) {
max_retval_len += sv_len(identifier) + 2;
if
( SvUTF8(identifier) ) {
utf8_sv_count++;
}
}
}
if
( utf8_sv_count != 0 && utf8_sv_count != items ) {
max_retval_len = max_retval_len * 2;
}
max_retval_len = (max_retval_len*2) + 3 + items-1;
RETVAL = newSV(max_retval_len);
SvUPGRADE(RETVAL, SVt_PV);
SvPOK_on(RETVAL);
if
( utf8_sv_count )
SvUTF8_on(RETVAL);
d = SvPVX(RETVAL);
for
( i = items_start; i < items_end; i++ ) {
SV *identifier = ST(i);
STRLEN identifier_len;
const
char
* identifier_pv;
bool
upgraded = FALSE;
if
( !SvOK(identifier) ) {
continue
;
}
if
( utf8_sv_count && !SvUTF8(identifier) ) {
sv_utf8_upgrade_nomg(identifier);
upgraded = TRUE;
}
identifier_pv = SvPV_nomg(identifier, identifier_len);
retval_actual_len = retval_actual_len + identifier_len;
*d++ = SQL_IDENTIFIER_QUOTE_CHAR;
while
( identifier_len-- ) {
if
( *identifier_pv == SQL_IDENTIFIER_QUOTE_CHAR ) {
retval_actual_len++;
*d++ = SQL_IDENTIFIER_QUOTE_CHAR;
}
*d++ = *identifier_pv++;
}
*d++ = SQL_IDENTIFIER_QUOTE_CHAR;
retval_actual_len += 2;
if
( (i+1) != items_end ) {
*d++ =
'.'
;
retval_actual_len++;
}
if
( upgraded ) {
sv_utf8_downgrade(identifier, 1);
}
}
if
( d == SvPVX(RETVAL) ) {
*d++ = SQL_IDENTIFIER_QUOTE_CHAR;
*d++ = SQL_IDENTIFIER_QUOTE_CHAR;
retval_actual_len += 2;
}
*d++ =
'\0'
;
SvCUR_set(RETVAL, (STRLEN)retval_actual_len);
}
OUTPUT: RETVAL
void
disconnect(SV* self)
CODE:
dMARIA;
disconnect_generic(maria);
SV*
get_simple_stacktrace(HV* ignored_packages)
PREINIT:
HV* stash;
I32 level = 0;
I32 found = 0;
const
PERL_CONTEXT *cx;
CODE:
RETVAL = newSVpvs(
""
);
while
( level < 20 && found < 3 ) {
SV* package;
STRLEN len;
const
char
*key, *frame_file;
const
COP *lcop;
IV frame_line;
cx = caller_cx(level++, NULL);
if
( !cx )
break
;
if
( CxTYPE(cx) != CXt_SUB )
continue
;
stash = CopSTASH(cx->blk_oldcop);
if
(!stash || SvTYPE(stash) != SVt_PVHV)
continue
;
package = newSVhek(HvNAME_HEK(stash));
key = SvPV_const(package, len);
if
( hv_exists(ignored_packages, key, (SvUTF8(package) ? -(I32)len : (I32)len)) )
continue
;
frame_file = OutCopFILE(cx->blk_oldcop);
lcop = NULL;
if
(!lcop)
lcop = cx->blk_oldcop;
frame_line = CopLINE(lcop);
sv_catpvf(RETVAL,
"\n %"
SVf
" (%s:%"
IVdf
")"
, package, frame_file, frame_line);
}
OUTPUT: RETVAL