pike.git
/
lib
/
modules
/
Sql.pmod
/
sql_util.pmod
version
»
Context lines:
10
20
40
80
file
none
3
pike.git/lib/modules/Sql.pmod/sql_util.pmod:1:
/*
-
* $Id: sql_util.pmod,v 1.7 2001/01/09 21:18:30 marcus Exp $
-
*
+
* Some SQL utility functions. * They are kept here to avoid circular references. * * Henrik Grubbström 1999-07-01 */ #pike __REAL_VERSION__
-
//
.
-
//.
File: sql_util.pmod
-
//. RCSID: $Id: sql_util.pmod,v 1.7 2001/01/09 21:18:30 marcus Exp $
-
//. Author: Henrik Grubbström (grubba@idonex.se)
-
//.
-
//. Synopsis:
Some SQL utility functions
-
//.
-
//. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
//.
-
//. These functions are kept here mainly to avoid circular references.
-
//.
+
//
!
Some SQL utility functions
-
//
.
- quote
-
//.
Quote a string so that it can safely be put in a query.
-
//
.
>
s
-
String to quote.
+
//
!
Quote a string so that it can safely be put in a query.
+
//
!
+
//!
@param
s
+
//!
String to quote.
string quote(string s) {
-
return
(
replace(s, "\'", "\'\'")
)
;
+
return
replace(s, "\'", "\'\'");
}
-
//
.
- fallback
-
//.
Throw an error in case an unimplemented function is called.
+
//
!
Throw an error in case an unimplemented function is called.
void fallback() {
-
throw
(
({
"Function not supported in this database."
,
backtrace(
)
}))
;
+
error
( "Function not supported in this database." );
}
-
//. -
build
a raw SQL query, given the cooked query and the variable bindings
-
//
.
It's meant to be used as an emulation engine for those drivers not
-
//
.
providing such a behaviour directly (i.e. Oracle).
-
//
.
The raw query can contain some variables (identified by prefixing
-
//
.
a colon to a name or a number(i.e. :var
,
:2). They will be
-
//
.
replaced by the corresponding value in the mapping.
-
//
.
>
query
-
//
.
The query
-
//
.
>
bindings
-
//
.
Optional mapping containing the variable bindings. Make sure that
-
//
.
no confusion is possible in the query. If necessary, change the
-
//
.
variables' names
+
//
! Wrapper to handle zero
.
+
//!
+
//!
@seealso
+
//! @[zero]
+
protected class ZeroWrapper
+
{
+
//! @returns
+
//! Returns the following:
+
//! @string
+
//! @value "NULL"
+
//! If @[fmt] is @expr{'s'@}.
+
//! @value "ZeroWrapper()"
+
//! If @[fmt] is @expr{'O'@}.
+
//! @endstring
+
//! Otherwise it formats a @expr{0@} (zero).
+
protected string _sprintf(int fmt, mapping(string:mixed) params)
+
{
+
if (fmt == 's') return "NULL";
+
if (fmt == 'O') return "ZeroWrapper()";
+
return sprintf(sprintf("%%*%c", fmt), params, 0);
+
}
+
}
+
+
//! Instance of @[ZeroWrapper] used by @[handle_extraargs()].
+
protected ZeroWrapper zero = ZeroWrapper();
+
+
protected class NullArg
+
{
+
protected string _sprintf (int fmt)
+
{return fmt == 'O' ? "NullArg()" : "NULL";}
+
}
+
protected NullArg null_arg = NullArg();
+
+
//! Handle @[sprintf]
-
based
quoted
arguments
+
//!
+
//! @param query
+
//! The query as sent to one of the query functions.
+
//!
+
//! @param extraargs
+
//! The arguments following the query.
+
//!
+
//! @param bindings
+
//! Optional bindings mapping to which additional bindings will be
+
//! added. It's returned as the second element in the return value.
+
//! A new mapping is used if this isn't specified.
+
//!
+
//! @returns
+
//! Returns an array with two elements:
+
//! @array
+
//! @elem string 0
+
//! The query altered to use bindings-syntax.
+
//! @elem mapping(string|int:mixed) 1
+
//! A bindings mapping. Zero if no bindings were added.
+
//! @endarray
+
array(string|mapping(string|int:mixed))
+
handle_extraargs(string query, array(mixed) extraargs,
+
void|mapping(string|int:mixed) bindings) {
+
+
array(mixed) args=allocate(sizeof(extraargs));
+
if (!bindings) bindings = ([]);
+
+
int
a
,
new_bindings;
+
foreach(extraargs; int j; mixed s) {
+
if (stringp(s) || multisetp(s)) {
+
string bind_name;
+
do {
+
bind_name = ":arg"+(a++);
+
} while (!zero_type (bindings[bind_name]));
+
args[j]=bind_name;
+
bindings[bind_name] = s;
+
new_bindings = 1;
+
continue;
+
}
+
if (intp(s) || floatp(s)) {
+
args[j] = s || zero;
+
continue;
+
}
+
if (objectp (s) && s->is_val_null) {
+
args[j] = null_arg;
+
continue;
+
}
+
error("Wrong type to query argument %d: %O\n", j + 1, s);
+
}
+
+
return ({sprintf(query,@args), new_bindings && bindings});
+
}
+
+
//! Build a
raw SQL query, given the cooked query and the variable bindings
+
//
!
It's meant to be used as an emulation engine for those drivers not
+
//
!
providing such a behaviour directly (i.e. Oracle).
+
//
!
The raw query can contain some variables (identified by prefixing
+
//
!
a colon to a name or a number
(i.e.
"
:var
"
or "
:2
"
). They will be
+
//
!
replaced by the corresponding value in the mapping.
+
//
!
+
//!
@param
query
+
//
!
The query
.
+
//
!
+
//!
@param
bindings
+
//
!
Optional mapping containing the variable bindings. Make sure that
+
//
!
no confusion is possible in the query. If necessary, change the
+
//
!
variables' names
.
string emulate_bindings(string query, mapping(string|int:mixed)|void bindings, void|object driver) { array(string)k, v;
-
function my_quote=(driver&&driver->quote?driver->quote:quote);
+
if (!bindings) return query;
-
v=
Array.
map(values(bindings),
+
function my_quote=(driver&&driver->quote?driver->quote:quote);
+
v=map(values(bindings),
lambda(mixed m) {
-
if(
multisetp
(m)
)
m
= indices(
m)[
0
];
-
return (
stringp(
m)?
"'"+my_quote
(m)
+
"
'
"
:
(string)m);
+
if(zero_type(m))
+
return "NULL";
+
if
(
objectp
(m)
&&
m
->is_val_null)
+
// Note: Could need bug compatibility here - in some cases
+
// we might be passed a null object that can be cast to
+
// "", and before this it would be. This is an observed
+
// compat issue in comment #7 in
[
bug 5900
]
.
+
return "NULL"
;
+
if(multisetp(m))
+
return
sizeof
(m)
?
indices(m)[0] :
"
";
+
return "
'"
+(intp(m)?(string)m:my
_quote(
(string)
m)
)
+"'";
});
-
k=
Array.
map(indices(bindings),lambda(string s){
-
return (stringp(s)? s : ":"+s);
+
// Throws if mapping key is empty string.
+
k=map(indices(bindings),lambda(string s){
+
return (
(
stringp(s)
&&s[0]==':')
?
+
s : ":"+s);
}); return replace(query,k,v); }
-
+
+
//! Result object wrapper performing utf8 decoding of all fields.
+
class UnicodeWrapper (
+
// The wrapped result object.
+
protected object master_result
+
)
+
{
+
//! Returns the number of rows in the result.
+
int num_rows()
+
{
+
return master_result->num_rows();
+
}
+
+
//! Returns the number of fields in the result.
+
int num_fields()
+
{
+
return master_result->num_fields();
+
}
+
+
//! Returns @expr{1@} if there are no more rows in the result.
+
int(0..1) eof()
+
{
+
return master_result->eof();
+
}
+
+
//! Cached @[fetch_fields()] result.
+
protected array(int|mapping(string:mixed)) field_info;
+
+
//! Returns Information about the fields in the result.
+
//!
+
//! The following fields are converted from UTF8 if present:
+
//! @mapping
+
//! @member string "name"
+
//! The name of the field. Always present.
+
//! @member string "table"
+
//! The table the field is from. Not present from all databases.
+
//! @member string "default"
+
//! The default value for the column. Not available from all databases.
+
//! @endmapping
+
array(int|mapping(string:mixed)) fetch_fields()
+
{
+
if (!field_info) {
+
field_info = master_result->fetch_fields();
+
foreach(field_info, int|mapping(string:mixed) field) {
+
if (mappingp(field)) {
+
field->name = utf8_to_string(field->name);
+
if (field->table) {
+
field->table = utf8_to_string(field->table);
+
}
+
if (field->default) {
+
field->default = utf8_to_string(field->default);
+
}
+
}
+
}
+
}
+
return field_info;
+
}
+
+
//! Skip ahead the specified number of rows.
+
void seek(int rows)
+
{
+
master_result->seek(rows);
+
}
+
+
//! Fetch the next row from the result.
+
//!
+
//! All strings in the result are decoded from UTF8.
+
int|array(string) fetch_row()
+
{
+
int|array(string) row = master_result->fetch_row();
+
if (!arrayp(row)) return row;
+
array(int|mapping(string:mixed)) field_info = fetch_fields();
+
foreach(row; int i; string|int val) {
+
if (stringp(val)) {
+
row[i] = utf8_to_string(val);
+
}
+
}
+
return row;
+
}
+
+
//! JSON is always utf8 default, do nothing.
+
int|string fetch_json_result()
+
{
+
return master_result->fetch_json_result();
+
}
+
}
+
+
#if constant (___Mysql.mysql.HAVE_MYSQL_FIELD_CHARSETNR)
+
+
class MySQLUnicodeWrapper
+
//! Result wrapper for MySQL that performs UTF-8 decoding of all
+
//! nonbinary fields. Useful if the result charset of the connection
+
//! has been set to UTF-8.
+
//!
+
//! @note
+
//! There's normally no need to use this class directly. It's used
+
//! automatically when @[mysql.set_unicode_decode_mode] is activated.
+
{
+
inherit UnicodeWrapper;
+
+
int|array(string) fetch_row()
+
{
+
int|array(string) row = master_result->fetch_row();
+
if (!arrayp(row)) return row;
+
array(int|mapping(string:mixed)) field_info = fetch_fields();
+
foreach(row; int i; string|int val) {
+
if (stringp(val) && field_info[i]->charsetnr != 63) {
+
row[i] = utf8_to_string(val);
+
}
+
}
+
return row;
+
}
+
}
+
+
#else
+
+
class MySQLBrokenUnicodeWrapper
+
// This one is used to get a buggy unicode support when compiled with
+
// an old MySQL client lib that doesn't have the charsetnr property in
+
// the field info. It looks at the binary flag instead, which is set
+
// for binary fields but might also be set for text fields (e.g. with
+
// a definition like "VARCHAR(255) BINARY").
+
//
+
// I.e. the effect of using this one is that text fields with the
+
// binary flag won't be correctly decoded in unicode decode mode.
+
//
+
// This has to be enabled either by passing "broken-unicode" as
+
// charset to Sql.mysql.create or Sql.mysql.set_charset, by calling
+
// Sql.mysql.set_unicode_decode_mode(-1), or by defining the
+
// environment variable PIKE_BROKEN_MYSQL_UNICODE_MODE. That will
+
// cause this buggy variant to be used if and only if the MySQL client
+
// lib doesn't support the charsetnr property.
+
{
+
inherit UnicodeWrapper;
+
+
int|array(string) fetch_row()
+
{
+
int|array(string) row = master_result->fetch_row();
+
if (!arrayp(row)) return row;
+
array(int|mapping(string:mixed)) field_info = fetch_fields();
+
foreach(row; int i; string|int val) {
+
if (stringp(val) && field_info[i]->flags &&
+
!field_info[i]->flags->binary) {
+
row[i] = utf8_to_string(val);
+
}
+
}
+
return row;
+
}
+
}
+
+
#endif