pike.git
/
lib
/
modules
/
Sql.pmod
/
mysql.pike
version
»
Context lines:
10
20
40
80
file
none
3
pike.git/lib/modules/Sql.pmod/mysql.pike:1:
/*
-
* $Id$
-
*
+
* Glue for the Mysql-module */
-
//
.
-
//.
File
:
mysql
.pike
-
//.
RCSID:
$Id$
-
//.
Author:
Henrik
Grubbström
(
grubba@idonex.se)
-
//.
-
//.
Synopsis:
Implements
the
glue
to
the
Mysql-module
.
-
//
.
-
//
.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
//
.
-
//
.
Implements
the
glue
needed
to
access
the
Mysql-module
from
the
generic
-
//.
SQL
module
.
-
//.
+
//
! This class encapsulates a connection to a MySQL server, and
+
//! implements the glue needed to access the Mysql module from the
+
//! generic SQL module
.
+
//
!
+
//!
@section Typed mode
+
//!
+
//! When query results are returned in typed mode, the MySQL data
+
//! types are represented like this
:
+
//!
+
//!
@dl
+
//!
@item
The
NULL
value
+
//!
Returned as @[Val
.
null].
+
//!
+
//! @item BIT, TINYINT, BOOL, SMALLINT, MEDIUMINT, INT, BIGINT
+
//! Returned as
pike
integers.
+
//
!
+
//! @item FLOAT, DOUBLE
+
//! Returned as pike floats
.
+
//!
+
//!
@item
DECIMAL
+
//!
Returned
as
pike integers for fields that are declared to
+
//
! contain zero decimals, otherwise returned as @[Gmp
.
mpq]
objects.
+
//!
+
//!
@item
DATE,
DATETIME,
TIME,
YEAR
+
//!
Returned as strings in their display representation
(
see the
+
//! MySQL manual)
.
+
//
!
+
//! @[Calendar] objects are not used partly because they always
+
//! represent a specific point or range in time, which these MySQL
+
//! types do not
.
+
//
!
+
//!
@item
TIMESTAMP
+
//!
Also
returned
as
strings in
the
display representation
.
+
//
!
+
//
!
The reason is that it's both more efficient and more robust (wrt
+
//
! time zone interpretations) to convert these to unix timestamps
+
//
!
on
the
MySQL
side
rather
than
in
the
client
glue.
I.e. use
the
+
//!
@tt{UNIX_TIMESTAMP@} function in the queries to retrieve them as
+
//
! unix timestamps on integer form
.
+
//!
+
//!
@item
String types
+
//! All string types are returned as pike strings
.
The MySQL glue
+
//
! can handle charset conversions for text strings - see
+
//! @[set_charset] and @[set_unicode_decode_mode]
.
+
//!
+
//! @enddl
+
//!
+
//! @endsection
#pike __REAL_VERSION__
-
+
// Cannot dump this since the #if constant(...) check below may depend
+
// on the presence of system libs at runtime.
+
constant dont_dump_program = 1;
+
#if constant(Mysql.mysql) inherit Mysql.mysql;
-
//. -
quote
-
//. Quote a string so that it can safely be put in a query.
-
//
.
>
s
-
String to quote.
+
#define UNICODE_DECODE_MODE 1
//
Unicode decode mode
+
#define LATIN1_UNICODE_ENCODE_MODE 2 // Unicode encode mode with latin1 charset
+
#define UTF8_UNICODE_ENCODE_MODE 4 // Unicode encode mode with utf8 charset
+
+
#ifdef MYSQL_CHARSET_DEBUG
+
#define CH_DEBUG(X
.
..)
\
+
werror(replace (sprintf ("%O", this), "%", "%%") + ": " + X)
+
#else
+
#define CH_DEBUG(X...)
+
#endif
+
+
#if !constant (Mysql.mysql.HAVE_MYSQL_FIELD_CHARSETNR)
+
// Recognition constant to tell that the unicode decode mode would use
+
// the buggy MySQLBrokenUnicodeWrapper if it would be enabled through
+
// any of the undocumented methods.
+
constant unicode_decode_mode_is_broken = 1;
+
#endif
+
+
// Set to the above if the connection is requested to be in one of the
+
// unicode modes. latin1 unicode encode mode is enabled by default; it
+
// should be compatible with earlier pike versions.
+
protected int utf8_mode;
+
+
// The charset, either "latin1" or "utf8", currently assigned to
+
// character_set_client when unicode encode mode is enabled. Zero when
+
// the connection charset has been set to something else than "latin1"
+
// or "unicode".
+
protected string send_charset;
+
+
protected void update_unicode_encode_mode_from_charset (string charset)
+
{
+
switch (charset) { // Lowercase assumed.
+
case "latin1":
+
utf8_mode |= LATIN1_UNICODE_ENCODE_MODE;
+
utf8_mode &= ~UTF8_UNICODE_ENCODE_MODE;
+
send_charset = "latin1";
+
CH_DEBUG ("Entering latin1 encode mode.\n");
+
break;
+
case "unicode":
+
utf8_mode |= UTF8_UNICODE_ENCODE_MODE;
+
utf8_mode &= ~LATIN1_UNICODE_ENCODE_MODE;
+
send_charset = "utf8";
+
CH_DEBUG ("Entering unicode encode mode.\n");
+
break;
+
default:
+
// Wrong charset
-
the mode can't be used.
+
utf8_mode |= LATIN1_UNICODE_ENCODE_MODE|UTF8_UNICODE_ENCODE_MODE;
+
send_charset = 0;
+
CH_DEBUG ("Not entering latin1
/
unicode encode mode "
+
"due to incompatible charset %O.\n", charset);
+
break;
+
}
+
}
+
+
int(0..1) set_unicode_encode_mode (int enable)
+
/
/! Enables or disables unicode encode mode
.
+
//!
+
//!
In
this
mode, if the server supports UTF-8 and the connection
+
//! charset is @expr{latin1@} (the default) or @expr{unicode@} then
+
//! @[big_query] handles wide unicode queries. Enabled by default.
+
//!
+
//! Unicode encode mode works as follows: Eight bit strings are sent
+
//! as @expr{latin1@} and wide strings are sent using @expr{utf8@}.
+
//! @[big_query] sends @expr{SET character_set_client@} statements as
+
//! necessary to update the charset on the server side. If the server
+
//! doesn't support that then it fails, but the wide string query
+
//! would fail anyway.
+
//!
+
//! To make this transparent, string literals with introducers (e.g.
+
//! @expr{_binary 'foo'@}) are excluded from the UTF-8 encoding. This
+
//! means that @[big_query] needs to do some superficial parsing of
+
//! the query when it is a wide string.
+
//!
+
//! @returns
+
//! @int
+
//! @value 1
+
//! Unicode encode mode is enabled.
+
//! @value 0
+
//! Unicode encode mode couldn't be enabled because an
+
//! incompatible connection charset is set. You need to do
+
//! @expr{@[set_charset]("latin1")@} or
+
//! @expr{@[set_charset]("unicode")@} to enable it.
+
//! @endint
+
//!
+
//! @note
+
//! Note that this mode doesn't affect the MySQL system variable
+
//! @expr{character_set_connection@}, i.e. it will still be set to
+
//! @expr{latin1@} by default which means server functions like
+
//! @expr{UPPER()@} won't handle non-@expr{latin1@} characters
+
//! correctly in all cases.
+
//!
+
//! To fix that, do @expr{@[set_charset]("unicode")@}. That will
+
//! allow unicode encode mode to work while @expr{utf8@} is fully
+
//! enabled at the server side.
+
//!
+
//! Tip: If you enable @expr{utf8@} on the server side, you need to
+
//! send raw binary strings as @expr{_binary'...'@}. Otherwise they
+
//! will get UTF-8 encoded by the server.
+
//!
+
//! @note
+
//! When unicode encode mode is enabled and the connection charset
+
//! is @expr{latin1@}, the charset accepted by @[big_query] is not
+
//! quite Unicode since @expr{latin1@} is based on @expr{cp1252@}.
+
//! The differences are in the range @expr{0x80..0x9f@} where
+
//! Unicode has control chars.
+
//!
+
//! This small discrepancy is not present when the connection
+
//! charset is @expr{unicode@}.
+
//!
+
//! @seealso
+
//! @[set_unicode_decode_mode], @[set_charset]
+
{
+
if (enable)
+
update_unicode_encode_mode_from_charset (lower_case (get_charset()));
+
else {
+
utf8_mode &= ~(LATIN1_UNICODE_ENCODE_MODE|UTF8_UNICODE_ENCODE_MODE);
+
send_charset = 0;
+
CH_DEBUG("Disabling unicode encode mode.\n");
+
}
+
return !!send_charset;
+
}
+
+
int get_unicode_encode_mode()
+
//! Returns nonzero if unicode encode mode is enabled, zero otherwise.
+
//!
+
//! @seealso
+
//! @[set_unicode_encode_mode]
+
{
+
return !!send_charset;
+
}
+
+
void set_unicode_decode_mode (int enable)
+
//! Enable or disable unicode decode mode.
+
//!
+
//! In this mode, if the server supports UTF-8 then non-binary text
+
//! strings in results are automatically decoded to (possibly wide)
+
//! unicode strings. Not enabled by default.
+
//!
+
//! The statement "@expr{SET character_set_results = utf8@}" is sent
+
//! to the server to enable the mode. When the mode is disabled,
+
//! "@expr{SET character_set_results = xxx@}" is sent, where
+
//! @expr{xxx@} is the connection charset that @[get_charset] returns.
+
//!
+
//! @param enable
+
//! Nonzero enables this feature, zero disables it.
+
//!
+
//! @throws
+
//! Throws an exception if the server doesn't support this, i.e. if
+
//! the statement above fails. The MySQL system variable
+
//! @expr{character_set_results@} was added in MySQL 4.1.1.
+
//!
+
//! An error is also thrown if Pike has been compiled with a MySQL
+
//! client library older than 4.1.0, which lack the necessary
+
//! support for this.
+
//!
+
//! @seealso
+
//! @[set_unicode_encode_mode]
+
{
+
#if !constant (Mysql.mysql.HAVE_MYSQL_FIELD_CHARSETNR)
+
// Undocumented feature for old mysql libs. See
+
// MySQLBrokenUnicodeWrapper for details.
+
if (!(<0, -1>)[enable] && !getenv("PIKE_BROKEN_MYSQL_UNICODE_MODE")) {
+
predef::error ("Unicode decode mode not supported - "
+
"compiled with MySQL client library < 4.1.0.\n");
+
}
+
#endif
+
+
if (enable) {
+
CH_DEBUG("Enabling unicode decode mode.\n");
+
::big_query ("SET character_set_results = utf8");
+
utf8_mode |= UNICODE_DECODE_MODE;
+
}
+
else {
+
CH_DEBUG("Disabling unicode decode mode.\n");
+
::big_query ("SET character_set_results = " + ::get_charset());
+
utf8_mode &= ~UNICODE_DECODE_MODE;
+
}
+
}
+
+
+
int get_unicode_decode_mode()
+
//! Returns nonzero if unicode decode mode is enabled, zero otherwise.
+
//!
+
//! @seealso
+
//! @[set_unicode_decode_mode]
+
{
+
return utf8_mode & UNICODE_DECODE_MODE;
+
}
+
+
void set_charset (string charset)
+
//! Changes the connection charset. Works similar to sending the query
+
//! @expr{SET NAMES @[charset]@} but also records the charset on the
+
//! client side so that various client functions work correctly.
+
//!
+
//! @[charset] is a MySQL charset name or the special value
+
//! @expr{"unicode"@} (see below). You can use @expr{SHOW CHARACTER
+
//! SET@} to get a list of valid charsets.
+
//!
+
//! Specifying @expr{"unicode"@} as charset is the same as
+
//! @expr{"utf8"@} except that unicode encode and decode modes are
+
//! enabled too. Briefly, this means that you can send queries as
+
//! unencoded unicode strings and will get back non-binary text
+
//! results as unencoded unicode strings. See
+
//! @[set_unicode_encode_mode] and @[set_unicode_decode_mode] for
+
//! further details.
+
//!
+
//! @throws
+
//! Throws an exception if the server doesn't support this, i.e. if
+
//! the statement @expr{SET NAMES@} fails. Support for it was added
+
//! in MySQL 4.1.0.
+
//!
+
//! @note
+
//! If @[charset] is @expr{"latin1"@} and unicode encode mode is
+
//! enabled (the default) then @[big_query] can send wide unicode
+
//! queries transparently if the server supports UTF-8. See
+
//! @[set_unicode_encode_mode].
+
//!
+
//! @note
+
//! If unicode decode mode is already enabled (see
+
//! @[set_unicode_decode_mode]) then this function won't affect the
+
//! result charset (i.e. the MySQL system variable
+
//! @expr{character_set_results@}).
+
//!
+
//! Actually, a query @expr{SET character_set_results = utf8@} will
+
//! be sent immediately after setting the charset as above if
+
//! unicode decode mode is enabled and @[charset] isn't
+
//! @expr{"utf8"@}.
+
//!
+
//! @note
+
//! You should always use either this function or the
+
//! @expr{"mysql_charset_name"@} option to @[create] to set the
+
//! connection charset, or more specifically the charset that the
+
//! server expects queries to have (i.e. the MySQL system variable
+
//! @expr{character_set_client@}). Otherwise @[big_query] might not
+
//! work correctly.
+
//!
+
//! Afterwards you may change the system variable
+
//! @expr{character_set_connection@}, and also
+
//! @expr{character_set_results@} if unicode decode mode isn't
+
//! enabled.
+
//!
+
//! @note
+
//! The MySQL @expr{latin1@} charset is close to Windows
+
//! @expr{cp1252@}. The difference from ISO-8859-1 is a bunch of
+
//! printable chars in the range @expr{0x80..0x9f@} (which contains
+
//! control chars in ISO-8859-1). For instance, the euro currency
+
//! sign is @expr{0x80@}.
+
//!
+
//! You can use the @expr{mysql-latin1@} encoding in the @[Charset]
+
//! module to do conversions, or just use the special
+
//! @expr{"unicode"@} charset instead.
+
//!
+
//! @seealso
+
//! @[get_charset], @[set_unicode_encode_mode], @[set_unicode_decode_mode]
+
{
+
charset = lower_case (charset);
+
+
CH_DEBUG("Setting charset to %O.\n", charset);
+
+
int broken_unicode = charset == "broken-unicode";
+
if (broken_unicode) charset = "unicode";
+
+
::set_charset (charset == "unicode" ? "utf8" : charset);
+
+
if (charset == "unicode" ||
+
utf8_mode & (LATIN1_UNICODE_ENCODE_MODE|UTF8_UNICODE_ENCODE_MODE))
+
update_unicode_encode_mode_from_charset (charset);
+
+
if (charset == "unicode") {
+
#if constant (Mysql.mysql.HAVE_MYSQL_FIELD_CHARSETNR)
+
utf8_mode |= UNICODE_DECODE_MODE;
+
#else
+
if (broken_unicode || getenv ("PIKE_BROKEN_MYSQL_UNICODE_MODE"))
+
// Undocumented feature for old mysql libs. See
+
// MySQLBrokenUnicodeWrapper for details.
+
utf8_mode |= UNICODE_DECODE_MODE;
+
else
+
predef::error ("Unicode decode mode not supported - "
+
"compiled with MySQL client library < 4.1.0.\n");
+
#endif
+
}
+
else if (utf8_mode & UNICODE_DECODE_MODE && charset != "utf8")
+
// This setting has been overridden by ::set_charset, so we need
+
// to reinstate it.
+
::big_query ("SET character_set_results = utf8");
+
}
+
+
string get_charset()
+
//! Returns the MySQL name for the current connection charset.
+
//!
+
//! Returns @expr{"unicode"@} if unicode encode mode is enabled and
+
//! UTF-8 is used on the server side (i.e. in
+
//! @expr{character_set_connection@}).
+
//!
+
//! @note
+
//! In servers with full charset support (i.e. MySQL 4.1.0 or
+
//! later), this corresponds to the MySQL system variable
+
//! @expr{character_set_client@} (with one exception - see next
+
//! note) and thus controls the charset in which queries are sent.
+
//! The charset used for text strings in results might be something
+
//! else (and typically is if unicode decode mode is enabled; see
+
//! @[set_unicode_decode_mode]).
+
//!
+
//! @note
+
//! If the returned charset is @expr{latin1@} or @expr{unicode@} and
+
//! unicode encode mode is enabled (the default) then
+
//! @expr{character_set_client@} in the server might be either
+
//! @expr{latin1@} or @expr{utf8@}, depending on the last sent
+
//! query. See @[set_unicode_encode_mode] for more info.
+
//!
+
//! @seealso
+
//! @[set_charset]
+
{
+
if (utf8_mode & UTF8_UNICODE_ENCODE_MODE && send_charset)
+
// We don't try to be symmetric with set_charset when the
+
// broken-unicode kludge is in use. That since this reflects the
+
// setting on the encode side only.
+
return "unicode";
+
return ::get_charset();
+
}
+
+
#if constant( Mysql.mysql.MYSQL_NO_ADD_DROP_DB )
+
// Documented in the C-file.
+
void create_db( string db )
+
{
+
::big_query( "CREATE DATABASE "+db );
+
}
+
+
void drop_db( string db )
+
{
+
::big_query( "DROP DATABASE "+db );
+
}
+
#endif
+
+
//!
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,
({ "\\", "\"", "\0", "\'", "\n", "\r" }),
-
({ "\\\\", "\\\"", "\\0", "\\\'", "\\n", "\\r" }))
)
;
+
({ "\\\\", "\\\"", "\\0", "\\\'", "\\n", "\\r" }));
}
-
+
string latin1_to_utf8 (string s)
+
//! Converts a string in MySQL @expr{latin1@} format to UTF-8.
+
{
+
return string_to_utf8 (replace (s, ([
+
"\x80": "\u20AC", /*"\x81": "\u0081",*/ "\x82": "\u201A", "\x83": "\u0192",
+
"\x84": "\u201E", "\x85": "\u2026", "\x86": "\u2020", "\x87": "\u2021",
+
"\x88": "\u02C6", "\x89": "\u2030", "\x8a": "\u0160", "\x8b": "\u2039",
+
"\x8c": "\u0152", /*"\x8d": "\u008D",*/ "\x8e": "\u017D", /*"\x8f": "\u008F",*/
+
/*"\x90": "\u0090",*/ "\x91": "\u2018", "\x92": "\u2019", "\x93": "\u201C",
+
"\x94": "\u201D", "\x95": "\u2022", "\x96": "\u2013", "\x97": "\u2014",
+
"\x98": "\u02DC", "\x99": "\u2122", "\x9a": "\u0161", "\x9b": "\u203A",
+
"\x9c": "\u0153", /*"\x9d": "\u009D",*/ "\x9e": "\u017E", "\x9f": "\u0178",
+
])));
+
}
+
+
string utf8_encode_query (string q, function(string:string) encode_fn)
+
//! Encodes the appropriate sections of the query with @[encode_fn].
+
//! Everything except strings prefixed by an introducer (i.e.
+
//! @expr{_something@} or @expr{N@}) is encoded.
+
{
+
// We need to find the segments that shouldn't be encoded.
+
string e = "";
+
while (1) {
+
sscanf(q, "%[^\'\"]%s", string prefix, string suffix);
+
e += encode_fn (prefix);
+
+
if (suffix == "") break;
+
+
string quote = suffix[..0];
+
int start = 1;
+
int end;
+
while ((end = search(suffix, quote, start)) >= 0) {
+
if (suffix[end-1] == '\\') {
+
// Count the number of preceding back-slashes.
+
// if odd, continue searching after the quote.
+
int i;
+
for (i = 2; i < end; i++) {
+
if (suffix[end - i] != '\\') break;
+
}
+
if (!(i & 1)) {
+
start = end+1;
+
continue;
+
}
+
}
+
if (sizeof(suffix) == end+1) break;
+
if (suffix[end+1] == quote[0]) {
+
// Quote quoted by doubling.
+
start = end+2;
+
continue;
+
}
+
break;
+
}
+
+
if (end < 0)
+
// The query ends in a quoted string. We pretend it continues to
+
// the end and let MySQL complain later.
+
end = sizeof (suffix);
+
+
#define IS_IDENTIFIER_CHAR(chr) (Unicode.is_wordchar (chr) || \
+
(<'_', '$'>)[chr])
+
+
int intpos = -1;
+
+
// Optimize the use of _binary.
+
if (has_suffix (prefix, "_binary"))
+
intpos = sizeof (prefix) - sizeof ("_binary");
+
else if (has_suffix (prefix, "_binary "))
+
intpos = sizeof (prefix) - sizeof ("_binary ");
+
+
else {
+
// Find the white-space suffix of the prefix.
+
int i = sizeof(prefix);
+
while (i--) {
+
if (!(< ' ', '\n', '\r', '\t' >)[prefix[i]]) break;
+
}
+
+
if (i >= 0) {
+
if ((<'n', 'N'>)[prefix[i]])
+
// Probably got a national charset string.
+
intpos = i;
+
else {
+
// The following assumes all possible charset names contain
+
// only [a-zA-Z0-9_$] and are max 32 chars (from
+
// MY_CS_NAME_SIZE in m_ctype.h).
+
sscanf (reverse (prefix[i - 33..i]), "%[a-zA-Z0-9_$]%s",
+
string rev_intro, string rest);
+
if (sizeof (rev_intro) && rev_intro[-1] == '_' && sizeof (rest))
+
intpos = i - sizeof (rev_intro) + 1;
+
}
+
}
+
}
+
+
int got_introducer;
+
if (intpos == 0)
+
// The prefix begins with the introducer.
+
got_introducer = 1;
+
else if (intpos > 0) {
+
// Check that the introducer sequence we found isn't a suffix of
+
// some longer keyword or identifier.
+
int prechar = prefix[intpos - 1];
+
if (!IS_IDENTIFIER_CHAR (prechar))
+
got_introducer = 1;
+
}
+
+
if (got_introducer) {
+
string s = suffix[..end];
+
if (String.width (s) > 8) {
+
string encoding = prefix[intpos..];
+
if (has_prefix (encoding, "_"))
+
sscanf (encoding[1..], "%[a-zA-Z0-9]", encoding);
+
else
+
encoding = "utf8"; // Gotta be "N".
+
s = s[1..<1];
+
if (sizeof (s) > 40) s = sprintf ("%O...", s[..37]);
+
else s = sprintf ("%O", s);
+
predef::error ("A string in the query should be %s encoded "
+
"but it is wide: %s\n", encoding, s);
+
}
+
e += s;
+
} else {
+
e += encode_fn (suffix[..end]);
+
}
+
+
q = suffix[end+1..];
+
}
+
return e;
+
}
+
// The following time conversion functions assumes the SQL server // handles time in this local timezone. They map the special zero // time/date spec to 0. private int timezone = localtime (0)->timezone;
-
//
.
- encode_time
-
//.
Converts a system time value to an appropriately formatted time
-
//
.
spec for the database.
-
//
.
>
time
-
Time to encode.
-
//
.
>
date
-
If nonzero then time is taken as a "full" unix time spec
-
//
.
(where the date part is ignored), otherwise it's converted as a
-
//
.
seconds-since-midnight value.
+
//
!
Converts a system time value to an appropriately formatted time
+
//
!
spec for the database.
+
//
!
+
//!
@param
time
+
//!
Time to encode.
+
//
!
+
//!
@param
date
+
//!
If nonzero then time is taken as a "full" unix time spec
+
//
!
(where the date part is ignored), otherwise it's converted as a
+
//
!
seconds-since-midnight value.
string encode_time (int time, void|int date) { if (date) { if (!time) return "000000"; mapping(string:int) ct = localtime (time); return sprintf ("%02d%02d%02d", ct->hour, ct->min, ct->sec); } else return sprintf ("%02d%02d%02d", time / 3600 % 24, time / 60 % 60, time % 60); }
-
//
.
- encode_date
-
//.
Converts a system time value to an appropriately formatted
-
//
.
date-only spec for the database.
-
//
.
>
time
-
Time to encode.
+
//
!
Converts a system time value to an appropriately formatted
+
//
!
date-only spec for the database.
+
//
!
+
//!
@param
time
+
//!
Time to encode.
string encode_date (int time) { if (!time) return "00000000"; mapping(string:int) ct = localtime (time); return sprintf ("%04d%02d%02d", ct->year + 1900, ct->mon + 1, ct->mday); }
-
//
.
- encode_datetime
-
//.
Converts a system time value to an appropriately formatted
-
//
.
date and time spec for the database.
-
//
.
>
time
-
Time to encode.
+
//
!
Converts a system time value to an appropriately formatted
+
//
!
date and time spec for the database.
+
//
!
+
//!
@param
time
+
//!
Time to encode.
string encode_datetime (int time) { if (!time) return "00000000000000"; mapping(string:int) ct = localtime (time); return sprintf ("%04d%02d%02d%02d%02d%02d", ct->year + 1900, ct->mon + 1, ct->mday, ct->hour, ct->min, ct->sec); }
-
//
.
- decode_time
-
//.
Converts a database time spec to a system time value.
-
//
.
>
timestr
-
Time spec to decode.
-
//
.
>
date
-
Take the date part from this system time value. If zero, a
-
//
.
seconds-since-midnight value is returned.
+
//
!
Converts a database time spec to a system time value.
+
//
!
+
//!
@param
timestr
+
//!
Time spec to decode.
+
//
!
+
//!
@param
date
+
//!
Take the date part from this system time value. If zero, a
+
//
!
seconds-since-midnight value is returned.
int decode_time (string timestr, void|int date) { int hour = 0, min = 0, sec = 0; if (sscanf (timestr, "%d:%d:%d", hour, min, sec) <= 1) sscanf (timestr, "%2d%2d%2d", hour, min, sec); if (date && (hour || min || sec)) { mapping(string:int) ct = localtime (date); return mktime (sec, min, hour, ct->mday, ct->mon, ct->year, ct->isdst, ct->timezone); } else return (hour * 60 + min) * 60 + sec; }
-
//
.
- decode_date
-
//.
Converts a database date-only spec to a system time value.
-
//
.
Assumes 4-digit years.
-
//
.
>
datestr
-
Date spec to decode.
+
//
!
Converts a database date-only spec to a system time value.
+
//
!
Assumes 4-digit years.
+
//
!
+
//!
@param
datestr
+
//!
Date spec to decode.
int decode_date (string datestr) { int year = 0, mon = 0, mday = 0, n; n = sscanf (datestr, "%d-%d-%d", year, mon, mday); if (n <= 1) n = sscanf (datestr, "%4d%2d%2d", year, mon, mday); if (year || mon || mday) return mktime (0, 0, 0, n == 3 ? mday : 1, n >= 2 && mon - 1, year - 1900, -1, timezone); else return 0; }
-
//
.
- decode_datetime
-
//.
Converts a database date and time spec to a system time value.
-
//
.
Can decode strings missing the time part.
-
//
.
>
datestr
-
Date and time spec to decode.
+
//
!
Converts a database date and time spec to a system time value.
+
//
!
Can decode strings missing the time part.
+
//
!
+
//!
@param
datestr
+
//!
Date and time spec to decode.
int decode_datetime (string timestr) { array(string) a = timestr / " "; if (sizeof (a) == 2) return decode_date (a[0]) + decode_time (a[1]); else { int n = sizeof (timestr); if (n >= 12) return decode_date (timestr[..n-7]) + decode_time (timestr[n-6..n-1]); else return decode_date (timestr); } }
-
int|object big_query(string
q
, mapping(string|int:mixed)|void bindings)
+
#if constant (Mysql.mysql.HAVE_MYSQL_FIELD_CHARSETNR)
+
#define HAVE_MYSQL_FIELD_CHARSETNR_IFELSE(TRUE, FALSE) TRUE
+
#else
+
#define HAVE_MYSQL_FIELD_CHARSETNR_IFELSE(TRUE, FALSE) FALSE
+
#endif
+
+
#define QUERY_BODY(do_query) \
+
if (bindings) \
+
query = .sql_util.emulate_bindings(query,bindings,this); \
+
\
+
string restore_charset; \
+
if (charset) { \
+
restore_charset = send_charset || get_charset(); \
+
if (charset != restore_charset) { \
+
CH_DEBUG ("Switching charset from %O to %O (due to charset arg).\n", \
+
restore_charset, charset); \
+
::big_query ("SET character_set_client=" + charset); \
+
/* Can't be changed automatically - has side effects. /mast */ \
+
/* ::big_query("SET character_set_connection=" + charset); */ \
+
} else \
+
restore_charset = 0; \
+
} \
+
\
+
else if (send_charset) { \
+
string new_send_charset = send_charset; \
+
\
+
if (utf8_mode & LATIN1_UNICODE_ENCODE_MODE) { \
+
if (String.width (query) == 8) \
+
new_send_charset = "latin1"; \
+
else { \
+
CH_DEBUG ("Converting (mysql-)latin1 query to utf8.\n"); \
+
query = utf8_encode_query (query, latin1_to_utf8); \
+
new_send_charset = "utf8"; \
+
} \
+
} \
+
\
+
else { /* utf8_mode & UTF8_UNICODE_ENCODE_MODE */ \
+
/* NB: The send_charset may only be upgraded from \
+
* "latin1" to "utf8", not the other way around. \
+
* This is to avoid extraneous charset changes \
+
* where the charset is changed from query to query. \
+
*/ \
+
if ((send_charset == "utf8") || !_can_send_as_latin1(query)) { \
+
CH_DEBUG ("Converting query to utf8.\n"); \
+
query = utf8_encode_query (query, string_to_utf8); \
+
new_send_charset = "utf8"; \
+
} \
+
} \
+
\
+
if (new_send_charset != send_charset) { \
+
CH_DEBUG ("Switching charset from %O to %O.\n", \
+
send_charset, new_send_charset); \
+
if (mixed err = catch { \
+
::big_query ("SET character_set_client=" + new_send_charset); \
+
/* Can't be changed automatically - has side effects. /mast */ \
+
/* ::big_query("SET character_set_connection=" + \
+
new_send_charset); */ \
+
}) { \
+
if (new_send_charset == "utf8") \
+
predef::error ("The query is a wide string " \
+
"and the MySQL server doesn't support UTF-8: %s\n", \
+
describe_error (err)); \
+
else \
+
throw (err); \
+
} \
+
send_charset = new_send_charset; \
+
} \
+
} \
+
\
+
CH_DEBUG ("Sending query with charset %O: %s.\n", \
+
charset || send_charset, \
+
(sizeof (query) > 200 ? \
+
sprintf ("%O...", query[..200]) : \
+
sprintf ("%O", query))); \
+
\
+
int|object
res = ::do_query(query); \
+
\
+
if (restore_charset) { \
+
if (send_charset && (<"latin1", "utf8">)[charset]) \
+
send_charset = charset; \
+
else { \
+
CH_DEBUG ("Restoring charset %O.\n", restore_charset); \
+
::
big_query
(
"SET character_set_client=" + restore_charset); \
+
/* Can't be changed automatically - has side effects. /mast */ \
+
/* ::big_query("SET character_set_connection=" + restore_charset); */ \
+
} \
+
} \
+
\
+
if (!objectp(res)) return res; \
+
\
+
if (utf8_mode & UNICODE_DECODE_MODE) { \
+
CH_DEBUG ("Using unicode wrapper for result.\n"); \
+
return \
+
HAVE_MYSQL_FIELD_CHARSETNR_IFELSE ( \
+
.sql_util.MySQLUnicodeWrapper(res), \
+
.sql_util.MySQLBrokenUnicodeWrapper (res)); \
+
} \
+
return res;
+
+
Mysql.mysql_result big_query (
string
query
,
+
mapping(string|int:mixed)|void bindings
,
+
void|string charset
)
+
//! Sends a query to the server.
+
//!
+
//! @param query
+
//! The SQL query.
+
//!
+
//! @param bindings
+
//! An optional bindings mapping. See @[Sql.query] for details about
+
//! this.
+
//!
+
//! @param charset
+
//! An optional charset that will be used temporarily while sending
+
//! @[query] to the server. If necessary, a query
+
//! @code
+
//! SET character_set_client=@[charset]
+
//! @endcode
+
//! is sent to the server first, then @[query] is sent as-is, and then
+
//! the connection charset is restored again (if necessary).
+
//!
+
//! Primarily useful with @[charset] set to @expr{"latin1"@} if
+
//! unicode encode mode (see @[set_unicode_encode_mode]) is enabled
+
//! (the default) and you have some large queries (typically blob
+
//! inserts) where you want to avoid the query parsing overhead.
+
//!
+
//! @returns
+
//! A @[Mysql.mysql_result] object is returned if the query is of a
+
//! kind that returns a result. Zero is returned otherwise.
+
//!
+
//! The individual fields are returned as strings except for @tt{NULL@},
+
//! which is returned as @[UNDEFINED].
+
//!
+
//! @seealso
+
//! @[Sql.big_query()], @[big_typed_query()], @[streaming_query()]
{
-
if
(
!bindings)
-
return ::
big_query
(q
);
-
return ::big_query(.sql_util.emulate_bindings(q,bindings,this_object()));
+
QUERY_BODY
(big_query);
}
-
#
else /
*
!constant(Mysql.mysql)
*
/
-
#
error
"Mysql
support
not
available
.\n"
+
Mysql.mysql_result streaming_query (string query,
+
mapping(string|int:mixed)|void bindings,
+
void|string charset)
+
//! Makes a streaming SQL query.
+
//!
+
//! This function sends the SQL query @[query] to the Mysql-server.
+
//! The result of the query is streamed through the returned
+
//! @[Mysql.mysql_result] object. Note that the involved database
+
//! tables are locked until all the results has been read.
+
//!
+
//! In all other respects, it behaves like @[big_query].
+
//!
+
//! @seealso
+
//! @[big_query()], @[streaming_typed_query()]
+
{
+
QUERY_BODY (streaming_query);
+
}
+
+
Mysql.mysql_result big_typed_query (string query,
+
mapping(string|int:mixed)|void bindings,
+
void|string charset)
+
//! Makes a typed SQL query.
+
//!
+
//! This function sends the SQL query @[query] to the MySQL server and
+
//! returns a result object in typed mode, which means that the types
+
//! of the result fields depend on the corresponding SQL types. See
+
//! the class docs for details.
+
//!
+
//! In all other respects, it behaves like @[big_query].
+
//!
+
//! @seealso
+
//! @[big_query()], @[streaming_typed_query()]
+
{
+
QUERY_BODY (big_typed_query);
+
}
+
+
Mysql.mysql_result streaming_typed_query (string query,
+
mapping(string|int:mixed)|void bindings,
+
void|string charset)
+
//! Makes a streaming typed SQL query.
+
//!
+
//! This function acts as the combination of @[streaming_query()]
+
//! and @[big_typed_query()].
+
//!
+
//! @seealso
+
//! @[big_typed_query()], @[streaming_typed_query()]
+
{
+
QUERY_BODY (streaming_typed_query);
+
}
+
+
int(0..1) is_keyword( string name )
+
//! Return 1 if the argument @[name] is a mysql keyword that needs to
+
//! be quoted in a query. The list is currently up-to-date with MySQL
+
//! 5.1.
+
{
+
return ([
+
"accessible": 1, "add": 1, "all": 1, "alter": 1, "analyze": 1, "and": 1,
+
"as": 1, "asc": 1, "asensitive": 1, "before": 1, "between": 1, "bigint": 1,
+
"binary": 1, "blob": 1, "both": 1, "by": 1, "call": 1, "cascade": 1,
+
"case": 1, "change": 1, "char": 1, "character": 1, "check": 1, "collate": 1,
+
"column": 1, "condition": 1, "constraint": 1, "continue": 1, "convert": 1,
+
"create": 1, "cross": 1, "current_date": 1, "current_time": 1,
+
"current_timestamp": 1, "current_user": 1, "cursor": 1, "database": 1,
+
"databases": 1, "day_hour": 1, "day_microsecond": 1, "day_minute": 1,
+
"day_second": 1, "dec": 1, "decimal": 1, "declare": 1, "default": 1,
+
"delayed": 1, "delete": 1, "desc": 1, "describe": 1, "deterministic": 1,
+
"distinct": 1, "distinctrow": 1, "div": 1, "double": 1, "drop": 1,
+
"dual": 1, "each": 1, "
else
":
1, "elseif": 1, "enclosed": 1, "escaped": 1,
+
"exists": 1, "exit": 1, "explain": 1, "false": 1, "fetch": 1, "float": 1,
+
"float4": 1, "float8": 1, "for": 1, "force": 1, "foreign": 1, "from": 1,
+
"fulltext": 1, "grant": 1, "group": 1, "having": 1, "high_priority": 1,
+
"hour_microsecond": 1, "hour_minute": 1, "hour_second": 1, "if": 1,
+
"ignore": 1, "in": 1, "index": 1, "infile": 1, "inner": 1, "inout": 1,
+
"insensitive": 1, "insert": 1, "int": 1, "int1": 1, "int2": 1, "int3": 1,
+
"int4": 1, "int8": 1, "integer": 1, "interval": 1, "into": 1, "is": 1,
+
"iterate": 1, "join": 1, "key": 1, "keys": 1, "kill": 1, "leading": 1,
+
"leave": 1, "left": 1, "like": 1, "limit": 1, "linear": 1, "lines": 1,
+
"load": 1, "localtime": 1, "localtimestamp": 1, "lock": 1, "long": 1,
+
"longblob": 1, "longtext": 1, "loop": 1, "low_priority": 1,
+
"master_ssl_verify_server_cert": 1, "match": 1, "mediumblob": 1,
+
"mediumint": 1, "mediumtext": 1, "middleint": 1, "minute_microsecond": 1,
+
"minute_second": 1, "mod": 1, "modifies": 1, "natural": 1, "not": 1,
+
"no_write_to_binlog": 1, "null": 1, "numeric": 1, "on": 1, "optimize": 1,
+
"option": 1, "optionally": 1, "or": 1, "order": 1, "out": 1, "outer": 1,
+
"outfile": 1, "precision": 1, "primary": 1, "procedure": 1, "purge": 1,
+
"range": 1, "read": 1, "reads": 1, "read_only": 1, "read_write": 1,
+
"real": 1, "references": 1, "regexp": 1, "release": 1, "rename": 1,
+
"repeat": 1, "replace": 1, "require": 1, "restrict": 1, "return": 1,
+
"revoke": 1, "right": 1, "rlike": 1, "schema": 1, "schemas": 1,
+
"second_microsecond": 1, "select": 1, "sensitive": 1, "separator": 1,
+
"set": 1, "show": 1, "smallint": 1, "spatial": 1, "specific": 1, "sql": 1,
+
"sqlexception": 1, "sqlstate": 1, "sqlwarning": 1, "sql_big_result": 1,
+
"sql_calc_found_rows": 1, "sql_small_result": 1, "ssl": 1, "starting": 1,
+
"straight_join": 1, "table": 1, "terminated": 1, "then": 1, "tinyblob": 1,
+
"tinyint": 1, "tinytext": 1, "to": 1, "trailing": 1, "trigger": 1,
+
"true": 1, "undo": 1, "union": 1, "unique": 1, "unlock": 1, "unsigned": 1,
+
"update": 1, "usage": 1, "use": 1, "using": 1, "utc_date": 1, "utc_time": 1,
+
"utc_timestamp": 1, "values": 1, "varbinary": 1, "varchar": 1,
+
"varcharacter": 1, "varying": 1, "when": 1, "where": 1, "while": 1,
+
"with": 1, "write": 1, "x509": 1, "xor": 1, "year_month": 1, "zerofill": 1,
+
/
/
The following keywords were in the old list, but according to MySQL
+
// docs they don't need to be quoted:
+
// "action", "after", "aggregate", "auto_increment", "avg",
+
// "avg_row_length", "bit", "bool", "change", "checksum", "columns",
+
// "comment", "data", "date", "datetime", "day", "dayofmonth", "dayofweek",
+
// "dayofyear", "delay_key_write", "end", "enum", "escape", "escaped",
+
// "explain", "fields", "file", "first", "flush", "for", "full", "function",
+
// "global", "grants", "heap", "hosts", "hour", "identified", "if",
+
// "insert_id", "integer", "interval", "isam", "last_insert_id", "length",
+
// "lines", "local", "logs", "max", "max_rows", "mediumtext", "min_rows",
+
// "minute", "modify", "month", "monthname", "myisam", "no", "numeric",
+
// "pack_keys", "partial", "password", "privileges", "process",
+
// "processlist", "reload", "returns", "row", "rows", "second", "shutdown",
+
// "soname", "sql_big_selects", "sql_big_tables", "sql_log_off",
+
// "sql_log_update", "sql_low_priority_updates", "sql_select_limit",
+
// "sql_small_result", "sql_warnings", "status", "straight_join", "string",
+
// "tables", "temporary", "text", "time", "timestamp", "tinytext",
+
// "trailing", "type", "use", "using", "varbinary", "variables", "with",
+
// "write", "year"
+
])[ lower_case(name) ];
+
}
+
+
protected void create(string|void host, string|void database,
+
string|void user, string|void _password,
+
mapping(string:string|int)|void options)
+
{
+
string password = _password;
+
_password = "CENSORED";
+
+
if (options) {
+
string charset = options->mysql_charset_name ?
+
lower_case (options->mysql_charset_name) : "latin1";
+
+
int broken_unicode = charset == "broken-unicode";
+
if (broken_unicode) charset = "unicode";
+
+
if (charset == "unicode")
+
options->mysql_charset_name = "utf8";
+
+
::create(host||"", database||"", user||"", password||"", options);
+
+
update_unicode_encode_mode_from_charset (lower_case (charset));
+
+
#if
!constant
(Mysql.mysql
.HAVE_MYSQL_FIELD_CHARSETNR
)
+
/
/ Undocumented feature for old mysql libs. See
+
// MySQLBrokenUnicodeWrapper for details.
+
if (broken_unicode || getenv ("PIKE_BROKEN_MYSQL_UNICODE_MODE")) {
+
#
endif
+
if (charset ==
"
unicode")
+
utf8_mode |= UNICODE_DECODE_MODE;
+
else if (options->unicode_decode_mode)
+
set_unicode_decode_mode (1);
+
#if !constant (
Mysql
.mysql.HAVE_MYSQL_FIELD_CHARSETNR)
+
}
+
else
+
if (charset == "unicode" || options->unicode_decode_mode)
+
predef::error ("Unicode decode mode
not
supported - "
+
"compiled with MySQL client library < 4
.
1.0.
\n"
);
+
#endif
+
+
} else {
+
::create(host||"", database||"", user||"", password||"");
+
+
update_unicode_encode_mode_from_charset ("latin1");
+
}
+
}
+
+
#else
+
constant this_program_does_not_exist=1;
#endif /* constant(Mysql.mysql) */