|
|
|
|
|
|
|
|
|
#pike __REAL_VERSION__ |
|
#if constant(Mysql.mysql) |
|
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("Sql.mysql: " + X) |
#else |
#define CH_DEBUG(X...) |
#endif |
|
|
|
|
static int utf8_mode; |
|
|
|
|
|
static string send_charset; |
|
static 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; |
} |
|
#if constant (Mysql.mysql.HAVE_MYSQL_FIELD_CHARSETNR) |
void set_unicode_decode_mode (int enable) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
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; |
} |
} |
#endif |
|
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); |
|
::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 |
predef::error ("Unicode decode mode not supported - " |
"compiled with MySQL client library < 4.1.0.\n"); |
#endif |
} |
#if constant (Mysql.mysql.HAVE_MYSQL_FIELD_CHARSETNR) |
else if (utf8_mode & UNICODE_DECODE_MODE && charset != "utf8") |
|
|
::big_query ("SET character_set_results = utf8"); |
#endif |
} |
|
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": "\x20AC", "\x82": "\x201A", "\x83": "\x0192", |
"\x84": "\x201E", "\x85": "\x2026", "\x86": "\x2020", "\x87": "\x2021", |
"\x88": "\x02C6", "\x89": "\x2030", "\x8a": "\x0160", "\x8b": "\x2039", |
"\x8c": "\x0152", "\x8e": "\x017D", |
"\x91": "\x2018", "\x92": "\x2019", "\x93": "\x201C", |
"\x94": "\x201D", "\x95": "\x2022", "\x96": "\x2013", "\x97": "\x2014", |
"\x98": "\x02DC", "\x99": "\x2122", "\x9a": "\x0161", "\x9b": "\x203A", |
"\x9c": "\x0153", "\x9e": "\x017E", "\x9f": "\x0178", |
]))); |
} |
|
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; |
} |
|
#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..sizeof (s) - 2]; |
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; |
} |
|
|
|
|
|
private constant timezone = localtime (0)->timezone; |
|
|
|
|
|
|
|
|
|
|
|
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); |
} |
|
|
|
|
|
|
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); |
} |
|
|
|
|
|
|
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); |
} |
|
|
|
|
|
|
|
|
|
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; |
} |
|
|
|
|
|
|
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; |
} |
|
|
|
|
|
|
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); |
} |
} |
|
Mysql.mysql_result big_query (string query, |
mapping(string|int:mixed)|void bindings, |
void|string charset) |
{ |
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); |
|
|
} else |
restore_charset = 0; |
} |
|
else if (send_charset) { |
string new_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 { |
if (_can_send_as_latin1 (query)) |
new_send_charset = "latin1"; |
else { |
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); |
|
|
|
}) { |
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: %O.\n", |
charset || send_charset, query); |
|
int|object res = ::big_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); |
|
|
} |
} |
|
if (!objectp(res)) return res; |
|
#if constant (Mysql.mysql.HAVE_MYSQL_FIELD_CHARSETNR) |
if (utf8_mode & UNICODE_DECODE_MODE) { |
CH_DEBUG ("Using MySQLUnicodeWrapper for result.\n"); |
return .sql_util.MySQLUnicodeWrapper(res); |
} |
#endif |
return res; |
} |
|
int(0..1) is_keyword( string name ) |
|
{ |
|
return (< |
"action", "add", "aggregate", "all", "alter", "after", "and", "as", |
"asc", "avg", "avg_row_length", "auto_increment", "between", "bigint", |
"bit", "binary", "blob", "bool", "both", "by", "cascade", "case", |
"char", "character", "change", "check", "checksum", "column", |
"columns", "comment", "constraint", "create", "cross", "current_date", |
"current_time", "current_timestamp", "data", "database", "databases", |
"date", "datetime", "day", "day_hour", "day_minute", "day_second", |
"dayofmonth", "dayofweek", "dayofyear", "dec", "decimal", "default", |
"delayed", "delay_key_write", "delete", "desc", "describe", "distinct", |
"distinctrow", "double", "drop", "end", "else", "escape", "escaped", |
"enclosed", "enum", "explain", "exists", "fields", "file", "first", |
"float", "float4", "float8", "flush", "foreign", "from", "for", "full", |
"function", "global", "grant", "grants", "group", "having", "heap", |
"high_priority", "hour", "hour_minute", "hour_second", "hosts", |
"identified", "ignore", "in", "index", "infile", "inner", "insert", |
"insert_id", "int", "integer", "interval", "int1", "int2", "int3", |
"int4", "int8", "into", "if", "is", "isam", "join", "key", "keys", |
"kill", "last_insert_id", "leading", "left", "length", "like", |
"lines", "limit", "load", "local", "lock", "logs", "long", "longblob", |
"longtext", "low_priority", "max", "max_rows", "match", "mediumblob", |
"mediumtext", "mediumint", "middleint", "min_rows", "minute", |
"minute_second", "modify", "month", "monthname", "myisam", "natural", |
"numeric", "no", "not", "null", "on", "optimize", "option", |
"optionally", "or", "order", "outer", "outfile", "pack_keys", |
"partial", "password", "precision", "primary", "procedure", "process", |
"processlist", "privileges", "read", "real", "references", "reload", |
"regexp", "rename", "replace", "restrict", "returns", "revoke", |
"rlike", "row", "rows", "second", "select", "set", "show", "shutdown", |
"smallint", "soname", "sql_big_tables", "sql_big_selects", |
"sql_low_priority_updates", "sql_log_off", "sql_log_update", |
"sql_select_limit", "sql_small_result", "sql_big_result", |
"sql_warnings", "straight_join", "starting", "status", "string", |
"table", "tables", "temporary", "terminated", "text", "then", "time", |
"timestamp", "tinyblob", "tinytext", "tinyint", "trailing", "to", |
"type", "use", "using", "unique", "unlock", "unsigned", "update", |
"usage", "values", "varchar", "variables", "varying", "varbinary", |
"with", "write", "when", "where", "year", "year_month", "zerofill", |
>)[ lower_case(name) ]; |
} |
|
static void create(string|void host, string|void database, |
string|void user, string|void password, |
mapping(string:string|int)|void options) |
{ |
if (options) { |
string charset = options->mysql_charset_name || "latin1"; |
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 (charset == "unicode") |
utf8_mode |= UNICODE_DECODE_MODE; |
else if (options->unicode_decode_mode) |
set_unicode_decode_mode (1); |
#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) */ |
|
|