|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pike __REAL_VERSION__ |
#require constant(Mysql.mysql) |
|
|
|
constant dont_dump_program = 1; |
|
|
inherit Mysql.mysql; |
|
#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) |
|
|
|
constant unicode_decode_mode_is_broken = 1; |
#endif |
|
|
|
|
protected int utf8_mode; |
|
|
|
|
|
protected string send_charset; |
|
protected void update_unicode_encode_mode_from_charset (string charset) |
{ |
switch (charset) { |
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: |
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
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() |
|
|
|
|
{ |
return !!send_charset; |
} |
|
void set_unicode_decode_mode (int enable) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
#if !constant (Mysql.mysql.HAVE_MYSQL_FIELD_CHARSETNR) |
|
|
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() |
|
|
|
|
{ |
return utf8_mode & UNICODE_DECODE_MODE; |
} |
|
void set_charset (string charset) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
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")) |
|
|
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") |
|
|
::big_query ("SET character_set_results = utf8"); |
} |
|
string get_charset() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
if (utf8_mode & UTF8_UNICODE_ENCODE_MODE && send_charset) |
|
|
|
return "unicode"; |
return ::get_charset(); |
} |
|
#if constant( Mysql.mysql.MYSQL_NO_ADD_DROP_DB ) |
|
void create_db( string db ) |
{ |
::big_query( "CREATE DATABASE "+db ); |
} |
|
void drop_db( string db ) |
{ |
::big_query( "DROP DATABASE "+db ); |
} |
#endif |
|
|
|
|
|
string quote(string s) |
{ |
return replace(s, |
({ "\\", "\"", "\0", "\'", "\n", "\r" }), |
({ "\\\\", "\\\"", "\\0", "\\\'", "\\n", "\\r" })); |
} |
|
string latin1_to_utf8 (string s) |
|
{ |
return string_to_utf8 (replace (s, ([ |
"\x80": "\u20AC", "\x82": "\u201A", "\x83": "\u0192", |
"\x84": "\u201E", "\x85": "\u2026", "\x86": "\u2020", "\x87": "\u2021", |
"\x88": "\u02C6", "\x89": "\u2030", "\x8a": "\u0160", "\x8b": "\u2039", |
"\x8c": "\u0152", "\x8e": "\u017D", |
"\x91": "\u2018", "\x92": "\u2019", "\x93": "\u201C", |
"\x94": "\u201D", "\x95": "\u2022", "\x96": "\u2013", "\x97": "\u2014", |
"\x98": "\u02DC", "\x99": "\u2122", "\x9a": "\u0161", "\x9b": "\u203A", |
"\x9c": "\u0153", "\x9e": "\u017E", "\x9f": "\u0178", |
]))); |
} |
|
string utf8_encode_query (string q, function(string:string) encode_fn) |
|
|
|
{ |
|
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] == '\\') { |
|
|
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]) { |
|
start = end+2; |
continue; |
} |
break; |
} |
|
if (end < 0) |
|
|
end = sizeof (suffix); |
|
#define IS_IDENTIFIER_CHAR(chr) (Unicode.is_wordchar (chr) || \ |
(<'_', '$'>)[chr]) |
|
int intpos = -1; |
|
|
if (has_suffix (prefix, "_binary")) |
intpos = sizeof (prefix) - sizeof ("_binary"); |
else if (has_suffix (prefix, "_binary ")) |
intpos = sizeof (prefix) - sizeof ("_binary "); |
|
else { |
|
int i = sizeof(prefix); |
while (i--) { |
if (!(< ' ', '\n', '\r', '\t' >)[prefix[i]]) break; |
} |
|
if (i >= 0) { |
if ((<'n', 'N'>)[prefix[i]]) |
|
intpos = i; |
else { |
|
|
|
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) |
|
got_introducer = 1; |
else if (intpos > 0) { |
|
|
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"; |
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; |
} |
|
|
|
|
|
|
|
|
|
|
|
string encode_time (int time, void|int date) |
{ |
return ::encode_time(time, date) - ":"; |
} |
|
|
|
|
|
|
string encode_date (int time) |
{ |
return ::encode_date(time) - "-"; |
} |
|
|
|
|
|
|
string encode_datetime (int time) |
{ |
return replace(::encode_datetime(time), "-:T"/"", ({"", "", ""})); |
} |
|
#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; |
|
Result big_query (string query, |
mapping(string|int:mixed)|void bindings, |
void|string charset) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
QUERY_BODY (big_query); |
} |
|
Result streaming_query (string query, |
mapping(string|int:mixed)|void bindings, |
void|string charset) |
|
|
|
|
|
|
|
|
|
|
|
{ |
QUERY_BODY (streaming_query); |
} |
|
Result big_typed_query (string query, |
mapping(string|int:mixed)|void bindings, |
void|string charset) |
|
|
|
|
|
|
|
|
|
|
|
{ |
QUERY_BODY (big_typed_query); |
} |
|
Result streaming_typed_query (string query, |
mapping(string|int:mixed)|void bindings, |
void|string charset) |
|
|
|
|
|
|
|
{ |
QUERY_BODY (streaming_typed_query); |
} |
|
int(0..1) is_keyword( string name ) |
|
|
|
{ |
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, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
])[ 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) |
|
|
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"); |
} |
} |
|
|