AdoNet.hx 8.8 KB

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