5b75942013-09-21Henrik Grubbström (Grubba)  //! An SQL-based storage manager //!
3524712015-05-26Martin Nilsson //! This storage manager provides the means to save data to an SQL-based
5b75942013-09-21Henrik Grubbström (Grubba) //! backend. //! //! For now it's mysql only, dialectization will be added at a later time. //! Serialization should be taken care of by the low-level SQL drivers. //! //! @note //! An administrator is supposed to create the database and give //! the user enough privileges to write to it. It will be care //! of this driver to create the database tables itself. //! //! @thanks
0ab1892013-10-05Henrik Grubbström (Grubba) //! Thanks to Francesco Chemolli <kinkie@@roxen.com> for the contribution.
219a872000-08-07Francesco Chemolli 
a580e12000-09-27Fredrik Hübinette (Hubbe) #pike __REAL_VERSION__
a20af62000-09-26Fredrik Hübinette (Hubbe) 
3ddaac2014-08-25Per Hedbor inherit Cache.Storage.Base;
219a872000-08-07Francesco Chemolli #define MAX_KEY_SIZE "255" #define CREATION_QUERY "create table cache ( \ cachekey varchar(" MAX_KEY_SIZE ") not null primary key, \ atime timestamp, \ ctime timestamp, \
bb06d82001-01-20Francesco Chemolli etime timestamp, \
219a872000-08-07Francesco Chemolli cost float unsigned DEFAULT 1.0 NOT NULL, \
bb06d82001-01-20Francesco Chemolli data longblob NOT NULL, \ dependants longblob \
219a872000-08-07Francesco Chemolli )"
c5757f2003-04-29Martin Nilsson Sql.Sql db;
bb06d82001-01-20Francesco Chemolli int have_dependants=0;
219a872000-08-07Francesco Chemolli  #if 0 // set to 1 to enable debugging #ifdef debug #undef debug #endif // ifdef debug #define debug(X...) werror("Cache.Storage.mysql: "+X);werror("\n") #else // 1 #ifndef debug // if there's a clash, let's let it show #define debug(X...) /**/ #endif // ifndef debug #endif // 1
5b75942013-09-21Henrik Grubbström (Grubba) //! Database manipulation is done externally. This class only returns //! values, with some lazy decoding.
219a872000-08-07Francesco Chemolli class Data { inherit Cache.Data; private int _size; private string db_data; private mixed _data; void create (mapping q) { debug("instantiating data object from %O",q); db_data=q->data; _size=sizeof(db_data); atime=(int)q->atime; ctime=(int)q->ctime; etime=(int)q->etime; cost=(float)q->cost; } int size() { debug("object size requested"); return _size; } mixed data() { debug("data requested"); if (_data) return _data; debug("lazy-decoding data"); _data=decode_value(db_data); db_data=0; return _data; } } //does MySQL support multiple outstanding resultsets? //we'll know now. //Notice: this will fail miserably with Sybase, for instance. //Notice: can and will throw exceptions if anything fails.
c9aaf62017-09-04Henrik Grubbström (Grubba) private object(Sql.Result) enum_result;
219a872000-08-07Francesco Chemolli int(0..0)|string first() { debug("first()"); if (enum_result) destruct(enum_result); enum_result=db->big_query("select cachekey from cache"); return next(); } int(0..0)|string next() { debug("next()"); array res; if (!enum_result) return 0; if (res=enum_result->fetch_row()) return res[0]; enum_result=0; // enumeration finished
3524712015-05-26Martin Nilsson  return 0;
219a872000-08-07Francesco Chemolli }
bb06d82001-01-20Francesco Chemolli //unfortunately with MySQL we can't optimize much, //if we want to properly follow the dependencies chain. //also, we're taking the easy way out using encode_value rather than // SQL tables. With other databases it would be easier to use // SQL-related functions rather than doing stuff ourselves (and // performing plenty of queries). // I'll try to use a local cache to avoid multiple deletions for // the same value, since queries are expensive. It could prove // to be a problem for HUGE caches. Let's hope it won't happen.. void delete(string key, void|int(0..1) hard, void|multiset already_deleted) { multiset dependants=0;
3524712015-05-26Martin Nilsson 
bb06d82001-01-20Francesco Chemolli  debug("deleting %s\n",key); if (have_dependants) { if (already_deleted && already_deleted[key]) // already deleted. Skip return; mixed rv=db->query("select dependants from cache where cachekey='%s'",key); if (rv && sizeof(rv) && rv[0]->dependants) { // there are dependants dependants=decode_value(rv[0]->dependants); } if (!already_deleted) already_deleted=(<>); }
3524712015-05-26Martin Nilsson 
bb06d82001-01-20Francesco Chemolli  if (already_deleted) already_deleted[key]=1;
219a872000-08-07Francesco Chemolli  db->query("delete from cache where cachekey='%s'",key);
3524712015-05-26Martin Nilsson 
bb06d82001-01-20Francesco Chemolli  if (dependants) { foreach (indices(dependants),string dep) { werror("chain-deleting %s\n",dep); delete(dep); } werror("done chain-deleting\n"); }
219a872000-08-07Francesco Chemolli } void set(string key, mixed value,
bb06d82001-01-20Francesco Chemolli  void|int expire_time, void|float preciousness, void|multiset(string) dependants) {
219a872000-08-07Francesco Chemolli  debug("setting value for key %s (e: %d, v: %f",key,expire_time, preciousness?preciousness:1.0); db->query("delete from cache where cachekey='%s'",key);
bb06d82001-01-20Francesco Chemolli  db->query("insert into cache " "(cachekey,atime,ctime,etime,cost,data, dependants) " "values('%s',CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,%s,%s,'%s','%s')", key, (expire_time?"FROM_UNIXTIME("+expire_time+")":"0"),
219a872000-08-07Francesco Chemolli  (preciousness?(string)preciousness:"1"),
bb06d82001-01-20Francesco Chemolli  encode_value(value), (dependants?encode_value(dependants):"NULL") //BUG: this fails when there are no dependants. ); if (dependants) have_dependants=1;
219a872000-08-07Francesco Chemolli } int(0..0)|Cache.Data get(string key,void|int notouch) { debug("getting value for key %s (nt: %d)",key,notouch); array(mapping) result=0; mixed err=0; catch (result=db->query("select unix_timestamp(atime) as atime," "unix_timestamp(ctime) as ctime," "unix_timestamp(etime) as etime,cost,data " "from cache where cachekey='%s'",key)); if (!result || !sizeof(result)) return 0; if (!notouch) catch(db->query("update cache set atime=CURRENT_TIMESTAMP " "where cachekey='%s'",key)); return Data(result[0]); } void aget(string key, function(string,int(0..0)|Cache.Data:void) callback) { callback(key,get(key)); }
5b75942013-09-21Henrik Grubbström (Grubba) //!
219a872000-08-07Francesco Chemolli void create(string sql_url) { array result=0; mixed err=0;
c5757f2003-04-29Martin Nilsson  db=Sql.Sql(sql_url);
219a872000-08-07Francesco Chemolli  // used to determine whether there already is a DB here. err=catch(result=db->query("select stamp from cache_admin")); if (err || !sizeof(result)) { db->query(CREATION_QUERY); db->query("create table cache_admin (stamp integer primary key)"); db->query("insert into cache_admin values ('1')"); } }