87e9262001-06-22Martin Nilsson // This file is part of Roxen Search // Copyright © 2001 Roxen IS. All rights reserved. //
47d8e92001-08-19Martin Nilsson // $Id: Utils.pmod,v 1.27 2001/08/19 14:34:52 nilsson Exp $
f672612001-07-26Martin Nilsson  #if !constant(report_error) #define report_error werror #endif
87e9262001-06-22Martin Nilsson 
a0fc192001-06-10Johan Schön public array(string) tokenize_and_normalize( string what )
4779302001-06-05Per Hedbor //! This can be optimized quite significantly when compared to //! tokenize( normalize( x ) ) in the future, currently it's not all //! that much faster, but still faster. { return Unicode.split_words_and_normalize( lower_case(what) ); }
90f5642001-05-17Johan Schön 
a0fc192001-06-10Johan Schön public array(string) tokenize(string in)
4779302001-06-05Per Hedbor //! Tokenize the input string (Note: You should first call normalize //! on it)
90f5642001-05-17Johan Schön {
4779302001-06-05Per Hedbor  return Unicode.split_words( in );
90f5642001-05-17Johan Schön }
a0fc192001-06-10Johan Schön public string normalize(string in)
4779302001-06-05Per Hedbor //! Normalize the input string. Performs unicode NFKD normalization //! and then lowercases the whole string
90f5642001-05-17Johan Schön {
4779302001-06-05Per Hedbor  return Unicode.normalize( lower_case(in), "KD" );
90f5642001-05-17Johan Schön }
c88b0b2001-07-04Martin Nilsson 
74de682001-07-13Martin Nilsson  #define THROW(X) throw( ({ (X), backtrace() }) )
46a6522001-08-06Martin Nilsson //! A result entry from the @[ProfileCache].
8898352001-07-21Martin Nilsson class ProfileEntry {
74de682001-07-13Martin Nilsson 
8898352001-07-21Martin Nilsson  private int last_stat; private int database_profile_id;
47d8e92001-08-19Martin Nilsson  private int query_profile_id;
8898352001-07-21Martin Nilsson  private ProfileCache my_cache; private mapping(string:mixed) database_values;
47d8e92001-08-19Martin Nilsson  private mapping(string:mixed) query_values;
8898352001-07-21Martin Nilsson  private Search.Database.MySQL db; private Search.RankingProfile ranking; private array(string) stop_words;
47d8e92001-08-19Martin Nilsson  //! @decl void create(int database_profile_id, int query_profile_id,@
46a6522001-08-06Martin Nilsson  //! ProfileCache cache) //! @param cache //! The parent cache object.
8898352001-07-21Martin Nilsson  void create(int _database_profile_id,
47d8e92001-08-19Martin Nilsson  int _query_profile_id,
8898352001-07-21Martin Nilsson  ProfileCache _my_cache) { database_profile_id = _database_profile_id;
47d8e92001-08-19Martin Nilsson  query_profile_id = _query_profile_id;
8898352001-07-21Martin Nilsson  my_cache = _my_cache;
46a6522001-08-06Martin Nilsson  int last_stat = time(1);
f501ee2001-08-14Martin Nilsson  // Prefetch.. get_ranking();
8898352001-07-21Martin Nilsson  }
f501ee2001-08-14Martin Nilsson  //! Checks if it is time to check if the profile values are //! to old. int(0..1) check_timeout() { if(time(1)-last_stat < 5*60) return 0;
46a6522001-08-06Martin Nilsson  last_stat = time(1);
f501ee2001-08-14Martin Nilsson  return 1;
8898352001-07-21Martin Nilsson  }
46a6522001-08-06Martin Nilsson  //! Returns the database profile value @[index].
8898352001-07-21Martin Nilsson  mixed get_database_value(string index) { if(!database_values) database_values = my_cache->get_value_mapping(database_profile_id); return database_values[index]; }
47d8e92001-08-19Martin Nilsson  //! Returns the query profile value @[index]. mixed get_query_value(string index) { if(!query_values) query_values = my_cache->get_value_mapping(query_profile_id); return query_values[index];
8898352001-07-21Martin Nilsson  }
46a6522001-08-06Martin Nilsson  //! Returns a cached search database for the current database profile.
8898352001-07-21Martin Nilsson  Search.Database.MySQL get_database() { if(!db) {
f672612001-07-26Martin Nilsson #if constant(DBManager)
8898352001-07-21Martin Nilsson  db = Search.Database.MySQL( DBManager.db_url( get_database_value("db_name"), 1) );
f672612001-07-26Martin Nilsson #endif
8898352001-07-21Martin Nilsson  if(!db) THROW("Could not aquire the database URL to database " + get_database_value("db_name") + ".\n"); } return db; }
46a6522001-08-06Martin Nilsson  //! Returns a cached ranking profile for the current database and
47d8e92001-08-19Martin Nilsson  //! query profile.
8898352001-07-21Martin Nilsson  Search.RankingProfile get_ranking() { if(!ranking)
47d8e92001-08-19Martin Nilsson  ranking = Search.RankingProfile(get_query_value("fi_cut"), get_query_value("px_rank"),
8898352001-07-21Martin Nilsson  get_database(),
47d8e92001-08-19Martin Nilsson  get_query_value("fi_rank"));
8898352001-07-21Martin Nilsson  return ranking; } class ADTSet { private mapping vals = ([]); ADTSet add (string|int|float in) { vals[in] = 1; return this_object(); } ADTSet sub (string|int|float out) { m_delete(vals, out); return this_object(); } ADTSet `+(mixed in) { if(stringp(in)||intp(in)||floatp(in)) add(in); else map((array)in, add); return this_object(); } ADTSet `-(mixed out) { if(stringp(out)||intp(out)||floatp(out)) sub(out); else map((array)out, sub); return this_object(); } mixed cast(string to) { switch(to) { case "object": return this_object(); case "array": return indices(vals); case "multiset": return (multiset)indices(vals); default: THROW("Can not cast ADTSet to "+to+".\n"); } } }
47d8e92001-08-19Martin Nilsson  //! Returns a cached array of stop words for the current query profile.
8898352001-07-21Martin Nilsson  array(string) get_stop_words() { if(!stop_words) { ADTSet words = ADTSet();
47d8e92001-08-19Martin Nilsson  foreach(get_query_value("sw_lists"), string fn) {
8898352001-07-21Martin Nilsson  string file = Stdio.read_file(fn); if(!fn) report_error("Could not load %O.\n", fn); else words + (Array.flatten(map(file/"\n", lambda(string in) { return in/" "; }))-({""})); }
47d8e92001-08-19Martin Nilsson  words + (Array.flatten(map(get_query_value("sw_words")/"\n",
8898352001-07-21Martin Nilsson  lambda(string in) { return in/" "; }))-({""})); stop_words = (array)words; } return stop_words; } }
46a6522001-08-06Martin Nilsson //!
8898352001-07-21Martin Nilsson class ProfileCache (string db_name) {
f501ee2001-08-14Martin Nilsson  private mapping(string:ProfileEntry) entry_cache = ([]);
74de682001-07-13Martin Nilsson  private mapping(int:mapping(string:mixed)) value_cache = ([]); private mapping(string:int) db_profile_names = ([]);
47d8e92001-08-19Martin Nilsson  private mapping(string:int) query_profile_names = ([]);
8898352001-07-21Martin Nilsson  private mapping(int:int) profile_stat = ([]);
74de682001-07-13Martin Nilsson  private Sql.Sql get_db() {
f672612001-07-26Martin Nilsson  Sql.Sql db; #if constant(DBManager) db = DBManager.cached_get(db_name); #endif
74de682001-07-13Martin Nilsson  if(!db) THROW("Could not connect to database " + db_name + ".\n"); return db; }
46a6522001-08-06Martin Nilsson  //! Checks if the profile @[profile_id] has been changed, and clears //! related caches if so. //! @returns //! @int //! @value -1 //! The profile is deleted. //! @value 0
f501ee2001-08-14Martin Nilsson  //! The profile is not up to date.
46a6522001-08-06Martin Nilsson  //! @value 1
f501ee2001-08-14Martin Nilsson  //! The profile is up to date.
46a6522001-08-06Martin Nilsson  //! @endint
8898352001-07-21Martin Nilsson  int(-1..1) up_to_datep(int profile_id) {
3208782001-07-26Martin Nilsson  // werror("Called up-to-date...\n");
8898352001-07-21Martin Nilsson  array(mapping(string:string)) res;
2208892001-08-09Per Hedbor  res = get_db()->query("SELECT altered,type FROM wf_profile WHERE id=%d", profile_id);
8898352001-07-21Martin Nilsson  // The profile is deleted. In such a rare event we take the // trouble to review all our cached values. if(!sizeof(res)) { array(int) existing = (array(int))get_db()->query("SELECT id FROM wf_profile")->id; foreach(indices(value_cache), int id) if(!has_value(existing, id)) m_delete(value_cache, id);
f501ee2001-08-14Martin Nilsson  foreach(indices(entry_cache), string id) {
47d8e92001-08-19Martin Nilsson  int dbp, qp; sscanf(id, "%d:%d", dbp, qp);
f501ee2001-08-14Martin Nilsson  if(!has_value(existing, dbp)) m_delete(entry_cache, id);
47d8e92001-08-19Martin Nilsson  if(!has_value(existing, qp))
8898352001-07-21Martin Nilsson  m_delete(entry_cache, id);
f501ee2001-08-14Martin Nilsson  }
8898352001-07-21Martin Nilsson  foreach(indices(db_profile_names), string name) if(!has_value(existing, db_profile_names[name])) m_delete(db_profile_names, name);
47d8e92001-08-19Martin Nilsson  foreach(indices(query_profile_names), string name) if(!has_value(existing, query_profile_names[name])) m_delete(query_profile_names, name);
8898352001-07-21Martin Nilsson  return -1; } // Not altered if((int)res[0]->altered == profile_stat[profile_id]) return 1;
3208782001-07-26Martin Nilsson  profile_stat[profile_id] = (int)res[0]->altered;
8898352001-07-21Martin Nilsson 
47d8e92001-08-19Martin Nilsson  // Query profile
2208892001-08-09Per Hedbor  if((int)res[0]->type == 2) {
8898352001-07-21Martin Nilsson  m_delete(value_cache, profile_id);
f501ee2001-08-14Martin Nilsson  foreach(indices(entry_cache), string id) if(array_sscanf(id, "%d:%d")[1]==profile_id) m_delete(entry_cache, id);
8898352001-07-21Martin Nilsson  return 0; } m_delete(value_cache, profile_id);
f501ee2001-08-14Martin Nilsson  foreach(indices(entry_cache), string id) if(array_sscanf(id, "%d:%d")[0]==profile_id) m_delete(entry_cache, id);
8898352001-07-21Martin Nilsson  return 0; }
46a6522001-08-06Martin Nilsson  //! Returns the profile number for the given database profile.
74de682001-07-13Martin Nilsson  int get_db_profile_number(string name) { int db_profile; if(db_profile=db_profile_names[name]) return db_profile; array res = get_db()->
2208892001-08-09Per Hedbor  query("SELECT id FROM wf_profile WHERE name=%s AND type=2", name);
74de682001-07-13Martin Nilsson  if(!sizeof(res)) THROW("No database profile " + name + " found.\n"); return db_profile_names[name] = (int)res[0]->id; }
47d8e92001-08-19Martin Nilsson  //! Returns the profile number for the given query profile. int get_query_profile_number(string name)
2208892001-08-09Per Hedbor  {
47d8e92001-08-19Martin Nilsson  int query_profile; if( query_profile=query_profile_names[name] ) return query_profile;
74de682001-07-13Martin Nilsson  array res = get_db()->
2208892001-08-09Per Hedbor  query("SELECT id FROM wf_profile WHERE name=%s AND type=1", name);
74de682001-07-13Martin Nilsson  if(!sizeof(res))
47d8e92001-08-19Martin Nilsson  THROW("No query profile " + name + " found.\n");
74de682001-07-13Martin Nilsson 
47d8e92001-08-19Martin Nilsson  return query_profile_names[name] = (int)res[0]->id;
74de682001-07-13Martin Nilsson  }
23a6582001-07-31David Norlin  private int last_db_prof_stat = 0; // 1970
46a6522001-08-06Martin Nilsson  //! Returns a list of available database profiles.
23a6582001-07-31David Norlin  array(string) list_db_profiles() { if (time(1) - last_db_prof_stat < 5*60) return indices(db_profile_names);
2208892001-08-09Per Hedbor  array res = get_db()->query("SELECT name, id FROM wf_profile WHERE type=2");
23a6582001-07-31David Norlin  db_profile_names = mkmapping( res->name, map(res->id, lambda(string s) { return (int) s; } ));
fbf68c2001-08-16Martin Nilsson  if(sizeof(res)) last_db_prof_stat = time(1);
23a6582001-07-31David Norlin  return res->name; }
47d8e92001-08-19Martin Nilsson  private int last_query_prof_stat = 0; // 1970
46a6522001-08-06Martin Nilsson 
47d8e92001-08-19Martin Nilsson  //! Returns a list of available query profiles. array(string) list_query_profiles()
2208892001-08-09Per Hedbor  {
47d8e92001-08-19Martin Nilsson  if (time(1) - last_query_prof_stat >= 5*60) {
2208892001-08-09Per Hedbor  array res = get_db()->query("SELECT name, id FROM wf_profile WHERE type=1");
47d8e92001-08-19Martin Nilsson  query_profile_names = mkmapping( res->name, (array(int)) res->id ); if(sizeof(query_profile_names)) last_query_prof_stat = time(1);
23a6582001-07-31David Norlin  }
47d8e92001-08-19Martin Nilsson  return indices(query_profile_names);
23a6582001-07-31David Norlin  }
74de682001-07-13Martin Nilsson  // Used when decoding text encoded pike data types. private object compile_handler = class { mapping(string:mixed) get_default_module() { return ([ "aggregate_mapping":aggregate_mapping, "aggregate_multiset":aggregate_multiset, "aggregate":aggregate, "allocate":allocate, "this_program":0 ]); } mixed resolv(string id, void|string fn, void|string ch) { throw( ({ sprintf("Found symbol %O while trying to decode Roxen Search " "settings. The database is corrupt or has been " "tampered with.\n", id), backtrace() }) ); } }(); // Decodes a "readable" pike data type to its actual pike value. private mixed reacodec_decode(string str) { return compile_string("mixed foo=" + str + ";", 0, compile_handler)()->foo; }
46a6522001-08-06Martin Nilsson  //! Returns the value mapping for the given profile.
74de682001-07-13Martin Nilsson  mapping get_value_mapping(int profile) { mapping val; if(val=copy_value(value_cache[profile])) return val; array res = get_db()-> query("SELECT name,value FROM wf_value WHERE pid=%d", profile); val = mkmapping( res->name, map(res->value, reacodec_decode) ); value_cache[profile] = copy_value(val); return val; }
46a6522001-08-06Martin Nilsson  //! Returns a @[ProfileEntry] object with the states needed for //! commiting searches in the database profile @[db_name] with
47d8e92001-08-19Martin Nilsson  //! the rules from query profile @[query_name]. ProfileEntry get_profile_entry(string db_name, void|string query_name) {
3208782001-07-26Martin Nilsson  // werror("Entry: %O\n", indices(entry_cache)); // werror("Value: %O\n", indices(value_cache)); // werror("Stat : %O\n", profile_stat);
74de682001-07-13Martin Nilsson  int db = get_db_profile_number(db_name);
47d8e92001-08-19Martin Nilsson  int query = get_query_profile_number(query_name);
8898352001-07-21Martin Nilsson  ProfileEntry entry;
47d8e92001-08-19Martin Nilsson  if(entry=entry_cache[query +":"+ db]) {
f501ee2001-08-14Martin Nilsson  if(!entry->check_timeout()) return entry; if(up_to_datep(db) &&
47d8e92001-08-19Martin Nilsson  up_to_datep(query)) return entry;
f501ee2001-08-14Martin Nilsson  }
8898352001-07-21Martin Nilsson 
47d8e92001-08-19Martin Nilsson  entry = ProfileEntry( db, query, this_object() ); return entry_cache[query +":"+ db] = entry;
74de682001-07-13Martin Nilsson  }
46a6522001-08-06Martin Nilsson  //! Flushes profile entry @[p] from the profile cache.
74de682001-07-13Martin Nilsson  void flush_profile(int p) { m_delete(value_cache, p); foreach(indices(db_profile_names), string name) if(db_profile_names[name]==p) m_delete(db_profile_names, name);
47d8e92001-08-19Martin Nilsson  m_delete(query_profile_names, p);
f501ee2001-08-14Martin Nilsson  foreach(indices(entry_cache), string id) { array ids = array_sscanf(id, "%d:%d"); if(ids[0]==p || ids[1]==p) m_delete(entry_cache, id); }
74de682001-07-13Martin Nilsson  } //! Empty the whole cache. void flush_cache() { value_cache = ([]); db_profile_names = ([]);
47d8e92001-08-19Martin Nilsson  query_profile_names = ([]);
74de682001-07-13Martin Nilsson  } }
f501ee2001-08-14Martin Nilsson private mapping(string:ProfileCache) profile_cache_cache = ([]); ProfileCache get_profile_cache(string db_name) { if(profile_cache_cache[db_name]) return profile_cache_cache[db_name]; return profile_cache_cache[db_name] = ProfileCache(db_name); } void flush_profile(int p) { values(profile_cache_cache)->flush_profile(p); }
46a6522001-08-06Martin Nilsson //!
c88b0b2001-07-04Martin Nilsson class Logger { private string|Sql.Sql logdb; private int profile; private Sql.Sql get_db() { Sql.Sql db; #if constant(DBManager) if(stringp(logdb)) db = DBManager.get(logdb); else #endif db = logdb; // if(!logdb || !logdb->query) // throw( ({ "Couldn't find any database object.\n", backtrace() }) ); return db; }
46a6522001-08-06Martin Nilsson  //! @decl void create(Sql.Sql db_object, int profile) //! @decl void create(string db_url, int profile)
c88b0b2001-07-04Martin Nilsson  void create(string|Sql.Sql _logdb, int _profile) { logdb = _logdb; profile = _profile; // create table eventlog (event int unsigned auto_increment primary key, // at timestamp(14) not null, code int unsigned not null, extra varchar(255)) Sql.Sql db = get_db(); if(catch(db->query("SELECT code FROM eventlog WHERE event=0"))) db->query("CREATE TABLE eventlog (" "event int unsigned auto_increment primary key," "at timestamp(14) not null," "profile int unsigned not null," "code int unsigned not null," "type enum('error','warning','notice') not null," "extra varchar(255))"); }
46a6522001-08-06Martin Nilsson  //!
5cc7ba2001-07-06Martin Nilsson  void log_event( int code, string type, void|string extra, void|int log_profile ) {
c88b0b2001-07-04Martin Nilsson  Sql.Sql db = get_db(); if(!db) return;
5cc7ba2001-07-06Martin Nilsson  if(zero_type(log_profile)) log_profile = profile;
c88b0b2001-07-04Martin Nilsson  if(extra) db->query("INSERT INTO eventlog (profile,code,type,extra) VALUES (%d,%d,%s,%s)",
5cc7ba2001-07-06Martin Nilsson  log_profile, code, type, extra);
c88b0b2001-07-04Martin Nilsson  else db->query("INSERT INTO eventlog (profile, code,type) VALUES (%d,%d,%s)",
5cc7ba2001-07-06Martin Nilsson  log_profile, code, type);
c88b0b2001-07-04Martin Nilsson  }
46a6522001-08-06Martin Nilsson  //!
5cc7ba2001-07-06Martin Nilsson  void log_error( int code, void|string extra, void|int log_profile ) {
46a6522001-08-06Martin Nilsson  log_event( code, "error", extra, log_profile );
c88b0b2001-07-04Martin Nilsson  }
46a6522001-08-06Martin Nilsson  //!
5cc7ba2001-07-06Martin Nilsson  void log_warning( int code, void|string extra, void|int log_profile ) { log_event( code, "warning", extra, log_profile ); }
46a6522001-08-06Martin Nilsson  //!
5cc7ba2001-07-06Martin Nilsson  void log_notice( int code, void|string extra, void|int log_profile ) { log_event( code, "notice", extra, log_profile );
c88b0b2001-07-04Martin Nilsson  }
46a6522001-08-06Martin Nilsson  //!
5cc7ba2001-07-06Martin Nilsson  int add_program_name(int code, string name) { int add = search( ({ "multiprocess_crawler", "buffer_c2f", "filter", "buffer_f2i", "indexer" }), name ); if(add==-1) throw( ({ "Unknown program name \""+name+"\".\n", backtrace() }) ); return code + add;
c88b0b2001-07-04Martin Nilsson  } private mapping codes = ([
5cc7ba2001-07-06Martin Nilsson  10 : "Started crawler with %s.", 11 : "Started crawler-to-filter buffer with %s.", 12 : "Started filter with %s.", 13 : "Started filter-to-indexer buffer with %s.", 14 : "Started indexer with %s.", 20 : "Exiting crawler due to signal.", 21 : "Exiting crawler-to-filter buffer due to signal.", 22 : "Exiting filter due to signal.", 23 : "Exiting filter-to-indexer buffer due to signal.", 24 : "Exiting indexer due to signal.", 30 : "Crawler failed to set up pipe.", 31 : "Crawler-to-filter buffer failed to set up pipe.", 32 : "Filter failed to set up pipe.", 33 : "Filter-to-indexer buffer failed to set up pipe.", 34 : "Indexer failed to set up pipe.", 40 : "Fetched %s.", 41 : "Unknown language code \"%s\".", 42 : "Crawler exited normally.", 43 : "Cleared search database.",
3e1a302001-07-11Johan Schön  44 : "Sitebuilder commit triggered indexing of %s.",
5cc7ba2001-07-06Martin Nilsson  50 : "Crawler did not get any connection from the process.", 51 : "Crawler-to-filter bufferdid not get any connection from the process.", 52 : "Filter did not get any connection from the process.", 53 : "Filter-to-indexer buffer did not get any connection from the process.", 54 : "Indexer did not get any connection from the process.",
c253d42001-07-12Johan Schön 
26d75c2001-08-08Per Hedbor  60 : "Starting database compactor with %s", 61 : "Failed to find any data in the database.", 62 : "Exiting compacter due to signal.", 63 : "Done with database compacting and maintenance.",
c253d42001-07-12Johan Schön  300: "300 Redirection: Multiple Choices (%s)", 301: "301 Redirection: Moved Permanently (%s)", 302: "302 Redirection: Found (%s)", 303: "303 Redirection: See Other (%s)", 304: "304 Redirection: Not Modified (%s)", 305: "305 Redirection: Use Proxy (%s)", 306: "306 Redirection: (Unused) (%s)", 307: "307 Redirection: Temporary Redirect (%s)", 400: "400 Client Error: Bad Request (%s)", 401: "401 Client Error: Unauthorized (%s)", 402: "402 Client Error: Payment Required (%s)", 403: "403 Client Error: Forbidden (%s)", 404: "404 Client Error: Not Found (%s)", 405: "405 Client Error: Method Not Allowed (%s)", 406: "406 Client Error: Not Acceptable (%s)", 407: "407 Client Error: Proxy Authentication Required (%s)", 408: "408 Client Error: Request Timeout (%s)", 409: "409 Client Error: Conflict (%s)", 410: "410 Client Error: Gone (%s)", 411: "411 Client Error: Length Required (%s)", 412: "412 Client Error: Precondition Failed (%s)", 413: "413 Client Error: Request Entity Too Large (%s)", 414: "414 Client Error: Request-URI Too Long (%s)", 415: "415 Client Error: Unsupported Media Type (%s)", 416: "416 Client Error: Requested Range Not Satisfiable (%s)", 417: "417 Client Error: Expectation Failed (%s)", 500: "500 Server Error: Internal Server Error (%s)", 501: "501 Server Error: Not Implemented (%s)", 502: "502 Server Error: Bad Gateway (%s)", 503: "503 Server Error: Service Unavailable (%s)", 504: "504 Server Error: Gateway Timeout (%s)", 505: "505 Server Error: HTTP Version Not Supported (%s)",
ae89482001-08-13Anders Johansson  1000: "Disallowed by robots.txt. (%s)", 1001: "Can't handle scheme. (%s)",
c88b0b2001-07-04Martin Nilsson  ]);
c253d42001-07-12Johan Schön 
c88b0b2001-07-04Martin Nilsson 
46a6522001-08-06Martin Nilsson  //!
7d85aa2001-07-04Martin Nilsson  array(array(string|int)) get_log( int profile, array(string) types, int from, int to ) {
c88b0b2001-07-04Martin Nilsson  string sql = "";
5990902001-07-05Martin Nilsson #define SQLADD(X) do{sizeof(sql)?(sql+=" AND "+(X)):(sql=" WHERE "+(X));}while(0)
c88b0b2001-07-04Martin Nilsson  if(profile) SQLADD("profile=" + profile); if(!sizeof(types)) return ({}); if(sizeof(types)!=3) SQLADD("(type='" + (types*"' OR type='") + "')"); if(from) SQLADD("at>" + from); if(to) SQLADD("to<" + to); #undef SQLADD Sql.Sql db = get_db();
7d85aa2001-07-04Martin Nilsson  if(!db) return ({});
c88b0b2001-07-04Martin Nilsson 
2cd10c2001-07-05Martin Nilsson  return map(db->query("SELECT unix_timestamp(at) as at,profile,code,type,extra FROM eventlog" + sql + " ORDER BY event DESC"),
c88b0b2001-07-04Martin Nilsson  lambda(mapping in) {
7d85aa2001-07-04Martin Nilsson  return ({ (int)in->at, (int)in->profile, in->type,
2cd10c2001-07-05Martin Nilsson  in->extra?sprintf(codes[(int)in->code], @(in->extra/"\n")): codes[(int)in->code] });
c88b0b2001-07-04Martin Nilsson  } ); } }