comparison src/fschmidt/db/base/DbRecordImpl.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.base;
24
25 import java.sql.SQLException;
26 import java.sql.PreparedStatement;
27 import java.sql.Connection;
28 import java.util.Map;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33 import fschmidt.db.DbTable;
34 import fschmidt.db.DbKey;
35 import fschmidt.db.DbKeySetter;
36 import fschmidt.db.DbObject;
37 import fschmidt.db.DbRecord;
38 import fschmidt.db.SQLRuntimeException;
39 import fschmidt.db.DbExpression;
40 import fschmidt.db.LongKey;
41 import fschmidt.db.DbUtils;
42 import fschmidt.db.DbArcana;
43 import fschmidt.db.ListenerList;
44 import fschmidt.db.extend.DbRecordExt;
45 import fschmidt.db.extend.DbTableExt;
46 import fschmidt.db.extend.DbTransaction;
47
48
49 final class DbRecordImpl<K extends DbKey,V extends DbObject<K,V>> implements DbRecordExt<K,V> {
50 private static final Logger logger = LoggerFactory.getLogger(DbRecordImpl.class);
51
52 public static boolean hasStackTrace = false;
53 private final V obj;
54 private K key;
55 private final DbTableExt<K,V> table;
56 private Map<String,Object> fields = new HashMap<String,Object>();
57 private boolean isInDb;
58 private boolean isStale = false;
59 // private final long whenLoaded = System.currentTimeMillis();
60 private DbTransaction trans;
61
62 public DbRecordImpl(DbTableExt<K,V> table,V obj,K key) {
63 this.table = table;
64 this.obj = obj;
65 this.key = key;
66 this.isInDb = true;
67 this.trans = table.getDbDatabaseExt().getTransaction();
68 }
69
70 public DbRecordImpl(DbTableExt<K,V> table,V obj) {
71 this.table = table;
72 this.obj = obj;
73 this.key = null;
74 this.isInDb = false;
75 this.trans = table.getDbDatabaseExt().getTransaction();
76 }
77
78 public V getDbObject() {
79 return obj;
80 }
81
82 public K getPrimaryKey() {
83 return key;
84 }
85
86 public DbTable<K,V> getDbTable() {
87 return table;
88 }
89
90 public Map<String,Object> fields() {
91 return fields;
92 }
93
94 /*
95 public long whenLoaded() {
96 return whenLoaded;
97 }
98 */
99 private void checkTrans() {
100 if( trans != table.getDbDatabaseExt().getTransaction() ) {
101 if( trans==null )
102 throw new IllegalStateException("record read outside of a transaction cannot be updated inside a transaction");
103 else
104 throw new IllegalStateException("transaction mismatch");
105 }
106 }
107
108 public void insert() {
109 if( isInDb() )
110 throw new IllegalStateException("already in database");
111 try {
112 ListenerList<V> list = table.getPreInsertListeners();
113 list.event(obj);
114 store();
115 this.trans = table.getDbDatabaseExt().getTransaction();
116 final Map<String,Object> updatedFields = fields;
117 final Exception ex = hasStackTrace ? new Exception("in "+Thread.currentThread()) : null;
118 table.getDbDatabaseExt().runBeforeCommit( new Runnable() {
119 public void run() {
120 ListenerList<V> list = table.getPostInsertListeners();
121 if( !list.isEmpty() ) {
122 V obj2 = DbUtils.getGoodCopy(obj);
123 if( obj2==null ) {
124 DbRecord<K,V> rec = obj.getDbRecord();
125 K key = rec.getPrimaryKey();
126 if( ex != null )
127 logger.error("insert null in "+Thread.currentThread()+" table="+table+" key="+key, ex);
128 throw new NullPointerException("in "+Thread.currentThread()+" table="+table+" key="+key);
129 }
130 DbRecordImpl rec2 = (DbRecordImpl)obj2.getDbRecord();
131 try {
132 list.event(obj2);
133 } finally {
134 updatedFields.clear();
135 }
136 }
137 }
138 } );
139 } finally {
140 fields = new HashMap<String,Object>();
141 makeStale();
142 }
143 }
144
145 public void update() {
146 checkTrans();
147 if( !isInDb() )
148 throw new IllegalStateException("not in database");
149 try {
150 ListenerList<V> list = table.getPreUpdateListeners();
151 list.event(obj);
152 if( fields.isEmpty() ) {
153 table.uncache(key);
154 } else {
155 store();
156 }
157 final Map<String,Object> updatedFields = fields;
158 table.getDbDatabaseExt().runBeforeCommit( new Runnable() {
159 public void run() {
160 ListenerList<V> list = table.getPostUpdateListeners();
161 if( !list.isEmpty() && isInDb ) {
162 V obj2 = DbUtils.getGoodCopy(obj);
163 if( obj2==null )
164 return; // presumably deleted later in transaction
165 DbRecordImpl rec2 = (DbRecordImpl)obj2.getDbRecord();
166 try {
167 list.event(obj2);
168 } finally {
169 updatedFields.clear();
170 }
171 }
172 }
173 } );
174 } finally {
175 fields = new HashMap<String,Object>();
176 }
177 }
178
179 private void store() {
180 StringBuilder buf = new StringBuilder();
181 try {
182 DbArcana arcana = table.getDbDatabase().arcana();
183 int n = 0;
184 if( isInDb() ) { // update
185 buf.append( "update " );
186 buf.append( table.getTableName() );
187 buf.append( " set " );
188 boolean first = true;
189 for( Map.Entry<String,Object> entry : fields.entrySet() ) {
190 if( first ) {
191 first = false;
192 } else {
193 buf.append( ',' );
194 }
195 buf.append( arcana.quoteIdentifier(entry.getKey()) );
196 if( entry.getValue() instanceof DbExpression ) {
197 buf.append( '=' );
198 buf.append( entry.getValue() );
199 } else {
200 buf.append( "=?" );
201 n++;
202 }
203 }
204 } else { // insert
205 buf.append( "insert into " );
206 buf.append( table.getTableName() );
207 if( fields.isEmpty() ) {
208 buf.append( " DEFAULT VALUES" );
209 } else {
210 buf.append( " (" );
211 boolean first = true;
212 for( Map.Entry<String,Object> entry : fields.entrySet() ) {
213 if( first ) {
214 first = false;
215 } else {
216 buf.append( ',' );
217 }
218 buf.append( arcana.quoteIdentifier(entry.getKey()) );
219 }
220 buf.append( ") values (" );
221 first = true;
222 for( Map.Entry<String,Object> entry : fields.entrySet() ) {
223 if( first ) {
224 first = false;
225 } else {
226 buf.append(',');
227 }
228 if( entry.getValue() instanceof DbExpression ) {
229 buf.append( entry.getValue() );
230 } else {
231 buf.append( '?' );
232 n++;
233 }
234 }
235 buf.append( ")" );
236 }
237 }
238 Connection con = table.getDbDatabaseExt().getConnection();
239 try {
240 DbKeySetter<K> keySetter = table.getKeySetter();
241 PreparedStatement stmt;
242 if( isInDb() ) {
243 stmt = keySetter.prepareStatement(
244 con, key, buf.toString(), 1+n
245 );
246 } else {
247 stmt = con.prepareStatement( buf.toString() );
248 }
249 int idx = 1;
250 for( Map.Entry<String,Object> entry : fields.entrySet() ) {
251 Object value = entry.getValue();
252 if( value instanceof DbExpression )
253 continue;
254 try {
255 arcana.setValue(stmt,idx++,value);
256 } catch(SQLException e) {
257 throw new SQLRuntimeException("field="+entry.getKey()+" value="+value,e);
258 }
259 }
260 stmt.executeUpdate();
261 boolean wasInDb = isInDb;
262 isInDb = true;
263 stmt.close();
264 if( !wasInDb ) {
265 key = keySetter.refreshKeyAfterInsert(con,this);
266 }
267 } finally {
268 try {
269 con.close();
270 } catch(SQLException e) {
271 logger.error("couldn't close connection",e);
272 throw e;
273 } catch(RuntimeException e) {
274 logger.error("couldn't close connection",e);
275 throw e;
276 }
277 }
278 } catch(SQLException e) {
279 throw new SQLRuntimeException("key = "+key+" sql = "+buf.toString(),e);
280 } finally {
281 table.uncache(key);
282 }
283 }
284
285 public void delete() {
286 checkTrans();
287 ListenerList<V> list = table.getPreDeleteListeners();
288 list.event(obj);
289 if( !isInDb() )
290 throw new IllegalStateException("not in database");
291 try {
292 Connection con = table.getDbDatabaseExt().getConnection();
293 PreparedStatement stmt = table.getKeySetter().prepareStatement(
294 con, key, "delete from "+table.getTableName(), 1
295 );
296 stmt.executeUpdate();
297 isInDb = false;
298 stmt.close();
299 con.close();
300 /*
301 table.getDbDatabaseExt().runBeforeCommit( new Runnable() {
302 public void run() {
303 table.getPostDeleteListeners().event(obj);
304 }
305 } );
306 */
307 } catch(SQLException e) {
308 throw new SQLRuntimeException("key = "+key,e);
309 } finally {
310 table.uncache(key);
311 fields.clear();
312 }
313 table.getDbDatabaseExt().runBeforeCommit( new Runnable() {
314 public void run() {
315 ListenerList<V> list = table.getPostDeleteListeners();
316 list.event(obj);
317 }
318 } );
319 }
320
321 public boolean isStale() {
322 return isStale || (isInDb || trans!=null) && trans != table.getDbDatabaseExt().getTransaction();
323 }
324
325 public void makeStale() {
326 isStale = true;
327 }
328
329 public boolean isInDb() {
330 return isInDb;
331 }
332
333 }