comparison 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
comparison
equal deleted inserted replaced
67:9d0fefce6985 68:00520880ad02
1 /*
2 Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com>
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21 */
22
23 package fschmidt.db.pool;
24
25 import java.sql.Connection;
26 import java.sql.SQLException;
27 import java.sql.Statement;
28 import java.sql.ResultSet;
29 import java.util.List;
30 import java.util.ArrayList;
31 import java.util.Set;
32 import java.util.HashSet;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35 import fschmidt.db.DbDatabase;
36 import fschmidt.db.DbObject;
37 import fschmidt.db.DbKey;
38 import fschmidt.db.DbObjectFactory;
39 import fschmidt.db.SQLRuntimeException;
40 import fschmidt.db.extend.DbDatabaseExt;
41 import fschmidt.db.extend.DbTableExt;
42 import fschmidt.db.extend.DbTransaction;
43 import fschmidt.db.extend.DbRecordExt;
44 import fschmidt.db.extend.FilterDatabase;
45 import fschmidt.util.java.Stack;
46 import fschmidt.util.java.ArrayStack;
47
48
49 public final class PooledConnection {
50 private static final Logger logger = LoggerFactory.getLogger(PooledConnection.class);
51
52 private final Pool pool;
53 private Connection con;
54 private int trans = 0;
55 final List<Runnable> beforeCommitList = new ArrayList<Runnable>();
56 final List<Runnable> afterCommitList = new ArrayList<Runnable>();
57 DbTransaction dbTrans = null;
58 long lastUsed;
59 boolean ignoreRunnablesInThisTransaction = false;
60 private final Stack<NestedConnection> nesting = new ArrayStack<NestedConnection>();
61 private volatile String user = null;
62
63 PooledConnection(DbDatabaseImpl db)
64 throws SQLException
65 {
66 this.pool = db.pool;
67 this.con = db.database().getConnection();
68 }
69
70 protected void finalize() throws Throwable {
71 super.finalize();
72 if( con == null )
73 return;
74 if( !nesting.isEmpty() )
75 logger.error("connection lost from pool: opened="+nesting.size()+" trans="+trans,nesting.peek().initException);
76 try {
77 this.con.close();
78 } catch(SQLException e) {
79 } finally {
80 this.con = null;
81 }
82 }
83
84 void setUser(String user) throws SQLException {
85 if( this.user == user ) {
86 /*
87 Statement stmt = con.createStatement();
88 ResultSet rs = stmt.executeQuery("select CURRENT_USER");
89 rs.next();
90 String s = rs.getString("CURRENT_USER");
91 rs.close();
92 stmt.close();
93 if( s.equals(user) )
94 return;
95 logger.error("setUser error, user should be "+user+" but is "+s,new Exception());
96 */
97 return;
98 }
99 this.user = null;
100 Statement stmt = con.createStatement();
101 stmt.executeUpdate(
102 "set role " + user
103 );
104 stmt.close();
105 this.user = user;
106 }
107
108 Connection nest(String user) {
109 NestedConnection nc = new NestedConnection(this,user);
110 nesting.push(nc);
111 return nc.proxyCon;
112 }
113
114 private void transOver()
115 throws SQLException
116 {
117 con.setAutoCommit(true);
118 trans = 0;
119 dbTrans = null;
120 ignoreRunnablesInThisTransaction = false;
121 beforeCommitList.clear();
122 afterCommitList.clear();
123 }
124
125 void setAutoCommit(boolean autoCommit)
126 throws SQLException
127 {
128 int opened = nesting.size();
129 if( autoCommit==true ) {
130 //logger.warn("setAutoCommit true not well supported");
131 if( trans > 0 ) {
132 if( trans != opened )
133 throw new IllegalStateException("setAutoCommit to true with unclosed connections");
134 transOver();
135 return;
136 }
137 } else if( trans == 0 ) {
138 trans = opened;
139 dbTrans = new DbTransaction();
140 }
141 con.setAutoCommit(autoCommit);
142 }
143
144 void commit()
145 throws SQLException
146 {
147 int opened = nesting.size();
148 if( trans != opened )
149 throw new IllegalStateException("commit failed: trans="+trans+" opened="+opened);
150 final int opened2 = opened;
151 while( !beforeCommitList.isEmpty() ) {
152 Runnable[] a = beforeCommitList.toArray(new Runnable[0]);
153 beforeCommitList.clear();
154 for( int i=0; i<a.length; i++ ) {
155 a[i].run();
156 if( opened != opened2 ) {
157 logger.error("before commit opened="+opened+" opened2="+opened2+" runnable="+a[i]);
158 opened = opened2;
159 }
160 a[i] = null;
161 }
162 }
163 { // for now, to catch aborted transactions
164 Statement stmt = con.createStatement();
165 stmt.executeQuery("select 1");
166 stmt.close();
167 }
168 con.commit();
169 if( afterCommitList.isEmpty() ) {
170 dbTrans = new DbTransaction();
171 } else {
172 Runnable[] a = afterCommitList.toArray(new Runnable[0]);
173 setAutoCommit(true);
174 for( int i=0; i<a.length; i++ ) {
175 a[i].run();
176 if( opened != opened2 ) {
177 logger.error("after commit opened="+opened+" opened2="+opened2+" runnable="+a[i]);
178 opened = opened2;
179 }
180 a[i] = null;
181 }
182 setAutoCommit(false);
183 }
184 }
185
186 void rollback()
187 throws SQLException
188 {
189 int opened = nesting.size();
190 if( trans != opened )
191 logger.warn("rollback called in nested transaction");
192 con.rollback();
193 beforeCommitList.clear();
194 afterCommitList.clear();
195 }
196
197 void close(NestedConnection nestedCon)
198 throws SQLException
199 {
200 int i = nesting.indexOf(nestedCon);
201 if( trans==nesting.size() ) {
202 if( i != trans - 1 )
203 logger.error("closing connection outside of transaction: i="+i+" trans="+trans,new Exception(nestedCon.initException));
204 con.rollback(); // rollback everything since last commit
205 transOver();
206 }
207 if( i < trans )
208 logger.error("closing connection outside of transaction: i="+i+" trans="+trans,new Exception(nestedCon.initException));
209 nesting.remove(nestedCon);
210 if( !nesting.isEmpty() ) {
211 nesting.get(nesting.size()-1).setUser(); // best guess
212 return;
213 }
214 if( trans != 0 )
215 logger.error("trans = "+trans,new Exception());
216 pool.localCon.remove();
217 if( !beforeCommitList.isEmpty() ) {
218 logger.error("beforeCommitList = "+beforeCommitList,new Exception());
219 beforeCommitList.clear();
220 }
221 if( !afterCommitList.isEmpty() ) {
222 logger.error("afterCommitList = "+afterCommitList,new Exception());
223 afterCommitList.clear();
224 }
225 lastUsed = System.currentTimeMillis();
226 synchronized(pool) {
227 if( pool.isExpired(lastUsed) ) {
228 con.close();
229 con = null;
230 } else {
231 if( con.getAutoCommit() == false ) {
232 logger.error("autoCommit is false at close",new Exception());
233 con.setAutoCommit(true);
234 }
235 pool.stack.push(this);
236 }
237 }
238 }
239
240 Connection con() {
241 return con;
242 }
243
244 boolean isInTransaction() {
245 return dbTrans!=null;
246 }
247
248 void commitTransaction() {
249 if( !isInTransaction() )
250 throw new IllegalStateException("commitTransaction called outside of transaction");
251 try {
252 if( con.getAutoCommit()==true )
253 throw new IllegalStateException("commitTransaction called outside of transaction");
254 int opened = nesting.size();
255 if( opened < trans )
256 throw new IllegalStateException();
257 if( opened > trans )
258 throw new IllegalStateException("commitTransaction called with unclosed connections");
259 commit();
260 setAutoCommit(true);
261 } catch(SQLException e) {
262 throw new SQLRuntimeException(e);
263 }
264 }
265
266 void endTransaction() {
267 try {
268 if( con.getAutoCommit()==false && nesting.size() > trans ) {
269 logger.error("endTransaction called with unclosed connections, closing them now",new Exception(nesting.peek().initException));
270 while( nesting.size() > trans ) {
271 nesting.peek().close();
272 }
273 }
274 nesting.peek().close();
275 } catch(SQLException e) {
276 throw new SQLRuntimeException(e);
277 }
278 }
279
280 void forceClose() {
281 logger.error("connection never closed: opened="+nesting.size()+" trans="+trans+" user="+user,nesting.peek().initException);
282 pool.localCon.remove();
283 try {
284 con.close();
285 } catch(SQLException e) {
286 logger.error("",e);
287 }
288 con = null;
289 }
290
291 }