123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- /*
- * Copyright (c) 2005, The haXe Project Contributors
- * All rights reserved.
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
- */
- package neko.db;
- import Reflect;
- import neko.db.Connection;
- /**
- SPOD Manager : the persistent object database manager. See the tutorial on
- haXe website to learn how to use SPOD.
- **/
- class Manager<T : Object> {
- /* ----------------------------- STATICS ------------------------------ */
- public static var cnx(default,setConnection) : Connection;
- private static var object_cache : Hash<Object> = new Hash();
- private static var init_list : List<Manager<Object>> = new List();
- private static var cache_field = "__cache__";
- private static var no_update : Dynamic = function() { throw "Cannot update not locked object"; }
- private static var FOR_UPDATE = "";
- private static var KEYWORDS = {
- var h = new Hash();
- for( k in ["read","write","desc","out","group","version","option",
- "primary","exists","from","key","keys","limit","lock","use"] )
- h.set(k,true);
- h;
- }
- private static function setConnection( c : Connection ) {
- Reflect.setField(Manager,"cnx",c);
- if( c != null )
- FOR_UPDATE = if( c.dbName() == "MySQL" ) " FOR UPDATE" else "";
- return c;
- }
- /* ---------------------------- BASIC API ----------------------------- */
- var table_name : String;
- var table_fields : List<String>;
- var table_keys : Array<String>;
- var class_proto : { prototype : Dynamic };
- public function new( classval : Class<neko.db.Object> ) {
- var cl : Dynamic = classval;
- // get basic infos
- table_name = quoteField(if( cl.TABLE_NAME != null ) cl.TABLE_NAME else cl.__name__[cl.__name__.length-1]);
- table_keys = if( cl.TABLE_IDS != null ) cl.TABLE_IDS else ["id"];
- class_proto = cl;
- // get the list of private fields
- var apriv : Array<String> = cl.PRIVATE_FIELDS;
- apriv = if( apriv == null ) new Array() else apriv.copy();
- apriv.push("local_manager");
- apriv.push("__class__");
- // get the proto fields not marked private (excluding methods)
- table_fields = new List();
- var proto : { local_manager : neko.db.Manager<T> } = class_proto.prototype;
- for( f in Reflect.fields(proto) ) {
- var isfield = !Reflect.isFunction(Reflect.field(proto,f));
- if( isfield )
- for( f2 in apriv )
- if( f == f2 ) {
- isfield = false;
- break;
- }
- if( isfield )
- table_fields.add(f);
- }
- // set the manager and ready for further init
- proto.local_manager = this;
- init_list.add(untyped this);
- }
- public function get( id : Int, ?lock : Bool ) : T {
- if( lock == null )
- lock = true;
- if( table_keys.length != 1 )
- throw "Invalid number of keys";
- if( id == null )
- return null;
- var x : Dynamic = untyped object_cache.get(id + table_name);
- if( x != null && (!lock || x.update != no_update) )
- return x;
- var s = new StringBuf();
- s.add("SELECT * FROM ");
- s.add(table_name);
- s.add(" WHERE ");
- s.add(quoteField(table_keys[0]));
- s.add(" = ");
- addQuote(s,id);
- if( lock )
- s.add(FOR_UPDATE);
- return object(s.toString(),lock);
- }
- public function getWithKeys( keys : {}, ?lock : Bool ) : T {
- if( lock == null )
- lock = true;
- var x : Dynamic = getFromCache(untyped keys,false);
- if( x != null && (!lock || x.update != no_update) )
- return x;
- var s = new StringBuf();
- s.add("SELECT * FROM ");
- s.add(table_name);
- s.add(" WHERE ");
- addKeys(s,keys);
- if( lock )
- s.add(FOR_UPDATE);
- return object(s.toString(),lock);
- }
- public function delete( x : {} ) {
- var s = new StringBuf();
- s.add("DELETE FROM ");
- s.add(table_name);
- s.add(" WHERE ");
- addCondition(s,x);
- execute(s.toString());
- }
- public function search( x : {}, ?lock : Bool ) : List<T> {
- if( lock == null )
- lock = true;
- var s = new StringBuf();
- s.add("SELECT * FROM ");
- s.add(table_name);
- s.add(" WHERE ");
- addCondition(s,x);
- if( lock )
- s.add(FOR_UPDATE);
- return objects(s.toString(),lock);
- }
- function addCondition(s : StringBuf,x) {
- var first = true;
- if( x != null )
- for( f in Reflect.fields(x) ) {
- if( first )
- first = false;
- else
- s.add(" AND ");
- s.add(quoteField(f));
- var d = Reflect.field(x,f);
- if( d == null )
- s.add(" IS NULL");
- else {
- s.add(" = ");
- addQuote(s,d);
- }
- }
- if( first )
- s.add("1");
- }
- public function all( ?lock: Bool ) : List<T> {
- if( lock == null )
- lock = true;
- return objects("SELECT * FROM " + table_name + if( lock ) FOR_UPDATE else "",lock);
- }
- public function count( ?x : {} ) : Int {
- var s = new StringBuf();
- s.add("SELECT COUNT(*) FROM ");
- s.add(table_name);
- s.add(" WHERE ");
- addCondition(s,x);
- return execute(s.toString()).getIntResult(0);
- }
- public function quote( s : String ) : String {
- return cnx.quote( s );
- }
- public function result( sql : String ) : Dynamic {
- return cnx.request(sql).next();
- }
- public function results<T>( sql : String ) : List<T> {
- return cast cnx.request(sql).results();
- }
- /* -------------------------- SPODOBJECT API -------------------------- */
- function doInsert( x : T ) {
- unmake(x);
- var s = new StringBuf();
- var fields = new List();
- var values = new List();
- for( f in table_fields ) {
- var v = Reflect.field(x,f);
- if( v != null ) {
- fields.add(quoteField(f));
- values.add(v);
- }
- }
- s.add("INSERT INTO ");
- s.add(table_name);
- s.add(" (");
- s.add(fields.join(","));
- s.add(") VALUES (");
- var first = true;
- for( v in values ) {
- if( first )
- first = false;
- else
- s.add(", ");
- addQuote(s,v);
- }
- s.add(")");
- execute(s.toString());
- // table with one key not defined : suppose autoincrement
- if( table_keys.length == 1 && Reflect.field(x,table_keys[0]) == null )
- Reflect.setField(x,table_keys[0],cnx.lastInsertId());
- addToCache(x);
- }
- function doUpdate( x : T ) {
- unmake(x);
- var s = new StringBuf();
- s.add("UPDATE ");
- s.add(table_name);
- s.add(" SET ");
- var cache = Reflect.field(x,cache_field);
- var mod = false;
- for( f in table_fields ) {
- var v = Reflect.field(x,f);
- var vc = Reflect.field(cache,f);
- if( v != vc ) {
- if( mod )
- s.add(", ");
- else
- mod = true;
- s.add(quoteField(f));
- s.add(" = ");
- addQuote(s,v);
- Reflect.setField(cache,f,v);
- }
- }
- if( !mod )
- return;
- s.add(" WHERE ");
- addKeys(s,x);
- execute(s.toString());
- }
- function doDelete( x : T ) {
- var s = new StringBuf();
- s.add("DELETE FROM ");
- s.add(table_name);
- s.add(" WHERE ");
- addKeys(s,x);
- execute(s.toString());
- }
- function doSync( i : T ) {
- object_cache.remove(makeCacheKey(i));
- var i2 = getWithKeys(i,(cast i).update != no_update);
- // delete all fields
- for( f in Reflect.fields(i) )
- Reflect.deleteField(i,f);
- // copy fields from new object
- for( f in Reflect.fields(i2) )
- Reflect.setField(i,f,Reflect.field(i2,f));
- // set same field-cache
- Reflect.setField(i,cache_field,Reflect.field(i2,cache_field));
- addToCache(i);
- }
- function objectToString( it : T ) : String {
- var s = new StringBuf();
- s.add(table_name);
- if( table_keys.length == 1 ) {
- s.add("#");
- s.add(Reflect.field(it,table_keys[0]));
- } else {
- s.add("(");
- var first = true;
- for( f in table_keys ) {
- if( first )
- first = false;
- else
- s.add(",");
- s.add(quoteField(f));
- s.add(":");
- s.add(Reflect.field(it,f));
- }
- s.add(")");
- }
- return s.toString();
- }
- /* ---------------------------- INTERNAL API -------------------------- */
- function cacheObject( x : T, lock : Bool ) {
- addToCache(x);
- untyped __dollar__objsetproto(x,class_proto.prototype);
- Reflect.setField(x,cache_field,untyped __dollar__new(x));
- if( !lock )
- x.update = no_update;
- }
- function make( x : T ) {
- }
- function unmake( x : T ) {
- }
- function quoteField(f : String) {
- return KEYWORDS.exists(f.toLowerCase()) ? "`"+f+"`" : f;
- }
- function addQuote( s : StringBuf, v : Dynamic ) {
- var t = untyped __dollar__typeof(v);
- if( untyped (t == __dollar__tint || t == __dollar__tnull) )
- s.add(v);
- else if( untyped t == __dollar__tbool )
- s.add(if( v ) 1 else 0);
- else
- s.add(cnx.quote(Std.string(v)));
- }
- function addKeys( s : StringBuf, x : {} ) {
- var first = true;
- for( k in table_keys ) {
- if( first )
- first = false;
- else
- s.add(" AND ");
- s.add(quoteField(k));
- s.add(" = ");
- var f = Reflect.field(x,k);
- if( f == null )
- throw ("Missing key "+k);
- addQuote(s,f);
- }
- }
- function execute( sql : String ) {
- return cnx.request(sql);
- }
- function select( cond : String ) {
- var s = new StringBuf();
- s.add("SELECT * FROM ");
- s.add(table_name);
- s.add(" WHERE ");
- s.add(cond);
- s.add(FOR_UPDATE);
- return s.toString();
- }
- function selectReadOnly( cond : String ) {
- var s = new StringBuf();
- s.add("SELECT * FROM ");
- s.add(table_name);
- s.add(" WHERE ");
- s.add(cond);
- return s.toString();
- }
- public function object( sql : String, lock : Bool ) : T {
- var r = cnx.request(sql).next();
- if( r == null )
- return null;
- var c = getFromCache(r,lock);
- if( c != null )
- return c;
- cacheObject(r,lock);
- make(r);
- return r;
- }
- public function objects( sql : String, lock : Bool ) : List<T> {
- var me = this;
- var l = cnx.request(sql).results();
- var l2 = new List<T>();
- for( x in l ) {
- var c = getFromCache(x,lock);
- if( c != null )
- l2.add(c);
- else {
- cacheObject(x,lock);
- make(x);
- l2.add(x);
- }
- }
- return l2;
- }
- public function dbClass() : Class<Dynamic> {
- return cast class_proto;
- }
- /* --------------------------- INIT / CLEANUP ------------------------- */
- public static function initialize() {
- var l = init_list;
- init_list = new List();
- for( m in l ) {
- var rl : Void -> Array<Dynamic> = untyped m.class_proto.RELATIONS;
- if( rl != null )
- for( r in rl() )
- m.initRelation(r);
- }
- }
- public static function cleanup() {
- object_cache = new Hash();
- }
- function initRelation(r : { prop : String, key : String, manager : Manager<Object>, lock : Bool } ) {
- // setup getter/setter
- var manager = r.manager;
- var hprop = "__"+r.prop;
- var hkey = r.key;
- var lock = r.lock;
- if( lock == null ) lock = true;
- if( manager == null || manager.table_keys == null ) throw ("Invalid manager for relation "+table_name+":"+r.prop);
- if( manager.table_keys.length != 1 ) throw ("Relation "+r.prop+"("+r.key+") on a multiple key table");
- Reflect.setField(class_proto.prototype,"get_"+r.prop,function() {
- var othis = untyped this;
- var f = Reflect.field(othis,hprop);
- if( f != null )
- return f;
- f = manager.get(Reflect.field(othis,hkey),lock);
- Reflect.setField(othis,hprop,f);
- return f;
- });
- Reflect.setField(class_proto.prototype,"set_"+r.prop,function(f) {
- var othis = untyped this;
- Reflect.setField(othis,hprop,f);
- Reflect.setField(othis,hkey,Reflect.field(f,manager.table_keys[0]));
- return f;
- });
- // remove prop from precomputed table_fields
- // always add key to table fields (even if not declared)
- table_fields.remove(r.prop);
- table_fields.remove(r.key);
- table_fields.add(r.key);
- }
- /* ---------------------------- OBJECT CACHE -------------------------- */
- function makeCacheKey( x : T ) : String {
- if( table_keys.length == 1 ) {
- var k = Reflect.field(x,table_keys[0]);
- if( k == null )
- throw("Missing key "+table_keys[0]);
- return Std.string(k)+table_name;
- }
- var s = new StringBuf();
- for( k in table_keys ) {
- var v = Reflect.field(x,k);
- if( k == null )
- throw("Missing key "+k);
- s.add(v);
- s.add("#");
- }
- s.add(table_name);
- return s.toString();
- }
- function addToCache( x : T ) {
- object_cache.set(makeCacheKey(x),x);
- }
- function getFromCache( x : T, lock : Bool ) : T {
- var c : Dynamic = object_cache.get(makeCacheKey(x));
- // restore update method since now the object is locked
- if( c != null && lock && c.update == no_update )
- c.update = class_proto.prototype.update;
- return c;
- }
- }
|