AdoNet.hx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. package cs.db;
  2. import sys.db.*;
  3. import cs.system.data.*;
  4. class AdoNet
  5. {
  6. public static function create(cnx:IDbConnection, dbName:String):Connection
  7. {
  8. return new AdoConnection(cnx,dbName);
  9. }
  10. }
  11. private class AdoConnection implements Connection
  12. {
  13. private static var ids = 0;
  14. private var id:Int;
  15. private var cnx:IDbConnection;
  16. //escape handling
  17. private var escapeRegex:EReg;
  18. private var escapes:Array<IDbDataParameter>;
  19. private var name:String;
  20. private var command:IDbCommand;
  21. private var transaction:IDbTransaction;
  22. public function new(cnx,name:String)
  23. {
  24. this.id = cs.system.threading.Interlocked.Increment(ids);
  25. this.cnx = cnx;
  26. this.name = name;
  27. this.escapes = [];
  28. this.command = cnx.CreateCommand();
  29. this.escapeRegex = ~/@HX_ESCAPE(\d+)_(\d+)/;
  30. }
  31. public function close() : Void
  32. {
  33. cnx.Close();
  34. }
  35. public function escape(s:String):String
  36. {
  37. var param = command.CreateParameter();
  38. var name = "@HX_ESCAPE" + id + "_" +escapes.push(param) + "";
  39. param.ParameterName = name;
  40. param.Value = s;
  41. return name;
  42. }
  43. public function quote(s:String):String
  44. {
  45. var param = command.CreateParameter();
  46. var name = "@HX_ESCAPE" + id + "_" +escapes.push(param) + "";
  47. param.ParameterName = name;
  48. param.Value = s;
  49. return name;
  50. }
  51. public function addValue(s:StringBuf, v:Dynamic)
  52. {
  53. if (Std.is(v, Date))
  54. {
  55. v = Std.string(v);
  56. } else if (Std.is(v, haxe.io.Bytes)) {
  57. var bt:haxe.io.Bytes = v;
  58. v = bt.getData();
  59. }
  60. var param = command.CreateParameter();
  61. var name = "@HX_ESCAPE" + id + "_" +escapes.push(param) + "";
  62. param.ParameterName = name;
  63. param.Value = v;
  64. s.add(name);
  65. }
  66. public function lastInsertId():Int
  67. {
  68. var ret = cnx.CreateCommand();
  69. ret.CommandText = switch(name) {
  70. case 'SQLite':
  71. 'SELECT last_insert_rowid()';
  72. case _:
  73. 'SELECT @@IDENTITY';
  74. }
  75. ret.CommandType = CommandType.Text;
  76. var r = cast ret.ExecuteScalar();
  77. ret.Dispose();
  78. return r;
  79. }
  80. public function dbName() : String
  81. {
  82. return name;
  83. }
  84. public function startTransaction() : Void
  85. {
  86. if (this.transaction != null)
  87. throw 'Transaction already active';
  88. this.transaction = cnx.BeginTransaction();
  89. }
  90. public function commit() : Void
  91. {
  92. if (this.transaction == null)
  93. throw 'No transaction was initiated';
  94. this.transaction.Commit();
  95. }
  96. public function rollback() : Void
  97. {
  98. if (this.transaction == null)
  99. throw 'No transaction was initiated';
  100. this.transaction.Rollback();
  101. }
  102. private static function getFirstStatement(s:String)
  103. {
  104. var buf = new StringBuf();
  105. var hasData = false;
  106. var chr = 0,
  107. i = 0;
  108. inline function getch() return chr = StringTools.fastCodeAt(s,i++);
  109. while ( !StringTools.isEof(getch()) )
  110. {
  111. inline function peek() { var c = StringTools.fastCodeAt(s,i); if (StringTools.isEof(c)) break; return c; }
  112. switch(chr)
  113. {
  114. case ' '.code | '\t'.code | '\n'.code:
  115. if (hasData)
  116. return buf.toString();
  117. case '-'.code if (peek() == '-'.code):
  118. if (hasData)
  119. return buf.toString();
  120. while (!StringTools.isEof(getch()))
  121. {
  122. if (chr == '\n'.code) break;
  123. }
  124. case '#'.code:
  125. if (hasData)
  126. return buf.toString();
  127. while (!StringTools.isEof(getch()))
  128. {
  129. if (chr == '\n'.code) break;
  130. }
  131. case '/'.code if (peek() == '*'.code):
  132. i++;
  133. if (hasData)
  134. return buf.toString();
  135. while (!StringTools.isEof(getch()))
  136. {
  137. if (chr == '*'.code && peek() == '/'.code)
  138. {
  139. i++;
  140. break;
  141. }
  142. }
  143. case _:
  144. hasData = true;
  145. buf.addChar(chr);
  146. }
  147. }
  148. return buf.toString();
  149. }
  150. public function request( s : String ) : ResultSet
  151. {
  152. var newst = new StringBuf();
  153. //cycle through the request string, adding any @HX_ESCAPE reference to the command
  154. var ret:ResultSet = null;
  155. var r = escapeRegex;
  156. var myid = id + "", escapes = escapes, elen = escapes.length;
  157. var cmd = this.command;
  158. try
  159. {
  160. while (r.match(s))
  161. {
  162. var id = r.matched(1);
  163. #if debug
  164. if (id != myid) throw "Request quotes are only valid for one single request; They can't be cached.";
  165. #end
  166. newst.add(r.matchedLeft());
  167. var eid = Std.parseInt(r.matched(2));
  168. #if debug
  169. if (eid == null || eid > elen)
  170. throw "Invalid request quote ID " + eid;
  171. #end
  172. cmd.Parameters.Add(escapes[eid - 1]);
  173. newst.add(escapes[eid-1].ParameterName);
  174. s = r.matchedRight();
  175. }
  176. newst.add(s);
  177. s = newst.toString();
  178. cmd.CommandText = s;
  179. var stmt = getFirstStatement(s).toLowerCase();
  180. if (stmt == 'select')
  181. {
  182. ret = new AdoResultSet( cmd.ExecuteReader() );
  183. } else {
  184. cmd.ExecuteNonQuery();
  185. ret = EmptyResultSet.empty;
  186. }
  187. if (escapes.length != 0)
  188. this.escapes = [];
  189. this.id = cs.system.threading.Interlocked.Increment(ids);
  190. cmd.Dispose();
  191. this.command = cnx.CreateCommand();
  192. return ret;
  193. }
  194. catch(e:Dynamic)
  195. {
  196. if (escapes.length != 0)
  197. this.escapes = [];
  198. this.id = cs.system.threading.Interlocked.Increment(ids);
  199. try { cmd.Dispose(); } catch(e:Dynamic) {}
  200. this.command = cnx.CreateCommand();
  201. cs.Lib.rethrow(e);
  202. }
  203. return null;
  204. }
  205. }
  206. private class AdoResultSet implements ResultSet
  207. {
  208. public var length(get,null) : Int;
  209. public var nfields(get,null) : Int;
  210. private var reader:IDataReader;
  211. private var didNext:Bool;
  212. private var names:Array<String>;
  213. private var types:Array<Class<Dynamic>>;
  214. public function new(reader)
  215. {
  216. this.reader = reader;
  217. this.names = [ for (i in 0...reader.FieldCount) reader.GetName(i) ];
  218. this.types = [ for (i in 0...names.length) cs.Lib.fromNativeType(reader.GetFieldType(i)) ];
  219. }
  220. private function get_length()
  221. {
  222. return reader.Depth;
  223. }
  224. private function get_nfields()
  225. {
  226. return names.length;
  227. }
  228. public function hasNext() : Bool
  229. {
  230. didNext = true;
  231. return reader.Read();
  232. }
  233. public function next() : Dynamic
  234. {
  235. if (!didNext && !hasNext())
  236. return null;
  237. didNext = false;
  238. var ret = {}, names = names, types = types;
  239. for (i in 0...names.length)
  240. {
  241. var name = names[i], t = types[i], val:Dynamic = null;
  242. if (t == cs.system.Single)
  243. {
  244. val = reader.GetDouble(i);
  245. } else if (t == cs.system.DateTime || t == cs.system.TimeSpan) {
  246. var d = reader.GetDateTime(i);
  247. if (d != null)
  248. val = Date.fromTime(cast(d.Ticks,Float) / cast(cs.system.TimeSpan.TicksPerMillisecond,Float));
  249. } else if (t == cs.system.DBNull) {
  250. val = null;
  251. } else if (t == cs.system.Byte) {
  252. var v2:cs.StdTypes.UInt8 = reader.GetValue(i);
  253. val = cast(v2,Int);
  254. } else if (Std.string(t) == 'System.Byte[]') {
  255. val = haxe.io.Bytes.ofData(reader.GetValue(i));
  256. } else {
  257. val = reader.GetValue(i);
  258. }
  259. if (Std.is(val,cs.system.DBNull))
  260. val = null;
  261. Reflect.setField(ret, name, val);
  262. }
  263. return ret;
  264. }
  265. public function results() : List<Dynamic>
  266. {
  267. var l = new List();
  268. while (hasNext())
  269. l.add(next());
  270. return l;
  271. }
  272. public function getResult( n : Int ) : String
  273. {
  274. return reader.GetString(n);
  275. }
  276. public function getIntResult( n : Int ) : Int
  277. {
  278. return reader.GetInt32(n);
  279. }
  280. public function getFloatResult( n : Int ) : Float
  281. {
  282. return reader.GetDouble(n);
  283. }
  284. public function getFieldsNames() : Null<Array<String>>
  285. {
  286. return names;
  287. }
  288. }
  289. private class EmptyResultSet implements ResultSet
  290. {
  291. public static var empty = new EmptyResultSet();
  292. public function new()
  293. {
  294. }
  295. public var length(get,null) : Int;
  296. public var nfields(get,null) : Int;
  297. private function get_length()
  298. {
  299. return 0;
  300. }
  301. private function get_nfields()
  302. {
  303. return 0;
  304. }
  305. public function hasNext() : Bool return false;
  306. public function next() : Dynamic return null;
  307. public function results() : List<Dynamic> return new List();
  308. public function getResult( n : Int ) : String return null;
  309. public function getIntResult( n : Int ) : Int return 0;
  310. public function getFloatResult( n : Int ) : Float return 0;
  311. public function getFieldsNames() : Null<Array<String>> return null;
  312. }