瀏覽代碼

added storage of arbitrary serialized data in SPOD

Nicolas Cannasse 13 年之前
父節點
當前提交
50a72ee013
共有 5 個文件被更改,包括 116 次插入12 次删除
  1. 1 0
      doc/CHANGES.txt
  2. 47 1
      std/sys/db/Manager.hx
  3. 1 0
      std/sys/db/SpodInfos.hx
  4. 65 11
      std/sys/db/SpodMacros.hx
  5. 2 0
      std/sys/db/Types.hx

+ 1 - 0
doc/CHANGES.txt

@@ -7,6 +7,7 @@
 	all : make property type optional (when a initial value is set)
 	all : new post-compilation DCE
 	all : Std.random(x) when x <= 0 is now always 0
+	spod : added serialized data with SData<T>
 
 2012-07-16: 2.10
 	java/cs : added two new targets (beta)

+ 47 - 1
std/sys/db/Manager.hx

@@ -119,6 +119,14 @@ class Manager<T : Object> {
 
 	/* -------------------------- SPODOBJECT API -------------------------- */
 
+	function doUpdateCache( x : T, name : String ) {
+		var cache : { v : Dynamic, m : Bool } = Reflect.field(x, "cache_" + name);
+		var v = doSerialize(name, cache.v);
+		Reflect.setField(x, name, v);
+		cache.m = false;
+		return v;
+	}
+	
 	function doInsert( x : T ) {
 		unmake(x);
 		var s = new StringBuf();
@@ -129,6 +137,10 @@ class Manager<T : Object> {
 			var v = Reflect.field(x,name);
 			if( v != null ) {
 				fields.add(quoteField(name));
+				switch( f.t ) {
+				case DData: v = doUpdateCache(x, name);
+				default:
+				}
 				values.add(v);
 			} else if( !f.isNull ) {
 				// if the field is not defined, give it a default value on insert
@@ -143,7 +155,7 @@ class Manager<T : Object> {
 					Reflect.setField(x, name, haxe.io.Bytes.alloc(0));
 				case DDate, DDateTime, DTimeStamp:
 					// default date might depend on database
-				case DId, DUId, DBigId, DNull, DInterval, DEncoded:
+				case DId, DUId, DBigId, DNull, DInterval, DEncoded, DData:
 					// no default value for these
 				}
 			}
@@ -173,6 +185,7 @@ class Manager<T : Object> {
 	inline function isBinary( t : SpodInfos.SpodType ) {
 		return switch( t ) {
 			case DSmallBinary, DNekoSerialized, DLongBinary, DBytes(_), DBinary: true;
+			//case DData: true // -- disabled for implementation purposes
 			default: false;
 		};
 	}
@@ -196,6 +209,13 @@ class Manager<T : Object> {
 			var v : Dynamic = Reflect.field(x,name);
 			var vc : Dynamic = Reflect.field(cache,name);
 			if( v != vc && (!isBinary(f.t) || hasBinaryChanged(v,vc)) ) {
+				switch( f.t ) {
+				case DData:
+					v = doUpdateCache(x, name);
+					if( !hasBinaryChanged(v,vc) )
+						continue;
+				default:
+				}
 				if( mod )
 					s.add(", ");
 				else
@@ -259,6 +279,32 @@ class Manager<T : Object> {
 		return s.toString();
 	}
 
+	function doSerialize( field : String, v : Dynamic ) : haxe.io.Bytes {
+		var s = new haxe.Serializer();
+		s.useEnumIndex = true;
+		s.serialize(v);
+		var str = s.toString();
+		#if neko
+		return neko.Lib.bytesReference(str);
+		#else
+		return haxe.io.Bytes.ofString(str);
+		#end
+	}
+	
+	function doUnserialize( field : String, b : haxe.io.Bytes ) : Dynamic {
+		if( b == null )
+			return null;
+		var str;
+		#if neko
+		str = neko.Lib.stringReference(b);
+		#else
+		str = b.toString();
+		#end
+		if( str == "" )
+			return null;
+		return haxe.Unserializer.run(str);
+	}
+	
 	/* ---------------------------- INTERNAL API -------------------------- */
 
 	function cacheObject( x : T, lock : Bool ) {

+ 1 - 0
std/sys/db/SpodInfos.hx

@@ -55,6 +55,7 @@ enum SpodType {
 	DSmallUInt;
 	DMediumInt;
 	DMediumUInt;
+	DData;
 	// specific for intermediate calculus
 	DInterval;
 	DNull;

+ 65 - 11
std/sys/db/SpodMacros.hx

@@ -105,9 +105,9 @@ class SpodMacros {
 		#end
 	}
 
-	public dynamic function follow( t : haxe.macro.Type ) : haxe.macro.Type {
+	public dynamic function follow( t : haxe.macro.Type, ?once ) : haxe.macro.Type {
 		#if macro
-		return Context.follow(t);
+		return Context.follow(t,once);
 		#else
 		throw "not implemented";
 		return null;
@@ -185,16 +185,17 @@ class SpodMacros {
 			case "String": DText;
 			case "Date": DDateTime;
 			case "haxe.io.Bytes": DBinary;
-			default: throw "Unsupported " + name;
+			default: throw "Unsupported SPOD Type " + name;
 			}
 		case TEnum(e, p):
 			var name = e.toString();
 			return switch( name ) {
 			case "Bool": DBool;
-			default: throw "Unsupported " + name;
+			default:
+				throw "Unsupported SPOD Type " + name + " (enums must be wrapped with SData<E>)";
 			}
-		case TType(t, p):
-			var name = t.toString();
+		case TType(td, p):
+			var name = td.toString();
 			if( StringTools.startsWith(name, "sys.db.") )
 				name = name.substr(7);
 			var k = g.types.get(name);
@@ -206,12 +207,13 @@ class SpodMacros {
 				case "SNull", "Null": isNull = true; return makeType(p[0]);
 				case "SFlags": return DFlags(getFlags(p[0]),false);
 				case "SSmallFlags": return DFlags(getFlags(p[0]),true);
+				case "SData": return DData;
 				default:
 				}
-			throw "Unsupported " + name;
+			return makeType(follow(t, true));
 		default:
 		}
-		throw "Unsupported " + Std.string(t);
+		throw "Unsupported SPOD Type " + Std.string(t);
 	}
 
 	function makeIdent( e : Expr ) {
@@ -295,7 +297,8 @@ class SpodMacros {
 				}
 				switch( g ) {
 				case AccCall(_):
-					error("Relation should be defined with @:relation(key)", f.pos);
+					if( !f.meta.has(":data") )
+						error("Relation should be defined with @:relation(key)", f.pos);
 				default:
 				}
 			}
@@ -426,7 +429,7 @@ class SpodMacros {
 		case DBool: 2;
 		case DString(_), DTinyText, DSmallText, DText, DSerialized: 3;
 		case DDate, DDateTime, DTimeStamp: 4;
-		case DSmallBinary, DLongBinary, DBinary, DBytes(_), DNekoSerialized: 5;
+		case DSmallBinary, DLongBinary, DBinary, DBytes(_), DNekoSerialized, DData: 5;
 		case DInterval: 6;
 		case DNull: 7;
 		};
@@ -1108,14 +1111,19 @@ class SpodMacros {
 		var fields = Context.getBuildFields();
 		var hasManager = false;
 		for( f in fields ) {
+			var skip = false;
 			if( f.name == "manager") hasManager = true;
 			for( m in f.meta )
-				if( m.name == ":relation" ) {
+				switch( m.name ) {
+				case ":skip":
+					skip = true;
+				case ":relation":
 					switch( f.kind ) {
 					case FVar(t, _):
 						f.kind = FProp("dynamic", "dynamic", t);
 						if( isNeko )
 							continue;
+						// create compile-time getter/setter for other platforms
 						var relKey = null;
 						var relParams = [];
 						var lock = false;
@@ -1181,7 +1189,53 @@ class SpodMacros {
 						Context.error("Invalid relation field type", f.pos);
 					}
 					break;
+				default:
 				}
+			if( skip )
+				continue;
+			switch( f.kind ) {
+			case FVar(t, _):
+				if( t != null )
+					switch( t ) {
+					case TPath(p):
+						if( p.name == "SData" && p.params.length == 1 ) {
+							f.kind = FProp("dynamic", "dynamic", t, null);
+
+							var t = switch( p.params[0] ) {
+							case TPExpr(_): continue;
+							case TPType(t): t;
+							};
+							var pos = f.pos;
+							f.meta.push( { name : ":data", params : [], pos : f.pos } );
+							var meta = [ { name : ":hide", params : [], pos : pos } ];
+							var cache = "cache_" + f.name;
+							var ecache = { expr : EConst(CIdent(cache)), pos : pos };
+							var efield = { expr : EConst(CIdent(f.name)), pos : pos };
+							var fname = { expr : EConst(CString(f.name)), pos : pos };
+							// note : we need to store the data in the same field, which is typed as t while it is actually a haxe.io.Bytes
+							// this might cause some issues with static platforms.
+							// In that case maybe a special handling of SData field compilation to haxe.io.Bytes will be necessary
+							var get = {
+								args : [],
+								params : [],
+								ret : t,
+								expr : macro { if( $ecache == null ) $ecache = { v : untyped manager.doUnserialize($fname,cast $efield), m : false }; return $ecache.v; },
+							};
+							var set = {
+								args : [{ name : "_v", opt : false, type : t, value : null }],
+								params : [],
+								ret : t,
+								// set efield to an empty object to make sure it will be != from previous value when insert/update is triggered
+								expr : macro { if( $ecache == null || !$ecache.m ) { $ecache = { v : _v, m : true }; $efield = cast { }; } else $ecache.v = _v; return _v; },
+							};
+							fields.push( { name : cache, pos : pos, meta : [meta[0], { name:":skip", params:[], pos:pos } ], access : [APrivate], doc : null, kind : FVar(macro : { v : $t, m : Bool }, null) } );
+							fields.push( { name : "get_" + f.name, pos : pos, meta : meta, access : [APrivate], doc : null, kind : FFun(get) } );
+							fields.push( { name : "set_" + f.name, pos : pos, meta : meta, access : [APrivate], doc : null, kind : FFun(set) } );
+						}
+					default:
+					}
+			default:
+			}
 		}
 		if( !hasManager ) {
 			var inst = Context.getLocalClass().get();

+ 2 - 0
std/sys/db/Types.hx

@@ -121,3 +121,5 @@ typedef SFlags<T:EnumValue> = haxe.EnumFlags<T>
 /** same as [SFlags] but will adapt the storage size to the number of flags **/
 typedef SSmallFlags<T:EnumValue> = SFlags<T>;
 
+/** allow to store any value in serialized form **/
+typedef SData<T> = T