/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.sql;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import divconq.hub.Hub;
import divconq.lang.op.FuncResult;
import divconq.lang.op.OperationContext;
import divconq.lang.op.OperationResult;
import divconq.struct.ListStruct;
import divconq.struct.RecordStruct;
import divconq.struct.Struct;
import divconq.struct.scalar.NullStruct;
import divconq.util.StringUtil;
import divconq.xml.XElement;
// TODO there is much much more to do to make this easier to support many different db engines
public class SqlManager {
public final DateTimeFormatter stampFmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS");
protected Map<String, SqlDatabase> databases = new HashMap<String, SqlManager.SqlDatabase>();
public String getNowAsString() {
return this.stampFmt.print(new DateTime(DateTimeZone.UTC));
}
public String getDateAsString(DateTime dt) {
return this.stampFmt.print(dt.toDateTime(DateTimeZone.UTC));
}
public String getDateAsString(long dt) {
return this.stampFmt.print(new DateTime(dt, DateTimeZone.UTC));
}
public SqlDatabase getDatabase(String name) {
return this.databases.get(name);
}
public void init(OperationResult or, XElement config) {
if (config == null)
return;
for (XElement del : config.selectAll("Database")) {
String name = del.getAttribute("Name", "default");
SqlDatabase db = new SqlDatabase();
db.name = name;
db.init(or, del);
this.databases.put(name, db);
}
}
public void stop() {
for (SqlDatabase db : this.databases.values())
db.stop();
}
public class SqlDatabase {
protected String connstring = null;
protected String name = null;
protected SqlEngine engine = null;
// single connection style engines
protected Connection conn = null;
protected Semaphore lock = new Semaphore(0);
protected boolean poolmode = false;
protected ConcurrentLinkedQueue<Connection> pool = new ConcurrentLinkedQueue<>();
public void init(OperationResult or, XElement del) {
if (del == null)
return;
try {
String driver = del.getAttribute("Driver");
Class.forName(driver);
this.connstring = Hub.instance.getClock().getObfuscator().decryptHexToString(
del.getAttribute("Connection")
);
// if null then try unencrypted
if (this.connstring == null)
this.connstring = del.getAttribute("Connection");
if (this.connstring.startsWith("jdbc:h2:"))
this.engine = SqlEngine.H2;
else if (this.connstring.startsWith("jdbc:sqlserver:"))
this.engine = SqlEngine.SqlServer;
else if (this.connstring.startsWith("jdbc:mariadb:"))
this.engine = SqlEngine.MariaDb;
else if (this.connstring.startsWith("jdbc:mysql:"))
this.engine = SqlEngine.MySQL;
else {
or.errorTr(189, this.connstring.substring(0, Math.min(this.connstring.length(), 15)));
return;
}
this.poolmode = "Pooled".equals(del.getAttribute("Mode"));
if (this.engine == SqlEngine.SqlServer || this.engine == SqlEngine.MariaDb || this.engine == SqlEngine.MySQL) {
or.info(0, "Using database " + this.name + " with multiple connections.");
}
else {
this.conn = DriverManager.getConnection(this.connstring);
or.info(0, "Connected to database " + this.name + " single connection.");
this.releaseConnection(this.conn);
}
}
catch (Exception x) {
or.errorTr(190, this.name, x);
return;
}
}
public void stop() {
try {
if (this.engine == SqlEngine.SqlServer || this.engine == SqlEngine.MariaDb || this.engine == SqlEngine.MySQL) {
if (this.poolmode) {
Connection conn = this.pool.poll();
while (conn != null) {
try {
conn.close();
}
catch (Exception x) {
// unimportant
}
conn = this.pool.poll();
}
}
}
else if (this.engine == SqlEngine.H2)
this.conn.close();
}
catch (Exception x) {
// unimportant
}
}
public Connection acquireConnection() {
Hub.instance.getCountManager().allocateNumberCounter("dcSqlAcquireConnection").increment();
if (this.engine == SqlEngine.SqlServer || this.engine == SqlEngine.MariaDb || this.engine == SqlEngine.MySQL) {
try {
Connection conn = null;
if (this.poolmode) {
conn = this.pool.poll();
if ((conn != null) && !conn.isValid(2))
conn = null;
}
if (conn == null) {
conn = DriverManager.getConnection(this.connstring);
Hub.instance.getCountManager().allocateNumberCounter("dcSqlConnectionCreate").increment();
}
return conn;
}
catch (SQLException x) {
}
}
else if (this.engine == SqlEngine.H2) {
try {
this.lock.acquire();
return this.conn;
}
catch (InterruptedException e) {
}
}
Hub.instance.getCountManager().allocateNumberCounter("dcSqlAcquireConnectionFail").increment();
return null;
}
public void releaseConnection(Connection conn) {
Hub.instance.getCountManager().allocateNumberCounter("dcSqlReleaseConnection").increment();
if (this.engine == SqlEngine.SqlServer || this.engine == SqlEngine.MariaDb || this.engine == SqlEngine.MySQL) {
if (this.poolmode) {
this.pool.add(conn);
return;
}
try {
conn.close();
}
catch (SQLException x) {
}
}
else if (this.engine == SqlEngine.H2) {
this.lock.release();
}
}
public SqlEngine getEngine() {
return this.engine;
}
public boolean testConnection() {
Connection conn = this.acquireConnection();
if (conn == null)
return false;
this.releaseConnection(conn);
return true;
}
// warning - may not use same connection between calls
public String getLastIdSql() {
if (this.engine == SqlEngine.H2)
return "SELECT IDENTITY() AS lid";
if (this.engine == SqlEngine.SqlServer)
return "SELECT @@IDENTITY AS lid";
if (this.engine == SqlEngine.MariaDb || this.engine == SqlEngine.MySQL)
return "SELECT LAST_INSERT_ID() AS lid";
return null;
}
/*
* We should always talk in UTC...
*
* @return
*/
public String nowFunc() {
if (this.getEngine() == SqlEngine.SqlServer)
return "GETUTCDATE()";
if (this.getEngine() == SqlEngine.MariaDb || this.getEngine() == SqlEngine.MySQL)
return "UTC_TIMESTAMP()";
return "NOW()";
}
// TODO
// only support MINUTES at present
public String timeUnit(TimeUnit unit) {
if (unit == TimeUnit.MINUTES)
return "MINUTE";
return null;
}
public String modNowFunc(TimeUnit unit, int amt) {
return this.modTimeFunc(this.nowFunc(), unit, amt);
}
public String modTimeFunc(String time, TimeUnit unit, int amt) {
String unitname = this.timeUnit(unit);
// h2 syntax
String expr = "DATEADD('" + unitname + "', " + amt + ", " + time + ") ";
if (this.getEngine() == SqlEngine.SqlServer)
expr = "DATEADD(" + unitname + ", " + amt + ", " + time + ") ";
if (this.getEngine() == SqlEngine.MariaDb || this.getEngine() == SqlEngine.MySQL)
expr = "DATE_ADD(" + time + ", INTERVAL " + amt + " " + unitname + ") ";
return expr;
}
public String formatColumn(String name) {
if (this.getEngine() == SqlEngine.SqlServer)
return "[" + name + "]";
if (this.getEngine() == SqlEngine.MariaDb || this.getEngine() == SqlEngine.MySQL)
return "`" + name + "`";
// TODO check what H2 uses...
return name;
}
public void processException(Exception x, OperationResult or) {
if (x instanceof SQLException) {
SQLException sx = (SQLException)x;
if (this.getEngine() == SqlEngine.MariaDb || this.getEngine() == SqlEngine.MySQL) {
// duplicate id error - this is not always an error for the caller
// we treat it as an error return code but log it as an Info
if (sx.getErrorCode() == 1062) {
or.exit(194, OperationContext.get().tr("_code_194", this.name, x));
return;
}
}
else if (this.getEngine() == SqlEngine.SqlServer) {
if (sx.getErrorCode() == 2627) {
or.exit(194, OperationContext.get().tr("_code_194", this.name, x));
return;
}
}
// TODO add other databases
or.errorTr(195, this.name, ((SQLException) x).getErrorCode(), x);
return;
}
or.errorTr(186, this.name, x);
}
public FuncResult<Integer> executeFreestyle(String sql, Object... params) {
FuncResult<Integer> res = new FuncResult<Integer>();
Connection conn = this.acquireConnection();
// if connection is bad/missing then just try again later
if (conn == null) {
res.errorTr(185, this.name);
return res;
}
try {
FuncResult<PreparedStatement> psres = this.prepStatement(conn, sql, params);
if (res.hasErrors())
return res;
PreparedStatement pstmt = psres.getResult();
Hub.instance.getCountManager().countObjects("dcSqlExecuteCount", pstmt);
res.setResult(pstmt.executeUpdate());
pstmt.close();
}
catch (Exception x) {
this.processException(x, res);
Hub.instance.getCountManager().countObjects("dcSqlExecuteFail", sql);
}
finally {
this.releaseConnection(conn);
}
return res;
}
// return a list of records where each row is a record in this collection
// -- NOTE: column names are all lower case
public FuncResult<ListStruct> executeQueryFreestyle(String sql, Object... params) {
FuncResult<ListStruct> res = new FuncResult<ListStruct>();
ListStruct list = new ListStruct();
res.setResult(list);
Connection conn = this.acquireConnection();
// if connection is bad/missing then just try again later
if (conn == null) {
res.errorTr(185, this.name);
return res;
}
ResultSet rs = null;
try {
FuncResult<PreparedStatement> psres = this.prepStatement(conn, sql, params);
if (res.hasErrors())
return res;
PreparedStatement pstmt = psres.getResult();
Hub.instance.getCountManager().countObjects("dcSqlQueryCount", pstmt);
// MariaDB hint that this turns on streaming... review TODO
//pstmt.setFetchSize(Integer.MIN_VALUE);
rs = pstmt.executeQuery();
ResultSetMetaData md = rs.getMetaData();
int columns = md.getColumnCount();
while (rs.next()) {
RecordStruct rec = new RecordStruct();
for(int i=1; i<=columns; i++)
rec.setField(md.getColumnLabel(i).toLowerCase(), rs.getObject(i));
list.addItem(rec);
}
pstmt.close();
}
catch (Exception x) {
this.processException(x, res);
Hub.instance.getCountManager().countObjects("dcSqlQueryFail", sql);
}
finally {
try {
if (rs != null)
rs.close();
}
catch (SQLException x) {
}
this.releaseConnection(conn);
}
return res;
}
// return a list of records where each row is a record in this collection
public FuncResult<ListStruct> executeQueryPage(SqlSelect[] select, String from, String where, String groupby, String orderby, int offset, int pagesize, Object... params) {
FuncResult<ListStruct> res = new FuncResult<ListStruct>();
// acquire
Connection conn = this.acquireConnection();
// if connection is bad/missing then just try again later
if (conn == null) {
res.errorTr(185, this.name);
return res;
}
// prepare
FuncResult<PreparedStatement> psres = this.prepPage(conn, select, from, where, groupby, orderby, offset, pagesize, params);
if (res.hasErrors())
return res;
PreparedStatement pstmt = psres.getResult();
// execute
res = callAndFormat(select, pstmt);
try {
if (pstmt != null)
pstmt.close();
}
catch (SQLException x) {
}
// release
this.releaseConnection(conn);
return res;
}
// return a list of records where each row is a record in this collection
public FuncResult<ListStruct> executeQueryLimit(SqlSelect[] select, String from, String where, String groupby, String orderby, int limit, boolean distinct, Object... params) {
FuncResult<ListStruct> res = new FuncResult<ListStruct>();
// acquire
Connection conn = this.acquireConnection();
// if connection is bad/missing then just try again later
if (conn == null) {
res.errorTr(185, this.name);
return res;
}
// prepare
FuncResult<PreparedStatement> psres = this.prepLimit(conn, select, from, where, groupby, orderby, limit, distinct, params);
if (res.hasErrors())
return res;
PreparedStatement pstmt = psres.getResult();
// execute
res = callAndFormat(select, pstmt);
try {
if (pstmt != null)
pstmt.close();
}
catch (SQLException x) {
}
// release
this.releaseConnection(conn);
return res;
}
// return a list of records where each row is a record in this collection
public FuncResult<ListStruct> executeQuery(SqlSelect[] select, String from, String where, String groupby, String orderby, Object... params) {
FuncResult<ListStruct> res = new FuncResult<ListStruct>();
// acquire
Connection conn = this.acquireConnection();
// if connection is bad/missing then just try again later
if (conn == null) {
res.errorTr(185, this.name);
return res;
}
// prepare
FuncResult<PreparedStatement> psres = this.prep(conn, select, from, where, groupby, orderby, params);
if (res.hasErrors())
return res;
PreparedStatement pstmt = psres.getResult();
// execute
res = this.callAndFormat(select, pstmt);
try {
if (pstmt != null)
pstmt.close();
}
catch (SQLException x) {
}
// release
this.releaseConnection(conn);
return res;
}
// return a single value (row/column) from table
public FuncResult<Struct> executeQueryScalar(SqlSelect select, String from, String where, String orderby, Object... params) {
FuncResult<Struct> res = new FuncResult<Struct>();
res.setResult(NullStruct.instance);
// acquire
Connection conn = this.acquireConnection();
// if connection is bad/missing then just try again later
if (conn == null) {
res.errorTr(185, this.name);
return res;
}
SqlSelect[] selects = new SqlSelect[] { select };
// prepare
FuncResult<PreparedStatement> psres = this.prep(conn, selects, from, where, null, orderby, params);
if (res.hasErrors())
return res;
PreparedStatement pstmt = psres.getResult();
// execute
FuncResult<ListStruct> res2 = this.callAndFormat(selects, pstmt);
ListStruct lrs = res2.getResult();
if (lrs.getSize() > 0) {
RecordStruct rec = lrs.getItemAsRecord(0);
res.setResult(rec.getField(select.name));
}
try {
if (pstmt != null)
pstmt.close();
}
catch (SQLException x) {
}
// release
this.releaseConnection(conn);
return res;
}
// return a single String value (row/column) from table
public String executeQueryString(String col, String from, String where, String orderby, Object... params) {
FuncResult<Struct> rsres = this.executeQueryScalar(new SqlSelectString(col), from, where, orderby, params);
if (rsres.hasErrors() || rsres.isEmptyResult())
return null;
return Struct.objectToString(rsres.getResult());
}
// return a single Integer value (row/column) from table
public Long executeQueryInteger(String col, String from, String where, String orderby, Object... params) {
FuncResult<Struct> rsres = this.executeQueryScalar(new SqlSelectInteger(col), from, where, orderby, params);
if (rsres.hasErrors() || rsres.isEmptyResult())
return null;
return Struct.objectToInteger(rsres.getResult());
}
// return a single Boolean value (row/column) from table
public Boolean executeQueryBoolean(String col, String from, String where, String orderby, Object... params) {
FuncResult<Struct> rsres = this.executeQueryScalar(new SqlSelectBoolean(col), from, where, orderby, params);
if (rsres.hasErrors() || rsres.isEmptyResult())
return null;
return Struct.objectToBoolean(rsres.getResult());
}
// return a single row from table
public FuncResult<RecordStruct> executeQueryRecord(SqlSelect[] selects, String from, String where, Object... params) {
FuncResult<RecordStruct> res = new FuncResult<RecordStruct>();
// acquire
Connection conn = this.acquireConnection();
// if connection is bad/missing then just try again later
if (conn == null) {
res.errorTr(185, this.name);
return res;
}
// prepare
FuncResult<PreparedStatement> psres = this.prep(conn, selects, from, where, null, null, params);
if (res.hasErrors())
return res;
PreparedStatement pstmt = psres.getResult();
// execute
FuncResult<ListStruct> res2 = this.callAndFormat(selects, pstmt);
ListStruct lrs = res2.getResult();
if (lrs.getSize() > 0)
res.setResult(lrs.getItemAsRecord(0));
else
res.setResult(new RecordStruct()); // always return something - caller should do an empty check
try {
if (pstmt != null)
pstmt.close();
}
catch (SQLException x) {
}
// release
this.releaseConnection(conn);
return res;
}
// return a single row (the first) from table
public FuncResult<RecordStruct> executeQueryRecordFirst(SqlSelect[] selects, String from, String where, String orderby, Object... params) {
FuncResult<RecordStruct> res = new FuncResult<RecordStruct>();
// acquire
Connection conn = this.acquireConnection();
// if connection is bad/missing then just try again later
if (conn == null) {
res.errorTr(185, this.name);
return res;
}
// prepare
FuncResult<PreparedStatement> psres = this.prep(conn, selects, from, where, null, orderby, params);
if (res.hasErrors())
return res;
PreparedStatement pstmt = psres.getResult();
// execute
FuncResult<ListStruct> res2 = this.callAndFormat(selects, pstmt);
ListStruct lrs = res2.getResult();
if (lrs.getSize() > 0)
res.setResult(lrs.getItemAsRecord(0));
else
res.setResult(new RecordStruct()); // always return something - caller should do an empty check
try {
if (pstmt != null)
pstmt.close();
}
catch (SQLException x) {
}
// release
this.releaseConnection(conn);
return res;
}
// return a list of records where each row is a record in this collection
public FuncResult<ListStruct> callAndFormat(SqlSelect[] select, PreparedStatement pstmt) {
FuncResult<ListStruct> res = new FuncResult<ListStruct>();
ListStruct list = new ListStruct();
res.setResult(list);
ResultSet rs = null;
try {
Hub.instance.getCountManager().countObjects("dcSqlQueryCount", pstmt);
// MariaDB hint that this turns on streaming... review TODO
//pstmt.setFetchSize(Integer.MIN_VALUE);
rs = pstmt.executeQuery();
ResultSetMetaData md = rs.getMetaData();
int columns = md.getColumnCount();
if (columns > select.length) {
res.error(1, "Mismatched column name list"); // TODO code tr
return res;
}
while (rs.next()) {
RecordStruct rec = new RecordStruct();
for(int i=1; i<=columns; i++) {
String name = select[i - 1].name;
rec.setField(name, select[i - 1].format(rs.getObject(i)));
}
list.addItem(rec);
}
}
catch (Exception x) {
this.processException(x, res);
Hub.instance.getCountManager().countObjects("dcSqlQueryFail", pstmt.toString());
}
finally {
try {
if (rs != null)
rs.close();
}
catch (SQLException x) {
}
}
return res;
}
// caller needs to close statememt
// will not return open statement and errors
public FuncResult<PreparedStatement> prepStatement(Connection conn, String sql, Object... params) {
FuncResult<PreparedStatement> res = new FuncResult<PreparedStatement>();
// if connection is bad/missing then just try again later
if (conn == null) {
res.errorTr(185, this.name);
return res;
}
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
Object param = params[i];
// null params are intentionally not supported - allows us to optionally add params to a complex query
// for NULL support see SqlNull enum
if (param == null)
continue;
if (param instanceof DateTime)
param = SqlManager.this.getDateAsString((DateTime) param);
if (param instanceof String) {
if (this.engine == SqlEngine.MariaDb || this.engine == SqlEngine.MySQL)
pstmt.setString(i + 1, (String)param);
else if (this.engine == SqlEngine.SqlServer)
pstmt.setNString(i + 1, (String)param);
else if (this.engine == SqlEngine.H2)
pstmt.setNString(i + 1, (String)param);
continue;
}
if (param instanceof BigDecimal) {
pstmt.setBigDecimal(i + 1, (BigDecimal) param);
continue;
}
if (param instanceof Double) {
pstmt.setDouble(i + 1, (double) param);
continue;
}
if (param instanceof Integer) {
pstmt.setInt(i + 1, (int) param);
continue;
}
if (param instanceof Long) {
pstmt.setLong(i + 1, (long) param);
continue;
}
if (param instanceof SqlNull) {
if (param == SqlNull.DateTime)
pstmt.setNull(i + 1, Types.DATE);
else if (param == SqlNull.VarChar) {
if (this.engine == SqlEngine.MariaDb || this.engine == SqlEngine.MySQL)
pstmt.setNull(i + 1, Types.VARCHAR);
else if (this.engine == SqlEngine.SqlServer)
pstmt.setNull(i + 1, Types.NVARCHAR);
else if (this.engine == SqlEngine.H2)
pstmt.setNull(i + 1, Types.NVARCHAR);
}
else if (param == SqlNull.BigDecimal)
pstmt.setNull(i + 1, Types.DECIMAL);
else if (param == SqlNull.Double)
pstmt.setNull(i + 1, Types.FLOAT);
else if (param == SqlNull.Int)
pstmt.setNull(i + 1, Types.INTEGER);
else if (param == SqlNull.Long)
pstmt.setNull(i + 1, Types.BIGINT);
else if (param == SqlNull.Text)
pstmt.setNull(i + 1, Types.CLOB); // TODO test
continue;
}
}
res.setResult(pstmt);
}
catch (Exception x) {
this.processException(x, res);
Hub.instance.getCountManager().countObjects("dcSqlPrepFail", sql);
try {
if (pstmt != null)
pstmt.close();
}
catch (SQLException x2) {
}
}
return res;
}
// caller needs to close statememt
public FuncResult<PreparedStatement> prepPage(Connection conn, SqlSelect[] select, String from, String where, String groupby, String orderby, int offset, int pagesize, Object... params) {
// if connection is bad/missing then just try again later
if (conn == null) {
FuncResult<PreparedStatement> res = new FuncResult<PreparedStatement>();
res.errorTr(185, this.name);
return res;
}
String sql = "SELECT ";
for (int i = 0; i < select.length; i++) {
if (i > 0)
sql += ", ";
sql += select[i].toSql(this);
}
sql += " FROM " + from;
if (StringUtil.isNotEmpty(where))
sql += " WHERE " + where;
if (StringUtil.isNotEmpty(groupby))
sql += " GROUP BY " + groupby;
if (StringUtil.isEmpty(orderby)) {
FuncResult<PreparedStatement> res = new FuncResult<PreparedStatement>();
res.error(1, "Order By required with paging");
return res;
}
if (this.engine == SqlEngine.MariaDb || this.engine == SqlEngine.MySQL) {
sql = "SELECT * FROM ( " + sql + " ) AS recset ORDER BY "
+ orderby + " LIMIT " + offset + "," + pagesize + ";";
}
else if (this.engine == SqlEngine.SqlServer) {
sql = "WITH RecordPager AS ( "
+ "SELECT *, ROW_NUMBER() OVER (ORDER BY " + orderby + ") AS RowNumber "
+ "FROM ( " + sql + " ) AS recset "
+ ") "
+ "SELECT * FROM RecordPager WHERE RowNumber BETWEEN " + (offset + 1)
+ " AND " + (offset + pagesize);
}
else if (this.engine == SqlEngine.H2) {
// TODO
}
// TODO support for other dbms
// http://en.wikipedia.org/wiki/Select_(SQL)
// http://stackoverflow.com/questions/2771439/jdbc-pagination
// http://stackoverflow.com/questions/1986998/resultset-to-pagination
// http://stackoverflow.com/questions/971964/limit-10-20-in-sqlserver
return this.prepStatement(conn, sql, params);
}
// caller needs to close statememt
public FuncResult<PreparedStatement> prepLimit(Connection conn, SqlSelect[] select, String from, String where, String groupby, String orderby, int limit, boolean distinct, Object... params) {
// if connection is bad/missing then just try again later
if (conn == null) {
FuncResult<PreparedStatement> res = new FuncResult<PreparedStatement>();
res.errorTr(185, this.name);
return res;
}
String sql = "SELECT ";
if (distinct)
sql += "DISTINCT ";
for (int i = 0; i < select.length; i++) {
if (i > 0)
sql += ", ";
sql += select[i].toSql(this);
}
sql += " FROM " + from;
if (StringUtil.isNotEmpty(where))
sql += " WHERE " + where;
if (StringUtil.isNotEmpty(groupby))
sql += " GROUP BY " + groupby;
if (StringUtil.isEmpty(orderby)) {
FuncResult<PreparedStatement> res = new FuncResult<PreparedStatement>();
res.error(1, "Order By required with limit");
return res;
}
if (this.engine == SqlEngine.MariaDb || this.engine == SqlEngine.MySQL) {
sql = "SELECT * FROM ( " + sql + " ) AS unset ORDER BY " + orderby + " LIMIT " + limit + ";";
}
else if (this.engine == SqlEngine.SqlServer) {
sql = "SELECT TOP " + limit + " * FROM ( " + sql + " ) AS unset ORDER BY " + orderby;
}
else if (this.engine == SqlEngine.H2) {
// TODO
}
return this.prepStatement(conn, sql, params);
}
// caller needs to close statememt
public FuncResult<PreparedStatement> prep(Connection conn, SqlSelect[] select, String from, String where, String groupby, String orderby, Object... params) {
// if connection is bad/missing then just try again later
if (conn == null) {
FuncResult<PreparedStatement> res = new FuncResult<PreparedStatement>();
res.errorTr(185, this.name);
return res;
}
String sql = "SELECT ";
for (int i = 0; i < select.length; i++) {
if (i > 0)
sql += ", ";
sql += select[i].toSql(this);
}
sql += " FROM " + from;
if (StringUtil.isNotEmpty(where))
sql += " WHERE " + where;
if (StringUtil.isNotEmpty(groupby))
sql += " GROUP BY " + groupby;
if (StringUtil.isNotEmpty(orderby))
sql += " ORDER BY " + orderby;
return this.prepStatement(conn, sql, params);
}
public FuncResult<Integer> executeUpdate(String sql, Object... params) {
FuncResult<Integer> res = new FuncResult<Integer>();
res.setResult(0);
// acquire
Connection conn = this.acquireConnection();
// if connection is bad/missing then just try again later
if (conn == null) {
res.errorTr(185, this.name);
return res;
}
// prep
FuncResult<PreparedStatement> psres = this.prepStatement(conn, sql, params);
if (res.hasErrors())
return res;
PreparedStatement pstmt = psres.getResult();
try {
Hub.instance.getCountManager().countObjects("dcSqlUpdateCount", pstmt);
// execute
int cnt = pstmt.executeUpdate();
res.setResult(cnt);
}
catch (Exception x) {
this.processException(x, res);
Hub.instance.getCountManager().countObjects("dcSqlUpdateFail", sql);
}
finally {
try {
if (pstmt != null)
pstmt.close();
}
catch (SQLException x) {
}
// release
this.releaseConnection(conn);
}
return res;
}
public FuncResult<Integer> executeDelete(String sql, Object... params) {
FuncResult<Integer> res = new FuncResult<Integer>();
res.setResult(0);
// acquire
Connection conn = this.acquireConnection();
// if connection is bad/missing then just try again later
if (conn == null) {
res.errorTr(185, this.name);
return res;
}
// prep
FuncResult<PreparedStatement> psres = this.prepStatement(conn, sql, params);
if (res.hasErrors())
return res;
PreparedStatement pstmt = psres.getResult();
try {
Hub.instance.getCountManager().countObjects("dcSqlDeleteCount", pstmt);
// execute
int cnt = pstmt.executeUpdate();
res.setResult(cnt);
}
catch (Exception x) {
this.processException(x, res);
Hub.instance.getCountManager().countObjects("dcSqlDeleteFail", sql);
}
finally {
try {
if (pstmt != null)
pstmt.close();
}
catch (SQLException x) {
}
// release
this.releaseConnection(conn);
}
return res;
}
public FuncResult<Long> executeInsertReturnId(String sql, Object... params) {
FuncResult<Long> res = new FuncResult<Long>();
res.setResult(0L);
// acquire
Connection conn = this.acquireConnection();
// if connection is bad/missing then just try again later
if (conn == null) {
res.errorTr(185, this.name);
return res;
}
// prep
FuncResult<PreparedStatement> psres = this.prepStatement(conn, sql, params);
if (res.hasErrors())
return res;
PreparedStatement pstmt = psres.getResult();
try {
Hub.instance.getCountManager().countObjects("dcSqlInsertCount", pstmt);
// execute
int cnt = pstmt.executeUpdate();
pstmt.close();
if (cnt == 1) {
pstmt = conn.prepareStatement(this.getLastIdSql());
ResultSet rs = pstmt.executeQuery();
if (rs.next())
res.setResult(rs.getLong("lid"));
}
}
catch (Exception x) {
this.processException(x, res);
Hub.instance.getCountManager().countObjects("dcSqlInsertFail", sql);
}
finally {
try {
if (pstmt != null)
pstmt.close();
}
catch (SQLException x) {
}
// release
this.releaseConnection(conn);
}
return res;
}
// TODO look into "get generated keys"
public FuncResult<Integer> executeInsert(String sql, Object... params) {
FuncResult<Integer> res = new FuncResult<Integer>();
res.setResult(0);
// acquire
Connection conn = this.acquireConnection();
// if connection is bad/missing then just try again later
if (conn == null) {
res.errorTr(185, this.name);
return res;
}
// prep
FuncResult<PreparedStatement> psres = this.prepStatement(conn, sql, params);
if (res.hasErrors())
return res;
PreparedStatement pstmt = psres.getResult();
try {
Hub.instance.getCountManager().countObjects("dcSqlInsertCount", pstmt);
// execute
int cnt = pstmt.executeUpdate();
pstmt.close();
res.setResult(cnt);
}
catch (Exception x) {
this.processException(x, res);
Hub.instance.getCountManager().countObjects("dcSqlInsertFail", sql);
}
finally {
try {
if (pstmt != null)
pstmt.close();
}
catch (SQLException x) {
}
// release
this.releaseConnection(conn);
}
return res;
}
}
}