Skip to content
Snippets Groups Projects
Select Git revision
  • d6d348fb78ce80c85370afde6ae320ed68e65807
  • master default protected
  • 9.0
  • 8.0
  • 7.8
  • 7.6
  • 7.4
  • 7.2
  • 7.0
  • 0.6
  • rosuav/latex-markdown-renderer
  • rxnpatch/rxnpatch
  • marcus/gobject-introspection
  • rxnpatch/8.0
  • rosuav/pre-listening-ports
  • nt-tools
  • rosuav/async-annotations
  • rosuav/pgsql-ssl
  • rxnpatch/rxnpatch-broken/2023-10-06T094250
  • grubba/fdlib
  • grubba/wip/sakura/8.0
  • v8.0.2004
  • v8.0.2002
  • v8.0.2000
  • v8.0.1998
  • v8.0.1996
  • v8.0.1994
  • v8.0.1992
  • v8.0.1990
  • v8.0.1988
  • v8.0.1986
  • rxnpatch/clusters/8.0/2025-04-29T124414
  • rxnpatch/2025-04-29T124414
  • v8.0.1984
  • v8.0.1982
  • v8.0.1980
  • v8.0.1978
  • v8.0.1976
  • v8.0.1974
  • v8.0.1972
  • v8.0.1970
41 results

error.h

Blame
    • Fredrik Hübinette (Hubbe)'s avatar
      dc7cc90d
      threads now works on NT · dc7cc90d
      Fredrik Hübinette (Hubbe) authored
      Rev: src/Makefile.in:1.51
      Rev: src/builtin_functions.c:1.60
      Rev: src/error.c:1.9
      Rev: src/error.h:1.8
      Rev: src/las.c:1.41
      Rev: src/main.c:1.33
      Rev: src/object.c:1.32
      Rev: src/threads.c:1.52
      Rev: src/threads.h:1.27
      dc7cc90d
      History
      threads now works on NT
      Fredrik Hübinette (Hubbe) authored
      Rev: src/Makefile.in:1.51
      Rev: src/builtin_functions.c:1.60
      Rev: src/error.c:1.9
      Rev: src/error.h:1.8
      Rev: src/las.c:1.41
      Rev: src/main.c:1.33
      Rev: src/object.c:1.32
      Rev: src/threads.c:1.52
      Rev: src/threads.h:1.27
    Sql.pike 19.63 KiB
    /*
     * $Id: Sql.pike,v 1.88 2006/11/04 19:06:49 nilsson Exp $
     *
     * Implements the generic parts of the SQL-interface
     *
     * Henrik Grubbström 1996-01-09
     */
    
    #pike __REAL_VERSION__
    
    //! Implements those functions that need not be present in all SQL-modules.
    
    #define ERROR(X ...)	predef::error(X)
    
    //! Object to use for the actual SQL-queries.
    object master_sql;
    
    //! Convert all field names in mappings to lower_case.
    //! Only relevant to databases which only implement big_query(),
    //! and use upper/mixed-case fieldnames (eg Oracle).
    //! @int
    //! @value 0
    //!   No (default)
    //! @value 1
    //!   Yes
    //! @endint
    int(0..1) case_convert;
    
    //! @decl string quote(string s)
    //! Quote a string @[s] so that it can safely be put in a query.
    //!
    //! All input that is used in SQL-querys should be quoted to prevent
    //! SQL injections.
    //! 
    //! Consider this harmfull code:
    //! @code
    //!   string my_input = "rob' OR name!='rob";
    //!   string my_query = "DELETE FROM tblUsers WHERE name='"+my_input+"'";
    //!   my_db->query(my_query);
    //! @endcode
    //! 
    //! This type of problems can be avoided by quoting @tt{my_input@}.
    //! @tt{my_input@} would then probably read something like 
    //! @i{rob\' OR name!=\'rob@} 
    //!
    //! Usually this is done - not by calling quote explicitly - but through
    //! using a @[sprintf] like syntax
    //! @code
    //!   string my_input = "rob' OR name!='rob";
    //!   my_db->query("DELETE FROM tblUsers WHERE name=%s",my_input);
    //! @endcode
    
    function(string:string) quote = .sql_util.quote;
    
    //! @decl string encode_time(int t, int|void is_utc)
    //! Converts a system time value to an appropriately formatted time
    //! spec for the database.
    //! @param t
    //!   Time to encode.
    //! @param is_utc
    //!   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.
    
    function(int,void|int:string) encode_time;
    
    //! @decl int decode_time(string t, int|void want_utc)
    //! Converts a database time spec to a system time value.
    //! @param t
    //!   Time spec to decode.
    //! @param want_utc
    //!   Take the date part from this system time value. If zero, a
    //!   seconds-since-midnight value is returned.
    
    function(string,void|int:int) decode_time;
    
    //! @decl string encode_date(int t)
    //! Converts a system time value to an appropriately formatted
    //! date-only spec for the database.
    //! @param t
    //!   Time to encode.
    
    function(int:string) encode_date;
    
    //! @decl int decode_date(string d)
    //! Converts a database date-only spec to a system time value.
    //! @param d
    //!   Date spec to decode.
    
    function(string:int) decode_date;
    
    //! @decl string encode_datetime(int t)
    //! Converts a system time value to an appropriately formatted
    //! date and time spec for the database.
    //! @param t
    //!   Time to encode.
    
    function(int:string) encode_datetime;
    
    //! @decl int decode_datetime(string datetime)
    //! Converts a database date and time spec to a system time value.
    //! @param datetime
    //!   Date and time spec to decode.
    
    function(string:int) decode_datetime;
    
    static program find_dbm(string program_name) {
      program p;
      // we look in Sql.type and Sql.Provider.type.type for a valid sql class.
      p = Sql[program_name];
      if(!p && Sql["Provider"] && Sql["Provider"][program_name])
        p = Sql["Provider"][program_name][program_name];
      return p;
    }
    
    //! @decl void create(string host)
    //! @decl void create(string host, string db)
    //! @decl void create(string host, mapping(string:int|string) options)
    //! @decl void create(string host, string db, string user)
    //! @decl void create(string host, string db, string user, @
    //!                   string password)
    //! @decl void create(string host, string db, string user, @
    //!                   string password, mapping(string:int|string) options)
    //! @decl void create(object host)
    //! @decl void create(object host, string db)
    //!
    //! Create a new generic SQL object.
    //!
    //! @param host
    //!   @mixed
    //!     @type object
    //!       Use this object to access the SQL-database.
    //!     @type string
    //!       Connect to the server specified. The string should be on the
    //!       format:
    //!       @tt{dbtype://[user[:password]@@]hostname[:port][/database]@}
    //!       Use the @i{dbtype@} protocol to connect to the database
    //!       server on the specified host. If the hostname is @expr{""@}
    //!       then the port can be a file name to access through a
    //!       UNIX-domain socket or similar, e g
    //!       @expr{"mysql://root@@:/tmp/mysql.sock/"@}.
    //!
    //!       There is a special dbtype @expr{"mysqls"@} which works like
    //!       @expr{"mysql"@} but sets the @tt{CLIENT_SSL@} option and
    //!       loads the @tt{/etc/my.cnf@} config file to find the SSL
    //!       parameters. The same function can be achieved using the
    //!       @expr{"mysql"@} dbtype.
    //!     @type int(0..0)
    //!       Access through a UNIX-domain socket or similar.
    //!   @endmixed
    //!
    //! @param db
    //!   Select this database.
    //!
    //! @param user
    //!   User name to access the database as.
    //!
    //! @param password
    //!   Password to access the database.
    //!
    //! @param options
    //!   Optional mapping of options.
    //!   See the SQL-database documentation for the supported options.
    //!   (eg @[Mysql.mysql()->create()]).
    //!
    //! @note
    //!   In versions of Pike prior to 7.2 it was possible to leave out the
    //!   dbtype, but that has been deprecated, since it never worked well.
    //!
    //! @note
    //!   Support for @[options] was added in Pike 7.3.
    //!
    void create(string|object host, void|string|mapping(string:int|string) db,
    	    void|string user, void|string password,
    	    void|mapping(string:int|string) options)
    {
      if (objectp(host)) {
        master_sql = host;
        if ((user && user != "") || (password && password != "") ||
    	(options && sizeof(options))) {
          ERROR("Only the database argument is supported when "
    	    "first argument is an object.\n");
        }
        if (db && db != "") {
          master_sql->select_db(db);
        }
      }
      else {
        if (mappingp(db)) {
          options = db;
          db = 0;
        }
        if (db == "") {
          db = 0;
        }
        if (user == "") {
          user = 0;
        }
        if (password == "") {
          password = 0;
        }
    
        string program_name;
    
        if (host && (host != replace(host, ([ ":":"", "/":"", "@":"" ]) ))) {
    
          // The hostname is on the format:
          //
          // dbtype://[user[:password]@]hostname[:port][/database]
    
          array(string) arr = host/"://";
          if ((sizeof(arr) > 1) && (arr[0] != "")) {
    	if (sizeof(arr[0]/".pike") > 1) {
    	  program_name = (arr[0]/".pike")[0];
    	} else {
    	  program_name = arr[0];
    	}
    	host = arr[1..] * "://";
          }
          arr = host/"@";
          if (sizeof(arr) > 1) {
    	// User and/or password specified
    	host = arr[-1];
    	arr = (arr[..<1]*"@")/":";
    	if (!user && sizeof(arr[0])) {
    	  user = arr[0];
    	}
    	if (!password && (sizeof(arr) > 1)) {
    	  password = arr[1..]*":";
    	  if (password == "") {
    	    password = 0;
    	  }
    	}
          }
          arr = host/"/";
          if (sizeof(arr) > 1) {
    	host = arr[..<1]*"/";
    	if (!db) {
    	  db = arr[-1];
    	}
          }
        }
    
        if (host == "") {
          host = 0;
        }
    
        if (!program_name) {
          ERROR("No protocol specified.\n");
        }
        // Don't call ourselves...
        if ((sizeof(program_name / "_result") != 1) ||
    	  ((< "Sql", "sql", "sql_util", "module" >)[program_name]) ) {
          ERROR("Unsupported protocol %O.\n", program_name);
        }
    
    
        program p;
    
        if (p = find_dbm(program_name)) {
          if (options) {
    	master_sql = p(host||"", db||"", user||"", password||"", options);
          } else if (password) {
    	master_sql = p(host||"", db||"", user||"", password);
          } else if (user) {
    	master_sql = p(host||"", db||"", user);
          } else if (db) {
    	master_sql = p(host||"", db);
          } else if (host) {
    	master_sql = p(host);
          } else {
    	master_sql = p();
          }
        } else {
          ERROR("Failed to index module Sql.%s or Sql.Provider.%s.\n",
    	    program_name, program_name);
        }
      }
    
      if (master_sql->quote) quote = master_sql->quote;
      encode_time = master_sql->encode_time || .sql_util.fallback;
      decode_time = master_sql->decode_time || .sql_util.fallback;
      encode_date = master_sql->encode_date || .sql_util.fallback;
      decode_date = master_sql->decode_date || .sql_util.fallback;
      encode_datetime = master_sql->encode_datetime || .sql_util.fallback;
      decode_datetime = master_sql->decode_datetime || .sql_util.fallback;
    }
    
    void set_charset (string charset)
    //! Changes the charset that the connection uses for queries and
    //! returned text strings.
    //!
    //! @param charset
    //!   The charset to use. The valid values and their meanings depends
    //!   on the database brand. However, the special value
    //!   @expr{"unicode"@} (if supported) selects a mode where the query
    //!   and result strings are unencoded (and possibly wide) unicode
    //!   strings.
    //!
    //! @throws
    //!   An error is thrown if the connection doesn't support the
    //!   specified charset, or doesn't support charsets being set this
    //!   way at all.
    //!
    //! @note
    //!   See the @expr{set_charset@} functions for each database
    //!   connection type for further details about the effects on the
    //!   connection.
    //!
    //! @seealso
    //!   @[get_charset], @[Sql.mysql.set_charset]
    {
      if (!master_sql->set_charset)
        predef::error ("This database connection does not "
    		   "support charset switching.\n");
      master_sql->set_charset (charset);
    }
    
    string get_charset()
    //! Returns the (database dependent) name of the charset used for (at
    //! least) query strings. Returns zero if the connection doesn't
    //! support charsets this way (typically means that a call to
    //! @[set_charset] will throw an error).
    //!
    //! @seealso
    //!   @[set_charset], @[Sql.mysql.get_charset]
    {
      return master_sql->get_charset && master_sql->get_charset();
    }
    
    static string _sprintf(int type, mapping|void flags)
    {
      if(type=='O' && master_sql && master_sql->_sprintf)
        return sprintf("Sql.%O", master_sql);
    }
    
    static array(mapping(string:mixed)) res_obj_to_array(object res_obj)
    {
      if (res_obj) 
      {
        // Not very efficient, but sufficient
        array(mapping(string:mixed)) res = ({});
        array(string) fieldnames;
        array(mixed) row;
    
        array(mapping) fields = res_obj->fetch_fields();
        if(!sizeof(fields)) return ({});
    
        int has_table = fields[0]->table && fields[0]->table!="";
    
        if(has_table)
          fieldnames = (map(fields,
    			lambda (mapping(string:mixed) m) {
    			  return (m->table||"") + "." + m->name;
    			}) +
    		    fields->name);
        else
          fieldnames = fields->name;
    
        if (case_convert)
          fieldnames = map(fieldnames, lower_case);
    
        if(has_table)
          while (row = res_obj->fetch_row())
    	res += ({ mkmapping(fieldnames, row + row) });
        else
          while (row = res_obj->fetch_row())
    	res += ({ mkmapping(fieldnames, row) });
    
        return res;
      }
      return 0;
    }
    
    //! Return last error message.
    int|string error()
    {
      if (functionp (master_sql->error))
        return master_sql->error();
      return "Unknown error";
    }
    
    //! Select database to access.
    void select_db(string db)
    {
      master_sql->select_db(db);
    }
    
    //! Compiles the query (if possible). Otherwise returns it as is.
    //! The resulting object can be used multiple times in query() and
    //! big_query().
    //!
    //! @param q
    //!   SQL-query to compile.
    string|object compile_query(string q)
    {
      if (functionp(master_sql->compile_query)) {
        return master_sql->compile_query(q);
      }
      return q;
    }
    
    //! Handle sprintf-based quoted arguments
    static array(string|mapping(string|int:mixed))
      handle_extraargs(string query, array(mixed) extraargs) {
    
      array(mixed) args=allocate(sizeof(extraargs));
      mapping(string|int:mixed) b = ([]);
    
      int a;
      foreach(extraargs; int j; mixed s) {
        if (stringp(s) || multisetp(s)) {
          args[j]=":arg"+(a++);
          b[args[j]] = s;
          continue;
        }
        if (intp(s) || floatp(s)) {
          args[j]=s;
          continue;
        }
        ERROR("Wrong type to query argument #"+(j+1)+".\n");
      }
      if(!sizeof(b)) b=0;
    
      return ({sprintf(query,@args), b});
    }
    
    //!   Send an SQL query to the underlying SQL-server.
    //! @param q
    //!   Query to send to the SQL-server. This can either be a string with the
    //!   query, or a previously compiled query (see compile_query()).
    //! @param extraargs
    //!   This parameter, if specified, can be in two forms:
    //!
    //!   @ol
    //!     @item
    //!     A mapping containing bindings of variables used in the query.
    //!     A variable is identified by a colon (:) followed by a name or number.
    //!     Each index in the mapping corresponds to one such variable, and the
    //!     value for that index is substituted (quoted) into the query wherever
    //!     the variable is used.
    //!
    //! @code
    //! res = query("SELECT foo FROM bar WHERE gazonk=:baz",
    //!             ([":baz":"value"]));
    //! @endcode
    //!
    //!     Binary values (BLOBs) may need to be placed in multisets.
    //!
    //!     @item
    //!     Arguments as you would use in sprintf. They are automatically
    //!     quoted.
    //!
    //! @code
    //! res = query("select foo from bar where gazonk=%s","value");
    //! @endcode
    //!   @endol
    //!
    //! @returns
    //!   Returns one of the following on success:
    //!   @mixed
    //!     @type array(mapping(string:string))
    //!       The result as an array of mappings indexed on the name
    //!       of the columns
    //!     @type zero
    //!       The value @expr{0@} (zero) if the query didn't return any
    //!       result (eg @tt{INSERT@} or similar).
    //!   @endmixed
    //!
    //! @throws
    //!   Throws an exception if the query fails.
    //!
    //! @seealso
    //!   @[big_query]
    array(mapping(string:mixed)) query(object|string q,
                                       mixed ... extraargs)
    {
      if (sizeof(extraargs)) {
        mapping(string|int:mixed) bindings;
    
        if (mappingp(extraargs[0]))
          bindings=extraargs[0];
        else
          [q,bindings]=handle_extraargs(q,extraargs);
    
        if(bindings) {
          if(master_sql->query)
    	return master_sql->query(q, bindings);
          return res_obj_to_array(master_sql->big_query(q, bindings));
        }
      }
    
      if (master_sql->query)
          return master_sql->query(q);
      return res_obj_to_array(master_sql->big_query(q));
    }
    
    //! Send an SQL query to the underlying SQL-server. The result is
    //! returned as an Sql.sql_result object. This allows for having some
    //! more info about the result as well as processing the result in a
    //! streaming fashion, although the result itself wasn't obtained
    //! streamingly from the server. Returns @expr{0@} if the query didn't
    //! return any result (e.g. INSERT or similar). For the other
    //! arguments, they are the same as for the @[query()] function.
    //!
    //! @seealso
    //!   @[query], @[streaming_query]
    int|object big_query(object|string q, mixed ... extraargs)
    {
      object|array(mapping) pre_res;
    
      switch( sizeof(extraargs) ) {
    
      default:
        mapping(string|int:mixed) bindings;
    
        if (mappingp(extraargs[0]))
          bindings=extraargs[0];
        else
          [q,bindings]=handle_extraargs(q,extraargs);
    
        if(bindings) {
          if(master_sql->big_query)
    	pre_res = master_sql->big_query(q, bindings);
          else
    	pre_res = master_sql->query(q, bindings);
          break;
        }
        // Fallthrough
    
      case 0:
        if (master_sql->big_query)
          pre_res = master_sql->big_query(q);
        else
          pre_res = master_sql->query(q);
        break;
      }
    
      if(pre_res) {
        if(objectp(pre_res))
          return .sql_object_result(pre_res);
        else
          return .sql_array_result(pre_res);
      }
      return 0;
    }
    
    //! Send an SQL query to the underlying SQL-server. The result is
    //! returned as a streaming Sql.sql_result object. This allows for
    //! having results larger than the available memory, and returning
    //! some more info about the result. Returns @expr{0@} if the query
    //! didn't return any result (e.g. INSERT or similar). For the other
    //! arguments, they are the same as for the @[query()] function.
    //!
    //! @seealso
    //!   @[big_query]
    int|object streaming_query(object|string q, mixed ... extraargs)
    {
      if(!master_sql->streaming_query) return 0;
      object pre_res;
    
      switch( sizeof(extraargs) ) {
    
      default:
        mapping(string|int:mixed) bindings;
    
        if(mappingp(extraargs[0]))
          bindings=extraargs[0];
        else
          [q,bindings]=handle_extraargs(q,extraargs);
    
        if(bindings) {
          pre_res = master_sql->streaming_query(q,bindings);
          break;
        }
        // Fallthough
    
      case 0:
        pre_res = master_sql->streaming_query(q);
        break;
      }
    
      if(pre_res)
        return .sql_object_result(pre_res);
      return 0;
    }
    
    //! Create a new database.
    //!
    //! @param db
    //!   Name of database to create.
    void create_db(string db)
    {
      master_sql->create_db(db);
    }
    
    //! Drop database
    //!
    //! @param db
    //!   Name of database to drop.
    void drop_db(string db)
    {
      master_sql->drop_db(db);
    }
    
    //! Shutdown a database server.
    void shutdown()
    {
      if (functionp(master_sql->shutdown)) {
        master_sql->shutdown();
      } else {
        ERROR("Not supported by this database.\n");
      }
    }
    
    //! Reload the tables.
    void reload()
    {
      if (functionp(master_sql->reload)) {
        master_sql->reload();
      } else {
        // Probably safe to make this a NOOP
      }
    }
    
    //! Return info about the current SQL-server.
    string server_info()
    {
      if (functionp(master_sql->server_info)) {
        return master_sql->server_info() ;
      }
      return "Unknown SQL-server";
    }
    
    //! Return info about the connection to the SQL-server.
    string host_info()
    {
      if (functionp(master_sql->host_info)) {
        return master_sql->host_info();
      } 
      return "Unknown connection to host";
    }
    
    //! List available databases on this SQL-server.
    //!
    //! @param wild
    //!   Optional wildcard to match against.
    array(string) list_dbs(string|void wild)
    {
      array(string)|array(mapping(string:mixed))|object res;
      
      if (functionp(master_sql->list_dbs)) {
        if (objectp(res = master_sql->list_dbs())) {
          res = res_obj_to_array(res);
        }
      } else {
        catch {
          res = query("show databases");
        };
      }
      if (res && sizeof(res) && mappingp(res[0])) {
        res = map(res, lambda (mapping m) {
    		     return values(m)[0]; // Hope that there's only one field
    		   } );
      }
      if (res && wild) {
        res = filter(res,
    		 Regexp(replace(wild, ({"%", "_"}), ({".*", "."})))->match);
      }
      return res;
    }
    
    //! List tables available in the current database.
    //!
    //! @param wild
    //!   Optional wildcard to match against.
    array(string) list_tables(string|void wild)
    {
      array(string)|array(mapping(string:mixed))|object res;
      
      if (functionp(master_sql->list_tables)) {
        if (objectp(res = master_sql->list_tables())) {
          res = res_obj_to_array(res);
        }
      } else {
        catch {
          res = query("show tables");
        };
      }
      if (res && sizeof(res) && mappingp(res[0])) {
        res = map(res, lambda (mapping m) {
    		     return values(m)[0]; // Hope that there's only one field
    		   } );
      }
      if (res && wild) {
        res = filter(res,
    		 Regexp(replace(wild, ({"%", "_"}), ({".*", "."})))->match);
      }
      return res;
    }
    
    //! List fields available in the specified table
    //!
    //! @param table
    //!   Table to list the fields of.
    //!
    //! @param wild
    //!   Optional wildcard to match against.
    array(mapping(string:mixed)) list_fields(string table, string|void wild)
    {
      array(mapping(string:mixed))|object res;
    
      if (functionp(master_sql->list_fields)) {
        if (objectp(res = master_sql->list_fields(table))) {
          res = res_obj_to_array(res);
        }
        if (wild) {
          res = filter(res,
    		   lambda(mapping row, function(string:int) match) {
    		     return match(row->name);
    		   },
    		   Regexp(replace(wild, ({"%", "_"}), ({".*", "."})))->match);
        }
        return res;
      }
      catch {
        if (wild) {
          res = query("show fields from \'" + table +
    		  "\' like \'" + wild + "\'");
        } else {
          res = query("show fields from \'" + table + "\'");
        }
      };
      res = res && map(res, lambda (mapping m, string table) {
        foreach(indices(m), string str) {
          // Add the lower case variants
          string low_str = lower_case(str);
          if (low_str != str && !m[low_str])
    	m[low_str] = m_delete(m, str);
        }
    
        if ((!m->name) && m->field)
          m["name"] = m_delete(m, "field");
    
        if (!m->table)
          m["table"] = table;
    
        return m;
      }, table);
      return res;
    }