Browse Source

added some neko.db classes

Nicolas Cannasse 19 years ago
parent
commit
e77942fd0d
4 changed files with 566 additions and 1 deletions
  1. 3 1
      std/haxe/ImportAll.hx
  2. 423 0
      std/neko/db/Manager.hx
  3. 66 0
      std/neko/db/Object.hx
  4. 74 0
      std/neko/db/Transaction.hx

+ 3 - 1
std/haxe/ImportAll.hx

@@ -121,6 +121,9 @@ import neko.Web;
 import neko.db.Mysql;
 import neko.db.Connection;
 import neko.db.ResultSet;
+import neko.db.Object;
+import neko.db.Manager;
+import neko.db.Loop;
 
 import tools.DocView;
 
@@ -150,7 +153,6 @@ import js.Lib;
 import js.Link;
 import js.Location;
 import js.Navigator;
-import js.Object;
 import js.Option;
 import js.Password;
 import js.Radio;

+ 423 - 0
std/neko/db/Manager.hx

@@ -0,0 +1,423 @@
+/*
+ * 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;
+
+class Manager<T : Object> {
+
+	/* ----------------------------- STATICS ------------------------------ */
+	public static var cnx : Connection = null;
+	public static var init_list : List<Manager<Object>> = new List();
+	public static var object_cache : Hash<Object> = new Hash();
+	private static var cache_field = "__cache__";
+
+	/* ---------------------------- BASIC API ----------------------------- */
+	var table_name : String;
+	var table_fields : List<String>;
+	var table_keys : Array<String>;
+	var class_proto : Class;
+
+	public function new( cl : Dynamic ) {
+		// get basic infos
+		table_name = if( cl.TABLE_NAME != null ) cl.TABLE_NAME else cl.__name__.join("_");
+		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;
+		if( apriv == null ) apriv = new Array();
+		apriv.push("local_manager");
+		apriv.push("__class__");
+
+		// get the proto fields not marked private (excluding methods)
+		table_fields = new List();
+		var proto = 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 ) : T {
+		return getGeneric(id,true);
+	}
+
+	public function getReadOnly( id : Int ) : T {
+		return getGeneric(id,false);
+	}
+
+	public function getWithKeys( keys : {} ) : T {
+		var s = new StringBuf();
+		s.add("SELECT * FROM ");
+		s.add(table_name);
+		s.add(" WHERE ");
+		addKeys(s,keys);
+		s.add(" FOR UPDATE");
+		return object(s.toString(),true);
+	}
+
+	public function search( x : {}, lock : Bool ) : List<T> {
+		var s = new StringBuf();
+		s.add("SELECT * FROM ");
+		s.add(table_name);
+		s.add(" WHERE ");
+		var first = true;
+		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( lock )
+			s.add("FOR UPDATE");
+		return objects(s.toString(),lock);
+	}
+
+	public function count() : Int {
+		return execute("SELECT COUNT(*) FROM "+table_name).getIntResult(0);
+	}
+
+	public function quote( s : String ) : String {
+		return "'" + cnx.escape( s ) + "'";
+	}
+
+	public function result( sql : String ) : Dynamic {
+		return cnx.request(sql).next();
+	}
+
+	public function results( sql : String ) : List<Dynamic> {
+		return cnx.request(sql).results();
+	}
+
+	/* -------------------------- SPODOBJECT API -------------------------- */
+
+	public 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],lastInsertId());
+		addToCache(x);
+	}
+
+	public 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());
+	}
+
+	public 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());
+	}
+
+
+	public function doSync( i : T ) {
+		object_cache.remove(makeCacheKey(i));
+		var i2 = getWithKeys(i);
+		// set all fields to null
+		for( f in Reflect.fields(i) )
+			Reflect.setField(i,f,null);
+		// 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);
+	}
+
+	public 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 makeObject( x : T, lock : Bool ) {
+		var x2 = getFromCache(x);
+		if( x2 != null )
+			return x2;
+		addToCache(x);
+		untyped __dollar__objsetproto(x,class_proto.prototype);
+		Reflect.setField(x,cache_field,untyped __dollar__new(x));
+		if( !lock )
+			x.update = function() { throw "Cannot update not locked object"; }
+		return x;
+	}
+
+	function make( x : T ) {
+	}
+
+	function unmake( x : T ) {
+	}
+
+	function quoteField(f : String) {
+		if( f == "read" || f == "desc" || f == "out" )
+			return "`"+f+"`";
+		return f;
+	}
+
+	function addQuote( s : StringBuf, v : Dynamic ) {
+		var t = untyped __dollar__typeof(v);
+		if( untyped (t == __dollar__tint || t == __dollar__tnull || t == __dollar__tbool) )
+			s.add(v);
+		else {
+			s.add("'");
+			s.add(cnx.escape(Std.string(v)));
+			s.add("'");
+		}
+	}
+
+	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 lastInsertId() : Int {
+		return execute("SELECT LAST_INSERT_ID()").getIntResult(0);
+	}
+
+	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();
+	}
+
+	function getGeneric( id : Int, lock : Bool ) : T {
+		if( table_keys.length != 1 )
+			throw "Invalid number of keys";
+		if( id == null )
+			return null;
+		var x : T = untyped object_cache.get(id + table_name);
+		if( x != null )
+			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);
+	}
+
+	function object( sql : String, lock : Bool ) : T {
+		var r = cnx.request(sql).next();
+		if( r == null )
+			return null;
+		r = makeObject(r,lock);
+		make(r);
+		return r;
+	}
+
+	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 ) {
+			x = makeObject(x,lock);
+			make(x);
+			l2.add(x);
+		}
+		return l2;
+	}
+
+	public function __initRelation(r : { prop : String, key : String, manager : Manager<Object> } ) {
+		// setup getter/setter
+		var manager = r.manager;
+		var hprop = r.prop;
+		var hkey = r.key;
+		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));
+			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 ) : T {
+		return untyped object_cache.get(makeCacheKey(x));
+	}
+
+}

+ 66 - 0
std/neko/db/Object.hx

@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+class Object {
+
+/*
+	(optional)
+	static var TABLE_NAME = "TableName";
+	static var TABLE_ID = ["id"];
+	static var PRIVATE_FIELDS = ["my_priv_field"];
+	static function RELATIONS() {
+		return [{ key : "uid", prop : "user", manager : User.manager }];
+	}
+
+	static var manager = new neko.db.Manager();
+*/
+
+	var local_manager : neko.db.Manager<neko.db.Object>;
+
+	public function new() {
+	}
+
+	public function insert() {
+		local_manager.doInsert(this);
+	}
+
+	public function update() {
+		local_manager.doUpdate(this);
+	}
+
+	public function sync() {
+		local_manager.doSync(this);
+	}
+
+	public function delete() {
+		local_manager.doDelete(this);
+	}
+
+	public function toString() {
+		return local_manager.objectToString(this);
+	}
+
+}

+ 74 - 0
std/neko/db/Transaction.hx

@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+class Loop {
+
+	private static function initialize() {
+		var l = Manager.init_list;
+		Manager.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);
+		}
+	}
+
+	private static function isDeadlock(e) {
+		return false;
+	}
+
+	private static function runMainLoop(mainFun,logError,count) {
+		try {
+			mainFun();
+		} catch( e : Dynamic ) {
+			if( count > 0 && isDeadlock(e) ) {
+				Manager.cnx.request("ROLLBACK"); // should be already done, but in case...
+				Manager.cnx.request("START TRANSACTION");
+				runMainLoop(mainFun,logError,count-1);
+				return;
+			}
+			if( logError == null ) {
+				Manager.cnx.request("ROLLBACK");
+				neko.Lib.rethrow(e);
+			}
+			logError(e); // should ROLLBACK if needed
+		}
+	}
+
+	public static function main( dbparams, dbname, mainFun : Void -> Void, logError : Dynamic -> Void ) {
+		initialize();
+		Manager.cnx = Mysql.connect(dbparams);
+		Manager.cnx.selectDB(dbname);
+		Manager.cnx.request("START TRANSACTION");
+		runMainLoop(mainFun,logError,3);
+		Manager.cnx.request("COMMIT");
+		Manager.cnx.close();
+		Manager.cnx = null;
+		Manager.object_cache = new Hash();
+	}
+
+}