/usr/lib/pike7.8/modules/Sql.pmod/mysql.pike is in pike7.8-mysql 7.8.866-5build1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 | /*
* $Id: ae19d7e26a062e8518ed934a40299e9fe4ff4d13 $
*
* Glue for the Mysql-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;
#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
//! @[Locale.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,
({ "\\", "\"", "\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 constant timezone = localtime (0)->timezone;
//! 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);
}
//! 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);
}
//! 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);
}
//! 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;
}
//! 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;
}
//! 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);
}
}
#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; \
\
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 */ \
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); \
/* 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()]
{
QUERY_BODY (big_query);
}
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.
//!
//! The types of the result fields depend on the corresponding SQL types.
//! They are mapped as follows:
//! @mixed
//! @type Sql.Null
//! The @tt{NULL@} value is returned as @[Sql.NULL].
//! @type int
//! Integer values are returned as @tt{int@} values.
//! @type float
//! Floating point values are returned as @tt{float@} values.
//! @type string
//! All other SQL field types are returned as @tt{string@} values.
//! @endmixed
//!
//! 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) */
|