/* * 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 java.db; import haxe.io.Bytes; import java.sql.Types; import java.util.concurrent.atomic.AtomicInteger; @:native('haxe.java.db.Jdbc') class Jdbc { public static function create(cnx:java.sql.Connection):sys.db.Connection { return new JdbcConnection(cnx); } } @:native('haxe.java.db.JdbcConnection') private class JdbcConnection implements sys.db.Connection { private static var ids = new AtomicInteger(0); private var id:Int; private var cnx:java.sql.Connection; private var _lastInsertId:Int; // escape handling private var escapeRegex:EReg; private var escapes:Array; public function new(cnx) { this.id = ids.getAndIncrement(); this.cnx = cnx; this.escapes = []; this.escapeRegex = ~/@@HX_ESCAPE(\d+)_(\d+)@@/; } public function close() { try this.cnx.close() catch (e:Dynamic) throw e; } public function escape(s:String):String { return "@@HX_ESCAPE" + id + "_" + escapes.push(s) + "@@"; } public function quote(s:String):String { return "@@HX_ESCAPE" + id + "_" + escapes.push(s) + "@@"; } public function addValue(s:StringBuf, v:Dynamic) { if (Std.isOfType(v, Date)) { v = Std.string(v); } else if (Std.isOfType(v, Bytes)) { var bt:Bytes = v; v = bt.getData(); } s.add("@@HX_ESCAPE"); s.add(id); s.add("_"); s.add(escapes.push(v)); s.add("@@"); } public function lastInsertId():Int { return _lastInsertId; } public function dbName():String { try { var ret = cnx.getMetaData().getDriverName(); var retc = ret.toLowerCase(); if (retc.indexOf("mysql") != -1) return "MySQL"; else if (retc.indexOf("sqlite") != -1) return "SQLite"; return ret; } catch (e:Dynamic) { throw e; } } public function startTransaction() { try { cnx.setAutoCommit(false); } catch (e:Dynamic) throw e; } public function commit() { try { cnx.commit(); } catch (e:Dynamic) { throw e; } } public function rollback() { try cnx.rollback() catch (e:Dynamic) throw e; } public function request(s:String):sys.db.ResultSet { var newst = new StringBuf(); var sentArray = []; // cycle through the request string, adding any @@HX_ESCAPE@@ reference to the sentArray var r = escapeRegex; var myid = id + "", escapes = escapes, elen = escapes.length; try { while (r.match(s)) { var id = r.matched(1); if (id != myid) throw "Request quotes are only valid for one single request; They can't be cached."; newst.add(r.matchedLeft()); var eid = Std.parseInt(r.matched(2)); if (eid == null || eid > elen) throw "Invalid request quote ID " + eid; sentArray.push(escapes[eid - 1]); newst.add("?"); s = r.matchedRight(); } newst.add(s); var stmt = cnx.prepareStatement(newst.toString(), #if jvm java.sql.Statement.RETURN_GENERATED_KEYS #else java.sql.Statement.Statement_Statics.RETURN_GENERATED_KEYS #end); for (i in 0...sentArray.length) { stmt.setObject(i + 1, sentArray[i]); } var ret = null, dbName = dbName(); if (stmt.execute()) { // is a result set var rs = stmt.getResultSet(); ret = new JdbcResultSet(rs, dbName, stmt.getMetaData()); } else { // is an update var affected = stmt.getUpdateCount(); if (affected == 1) { var autogen = stmt.getGeneratedKeys(); if (autogen.next()) { this._lastInsertId = autogen.getInt(1); } } ret = new JdbcResultSet(null, dbName, null); } if (escapes.length != 0) escapes = []; this.id = ids.getAndIncrement(); return ret; } catch (e:Dynamic) { if (escapes.length != 0) escapes = []; this.id = ids.getAndIncrement(); throw e; } } } @:native('haxe.java.db.JdbcResultSet') private class JdbcResultSet implements sys.db.ResultSet { @:isVar public var length(get, null):Int; public var nfields(get, null):Int; private var rs:java.sql.ResultSet; private var names:Array; private var types:java.NativeArray; private var dbName:String; private var didNext:Bool; public function new(rs, dbName, meta:java.sql.ResultSetMetaData) { this.dbName = dbName; this.rs = rs; if (meta != null) { try { var count = meta.getColumnCount(); var names = [], types = new NativeArray(count); for (i in 0...count) { names.push(meta.getColumnName(i + 1)); types[i] = meta.getColumnType(i + 1); } this.types = types; this.names = names; } catch (e:Dynamic) throw e; } } private function get_length():Int { if (length == 0) { try { var cur = rs.getRow(); rs.last(); this.length = rs.getRow(); rs.absolute(cur); } catch (e:Dynamic) throw e; } return length; } private function get_nfields():Int { return names == null ? 0 : names.length; } public function hasNext():Bool { try { didNext = true; return rs != null && rs.next(); } catch (e:Dynamic) { return throw e; } } public function next():Dynamic { try { if (rs == null) return null; if (didNext) { didNext = false; } else { if (!rs.next()) { return null; } } var ret = {}, names = names, types = types; for (i in 0...names.length) { var name = names[i], t = types[i], val:Dynamic = null; if (t == Types.FLOAT) { val = rs.getDouble(i + 1); } else if (t == Types.DATE || t == Types.TIME) { if (dbName == "SQLite") { var str = rs.getString(i + 1); if (str != null) { var d:Date = Date.fromString(str); val = d; } } else { var d:java.sql.Date = rs.getDate(i + 1); if (d != null) val = Date.fromTime(cast d.getTime()); } } else if (t == Types.LONGVARBINARY || t == Types.VARBINARY || t == Types.BINARY || t == Types.BLOB) { var b = rs.getBytes(i + 1); if (b != null) val = Bytes.ofData(b); } else { val = rs.getObject(i + 1); } Reflect.setField(ret, name, val); } return ret; } catch (e:Dynamic) throw e; } public function results():List { var l = new List(); if (rs == null) return l; try { while (hasNext()) l.add(next()); } catch (e:Dynamic) throw e; return l; } public function getResult(n:Int):String { try { return rs.getString(n); } catch (e:Dynamic) throw e; } public function getIntResult(n:Int):Int { try { return rs.getInt(n); } catch (e:Dynamic) { return throw e; }; } public function getFloatResult(n:Int):Float { try { return rs.getFloat(n); } catch (e:Dynamic) { return throw e; }; } public function getFieldsNames():Null> { return this.names; } }