Manager.hx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. /*
  2. * Copyright (c) 2005, The haXe Project Contributors
  3. * All rights reserved.
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are met:
  6. *
  7. * - Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * - Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. *
  13. * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
  14. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  15. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  16. * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
  17. * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  18. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  19. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  20. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  21. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  22. * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  23. * DAMAGE.
  24. */
  25. package neko.db;
  26. import Reflect;
  27. import neko.db.Connection;
  28. /**
  29. SPOD Manager : the persistent object database manager. See the tutorial on
  30. haXe website to learn how to use SPOD.
  31. **/
  32. class Manager<T : Object> {
  33. /* ----------------------------- STATICS ------------------------------ */
  34. public static var cnx(default,setConnection) : Connection;
  35. private static var object_cache : Hash<Object> = new Hash();
  36. private static var init_list : List<Manager<Object>> = new List();
  37. private static var cache_field = "__cache__";
  38. private static var no_update : Dynamic = function() { throw "Cannot update not locked object"; }
  39. private static var FOR_UPDATE = "";
  40. private static var KEYWORDS = {
  41. var h = new Hash();
  42. for( k in ["read","write","desc","out","group","version","option",
  43. "primary","exists","from","key","keys","limit","lock","use"] )
  44. h.set(k,true);
  45. h;
  46. }
  47. private static function setConnection( c : Connection ) {
  48. Reflect.setField(Manager,"cnx",c);
  49. if( c != null )
  50. FOR_UPDATE = if( c.dbName() == "MySQL" ) " FOR UPDATE" else "";
  51. return c;
  52. }
  53. /* ---------------------------- BASIC API ----------------------------- */
  54. var table_name : String;
  55. var table_fields : List<String>;
  56. var table_keys : Array<String>;
  57. var class_proto : { prototype : Dynamic };
  58. public function new( classval : Class<neko.db.Object> ) {
  59. var cl : Dynamic = classval;
  60. // get basic infos
  61. table_name = quoteField(if( cl.TABLE_NAME != null ) cl.TABLE_NAME else cl.__name__[cl.__name__.length-1]);
  62. table_keys = if( cl.TABLE_IDS != null ) cl.TABLE_IDS else ["id"];
  63. class_proto = cl;
  64. // get the list of private fields
  65. var apriv : Array<String> = cl.PRIVATE_FIELDS;
  66. apriv = if( apriv == null ) new Array() else apriv.copy();
  67. apriv.push("local_manager");
  68. apriv.push("__class__");
  69. // get the proto fields not marked private (excluding methods)
  70. table_fields = new List();
  71. var proto : { local_manager : neko.db.Manager<T> } = class_proto.prototype;
  72. for( f in Reflect.fields(proto) ) {
  73. var isfield = !Reflect.isFunction(Reflect.field(proto,f));
  74. if( isfield )
  75. for( f2 in apriv )
  76. if( f == f2 ) {
  77. isfield = false;
  78. break;
  79. }
  80. if( isfield )
  81. table_fields.add(f);
  82. }
  83. // set the manager and ready for further init
  84. proto.local_manager = this;
  85. init_list.add(untyped this);
  86. }
  87. public function get( id : Int, ?lock : Bool ) : T {
  88. if( lock == null )
  89. lock = true;
  90. if( table_keys.length != 1 )
  91. throw "Invalid number of keys";
  92. if( id == null )
  93. return null;
  94. var x : Dynamic = untyped object_cache.get(id + table_name);
  95. if( x != null && (!lock || x.update != no_update) )
  96. return x;
  97. var s = new StringBuf();
  98. s.add("SELECT * FROM ");
  99. s.add(table_name);
  100. s.add(" WHERE ");
  101. s.add(quoteField(table_keys[0]));
  102. s.add(" = ");
  103. addQuote(s,id);
  104. if( lock )
  105. s.add(FOR_UPDATE);
  106. return object(s.toString(),lock);
  107. }
  108. public function getWithKeys( keys : {}, ?lock : Bool ) : T {
  109. if( lock == null )
  110. lock = true;
  111. var x : Dynamic = getFromCache(untyped keys,false);
  112. if( x != null && (!lock || x.update != no_update) )
  113. return x;
  114. var s = new StringBuf();
  115. s.add("SELECT * FROM ");
  116. s.add(table_name);
  117. s.add(" WHERE ");
  118. addKeys(s,keys);
  119. if( lock )
  120. s.add(FOR_UPDATE);
  121. return object(s.toString(),lock);
  122. }
  123. public function delete( x : {} ) {
  124. var s = new StringBuf();
  125. s.add("DELETE FROM ");
  126. s.add(table_name);
  127. s.add(" WHERE ");
  128. addCondition(s,x);
  129. execute(s.toString());
  130. }
  131. public function search( x : {}, ?lock : Bool ) : List<T> {
  132. if( lock == null )
  133. lock = true;
  134. var s = new StringBuf();
  135. s.add("SELECT * FROM ");
  136. s.add(table_name);
  137. s.add(" WHERE ");
  138. addCondition(s,x);
  139. if( lock )
  140. s.add(FOR_UPDATE);
  141. return objects(s.toString(),lock);
  142. }
  143. function addCondition(s : StringBuf,x) {
  144. var first = true;
  145. if( x != null )
  146. for( f in Reflect.fields(x) ) {
  147. if( first )
  148. first = false;
  149. else
  150. s.add(" AND ");
  151. s.add(quoteField(f));
  152. var d = Reflect.field(x,f);
  153. if( d == null )
  154. s.add(" IS NULL");
  155. else {
  156. s.add(" = ");
  157. addQuote(s,d);
  158. }
  159. }
  160. if( first )
  161. s.add("1");
  162. }
  163. public function all( ?lock: Bool ) : List<T> {
  164. if( lock == null )
  165. lock = true;
  166. return objects("SELECT * FROM " + table_name + if( lock ) FOR_UPDATE else "",lock);
  167. }
  168. public function count( ?x : {} ) : Int {
  169. var s = new StringBuf();
  170. s.add("SELECT COUNT(*) FROM ");
  171. s.add(table_name);
  172. s.add(" WHERE ");
  173. addCondition(s,x);
  174. return execute(s.toString()).getIntResult(0);
  175. }
  176. public function quote( s : String ) : String {
  177. return cnx.quote( s );
  178. }
  179. public function result( sql : String ) : Dynamic {
  180. return cnx.request(sql).next();
  181. }
  182. public function results<T>( sql : String ) : List<T> {
  183. return cast cnx.request(sql).results();
  184. }
  185. /* -------------------------- SPODOBJECT API -------------------------- */
  186. function doInsert( x : T ) {
  187. unmake(x);
  188. var s = new StringBuf();
  189. var fields = new List();
  190. var values = new List();
  191. for( f in table_fields ) {
  192. var v = Reflect.field(x,f);
  193. if( v != null ) {
  194. fields.add(quoteField(f));
  195. values.add(v);
  196. }
  197. }
  198. s.add("INSERT INTO ");
  199. s.add(table_name);
  200. s.add(" (");
  201. s.add(fields.join(","));
  202. s.add(") VALUES (");
  203. var first = true;
  204. for( v in values ) {
  205. if( first )
  206. first = false;
  207. else
  208. s.add(", ");
  209. addQuote(s,v);
  210. }
  211. s.add(")");
  212. execute(s.toString());
  213. // table with one key not defined : suppose autoincrement
  214. if( table_keys.length == 1 && Reflect.field(x,table_keys[0]) == null )
  215. Reflect.setField(x,table_keys[0],cnx.lastInsertId());
  216. addToCache(x);
  217. }
  218. function doUpdate( x : T ) {
  219. unmake(x);
  220. var s = new StringBuf();
  221. s.add("UPDATE ");
  222. s.add(table_name);
  223. s.add(" SET ");
  224. var cache = Reflect.field(x,cache_field);
  225. var mod = false;
  226. for( f in table_fields ) {
  227. var v = Reflect.field(x,f);
  228. var vc = Reflect.field(cache,f);
  229. if( v != vc ) {
  230. if( mod )
  231. s.add(", ");
  232. else
  233. mod = true;
  234. s.add(quoteField(f));
  235. s.add(" = ");
  236. addQuote(s,v);
  237. Reflect.setField(cache,f,v);
  238. }
  239. }
  240. if( !mod )
  241. return;
  242. s.add(" WHERE ");
  243. addKeys(s,x);
  244. execute(s.toString());
  245. }
  246. function doDelete( x : T ) {
  247. var s = new StringBuf();
  248. s.add("DELETE FROM ");
  249. s.add(table_name);
  250. s.add(" WHERE ");
  251. addKeys(s,x);
  252. execute(s.toString());
  253. }
  254. function doSync( i : T ) {
  255. object_cache.remove(makeCacheKey(i));
  256. var i2 = getWithKeys(i,(cast i).update != no_update);
  257. // delete all fields
  258. for( f in Reflect.fields(i) )
  259. Reflect.deleteField(i,f);
  260. // copy fields from new object
  261. for( f in Reflect.fields(i2) )
  262. Reflect.setField(i,f,Reflect.field(i2,f));
  263. // set same field-cache
  264. Reflect.setField(i,cache_field,Reflect.field(i2,cache_field));
  265. addToCache(i);
  266. }
  267. function objectToString( it : T ) : String {
  268. var s = new StringBuf();
  269. s.add(table_name);
  270. if( table_keys.length == 1 ) {
  271. s.add("#");
  272. s.add(Reflect.field(it,table_keys[0]));
  273. } else {
  274. s.add("(");
  275. var first = true;
  276. for( f in table_keys ) {
  277. if( first )
  278. first = false;
  279. else
  280. s.add(",");
  281. s.add(quoteField(f));
  282. s.add(":");
  283. s.add(Reflect.field(it,f));
  284. }
  285. s.add(")");
  286. }
  287. return s.toString();
  288. }
  289. /* ---------------------------- INTERNAL API -------------------------- */
  290. function cacheObject( x : T, lock : Bool ) {
  291. addToCache(x);
  292. untyped __dollar__objsetproto(x,class_proto.prototype);
  293. Reflect.setField(x,cache_field,untyped __dollar__new(x));
  294. if( !lock )
  295. x.update = no_update;
  296. }
  297. function make( x : T ) {
  298. }
  299. function unmake( x : T ) {
  300. }
  301. function quoteField(f : String) {
  302. return KEYWORDS.exists(f.toLowerCase()) ? "`"+f+"`" : f;
  303. }
  304. function addQuote( s : StringBuf, v : Dynamic ) {
  305. var t = untyped __dollar__typeof(v);
  306. if( untyped (t == __dollar__tint || t == __dollar__tnull) )
  307. s.add(v);
  308. else if( untyped t == __dollar__tbool )
  309. s.add(if( v ) 1 else 0);
  310. else
  311. s.add(cnx.quote(Std.string(v)));
  312. }
  313. function addKeys( s : StringBuf, x : {} ) {
  314. var first = true;
  315. for( k in table_keys ) {
  316. if( first )
  317. first = false;
  318. else
  319. s.add(" AND ");
  320. s.add(quoteField(k));
  321. s.add(" = ");
  322. var f = Reflect.field(x,k);
  323. if( f == null )
  324. throw ("Missing key "+k);
  325. addQuote(s,f);
  326. }
  327. }
  328. function execute( sql : String ) {
  329. return cnx.request(sql);
  330. }
  331. function select( cond : String ) {
  332. var s = new StringBuf();
  333. s.add("SELECT * FROM ");
  334. s.add(table_name);
  335. s.add(" WHERE ");
  336. s.add(cond);
  337. s.add(FOR_UPDATE);
  338. return s.toString();
  339. }
  340. function selectReadOnly( cond : String ) {
  341. var s = new StringBuf();
  342. s.add("SELECT * FROM ");
  343. s.add(table_name);
  344. s.add(" WHERE ");
  345. s.add(cond);
  346. return s.toString();
  347. }
  348. public function object( sql : String, lock : Bool ) : T {
  349. var r = cnx.request(sql).next();
  350. if( r == null )
  351. return null;
  352. var c = getFromCache(r,lock);
  353. if( c != null )
  354. return c;
  355. cacheObject(r,lock);
  356. make(r);
  357. return r;
  358. }
  359. public function objects( sql : String, lock : Bool ) : List<T> {
  360. var me = this;
  361. var l = cnx.request(sql).results();
  362. var l2 = new List<T>();
  363. for( x in l ) {
  364. var c = getFromCache(x,lock);
  365. if( c != null )
  366. l2.add(c);
  367. else {
  368. cacheObject(x,lock);
  369. make(x);
  370. l2.add(x);
  371. }
  372. }
  373. return l2;
  374. }
  375. public function dbClass() : Class<Dynamic> {
  376. return cast class_proto;
  377. }
  378. /* --------------------------- INIT / CLEANUP ------------------------- */
  379. public static function initialize() {
  380. var l = init_list;
  381. init_list = new List();
  382. for( m in l ) {
  383. var rl : Void -> Array<Dynamic> = untyped m.class_proto.RELATIONS;
  384. if( rl != null )
  385. for( r in rl() )
  386. m.initRelation(r);
  387. }
  388. }
  389. public static function cleanup() {
  390. object_cache = new Hash();
  391. }
  392. function initRelation(r : { prop : String, key : String, manager : Manager<Object>, lock : Bool } ) {
  393. // setup getter/setter
  394. var manager = r.manager;
  395. var hprop = "__"+r.prop;
  396. var hkey = r.key;
  397. var lock = r.lock;
  398. if( lock == null ) lock = true;
  399. if( manager == null || manager.table_keys == null ) throw ("Invalid manager for relation "+table_name+":"+r.prop);
  400. if( manager.table_keys.length != 1 ) throw ("Relation "+r.prop+"("+r.key+") on a multiple key table");
  401. Reflect.setField(class_proto.prototype,"get_"+r.prop,function() {
  402. var othis = untyped this;
  403. var f = Reflect.field(othis,hprop);
  404. if( f != null )
  405. return f;
  406. f = manager.get(Reflect.field(othis,hkey),lock);
  407. Reflect.setField(othis,hprop,f);
  408. return f;
  409. });
  410. Reflect.setField(class_proto.prototype,"set_"+r.prop,function(f) {
  411. var othis = untyped this;
  412. Reflect.setField(othis,hprop,f);
  413. Reflect.setField(othis,hkey,Reflect.field(f,manager.table_keys[0]));
  414. return f;
  415. });
  416. // remove prop from precomputed table_fields
  417. // always add key to table fields (even if not declared)
  418. table_fields.remove(r.prop);
  419. table_fields.remove(r.key);
  420. table_fields.add(r.key);
  421. }
  422. /* ---------------------------- OBJECT CACHE -------------------------- */
  423. function makeCacheKey( x : T ) : String {
  424. if( table_keys.length == 1 ) {
  425. var k = Reflect.field(x,table_keys[0]);
  426. if( k == null )
  427. throw("Missing key "+table_keys[0]);
  428. return Std.string(k)+table_name;
  429. }
  430. var s = new StringBuf();
  431. for( k in table_keys ) {
  432. var v = Reflect.field(x,k);
  433. if( k == null )
  434. throw("Missing key "+k);
  435. s.add(v);
  436. s.add("#");
  437. }
  438. s.add(table_name);
  439. return s.toString();
  440. }
  441. function addToCache( x : T ) {
  442. object_cache.set(makeCacheKey(x),x);
  443. }
  444. function getFromCache( x : T, lock : Bool ) : T {
  445. var c : Dynamic = object_cache.get(makeCacheKey(x));
  446. // restore update method since now the object is locked
  447. if( c != null && lock && c.update == no_update )
  448. c.update = class_proto.prototype.update;
  449. return c;
  450. }
  451. }