355 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			355 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
|  | /* | ||
|  |  * 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<IDbDataParameter>; | ||
|  | 	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<String>; | ||
|  | 	private var types:Array<Class<Dynamic>>; | ||
|  | 
 | ||
|  | 	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<Dynamic> { | ||
|  | 		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<Array<String>> { | ||
|  | 		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<Dynamic> | ||
|  | 		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<Array<String>> | ||
|  | 		return null; | ||
|  | } |