/* * Copyright (C)2005-2019 Haxe Foundation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package cs.db; import sys.db.*; import cs.system.data.*; class AdoNet { public static function create(cnx:IDbConnection, dbName:String):Connection { return new AdoConnection(cnx, dbName); } } private class AdoConnection implements Connection { private static var ids = 0; private var id:Int; private var cnx:IDbConnection; // escape handling private var escapeRegex:EReg; private var escapes:Array; private var name:String; private var command:IDbCommand; private var transaction:IDbTransaction; public function new(cnx, name:String) { this.id = cs.system.threading.Interlocked.Increment(ids); this.cnx = cnx; this.name = name; this.escapes = []; this.command = cnx.CreateCommand(); this.escapeRegex = ~/@HX_ESCAPE(\d+)_(\d+)/; } public function close():Void { cnx.Close(); } public function escape(s:String):String { var param = command.CreateParameter(); var name = "@HX_ESCAPE" + id + "_" + escapes.push(param) + ""; param.ParameterName = name; param.Value = s; return name; } public function quote(s:String):String { var param = command.CreateParameter(); var name = "@HX_ESCAPE" + id + "_" + escapes.push(param) + ""; param.ParameterName = name; param.Value = s; return name; } public function addValue(s:StringBuf, v:Dynamic) { if (Std.isOfType(v, Date)) { v = Std.string(v); } else if (Std.isOfType(v, haxe.io.Bytes)) { var bt:haxe.io.Bytes = v; v = bt.getData(); } var param = command.CreateParameter(); var name = "@HX_ESCAPE" + id + "_" + escapes.push(param) + ""; param.ParameterName = name; param.Value = v; s.add(name); } public function lastInsertId():Int { var ret = cnx.CreateCommand(); ret.CommandText = switch (name) { case 'SQLite': 'SELECT last_insert_rowid()'; case _: 'SELECT @@IDENTITY'; } ret.CommandType = CommandType.Text; var r = cast ret.ExecuteScalar(); ret.Dispose(); return r; } public function dbName():String { return name; } public function startTransaction():Void { if (this.transaction != null) throw 'Transaction already active'; this.transaction = cnx.BeginTransaction(); } public function commit():Void { if (this.transaction == null) throw 'No transaction was initiated'; this.transaction.Commit(); } public function rollback():Void { if (this.transaction == null) throw 'No transaction was initiated'; this.transaction.Rollback(); } private static function getFirstStatement(s:String) { var buf = new StringBuf(); var hasData = false; var chr = 0, i = 0; inline function getch() return chr = StringTools.fastCodeAt(s, i++); while (!StringTools.isEof(getch())) { inline function peek() { var c = StringTools.fastCodeAt(s, i); if (StringTools.isEof(c)) break; return c; } switch (chr) { case ' '.code | '\t'.code | '\n'.code: if (hasData) return buf.toString(); case '-'.code if (peek() == '-'.code): if (hasData) return buf.toString(); while (!StringTools.isEof(getch())) { if (chr == '\n'.code) break; } case '#'.code: if (hasData) return buf.toString(); while (!StringTools.isEof(getch())) { if (chr == '\n'.code) break; } case '/'.code if (peek() == '*'.code): i++; if (hasData) return buf.toString(); while (!StringTools.isEof(getch())) { if (chr == '*'.code && peek() == '/'.code) { i++; break; } } case _: hasData = true; buf.addChar(chr); } } return buf.toString(); } public function request(s:String):ResultSet { var newst = new StringBuf(); // cycle through the request string, adding any @HX_ESCAPE reference to the command var ret:ResultSet = null; var r = escapeRegex; var myid = id + "", escapes = escapes, elen = escapes.length; var cmd = this.command; try { while (r.match(s)) { var id = r.matched(1); #if debug if (id != myid) throw "Request quotes are only valid for one single request; They can't be cached."; #end newst.add(r.matchedLeft()); var eid = Std.parseInt(r.matched(2)); #if debug if (eid == null || eid > elen) throw "Invalid request quote ID " + eid; #end cmd.Parameters.Add(escapes[eid - 1]); newst.add(escapes[eid - 1].ParameterName); s = r.matchedRight(); } newst.add(s); s = newst.toString(); cmd.CommandText = s; var stmt = getFirstStatement(s).toLowerCase(); if (stmt == 'select') { ret = new AdoResultSet(cmd.ExecuteReader()); } else { cmd.ExecuteNonQuery(); ret = EmptyResultSet.empty; } if (escapes.length != 0) this.escapes = []; this.id = cs.system.threading.Interlocked.Increment(ids); cmd.Dispose(); this.command = cnx.CreateCommand(); return ret; } catch (e:Dynamic) { if (escapes.length != 0) this.escapes = []; this.id = cs.system.threading.Interlocked.Increment(ids); try { cmd.Dispose(); } catch (e:Dynamic) {} this.command = cnx.CreateCommand(); cs.Lib.rethrow(e); } return null; } } private class AdoResultSet implements ResultSet { public var length(get, null):Int; public var nfields(get, null):Int; private var reader:IDataReader; private var didNext:Bool; private var names:Array; private var types:Array>; public function new(reader) { this.reader = reader; this.names = [for (i in 0...reader.FieldCount) reader.GetName(i)]; this.types = [for (i in 0...names.length) cs.Lib.fromNativeType(reader.GetFieldType(i))]; } private function get_length() { return reader.Depth; } private function get_nfields() { return names.length; } public function hasNext():Bool { didNext = true; return reader.Read(); } public function next():Dynamic { if (!didNext && !hasNext()) return null; didNext = false; var ret = {}, names = names, types = types; for (i in 0...names.length) { var name = names[i], t = types[i], val:Dynamic = null; if (reader.IsDBNull(i)) { val = null; } else if (t == cs.system.Single) { val = reader.GetDouble(i); } else if (t == cs.system.DateTime || t == cs.system.TimeSpan) { var d = reader.GetDateTime(i); if (d != null) val = @:privateAccess Date.fromNative(d); } else if (t == cs.system.DBNull) { val = null; } else if (t == cs.system.Byte) { var v2:cs.StdTypes.UInt8 = reader.GetValue(i); val = cast(v2, Int); } else if (Std.string(t) == 'System.Byte[]') { val = haxe.io.Bytes.ofData(reader.GetValue(i)); } else { val = reader.GetValue(i); } if (Std.isOfType(val, cs.system.DBNull)) val = null; Reflect.setField(ret, name, val); } return ret; } public function results():List { var l = new List(); while (hasNext()) l.add(next()); return l; } public function getResult(n:Int):String { return reader.GetString(n); } public function getIntResult(n:Int):Int { return reader.GetInt32(n); } public function getFloatResult(n:Int):Float { return reader.GetDouble(n); } public function getFieldsNames():Null> { return names; } } private class EmptyResultSet implements ResultSet { public static var empty = new EmptyResultSet(); public function new() {} public var length(get, null):Int; public var nfields(get, null):Int; private function get_length() { return 0; } private function get_nfields() { return 0; } public function hasNext():Bool return false; public function next():Dynamic return null; public function results():List return new List(); public function getResult(n:Int):String return null; public function getIntResult(n:Int):Int return 0; public function getFloatResult(n:Int):Float return 0; public function getFieldsNames():Null> return null; }