/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 gnosygnu@gmail.com

XOWA is licensed under the terms of the General Public License (GPL) Version 3,
or alternatively under the terms of the Apache License Version 2.0.

You may use XOWA according to either of these licenses as is most appropriate
for your project on a case-by-case basis.

The terms of each license can be found in the source code repository:

GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
*/
package gplx.dbs.engines; import gplx.*; import gplx.dbs.*;
import java.sql.*; 
import gplx.core.stores.*;
import gplx.dbs.engines.*; import gplx.dbs.metas.*; import gplx.dbs.qrys.*; import gplx.dbs.sqls.*; import gplx.dbs.sqls.wtrs.*; import gplx.dbs.conn_props.*; import gplx.dbs.qrys.bats.*;
public abstract class Db_engine_sql_base implements Db_engine {
	@gplx.Internal protected void Ctor(Db_conn_info conn_info) {this.conn_info = conn_info;}
	public abstract String		Tid();
	public Db_conn_info			Conn_info() {return conn_info;} protected Db_conn_info conn_info;
	public Db_conn_props_mgr	Props() {return props;} private final    Db_conn_props_mgr props = new Db_conn_props_mgr();
	public Db_batch_mgr			Batch_mgr() {return batch_mgr;} private final    Db_batch_mgr batch_mgr = new Db_batch_mgr();
	public abstract Sql_qry_wtr	Sql_wtr();
	public abstract				Db_engine New_clone(Db_conn_info conn_info);
	public Db_rdr				Exec_as_rdr__rls_manual(Object rdr_obj, String sql)				{return New_rdr(null, rdr_obj, sql);}
	public Db_rdr				Exec_as_rdr__rls_auto(Db_stmt stmt, Object rdr_obj, String sql)	{return New_rdr(stmt, rdr_obj, sql);}
	@gplx.Virtual public 			Db_rdr New_rdr_clone() {return new Db_rdr__basic();}
	@gplx.Virtual public Db_stmt		Stmt_by_qry(Db_qry qry) {return new Db_stmt_cmd(this, qry);}
	@gplx.Virtual public void			Txn_bgn(String name)	{Exec_as_obj(Db_qry_sql.xtn_("BEGIN TRANSACTION;"));}
	@gplx.Virtual public String		Txn_end()				{Exec_as_obj(Db_qry_sql.xtn_("COMMIT TRANSACTION;")); batch_mgr.Txn_end().Run(this); return "";}
	@gplx.Virtual public void			Txn_cxl()				{Exec_as_obj(Db_qry_sql.xtn_("ROLLBACK TRANSACTION;"));}
	@gplx.Virtual public void			Txn_sav() {
		String txn_name = this.Txn_end();
		this.Txn_bgn(txn_name);
	}
	public Object		Exec_as_obj(Db_qry qry) {
		if (qry.Tid() == Db_qry_.Tid_flush) return null;	// ignore flush (delete-db) statements
		String sql = this.Sql_wtr().To_sql_str(qry, false); // DBG: Tfds.Write(sql);
		return qry.Exec_is_rdr() ? (Object)this.Exec_as_rdr(sql) : this.Exec_as_int(sql);
	}
	protected int Exec_as_int(String sql) {
		try {
			Statement cmd = New_stmt_exec(sql);	
			return cmd.executeUpdate(sql);			
		}
		catch (Exception e) {throw Err_.new_exc(e, "db", "db.engine:exec failed", "url", conn_info.Db_api(), "sql", sql);}
	}
	private DataRdr Exec_as_rdr(String sql) {
		try {
			Statement cmd = New_stmt_exec(sql);	
			cmd.execute(sql);										
			ResultSet rdr = cmd.getResultSet();	
			return New_rdr(rdr, sql);
		}
		catch (Exception e) {throw Err_.new_exc(e, "db", "db.engine:rdr failed", "url", conn_info.Db_api(), "sql", sql);}
	}
	public void Meta_tbl_create(Dbmeta_tbl_itm tbl) {Exec_as_int(tbl.To_sql_create(this.Sql_wtr()));				this.Meta_mgr().Load_all();}
	public void Meta_tbl_delete(String tbl)			{Exec_as_int(this.Sql_wtr().Schema_wtr().Bld_drop_tbl(tbl));	this.Meta_mgr().Load_all();}
	public void Meta_idx_create(Gfo_usr_dlg usr_dlg, Dbmeta_idx_itm... ary) {
		int len = ary.length;
		for (int i = 0; i < len; ++i) {
			Dbmeta_idx_itm idx = ary[i];
			usr_dlg.Plog_many("", "", "creating db index (please wait); db=~{0} idx=~{1}", conn_info.Database(), idx.Name());
			Exec_as_int(idx.To_sql_create(Sql_wtr()));
		}
		this.Meta_mgr().Load_all();
	}
	@gplx.Virtual public void Meta_idx_delete(String idx) {
		if (Meta_idx_exists(idx)) Exec_as_int("DROP INDEX " + idx);
	}
	public void Meta_fld_append(String tbl, Dbmeta_fld_itm fld) {
		Gfo_usr_dlg_.Instance.Plog_many("", "", "adding column to table: db=~{0} tbl=~{1} fld=~{2}", conn_info.Database(), tbl, fld.Name());
		try {
			Exec_as_int(this.Sql_wtr().Schema_wtr().Bld_alter_tbl_add(tbl, fld));
			Gfo_usr_dlg_.Instance.Plog_many("", "", "column added to table: db=~{0} tbl=~{1} fld=~{2}", conn_info.Database(), tbl, fld.Name());
		}
		catch (Exception e) {	// catch error if column already added to table
			Gfo_usr_dlg_.Instance.Warn_many("", "", "column not added to table: db=~{0} tbl=~{1} fld=~{2} err=~{3}", conn_info.Database(), tbl, fld.Name(), Err_.Message_gplx_full(e));
		}
		this.Meta_mgr().Load_all();
	}
	@gplx.Virtual public boolean Meta_tbl_exists(String tbl)					{return false;}
	@gplx.Virtual public boolean	Meta_fld_exists(String tbl, String fld)		{return false;}
	@gplx.Virtual public boolean	Meta_idx_exists(String idx)					{return false;}
	public abstract Dbmeta_tbl_mgr Meta_mgr();
	@gplx.Virtual public void Env_db_attach(String alias, Io_url db_url)	{}
	@gplx.Virtual public void Env_db_attach(String alias, Db_conn db_url)	{}
	@gplx.Virtual public void	Env_db_detach(String alias)					{}
	@gplx.Virtual public DataRdr New_rdr(ResultSet rdr, String sql)		{return gplx.core.stores.Db_data_rdr_.new_(rdr, sql);}
	private Db_rdr New_rdr(Db_stmt stmt, Object rdr, String sql) {
		Db_rdr__basic rv = (Db_rdr__basic)New_rdr_clone();	
		rv.Ctor(stmt, (ResultSet)rdr, sql);	
		return rv;
	}
	@gplx.Internal protected abstract Connection Conn_make();	
	private		void Batch_mgr__conn_bgn() {batch_mgr.Conn_bgn().Run(this);}
	private		void Batch_mgr__conn_end() {batch_mgr.Conn_end().Run(this);}
	private void Conn_assert() {
		this.connection = Conn_make();	// auto-open connection
		Batch_mgr__conn_bgn();
	}
		protected Connection connection;
	public void Conn_open() {connection = Conn_make();}
	public void Conn_term() {
		if (connection == null) return;	// connection never opened; just exit
		this.Batch_mgr__conn_end();
		try 	{connection.close();}
		catch 	(Exception e) {throw Err_.new_exc(e, "db", "Conn_term failed", "url", conn_info.Raw());}
		connection = null;
	}
	@Override public Object Stmt_by_sql(String sql) {
		if (connection == null) Conn_assert();
		try 	{return connection.prepareStatement(sql);}
		catch 	(Exception e) {
			throw Err_.new_exc(e, "db", "New_stmt_prep failed", "sql", sql);
			}
	}
	private Statement New_stmt_exec(String sql) {
		if (connection == null) Conn_assert();
		try 	{return connection.createStatement();}
		catch 	(Exception e) {throw Err_.new_exc(e, "db", "New_stmt_exec failed", "sql", sql);}
	}
	protected Connection Conn_make_by_url(String url, String uid, String pwd) {
		try {return DriverManager.getConnection(url, uid, pwd);}
		catch (SQLException e) {throw Err_.new_exc(e, "db", "connection open failed", "info", Conn_info().Raw());}
	}
	protected Connection Conn__new_by_url_and_props(String url, Keyval... props) {
		try {
			java.util.Properties properties = new java.util.Properties();
			for (Keyval prop : props)
				properties.setProperty(prop.Key(), prop.Val_to_str_or_empty());
			return DriverManager.getConnection(url, properties);
		}
		catch (SQLException e) {throw Err_.new_exc(e, "db", "connection open failed", "info", Conn_info().Raw());}
	}
	}
