AdoNet.hx 8.8 KB

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