Manager.hx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. /*
  2. * Copyright (C)2005-2012 Haxe Foundation
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a
  5. * copy of this software and associated documentation files (the "Software"),
  6. * to deal in the Software without restriction, including without limitation
  7. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. * and/or sell copies of the Software, and to permit persons to whom the
  9. * Software is furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. * DEALINGS IN THE SOFTWARE.
  21. */
  22. package sys.db;
  23. import Reflect;
  24. import sys.db.Connection;
  25. /**
  26. Record Manager : the persistent object database manager. See the tutorial on
  27. Haxe website to learn how to use Record.
  28. **/
  29. #if !macro @:build(sys.db.RecordMacros.addRtti()) #end
  30. class Manager<T : Object> {
  31. /* ----------------------------- STATICS ------------------------------ */
  32. public static var cnx(default, set) : Connection;
  33. public static var lockMode : String;
  34. private static inline var cache_field = "__cache__";
  35. private static var object_cache : haxe.ds.StringMap<Object> = new haxe.ds.StringMap();
  36. private static var init_list : List<Manager<Dynamic>> = new List();
  37. private static var KEYWORDS = {
  38. var h = new haxe.ds.StringMap();
  39. for( k in "ADD|ALL|ALTER|ANALYZE|AND|AS|ASC|ASENSITIVE|BEFORE|BETWEEN|BIGINT|BINARY|BLOB|BOTH|BY|CALL|CASCADE|CASE|CHANGE|CHAR|CHARACTER|CHECK|COLLATE|COLUMN|CONDITION|CONSTRAINT|CONTINUE|CONVERT|CREATE|CROSS|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATABASE|DATABASES|DAY_HOUR|DAY_MICROSECOND|DAY_MINUTE|DAY_SECOND|DEC|DECIMAL|DECLARE|DEFAULT|DELAYED|DELETE|DESC|DESCRIBE|DETERMINISTIC|DISTINCT|DISTINCTROW|DIV|DOUBLE|DROP|DUAL|EACH|ELSE|ELSEIF|ENCLOSED|ESCAPED|EXISTS|EXIT|EXPLAIN|FALSE|FETCH|FLOAT|FLOAT4|FLOAT8|FOR|FORCE|FOREIGN|FROM|FULLTEXT|GRANT|GROUP|HAVING|HIGH_PRIORITY|HOUR_MICROSECOND|HOUR_MINUTE|HOUR_SECOND|IF|IGNORE|IN|INDEX|INFILE|INNER|INOUT|INSENSITIVE|INSERT|INT|INT1|INT2|INT3|INT4|INT8|INTEGER|INTERVAL|INTO|IS|ITERATE|JOIN|KEY|KEYS|KILL|LEADING|LEAVE|LEFT|LIKE|LIMIT|LINES|LOAD|LOCALTIME|LOCALTIMESTAMP|LOCK|LONG|LONGBLOB|LONGTEXT|LOOP|LOW_PRIORITY|MATCH|MEDIUMBLOB|MEDIUMINT|MEDIUMTEXT|MIDDLEINT|MINUTE_MICROSECOND|MINUTE_SECOND|MOD|MODIFIES|NATURAL|NOT|NO_WRITE_TO_BINLOG|NULL|NUMERIC|ON|OPTIMIZE|OPTION|OPTIONALLY|OR|ORDER|OUT|OUTER|OUTFILE|PRECISION|PRIMARY|PROCEDURE|PURGE|READ|READS|REAL|REFERENCES|REGEXP|RELEASE|RENAME|REPEAT|REPLACE|REQUIRE|RESTRICT|RETURN|REVOKE|RIGHT|RLIKE|SCHEMA|SCHEMAS|SECOND_MICROSECOND|SELECT|SENSITIVE|SEPARATOR|SET|SHOW|SMALLINT|SONAME|SPATIAL|SPECIFIC|SQL|SQLEXCEPTION|SQLSTATE|SQLWARNING|SQL_BIG_RESULT|SQL_CALC_FOUND_ROWS|SQL_SMALL_RESULT|SSL|STARTING|STRAIGHT_JOIN|TABLE|TERMINATED|THEN|TINYBLOB|TINYINT|TINYTEXT|TO|TRAILING|TRIGGER|TRUE|UNDO|UNION|UNIQUE|UNLOCK|UNSIGNED|UPDATE|USAGE|USE|USING|UTC_DATE|UTC_TIME|UTC_TIMESTAMP|VALUES|VARBINARY|VARCHAR|VARCHARACTER|VARYING|WHEN|WHERE|WHILE|WITH|WRITE|XOR|YEAR_MONTH|ZEROFILL|ASENSITIVE|CALL|CONDITION|CONNECTION|CONTINUE|CURSOR|DECLARE|DETERMINISTIC|EACH|ELSEIF|EXIT|FETCH|GOTO|INOUT|INSENSITIVE|ITERATE|LABEL|LEAVE|LOOP|MODIFIES|OUT|READS|RELEASE|REPEAT|RETURN|SCHEMA|SCHEMAS|SENSITIVE|SPECIFIC|SQL|SQLEXCEPTION|SQLSTATE|SQLWARNING|TRIGGER|UNDO|UPGRADE|WHILE".split("|") )
  40. h.set(k.toLowerCase(),true);
  41. h;
  42. }
  43. private static function set_cnx( c : Connection ) {
  44. cnx = c;
  45. lockMode = (c != null && c.dbName() == "MySQL") ? " FOR UPDATE" : "";
  46. return c;
  47. }
  48. /* ---------------------------- BASIC API ----------------------------- */
  49. var table_infos : RecordInfos;
  50. var table_name : String;
  51. var table_keys : Array<String>;
  52. var class_proto : { prototype : Dynamic };
  53. public function new( classval : Class<T> ) {
  54. var m : Array<Dynamic> = haxe.rtti.Meta.getType(classval).rtti;
  55. if( m == null ) throw "Missing @rtti for class " + Type.getClassName(classval);
  56. table_infos = haxe.Unserializer.run(m[0]);
  57. table_name = quoteField(table_infos.name);
  58. table_keys = table_infos.key;
  59. // set the manager and ready for further init
  60. class_proto = cast classval;
  61. #if neko
  62. class_proto.prototype._manager = this;
  63. init_list.add(this);
  64. #end
  65. }
  66. public function all( ?lock: Null<Bool> ) : List<T> {
  67. return unsafeObjects("SELECT * FROM " + table_name,lock);
  68. }
  69. public macro function get(ethis,id,?lock:haxe.macro.Expr.ExprOf<Bool>) : #if macro haxe.macro.Expr #else haxe.macro.Expr.ExprOf<T> #end {
  70. return RecordMacros.macroGet(ethis,id,lock);
  71. }
  72. public macro function select(ethis, cond, ?options, ?lock:haxe.macro.Expr.ExprOf<Bool>) : #if macro haxe.macro.Expr #else haxe.macro.Expr.ExprOf<T> #end {
  73. return RecordMacros.macroSearch(ethis, cond, options, lock, true);
  74. }
  75. public macro function search(ethis, cond, ?options, ?lock:haxe.macro.Expr.ExprOf<Bool>) : #if macro haxe.macro.Expr #else haxe.macro.Expr.ExprOf<List<T>> #end {
  76. return RecordMacros.macroSearch(ethis, cond, options, lock);
  77. }
  78. public macro function count(ethis, cond) : #if macro haxe.macro.Expr #else haxe.macro.Expr.ExprOf<Int> #end {
  79. return RecordMacros.macroCount(ethis, cond);
  80. }
  81. public macro function delete(ethis, cond, ?options) : #if macro haxe.macro.Expr #else haxe.macro.Expr.ExprOf<Void> #end {
  82. return RecordMacros.macroDelete(ethis, cond, options);
  83. }
  84. public function dynamicSearch( x : {}, ?lock : Null<Bool> ) : List<T> {
  85. var s = new StringBuf();
  86. s.add("SELECT * FROM ");
  87. s.add(table_name);
  88. s.add(" WHERE ");
  89. addCondition(s,x);
  90. return unsafeObjects(s.toString(),lock);
  91. }
  92. function quote( s : String ) : String {
  93. return getCnx().quote( s );
  94. }
  95. /* -------------------------- RECORDOBJECT API -------------------------- */
  96. function doUpdateCache( x : T, name : String, v : Dynamic ) {
  97. var cache : { v : Dynamic } = Reflect.field(x, "cache_" + name);
  98. // if the cache has not been fetched (for instance if the field was set by reflection)
  99. // then we directly use the new value
  100. if( cache == null )
  101. return v;
  102. var v = doSerialize(name, cache.v);
  103. // don't set it since the value might change again later
  104. // Reflect.setField(x, name, v);
  105. return v;
  106. }
  107. function doInsert( x : T ) {
  108. unmake(x);
  109. var s = new StringBuf();
  110. var fields = new List();
  111. var values = new List();
  112. for( f in table_infos.fields ) {
  113. var name = f.name, fieldName = f.name;
  114. switch(f.t)
  115. {
  116. case DData:
  117. fieldName = "data_" + name;
  118. default:
  119. }
  120. var v:Dynamic = Reflect.field(x,fieldName);
  121. if( v != null ) {
  122. fields.add(quoteField(name));
  123. switch( f.t ) {
  124. case DData: v = doUpdateCache(x, name, v);
  125. default:
  126. }
  127. values.add(v);
  128. } else if( !f.isNull ) {
  129. // if the field is not defined, give it a default value on insert
  130. switch( f.t ) {
  131. case DUInt, DTinyInt, DInt, DSingle, DFloat, DFlags(_), DBigInt, DTinyUInt, DSmallInt, DSmallUInt, DMediumInt, DMediumUInt, DEnum(_):
  132. Reflect.setField(x, name, 0);
  133. case DBool:
  134. Reflect.setField(x, name, false);
  135. case DTinyText, DText, DString(_), DSmallText, DSerialized:
  136. Reflect.setField(x, name, "");
  137. case DSmallBinary, DNekoSerialized, DLongBinary, DBytes(_), DBinary:
  138. Reflect.setField(x, name, haxe.io.Bytes.alloc(0));
  139. case DDate, DDateTime, DTimeStamp:
  140. // default date might depend on database
  141. case DId, DUId, DBigId, DNull, DInterval, DEncoded, DData:
  142. // no default value for these
  143. }
  144. }
  145. }
  146. s.add("INSERT INTO ");
  147. s.add(table_name);
  148. s.add(" (");
  149. s.add(fields.join(","));
  150. s.add(") VALUES (");
  151. var first = true;
  152. for( v in values ) {
  153. if( first )
  154. first = false;
  155. else
  156. s.add(", ");
  157. getCnx().addValue(s,v);
  158. }
  159. s.add(")");
  160. unsafeExecute(s.toString());
  161. untyped x._lock = true;
  162. // table with one key not defined : suppose autoincrement
  163. if( table_keys.length == 1 && Reflect.field(x,table_keys[0]) == null )
  164. Reflect.setField(x,table_keys[0],getCnx().lastInsertId());
  165. addToCache(x);
  166. }
  167. inline function isBinary( t : RecordInfos.RecordType ) {
  168. return switch( t ) {
  169. case DSmallBinary, DNekoSerialized, DLongBinary, DBytes(_), DBinary: true;
  170. //case DData: true // -- disabled for implementation purposes
  171. default: false;
  172. };
  173. }
  174. inline function hasBinaryChanged( a : haxe.io.Bytes, b : haxe.io.Bytes ) {
  175. return a != b && (a == null || b == null || a.compare(b) != 0);
  176. }
  177. function doUpdate( x : T ) {
  178. if( untyped !x._lock )
  179. throw "Cannot update a not locked object";
  180. unmake(x);
  181. var s = new StringBuf();
  182. s.add("UPDATE ");
  183. s.add(table_name);
  184. s.add(" SET ");
  185. var cache = Reflect.field(x,cache_field);
  186. if(cache == null)
  187. {
  188. cache = {};
  189. Reflect.setField(x, cache_field, cache);
  190. }
  191. var mod = false;
  192. for( f in table_infos.fields ) {
  193. var name = f.name, fieldName = f.name;
  194. switch(f.t)
  195. {
  196. case DData:
  197. fieldName = "data_" + name;
  198. default:
  199. }
  200. var v : Dynamic = Reflect.field(x,fieldName);
  201. var vc : Dynamic = Reflect.field(cache,name);
  202. if( v != vc && (!isBinary(f.t) || hasBinaryChanged(v,vc)) ) {
  203. switch( f.t ) {
  204. case DData:
  205. v = doUpdateCache(x, name, v);
  206. if( !hasBinaryChanged(v,vc) )
  207. continue;
  208. default:
  209. }
  210. if( mod )
  211. s.add(", ");
  212. else
  213. mod = true;
  214. s.add(quoteField(name));
  215. s.add(" = ");
  216. getCnx().addValue(s,v);
  217. Reflect.setField(cache,name,v);
  218. }
  219. }
  220. if( !mod )
  221. return;
  222. s.add(" WHERE ");
  223. addKeys(s,x);
  224. unsafeExecute(s.toString());
  225. }
  226. function doDelete( x : T ) {
  227. var s = new StringBuf();
  228. s.add("DELETE FROM ");
  229. s.add(table_name);
  230. s.add(" WHERE ");
  231. addKeys(s,x);
  232. unsafeExecute(s.toString());
  233. removeFromCache(x);
  234. }
  235. function doLock( i : T ) {
  236. if( untyped i._lock )
  237. return;
  238. var s = new StringBuf();
  239. s.add("SELECT * FROM ");
  240. s.add(table_name);
  241. s.add(" WHERE ");
  242. addKeys(s, i);
  243. // will force sync
  244. if( unsafeObject(s.toString(),true) != i )
  245. throw "Could not lock object (was deleted ?); try restarting transaction";
  246. }
  247. function objectToString( it : T ) : String {
  248. var s = new StringBuf();
  249. s.add(table_name);
  250. if( table_keys.length == 1 ) {
  251. s.add("#");
  252. s.add(Reflect.field(it,table_keys[0]));
  253. } else {
  254. s.add("(");
  255. var first = true;
  256. for( f in table_keys ) {
  257. if( first )
  258. first = false;
  259. else
  260. s.add(",");
  261. s.add(quoteField(f));
  262. s.add(":");
  263. s.add(Reflect.field(it,f));
  264. }
  265. s.add(")");
  266. }
  267. return s.toString();
  268. }
  269. function doSerialize( field : String, v : Dynamic ) : haxe.io.Bytes {
  270. var s = new haxe.Serializer();
  271. s.useEnumIndex = true;
  272. s.serialize(v);
  273. var str = s.toString();
  274. #if neko
  275. return neko.Lib.bytesReference(str);
  276. #else
  277. return haxe.io.Bytes.ofString(str);
  278. #end
  279. }
  280. function doUnserialize( field : String, b : haxe.io.Bytes ) : Dynamic {
  281. if( b == null )
  282. return null;
  283. var str;
  284. #if neko
  285. str = neko.Lib.stringReference(b);
  286. #else
  287. str = b.toString();
  288. #end
  289. if( str == "" )
  290. return null;
  291. return haxe.Unserializer.run(str);
  292. }
  293. /* ---------------------------- INTERNAL API -------------------------- */
  294. function cacheObject( x : Dynamic, lock : Null<Bool> ) : T {
  295. #if neko
  296. var o = untyped __dollar__new(x);
  297. untyped __dollar__objsetproto(o, class_proto.prototype);
  298. #else
  299. var o : T = Type.createEmptyInstance(cast class_proto);
  300. untyped o._manager = this;
  301. #end
  302. for( f in Reflect.fields(x) )
  303. {
  304. var val:Dynamic = Reflect.field(x, f), info = table_infos.hfields.get(f);
  305. if (val != null && info != null) switch(info.t)
  306. {
  307. case DDate, DDateTime if (!Std.is(val, Date)):
  308. val = Date.fromString(Std.string(val));
  309. case DSmallBinary, DLongBinary, DBinary, DBytes(_) if (Std.is(val, String)):
  310. val = haxe.io.Bytes.ofString(val);
  311. case DBool if (Std.is(val, Int)):
  312. val = val != 0;
  313. case DData:
  314. if (Std.is(val, String))
  315. val = haxe.io.Bytes.ofString(val);
  316. Reflect.setField(o, f + "_data", val);
  317. continue;
  318. default:
  319. }
  320. Reflect.setField(o, f, val);
  321. }
  322. Reflect.setField(o,cache_field,x);
  323. addToCache(o);
  324. untyped o._lock = lock;
  325. return o;
  326. }
  327. function make( x : T ) {
  328. }
  329. function unmake( x : T ) {
  330. }
  331. function quoteField(f : String) {
  332. return KEYWORDS.exists(f.toLowerCase()) ? "`"+f+"`" : f;
  333. }
  334. function addKeys( s : StringBuf, x : {} ) {
  335. var first = true;
  336. for( k in table_keys ) {
  337. if( first )
  338. first = false;
  339. else
  340. s.add(" AND ");
  341. s.add(quoteField(k));
  342. s.add(" = ");
  343. var f = Reflect.field(x,k);
  344. if( f == null )
  345. throw ("Missing key "+k);
  346. getCnx().addValue(s,f);
  347. }
  348. }
  349. function unsafeExecute( sql : String ) {
  350. return getCnx().request(sql);
  351. }
  352. public function unsafeObject( sql : String, lock : Null<Bool> ) : T {
  353. if( lock != false ) {
  354. lock = true;
  355. sql += getLockMode();
  356. }
  357. var r = unsafeExecute(sql).next();
  358. if( r == null )
  359. return null;
  360. var c = getFromCache(r,lock);
  361. if( c != null )
  362. return c;
  363. r = cacheObject(r,lock);
  364. make(r);
  365. return r;
  366. }
  367. public function unsafeObjects( sql : String, lock : Null<Bool> ) : List<T> {
  368. if( lock != false ) {
  369. lock = true;
  370. sql += getLockMode();
  371. }
  372. var l = unsafeExecute(sql).results();
  373. var l2 = new List<T>();
  374. for( x in l ) {
  375. var c = getFromCache(x,lock);
  376. if( c != null )
  377. l2.add(c);
  378. else {
  379. x = cacheObject(x,lock);
  380. make(x);
  381. l2.add(x);
  382. }
  383. }
  384. return l2;
  385. }
  386. public function unsafeCount( sql : String ) {
  387. return unsafeExecute(sql).getIntResult(0);
  388. }
  389. public function unsafeDelete( sql : String ) {
  390. unsafeExecute(sql);
  391. }
  392. public function unsafeGet( id : Dynamic, ?lock : Null<Bool> ) : T {
  393. if( lock == null ) lock = true;
  394. if( table_keys.length != 1 )
  395. throw "Invalid number of keys";
  396. if( id == null )
  397. return null;
  398. var x : Dynamic = getFromCacheKey(Std.string(id) + table_name);
  399. if( x != null && (!lock || x._lock) )
  400. return x;
  401. var s = new StringBuf();
  402. s.add("SELECT * FROM ");
  403. s.add(table_name);
  404. s.add(" WHERE ");
  405. s.add(quoteField(table_keys[0]));
  406. s.add(" = ");
  407. getCnx().addValue(s,id);
  408. return unsafeObject(s.toString(), lock);
  409. }
  410. public function unsafeGetWithKeys( keys : { }, ?lock : Null<Bool> ) : T {
  411. if( lock == null ) lock = true;
  412. var x : Dynamic = getFromCacheKey(makeCacheKey(cast keys));
  413. if( x != null && (!lock || x._lock) )
  414. return x;
  415. var s = new StringBuf();
  416. s.add("SELECT * FROM ");
  417. s.add(table_name);
  418. s.add(" WHERE ");
  419. addKeys(s,keys);
  420. return unsafeObject(s.toString(),lock);
  421. }
  422. public function unsafeGetId( o : T ) : Dynamic {
  423. return o == null ? null : Reflect.field(o, table_keys[0]);
  424. }
  425. public static function nullCompare( a : String, b : String, eq : Bool ) {
  426. // we can't use a null-safe operator here
  427. if( cnx.dbName() != "MySQL" )
  428. return a + (eq ? " = " : " != ") + b;
  429. var sql = a+" <=> "+b;
  430. if( !eq ) sql = "NOT("+sql+")";
  431. return sql;
  432. }
  433. function addCondition(s : StringBuf,x) {
  434. var first = true;
  435. if( x != null )
  436. for( f in Reflect.fields(x) ) {
  437. if( first )
  438. first = false;
  439. else
  440. s.add(" AND ");
  441. s.add(quoteField(f));
  442. var d = Reflect.field(x,f);
  443. if( d == null )
  444. s.add(" IS NULL");
  445. else {
  446. s.add(" = ");
  447. getCnx().addValue(s,d);
  448. }
  449. }
  450. if( first )
  451. s.add("1");
  452. }
  453. /* --------------------------- MISC API ------------------------------ */
  454. public function dbClass() : Class<Dynamic> {
  455. return cast class_proto;
  456. }
  457. public function dbInfos() {
  458. return table_infos;
  459. }
  460. function getCnx() {
  461. return cnx;
  462. }
  463. function getLockMode() {
  464. return lockMode;
  465. }
  466. /**
  467. Remove the cached value for the given Object field : this will ensure
  468. that the value is updated when calling .update(). This is necessary if
  469. you are modifying binary data in-place since the cache will be modified
  470. as well.
  471. **/
  472. public function forceUpdate( o : T, field : String ) {
  473. // set a reference that will ensure != and .compare() != 0
  474. Reflect.setField(Reflect.field(o,cache_field),field,null);
  475. }
  476. /* --------------------------- INIT / CLEANUP ------------------------- */
  477. public static function initialize() {
  478. var l = init_list;
  479. init_list = new List();
  480. for( m in l )
  481. for( r in m.table_infos.relations )
  482. m.initRelation(r);
  483. }
  484. public static function cleanup() {
  485. object_cache = new haxe.ds.StringMap();
  486. }
  487. function initRelation( r : RecordInfos.RecordRelation ) {
  488. // setup getter/setter
  489. var spod : Dynamic = Type.resolveClass(r.type);
  490. if( spod == null ) throw "Missing spod type " + r.type;
  491. var manager : Manager<Dynamic> = spod.manager;
  492. var hprop = "__"+r.prop;
  493. var hkey = r.key;
  494. var lock = r.lock;
  495. if( manager == null || manager.table_keys == null ) throw ("Invalid manager for relation "+table_name+":"+r.prop);
  496. if( manager.table_keys.length != 1 ) throw ("Relation " + r.prop + "(" + r.key + ") on a multiple key table");
  497. #if neko
  498. Reflect.setField(class_proto.prototype,"get_"+r.prop,function() {
  499. var othis = untyped __this__;
  500. var f = Reflect.field(othis,hprop);
  501. if( f != null )
  502. return f;
  503. var id = Reflect.field(othis, hkey);
  504. if( id == null )
  505. return null;
  506. f = manager.unsafeGet(id,lock);
  507. // it's highly possible that in that case the object has been inserted
  508. // after we started our transaction : in that case, let's lock it, since
  509. // it's still better than returning 'null' while it exists
  510. if( f == null && id != null && !lock )
  511. f = manager.unsafeGet(id,true);
  512. Reflect.setField(othis,hprop,f);
  513. return f;
  514. });
  515. Reflect.setField(class_proto.prototype,"set_"+r.prop,function(f) {
  516. var othis = untyped __this__;
  517. Reflect.setField(othis,hprop,f);
  518. Reflect.setField(othis,hkey,Reflect.field(f,manager.table_keys[0]));
  519. return f;
  520. });
  521. #end
  522. }
  523. #if !neko
  524. function __get( x : Dynamic, prop : String, key : String, lock ) {
  525. #if php
  526. var v = Reflect.field(x,prop);
  527. if( v != null )
  528. return v.value;
  529. var y = unsafeGet(Reflect.field(x, key), lock);
  530. Reflect.setField(x,prop,{ value : y });
  531. return y;
  532. #else
  533. var v = Reflect.field(x,prop);
  534. if( v != null )
  535. return v;
  536. var y = unsafeGet(Reflect.field(x, key), lock);
  537. Reflect.setField(x,prop,y);
  538. return y;
  539. #end
  540. }
  541. function __set( x : Dynamic, prop : String, key : String, v : T ) {
  542. #if php
  543. Reflect.setField(x,prop,{ value : v });
  544. if( v == null )
  545. Reflect.setField(x,key,null);
  546. else
  547. Reflect.setField(x,key,Reflect.field(v,table_keys[0]));
  548. #else
  549. Reflect.setField(x,prop,v);
  550. if( v == null )
  551. Reflect.setField(x,key,null);
  552. else
  553. Reflect.setField(x,key,Reflect.field(v,table_keys[0]));
  554. #end
  555. }
  556. #end
  557. /* ---------------------------- OBJECT CACHE -------------------------- */
  558. function makeCacheKey( x : T ) : String {
  559. if( table_keys.length == 1 ) {
  560. var k = Reflect.field(x,table_keys[0]);
  561. if( k == null )
  562. throw("Missing key "+table_keys[0]);
  563. return Std.string(k)+table_name;
  564. }
  565. var s = new StringBuf();
  566. for( k in table_keys ) {
  567. var v = Reflect.field(x,k);
  568. if( k == null )
  569. throw("Missing key "+k);
  570. s.add(v);
  571. s.add("#");
  572. }
  573. s.add(table_name);
  574. return s.toString();
  575. }
  576. function addToCache( x : T ) {
  577. object_cache.set(makeCacheKey(x),x);
  578. }
  579. function removeFromCache( x : T ) {
  580. object_cache.remove(makeCacheKey(x));
  581. }
  582. function getFromCacheKey( key : String ) : T {
  583. return cast object_cache.get(key);
  584. }
  585. function getFromCache( x : T, lock : Bool ) : T {
  586. var c : Dynamic = object_cache.get(makeCacheKey(x));
  587. if( c != null && lock && !c._lock ) {
  588. // synchronize the fields since our result is up-to-date !
  589. for( f in Reflect.fields(c) )
  590. Reflect.deleteField(c,f);
  591. for( f in Reflect.fields(x) )
  592. Reflect.setField(c,f,Reflect.field(x,f));
  593. // mark as locked
  594. c._lock = true;
  595. // restore our manager
  596. #if !neko
  597. untyped c._manager = this;
  598. #end
  599. // use the new object as our cache of fields
  600. Reflect.setField(c,cache_field,x);
  601. // remake object
  602. make(c);
  603. }
  604. return c;
  605. }
  606. /* ---------------------------- QUOTES -------------------------- */
  607. public static function quoteAny( v : Dynamic ) {
  608. var s = new StringBuf();
  609. cnx.addValue(s, v);
  610. return s.toString();
  611. }
  612. public static function quoteList( v : String, it : Iterable<Dynamic> ) {
  613. var b = new StringBuf();
  614. var first = true;
  615. if( it != null )
  616. for( v in it ) {
  617. if( first ) first = false else b.addChar(','.code);
  618. cnx.addValue(b, v);
  619. }
  620. if( first )
  621. return "FALSE";
  622. return v + " IN (" + b.toString() + ")";
  623. }
  624. }