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;
 | 
						|
}
 |