pike.git / lib / modules / Mysql.pmod / SqlTable.pike

version» Context lines:

pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1:   #pike __REAL_VERSION__ - #if !constant (___Mysql) - constant this_program_does_not_exist = 1; - #else // !___Mysql + #require constant(___Mysql)      //! This class provides some abstractions on top of an SQL table.   //!   //! At the core it is generic for any SQL database, but the current   //! implementation is MySQL specific on some points, notably the   //! semantics of AUTO_INCREMENT, the quoting method, knowledge about   //! column types, and some conversion functions. Hence the location in   //! the @[Mysql] module.   //!   //! Among other things, this class handles some convenient conversions
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:60:   //!   //! @note   //! The generated SQL queries always quote table and column names   //! according to MySQL syntax using backticks (`). However, literal   //! backticks in names are not quoted and might therefore lead to SQL   //! syntax errors. This might change if it becomes a problem.   //!   //! @note   //! The handling of TIMESTAMP columns in MySQL (as of 5.1 at least)   //! through UNIX_TIMESTAMP and FROM_UNIXTIME has one problem if the - //! active time zone uses daylight savings time: + //! active time zone uses daylight-saving time:   //!   //! Apparently FROM_UNIXTIME internally formats the integer to a MySQL   //! date/time string, which is then parsed again to set the unix   //! timestamp in the TIMESTAMP column. The formatting and the parsing   //! uses the same time zone, so the conversions generally cancel   //! themselves out. However, there is one exception with the 1 hour   //! overlap in the fall when going from summer time to normal time.   //!   //! E.g. if the active time zone on the connection is Central European   //! Time, which uses DST, then setting 1130630400 (Sun 30 Oct 2005
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:160:   // Mysql decimal types. These are represented as integers if they have   // no fraction part, otherwise as Gmp.mpq objects.   protected constant mysql_decimal_types = ([    "decimal": 1, "newdecimal": 1,   ]);      protected constant mysql_datetime_types = ([    "datetime": 1, "date": 1, "time": 1, "year": 1,   ]);    + // Cache for _sizeof(). + protected int num_entries = -1; +  + protected void invalidate_cache() + { +  num_entries = -1; + } +    // FIXME: Add a setting to be able to "deprecate" columns for   // migration from a column to a field in the properties blob. The   // deprecation means the column will be queried, but the value will be   // put into the properties mapping on update.      // FIXME: Conversely, add a setting to deprecate properties be able to   // migrate them to columns. Normally it solves itself since properties   // have priority over columns, but select() and get() need to be aware   // of such columns so they don't incorrectly skip prop_col altogether   // in the query.
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:191:   //! @param table   //! The name of the table.   //!   //! @param prop_col   //! The column in which all fields which don't have explicit columns   //! are stored. It has to be a non-null blob or varbinary column. If   //! this isn't specified and there is such a column called   //! "properties" then it is used for this purpose. Set to @expr{"-"@}   //! to force this feature to be disabled.   { -  this_program::get_db = get_db; -  this_program::table = table; +  this::get_db = get_db; +  this::table = table;       Sql.Sql conn = get_db();       query_charset = String.width (table) == 8 && "latin1";       {    col_types = ([]);    timestamp_cols = ([]);    datetime_cols = ([]);    array(mapping(string:mixed)) col_list =
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:234:    !prop_col_info->flags->not_null ||    !(<"var string", "blob">)[prop_col_info->type])    prop_col_info = 0;    if (prop_col && !prop_col_info)    error ("Table doesn't have a non-null binary column %O "    "to store extra fields in.\n", prop_col);    if (prop_col_info) {    if (!prop_col_info->length)    error ("Unable to determine maximum length of the property "    "column %O. Got column info: %O\n", prop_col, prop_col_info); -  this_program::prop_col = prop_col_info->name; -  this_program::prop_col_max_length = prop_col_info->length; +  this::prop_col = prop_col_info->name; +  this::prop_col_max_length = prop_col_info->length;    }    }    }       foreach (conn->query ("SHOW COLUMNS FROM `" + table + "`"),    mapping(string:string) col_info)    if (col_info->Extra == "auto_increment") {    id_col = col_info->Field;    break;    }
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:695:   {    return conn_get_multi (get_db(), ids, fields);   }      // Explicit connection handling.      int conn_insert (Sql.Sql db_conn, mapping(string:mixed)... records)   //! Like @[insert], but a database connection object is passed   //! explicitly instead of being retrieved via @[get_db].   { -  Sql.mysql conn = db_conn->master_sql; - #ifdef DEBUG +  Sql.mysql conn = db_conn; + #ifdef MYSQL_DEBUG    if (!sizeof (records)) error ("Must give at least one record.\n");   #endif    UPDATE_MSG ("%O: insert %O\n",    this, sizeof (records) == 1 ? records[0] : records);    mapping(string:mixed) first_rec = records[0];    conn->big_query ("INSERT `" + table + "` " +    make_insert_clause (records), -  0, query_charset); +  UNDEFINED, query_charset); +  invalidate_cache();    if (!id_col) return 0; -  if (zero_type (first_rec[id_col])) +  if (!has_index (first_rec, id_col))    return first_rec[id_col] = conn->insert_id();    else    return first_rec[id_col];   }      int conn_insert_ignore (Sql.Sql db_conn, mapping(string:mixed)... records)   //! Like @[insert_ignore], but a database connection object is passed   //! explicitly instead of being retrieved via @[get_db].   { -  Sql.mysql conn = db_conn->master_sql; - #ifdef DEBUG +  Sql.mysql conn = db_conn; + #ifdef MYSQL_DEBUG    if (!sizeof (records)) error ("Must give at least one record.\n");   #endif    UPDATE_MSG ("%O: insert_ignore %O\n",    this, sizeof (records) == 1 ? records[0] : records);    mapping(string:mixed) first_rec = records[0];    conn->big_query ("INSERT IGNORE `" + table + "` " +    make_insert_clause (records), -  0, query_charset); +  UNDEFINED, query_charset); +  invalidate_cache();    if (!id_col) return 0;    int last_insert_id = conn->insert_id();    if (last_insert_id && -  sizeof (records) == 1 && zero_type (first_rec[id_col])) +  sizeof (records) == 1 && !has_index (first_rec, id_col))    // Only set the field if we got a single record. Otherwise we    // don't really know which record it applies to.    first_rec[id_col] = last_insert_id;    return last_insert_id;   }      int conn_replace (Sql.Sql db_conn, mapping(string:mixed)... records)   //! Like @[replace], but a database connection object is passed   //! explicitly instead of being retrieved via @[get_db].   { -  Sql.mysql conn = db_conn->master_sql; - #ifdef DEBUG +  Sql.mysql conn = db_conn; + #ifdef MYSQL_DEBUG    if (!sizeof (records)) error ("Must give at least one record.\n");   #endif    UPDATE_MSG ("%O: replace %O\n",    this, sizeof (records) == 1 ? records[0] : records);    mapping(string:mixed) first_rec = records[0];    conn->big_query ("REPLACE `" + table + "` " +    make_insert_clause (records), -  0, query_charset); +  UNDEFINED, query_charset); +  invalidate_cache();    if (!id_col) return 0; -  if (zero_type (first_rec[id_col])) +  if (!has_index (first_rec, id_col))    return first_rec[id_col] = conn->insert_id();    else    return first_rec[id_col];   }      void conn_update (Sql.Sql db_conn, mapping(string:mixed) record,    void|int(0..2) clear_other_fields)   //! Like @[update], but a database connection object is passed   //! explicitly instead of being retrieved via @[get_db].   { -  Sql.mysql conn = db_conn->master_sql; - #ifdef DEBUG +  Sql.mysql conn = db_conn; + #ifdef MYSQL_DEBUG    if (!(<0,1,2>)[clear_other_fields])    error ("Invalid clear_other_fields flag.\n");   #endif    UPDATE_MSG ("%O: update%s %O\n", this,    clear_other_fields == 2 ? " (clear all other fields)" :    clear_other_fields == 1 ? " (clear other props)" : "", record);       record += ([]);    string pk_where = make_pk_where (record);    if (!pk_where)    error ("The record lacks a value for a primary key column.\n");    record = update_pack_fields (conn, record, pk_where, clear_other_fields);       conn->big_query ("UPDATE `" + table + "` "    "SET " + make_set_clause (record,    clear_other_fields == 2) + " "    "WHERE " + pk_where + " "    "LIMIT 1", // The limit is just extra paranoia. -  0, query_charset); +  UNDEFINED, query_charset); +  invalidate_cache();   }      int conn_insert_or_update (Sql.Sql db_conn, mapping(string:mixed) record,    void|int(0..2) clear_other_fields)   //! Like @[insert_or_update], but a database connection object is   //! passed explicitly instead of being retrieved via @[get_db].   { -  Sql.mysql conn = db_conn->master_sql; - #ifdef DEBUG +  Sql.mysql conn = db_conn; + #ifdef MYSQL_DEBUG    if (!(<0,1,2>)[clear_other_fields])    error ("Invalid clear_other_fields flag.\n");   #endif    UPDATE_MSG ("%O: insert_or_update%s %O\n", this,    clear_other_fields == 2 ? " (clear all other fields)" :    clear_other_fields == 1 ? " (clear other props)" : "", record);       // If we have properties to merge then do that with separate queries    // afterwards. We never do it by first querying the properties from    // an existing record (if any), even though that could save one
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:814:    // possibly all fields if pk_cols aren't given) - in the latter case    // we could retrieve the properties from a different record that got    // removed in the race window. Doing it afterwards might change the    // wrong record only if id_col isn't used.       mapping(string:mixed) real_cols = col_types & record;    mapping(string:mixed) other_fields = record - real_cols;    string prop_col_value;       if (sizeof (other_fields)) { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (!prop_col) error ("Column(s) %s missing in table %O.\n",    String.implode_nicely (indices (other_fields)),    table);   #endif    // These encoded properties are normally for the INSERT clause    // only - not used on update. That way we can avoid the two extra    // queries in the INSERT case, but otoh that query gets larger.    // It's used in both cases if clear_other_fields is set, though.    prop_col_value = get_and_merge_props (0, 0, other_fields);    }    else if (clear_other_fields && prop_col)    prop_col_value = "";       array(string) update_set = allocate (sizeof (real_cols));    {    int i;    foreach (real_cols; string col;)    update_set[i++] = "`" + col + "`=VALUES(`" + col + "`)";    }    -  if (prop_col_value && zero_type (real_cols[prop_col])) { +  if (prop_col_value && !has_index (real_cols, prop_col)) {    if (clear_other_fields)    update_set += ({"`" + prop_col + "`=VALUES(`" + prop_col + "`)"});    real_cols[prop_col] = prop_col_value;    }    -  if (id_col && zero_type (real_cols[id_col])) +  if (id_col && !has_index (real_cols, id_col))    update_set += ({"`" + id_col + "`=LAST_INSERT_ID(`" + id_col + "`)"});       if (clear_other_fields == 2) {    foreach (col_types - real_cols - ({id_col}); string col;)    update_set += ({"`" + col + "`=DEFAULT(`" + col + "`)"});    }       conn->big_query ("INSERT `" + table + "` " +    make_insert_clause (({real_cols})) + " " +    (sizeof (update_set) ?    "ON DUPLICATE KEY UPDATE " + update_set * "," : ""), -  0, query_charset); +  UNDEFINED, query_charset);    -  if (id_col && zero_type (record[id_col])) +  invalidate_cache(); +  +  if (id_col && !has_index (record, id_col))    record[id_col] = conn->insert_id();       if (sizeof (other_fields) && !clear_other_fields &&    // affected_rows() returns 2 if a record was updated, 1 if a new    // one was added, and 0 if nothing happened. We should merge in    // the property changes for both 2 and 0.    conn->affected_rows() != 1) {    string where = make_pk_where (record + ([]));    if (!where) {    // There's no AUTO_INCREMENT id column and the record doesn't
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:898:   //! explicitly instead of being retrieved via @[get_db].   {    UPDATE_MSG ("%O: delete WHERE (%O)%s\n", this, where,    rest ? sprintf (" %O", rest) : "");       mapping(string|int:mixed) bindings = ([]);    if (arrayp (where)) where = handle_argspec (where, bindings);    if (arrayp (rest)) rest = handle_argspec (rest, bindings);    if (!sizeof (bindings)) bindings = 0;    -  db_conn->master_sql->big_query ("DELETE FROM `" + table + "` " +  db_conn->big_query ("DELETE FROM `" + table + "` "    "WHERE (" + where + ") " + (rest || ""),    bindings); -  +  invalidate_cache();   }      void conn_remove (Sql.Sql db_conn, mixed id)   //! Like @[remove], but a database connection object is passed   //! explicitly instead of being retrieved via @[get_db].   {    UPDATE_MSG ("%O: remove %O\n", this, id); -  Sql.mysql conn = db_conn->master_sql; +  Sql.mysql conn = db_conn;    conn->big_query ("DELETE FROM `" + table + "` "    "WHERE " + simple_make_pk_where (id), -  0, query_charset); +  UNDEFINED, query_charset); +  invalidate_cache();   }      void conn_remove_multi (Sql.Sql db_conn, array(mixed) ids)   //! Like @[remove_multi], but a database connection object is passed   //! explicitly instead of being retrieved via @[get_db].   {    UPDATE_MSG ("%O: remove_multi %{%O,%}\n", this, ids); -  Sql.mysql conn = db_conn->master_sql; +  Sql.mysql conn = db_conn;    // FIXME: Split into several queries if the list is very long.    conn->big_query ("DELETE FROM `" + table + "` "    "WHERE " + make_multi_pk_where (ids), -  0, query_charset); +  UNDEFINED, query_charset); +  invalidate_cache();   }      Result conn_select (Sql.Sql db_conn, string|array where,    void|array(string) fields, void|string|array select_exprs,    void|string table_refs, void|string|array rest,    void|string select_flags)   //! Like @[select], but a database connection object is passed   //! explicitly instead of being retrieved via @[get_db].   {    mapping(string:mixed) bindings = ([]);
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:947:    if (arrayp (rest)) rest = handle_argspec (rest, bindings);    if (!sizeof (bindings)) bindings = 0;       Result res = Result();       string query = "SELECT ";    if (select_flags) query += select_flags + " ";    query += res->prepare_select_expr (fields, select_exprs, !!table_refs) +    " FROM `" + table + "` " + (table_refs || "");    -  res->res = db_conn->master_sql->big_typed_query ( +  res->res = db_conn->big_typed_query (    query + " WHERE (" + where + ") " + (rest || ""), bindings);       return res;   }      array conn_select1 (Sql.Sql db_conn, string|array select_expr,    string|array where, void|string table_refs,    void|string|array rest, void|string select_flags)   //! Like @[select1], but a database connection object is passed   //! explicitly instead of being retrieved via @[get_db].   {    mapping(string:mixed) bindings = ([]);    if (arrayp (select_expr))    select_expr = handle_argspec (select_expr, bindings);    if (arrayp (where)) where = handle_argspec (where, bindings);    if (arrayp (rest)) rest = handle_argspec (rest, bindings); -  if (!sizeof (bindings)) bindings = 0; +  if (!sizeof (bindings)) bindings = UNDEFINED;       string property;    string col_type = col_types[select_expr];    if (!col_type && prop_col &&    sscanf (select_expr, "%*[^ .(]%*1[ .(]") != 2) {    property = select_expr;    select_expr = prop_col;    col_type = "string";    }       string query = "SELECT ";    if (select_flags) query += select_flags + " ";    if (timestamp_cols[select_expr])    query += "UNIX_TIMESTAMP(`" + table + "`.`" + select_expr + "`)";    else if (col_type)    query += "`" + table + "`.`" + select_expr + "`";    else    query += select_expr;    query += " FROM `" + table + "` " + (table_refs || "");    -  Sql.mysql_result res = db_conn->master_sql->big_typed_query ( +  Sql.Result res = db_conn->big_typed_query (    query + " WHERE (" + where + ") " + (rest || ""), bindings);    - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (res->num_fields() != 1)    error ("Result from %O did not contain a single field (got %d fields).\n",    query, res->num_fields());   #endif       array ret = allocate (res->num_rows());       if (property) {    int i = 0;    while (array(string) ent = res->fetch_row())
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1017:    }       return ret;   }      mapping(string:mixed) conn_get (Sql.Sql db_conn, mixed id,    void|array(string) fields)   //! Like @[get], but a database connection object is passed explicitly   //! instead of being retrieved via @[get_db].   { -  Sql.mysql conn = db_conn->master_sql; +  Sql.mysql conn = db_conn;    - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (fields && !sizeof (fields)) error ("No fields selected.\n");   #endif       int want_all = !fields;    if (want_all) {    if (prop_col)    // Some dwim: Probably don't want the prop_col value verbatim as    // well in the result.    fields = indices (col_types - ([prop_col: ""]));    else
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1041:    }       mapping(string:string) real_cols = col_types & fields;    mapping(string:string) other_fields;       if (sizeof (real_cols) < sizeof (fields)) {    if (!has_value (fields, 0)) {    mapping(string:string) field_map = mkmapping (fields, fields);    other_fields = field_map - real_cols;    } - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (!prop_col)    error ("Requested nonexisting column(s) %s.\n",    String.implode_nicely (indices (other_fields ||    ([prop_col: ""]))));   #endif    }    else if (!want_all)    other_fields = ([]);       int exclude_prop_col;
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1069:       array(string) select_cols = allocate (sizeof (real_col_types));    foreach (real_col_names; int i; string name) {    if (timestamp_cols[name])    select_cols[i] = "UNIX_TIMESTAMP(`" + name + "`)";    else    select_cols[i] = "`" + name + "`";    }       string pk_where = simple_make_pk_where (id); -  Mysql.mysql_result res = +  Sql.Result res =    conn->big_typed_query ("SELECT " + (select_cols * ",") + " "    "FROM `" + table + "` "    "WHERE " + pk_where,    0, query_charset);       array(string) ent = res->fetch_row();    if (!ent) return 0;       mapping(string:mixed) rec = ([]);   
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1105:    }       return rec;   }      Result conn_get_multi (Sql.Sql db_conn, array(mixed) ids,    void|array(string) fields)   //! Like @[get_multi], but a database connection object is passed   //! explicitly instead of being retrieved via @[get_db].   { -  Sql.mysql conn = db_conn->master_sql; +  Sql.mysql conn = db_conn;    Result res = Result();    // FIXME: Split into several queries if the list is very long.    res->res =    conn->big_typed_query ("SELECT " +    res->prepare_select_expr (fields, 0, 0) +    " FROM `" + table + "` "    "WHERE " + make_multi_pk_where (ids),    0, query_charset);    return res;   }      // The Result object.      class Result   //! Result object returned by e.g. @[select]. This is similar in   //! function to an @[Sql.sql_result] object. It also implements the   //! iterator interface and can therefore be used directly in e.g.   //! @expr{foreach@}.   { -  Sql.mysql_result res; +  Sql.Result res;    //! The underlying result object from the db connection.       protected array(string) real_col_names;    // The names of the sql columns to retrieve. Initially this contains    // only the column names built from fields, not the names of the    // user supplied select expressions. The latter are added when the    // first row is fetched.    //    // If properties are being fetched but not the property column    // itself then real_col_names has a zero in the prop_pos position.
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1238:    while (mapping(string:mixed) rec = fetch())    res += ({rec});    return res;    }       string prepare_select_expr (array(string) fields, string select_exprs,    int with_table_qualifiers)    // Internal function to initialize all the variables from an    // optional array of requested fields.    { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (fields && !sizeof (fields)) error ("No fields selected.\n");   #endif       string select_clause;    string tbl_qual = with_table_qualifiers ? table + "`.`" : "";    int want_all = !fields;       prop_pos = -1;       if (want_all) {
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1286:    }    } else {    real_col_names = ({});    }       if (sizeof (real_cols) < sizeof (fields)) {    if (!has_value (fields, 0)) {    mapping(string:string) field_map = mkmapping (fields, fields);    other_fields = field_map - real_cols;    } - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (!prop_col)    error ("Requested nonexisting column(s) %s.\n",    String.implode_nicely (indices (other_fields ||    ([prop_col: ""]))));   #endif    }    else if (!want_all)    other_fields = ([]);       if (prop_col && !equal (other_fields, ([]))) {
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1319:    if (select_clause) select_clause += ", " + select_exprs;    else select_clause = select_exprs;    }       return select_clause || "";    }       // Iterator interface. This is a separate object only to avoid    // implementing a `! in Result, which would make it behave oddly.    - #ifdef DEBUG + #ifdef MYSQL_DEBUG    protected int got_iterator;   #endif       Iterator _get_iterator()    //! Returns an iterator for the result. Only one iterator may be    //! created per @[Result] object.    { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (got_iterator)    error ("Cannot create more than one iterator for a Result object.\n");    got_iterator = 1;   #endif    return Iterator (num_rows());    }       protected class Iterator (protected int cached_num_rows)    {    protected int `!() {return cur_row >= cached_num_rows;}
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1417:    (err ? describe_error (err) :    sprintf ("Expected mapping, got %O.\n", decoded)),    prop_val);    else    return [mapping(string:mixed)] decoded;    }    return ([]);   }      protected void add_mysql_value (String.Buffer buf, string col_name, mixed val) - // A value with zero_type is formatted as "DEFAULT". + // An undefined value is formatted as "DEFAULT".   {    if (stringp (val)) { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (col_types[col_name] != "string")    error ("Got string value %q for %s column `%s`.\n",    val, col_types[col_name], col_name);   #endif    if (String.width (val) == 8)    // _latin1 works fine for binary data since the actual charset    // isn't significant. The only problem is for 8-bit text where    // mysql latin1 has chars in the control range 0x80..0x9f. Since    // those control chars are very uncommon in text we just ignore    // that problem for now.    buf->add ("_latin1\"", quote (val), "\"");    else    // FIXME: If the column holds binary data we should throw an    // error here instead of sending what is effectively garbled    // data. -  buf->add ("_utf8\"", string_to_utf8 (quote (val)), "\""); +  buf->add ("_utf8\"", string_to_utf8 (quote (val), 2), "\"");    }    else if (intp (val)) { -  if (zero_type (val)) +  if (undefinedp (val))    buf->add ("DEFAULT");    else { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (col_types[col_name] != "int" && !datetime_cols[col_name])    error ("Got integer value %O for %s column `%s`.\n",    val, col_types[col_name] || "string", col_name);   #endif    if (timestamp_cols[col_name])    buf->add ("FROM_UNIXTIME(", (string) val, ")");    else    buf->add ((string) val);    }    }    else if (val == Val.null)    buf->add ("NULL");    else { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (objectp (val) && functionp (val->den)) {    // Allow Gmp.mpq for float fields, and for int fields if they    // have no fractional part.    if (col_types[col_name] != "float" &&    !(val->den() == 1 && col_types[col_name] == "int"))    error ("Got %O for %s column `%s`.\n",    val, col_types[col_name] || "string", col_name);    }    else {    if (!floatp (val))
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1488:   // Returns an "(a,b,c) VALUES (1,2,3),(4,5,6)" clause as used in   // INSERT statements. Fields that don't have columns are packed into   // prop_col updates. Destructive on the records array, but not on the   // mapping elements.   {    mapping(string:mixed) query_cols = ([]); // Only indices are relevant.       // FIXME: Ought to use bindings, but Mysql.mysql doesn't support it    // yet (as of pike 7.8.191).    - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (!sizeof (records)) error ("Must give at least one record.\n");   #endif       foreach (records; int i; mapping(string:mixed) rec) {    mapping(string:mixed) real_cols = col_types & rec;    mapping(string:mixed) other_fields = rec - real_cols;       if (sizeof (other_fields)) { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (!prop_col) error ("Column(s) %s missing.\n",    String.implode_nicely (indices (other_fields)));   #endif       string encoded_props;    if (mixed err = catch {    encoded_props = encode_value_canonic (other_fields);    })    error ("Failed to encode properties: %sThe properties are: %O\n",    describe_error (err), other_fields);
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1550:    }       return buf->get();   }      protected string make_pk_where (mapping(string:mixed) rec)   // Returns a WHERE expression like "a=1 AND b=2" for matching the   // primary key, or zero if the record doesn't have values for all pk   // columns. The pk fields are also removed from the rec mapping.   { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (!sizeof (pk_cols)) error ("There is no primary key in this table.\n");   #endif    String.Buffer buf = String.Buffer();    int first = 1;    foreach (pk_cols, string pk_col) {    if (first) first = 0; else buf->add (" AND ");    mixed val = m_delete (rec, pk_col); -  if (zero_type (val) || val == Val.null) +  if (undefinedp (val) || val == Val.null)    return 0;    buf->add ("`", pk_col, "`=");    add_mysql_value (buf, pk_col, val);    }    return buf->get();   }      protected string simple_make_pk_where (mixed id)   // Returns a WHERE expression like "a=1 AND b=2" for matching the   // primary key. id is like the argument to get().   { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (!sizeof (pk_cols)) error ("There is no primary key in this table.\n");   #endif       String.Buffer buf = String.Buffer();    if (sizeof (pk_cols) == 1) {    buf->add ("`", pk_cols[0], "`="); - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (id == Val.null)    error ("Cannot use Val.null for primary key column %O.\n",    pk_cols[0]);   #endif    add_mysql_value (buf, pk_cols[0],    id || 0); // Clear any zero_type in id.    }       else { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (!arrayp (id) || sizeof (id) != sizeof (pk_cols))    error ("The id must be an array with %d elements.\n", sizeof (pk_cols));   #endif    int first = 1;    foreach (pk_cols; int i; string pk_col) {    if (first) first = 0; else buf->add (" AND ");    buf->add ("`", pk_cols[0], "`="); - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (id[i] == Val.null)    error ("Cannot use Val.null for primary key column %O.\n", pk_col);   #endif    add_mysql_value (buf, pk_col,    id[i] || 0); // Clear any zero_type in id[i].    }    }       return buf->get();   }      protected string make_multi_pk_where (array(mixed) ids, int|void negated)   // Returns a WHERE expression like "foo IN (2,3,17,4711)" for matching   // a bunch of records by primary key.   { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (sizeof (pk_cols) != 1)    error ("The table must have a single column primary key.\n");   #endif       if (!sizeof (ids)) return negated?"TRUE":"FALSE";       string pk_col = pk_cols[0];    string pk_type = col_types[pk_col];       string optional_not = negated?" NOT ":"";       if ((<"float", "int">)[pk_type]) { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    foreach (ids; int i; mixed id)    if (!intp (id) && !floatp (id))    error ("Expected numeric value for primary key column %O, "    "got %t at position %d.\n",    pk_col, id, i);   #endif    return (timestamp_cols[pk_col] ?    "UNIX_TIMESTAMP(`" + pk_col + "`)" : "`" + pk_col + "`") +    optional_not + " IN (" + (((array(string)) ids) * ",") + ")";    }       else { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    foreach (ids; int i; mixed id)    if (!stringp (id))    error ("Expected string value for primary key column %O, "    "got %t at position %d.\n",    pk_col, id, i);   #endif    String.Buffer buf = String.Buffer();    buf->add ("`", pk_col, "` ", optional_not, " IN (");    int first = 1;    foreach (ids, string id) {
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1667:    mapping(string:mixed) prop_changes)   // Retrieves the current properties for a record (if pk_where is set),   // merges prop_changes into them, and returns the new value to assign   // to the properties column.   {    mapping(string:mixed) old_props;    if (array(string) ent = pk_where &&    conn->big_query ("SELECT `" + prop_col + "` "    "FROM `" + table + "` "    "WHERE " + pk_where, -  0, query_charset)->fetch_row()) +  UNDEFINED, query_charset)->fetch_row())    old_props = decode_props (ent[0], pk_where);    else    old_props = ([]);       mapping(string:mixed) rem_props = filter (prop_changes, `==, Val.null);    mapping(string:mixed) new_props = old_props + prop_changes - rem_props;       string encoded_props;    if (mixed err = catch {    encoded_props = encode_value_canonic (new_props);
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1704:   // Updates the properties column by retrieving the current properties   // and merging prop_changes into them. prop_changes is assumed to only   // contain properties.   {    string encoded_props = get_and_merge_props (conn, pk_where, prop_changes);    conn->big_query ("UPDATE `" + table + "` "    "SET `" + prop_col + "`="    "_binary\"" + quote (encoded_props) + "\" "    "WHERE " + pk_where + " "    "LIMIT 1", // In case the WHERE condition is bad. -  0, query_charset); +  UNDEFINED, query_charset);   }      protected mapping(string:mixed) update_pack_fields (    Sql.mysql conn, mapping(string:mixed) rec, string pk_where,    int replace_properties)   // Returns a record mapping where all fields that don't have columns   // have been packed into a prop_col entry. The table is queried if   // necessary to obtain the old prop_col fields for merging.   {    mapping(string:mixed) real_cols = col_types & rec;    mapping(string:mixed) other_fields = rec - real_cols;       if (sizeof (other_fields)) { - #ifdef DEBUG + #ifdef MYSQL_DEBUG    if (!prop_col) error ("Column(s) %s missing in table %O.\n",    String.implode_nicely (indices (other_fields)),    table);   #endif    if (replace_properties) pk_where = 0;    else if (!pk_where) pk_where = make_pk_where (rec + ([]));    real_cols[prop_col] = get_and_merge_props (conn, pk_where, other_fields);    }       else if (replace_properties && prop_col)
pike.git/lib/modules/Mysql.pmod/SqlTable.pike:1758:    }       if (set_all_columns)    foreach (col_types - rec - pk_cols; string col;) {    if (first) first = 0; else buf->add (",");    buf->add ("`", col, "`=DEFAULT(`", col, "`)");    }       return buf->get();   } - #endif +  + protected int _sizeof() + { +  int ret = num_entries; +  if (ret < 0) { +  // NB: Thread safe! +  ret = num_entries = (int)(select1("COUNT(*)", "TRUE")[0]); +  } +  return ret; + }