diff src/fschmidt/db/pool/PooledConnection.java @ 68:00520880ad02

add fschmidt source
author Franklin Schmidt <fschmidt@gmail.com>
date Sun, 05 Oct 2025 17:24:15 -0600
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/fschmidt/db/pool/PooledConnection.java	Sun Oct 05 17:24:15 2025 -0600
@@ -0,0 +1,291 @@
+/*
+Copyright (c) 2008  Franklin Schmidt <fschmidt@gmail.com>
+
+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 fschmidt.db.pool;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.ResultSet;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.HashSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbObject;
+import fschmidt.db.DbKey;
+import fschmidt.db.DbObjectFactory;
+import fschmidt.db.SQLRuntimeException;
+import fschmidt.db.extend.DbDatabaseExt;
+import fschmidt.db.extend.DbTableExt;
+import fschmidt.db.extend.DbTransaction;
+import fschmidt.db.extend.DbRecordExt;
+import fschmidt.db.extend.FilterDatabase;
+import fschmidt.util.java.Stack;
+import fschmidt.util.java.ArrayStack;
+
+
+public final class PooledConnection {
+	private static final Logger logger = LoggerFactory.getLogger(PooledConnection.class);
+
+	private final Pool pool;
+	private Connection con;
+	private int trans = 0;
+	final List<Runnable> beforeCommitList = new ArrayList<Runnable>();
+	final List<Runnable> afterCommitList = new ArrayList<Runnable>();
+	DbTransaction dbTrans = null;
+	long lastUsed;
+	boolean ignoreRunnablesInThisTransaction = false;
+	private final Stack<NestedConnection> nesting = new ArrayStack<NestedConnection>();
+	private volatile String user = null;
+
+	PooledConnection(DbDatabaseImpl db)
+		throws SQLException
+	{
+		this.pool = db.pool;
+		this.con = db.database().getConnection();
+	}
+
+	protected void finalize() throws Throwable {
+		super.finalize();
+		if( con == null )
+			return;
+		if( !nesting.isEmpty() )
+			logger.error("connection lost from pool: opened="+nesting.size()+" trans="+trans,nesting.peek().initException);
+		try {
+			this.con.close();
+		} catch(SQLException e) {
+		} finally {
+			this.con = null;
+		}
+	}
+
+	void setUser(String user) throws SQLException {
+		if( this.user == user ) {
+/*
+			Statement stmt = con.createStatement();
+			ResultSet rs = stmt.executeQuery("select CURRENT_USER");
+			rs.next();
+			String s = rs.getString("CURRENT_USER");
+			rs.close();
+			stmt.close();
+			if( s.equals(user) )
+				return;
+			logger.error("setUser error, user should be "+user+" but is "+s,new Exception());
+*/
+			return;
+		}
+		this.user = null;
+		Statement stmt = con.createStatement();
+		stmt.executeUpdate(
+			"set role " + user
+		);
+		stmt.close();
+		this.user = user;
+	}
+
+	Connection nest(String user) {
+		NestedConnection nc = new NestedConnection(this,user);
+		nesting.push(nc);
+		return nc.proxyCon;
+	}
+
+	private void transOver()
+		throws SQLException
+	{
+		con.setAutoCommit(true);
+		trans = 0;
+		dbTrans = null;
+		ignoreRunnablesInThisTransaction = false;
+		beforeCommitList.clear();
+		afterCommitList.clear();
+	}
+
+	void setAutoCommit(boolean autoCommit)
+		throws SQLException
+	{
+		int opened = nesting.size();
+		if( autoCommit==true ) {
+			//logger.warn("setAutoCommit true not well supported");
+			if( trans > 0 ) {
+				if( trans != opened )
+					throw new IllegalStateException("setAutoCommit to true with unclosed connections");
+				transOver();
+				return;
+			}
+		} else if( trans == 0 ) {
+			trans = opened;
+			dbTrans = new DbTransaction();
+		}
+		con.setAutoCommit(autoCommit);
+	}
+
+	void commit()
+		throws SQLException
+	{
+		int opened = nesting.size();
+		if( trans != opened )
+			throw new IllegalStateException("commit failed: trans="+trans+" opened="+opened);
+		final int opened2 = opened;
+		while( !beforeCommitList.isEmpty() ) {
+			Runnable[] a = beforeCommitList.toArray(new Runnable[0]);
+			beforeCommitList.clear();
+			for( int i=0; i<a.length; i++ ) {
+				a[i].run();
+				if( opened != opened2 ) {
+					logger.error("before commit opened="+opened+" opened2="+opened2+" runnable="+a[i]);
+					opened = opened2;
+				}
+				a[i] = null;
+			}
+		}
+		{	// for now, to catch aborted transactions
+			Statement stmt = con.createStatement();
+			stmt.executeQuery("select 1");
+			stmt.close();
+		}
+		con.commit();
+		if( afterCommitList.isEmpty() ) {
+			dbTrans = new DbTransaction();
+		} else {
+			Runnable[] a = afterCommitList.toArray(new Runnable[0]);
+			setAutoCommit(true);
+			for( int i=0; i<a.length; i++ ) {
+				a[i].run();
+				if( opened != opened2 ) {
+					logger.error("after commit opened="+opened+" opened2="+opened2+" runnable="+a[i]);
+					opened = opened2;
+				}
+				a[i] = null;
+			}
+			setAutoCommit(false);
+		}
+	}
+
+	void rollback()
+		throws SQLException
+	{
+		int opened = nesting.size();
+		if( trans != opened )
+			logger.warn("rollback called in nested transaction");
+		con.rollback();
+		beforeCommitList.clear();
+		afterCommitList.clear();
+	}
+
+	void close(NestedConnection nestedCon)
+		throws SQLException
+	{
+		int i = nesting.indexOf(nestedCon);
+		if( trans==nesting.size() ) {
+			if( i != trans - 1 )
+				logger.error("closing connection outside of transaction: i="+i+" trans="+trans,new Exception(nestedCon.initException));
+			con.rollback();  // rollback everything since last commit
+			transOver();
+		}
+		if( i < trans )
+			logger.error("closing connection outside of transaction: i="+i+" trans="+trans,new Exception(nestedCon.initException));
+		nesting.remove(nestedCon);
+		if( !nesting.isEmpty() ) {
+			nesting.get(nesting.size()-1).setUser();  // best guess
+			return;
+		}
+		if( trans != 0 )
+			logger.error("trans = "+trans,new Exception());
+		pool.localCon.remove();
+		if( !beforeCommitList.isEmpty() ) {
+			logger.error("beforeCommitList = "+beforeCommitList,new Exception());
+			beforeCommitList.clear();
+		}
+		if( !afterCommitList.isEmpty() ) {
+			logger.error("afterCommitList = "+afterCommitList,new Exception());
+			afterCommitList.clear();
+		}
+		lastUsed = System.currentTimeMillis();
+		synchronized(pool) {
+			if( pool.isExpired(lastUsed) ) {
+				con.close();
+				con = null;
+			} else {
+				if( con.getAutoCommit() == false ) {
+					logger.error("autoCommit is false at close",new Exception());
+					con.setAutoCommit(true);
+				}
+				pool.stack.push(this);
+			}
+		}
+	}
+
+	Connection con() {
+		return con;
+	}
+
+	boolean isInTransaction() {
+		return dbTrans!=null;
+	}
+
+	void commitTransaction() {
+		if( !isInTransaction() )
+			throw new IllegalStateException("commitTransaction called outside of transaction"); 
+		try {
+			if( con.getAutoCommit()==true )
+				throw new IllegalStateException("commitTransaction called outside of transaction"); 
+			int opened = nesting.size();
+			if( opened < trans )
+				throw new IllegalStateException(); 
+			if( opened > trans )
+				throw new IllegalStateException("commitTransaction called with unclosed connections"); 
+			commit();
+			setAutoCommit(true);
+		} catch(SQLException e) {
+			throw new SQLRuntimeException(e);
+		}
+	}
+
+	void endTransaction() {
+		try {
+			if( con.getAutoCommit()==false && nesting.size() > trans ) {
+				logger.error("endTransaction called with unclosed connections, closing them now",new Exception(nesting.peek().initException));
+				while( nesting.size() > trans ) {
+					nesting.peek().close();
+				}
+			}
+			nesting.peek().close();
+		} catch(SQLException e) {
+			throw new SQLRuntimeException(e);
+		}
+	}
+
+	void forceClose() {
+		logger.error("connection never closed: opened="+nesting.size()+" trans="+trans+" user="+user,nesting.peek().initException);
+		pool.localCon.remove();
+		try {
+			con.close();
+		} catch(SQLException e) {
+			logger.error("",e);
+		}
+		con = null;
+	}
+
+}