Mercurial Hosting > nabble
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/base/DbRecordImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,333 @@ +/* +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.base; + +import java.sql.SQLException; +import java.sql.PreparedStatement; +import java.sql.Connection; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import fschmidt.db.DbTable; +import fschmidt.db.DbKey; +import fschmidt.db.DbKeySetter; +import fschmidt.db.DbObject; +import fschmidt.db.DbRecord; +import fschmidt.db.SQLRuntimeException; +import fschmidt.db.DbExpression; +import fschmidt.db.LongKey; +import fschmidt.db.DbUtils; +import fschmidt.db.DbArcana; +import fschmidt.db.ListenerList; +import fschmidt.db.extend.DbRecordExt; +import fschmidt.db.extend.DbTableExt; +import fschmidt.db.extend.DbTransaction; + + +final class DbRecordImpl<K extends DbKey,V extends DbObject<K,V>> implements DbRecordExt<K,V> { + private static final Logger logger = LoggerFactory.getLogger(DbRecordImpl.class); + + public static boolean hasStackTrace = false; + private final V obj; + private K key; + private final DbTableExt<K,V> table; + private Map<String,Object> fields = new HashMap<String,Object>(); + private boolean isInDb; + private boolean isStale = false; +// private final long whenLoaded = System.currentTimeMillis(); + private DbTransaction trans; + + public DbRecordImpl(DbTableExt<K,V> table,V obj,K key) { + this.table = table; + this.obj = obj; + this.key = key; + this.isInDb = true; + this.trans = table.getDbDatabaseExt().getTransaction(); + } + + public DbRecordImpl(DbTableExt<K,V> table,V obj) { + this.table = table; + this.obj = obj; + this.key = null; + this.isInDb = false; + this.trans = table.getDbDatabaseExt().getTransaction(); + } + + public V getDbObject() { + return obj; + } + + public K getPrimaryKey() { + return key; + } + + public DbTable<K,V> getDbTable() { + return table; + } + + public Map<String,Object> fields() { + return fields; + } + +/* + public long whenLoaded() { + return whenLoaded; + } +*/ + private void checkTrans() { + if( trans != table.getDbDatabaseExt().getTransaction() ) { + if( trans==null ) + throw new IllegalStateException("record read outside of a transaction cannot be updated inside a transaction"); + else + throw new IllegalStateException("transaction mismatch"); + } + } + + public void insert() { + if( isInDb() ) + throw new IllegalStateException("already in database"); + try { + ListenerList<V> list = table.getPreInsertListeners(); + list.event(obj); + store(); + this.trans = table.getDbDatabaseExt().getTransaction(); + final Map<String,Object> updatedFields = fields; + final Exception ex = hasStackTrace ? new Exception("in "+Thread.currentThread()) : null; + table.getDbDatabaseExt().runBeforeCommit( new Runnable() { + public void run() { + ListenerList<V> list = table.getPostInsertListeners(); + if( !list.isEmpty() ) { + V obj2 = DbUtils.getGoodCopy(obj); + if( obj2==null ) { + DbRecord<K,V> rec = obj.getDbRecord(); + K key = rec.getPrimaryKey(); + if( ex != null ) + logger.error("insert null in "+Thread.currentThread()+" table="+table+" key="+key, ex); + throw new NullPointerException("in "+Thread.currentThread()+" table="+table+" key="+key); + } + DbRecordImpl rec2 = (DbRecordImpl)obj2.getDbRecord(); + try { + list.event(obj2); + } finally { + updatedFields.clear(); + } + } + } + } ); + } finally { + fields = new HashMap<String,Object>(); + makeStale(); + } + } + + public void update() { + checkTrans(); + if( !isInDb() ) + throw new IllegalStateException("not in database"); + try { + ListenerList<V> list = table.getPreUpdateListeners(); + list.event(obj); + if( fields.isEmpty() ) { + table.uncache(key); + } else { + store(); + } + final Map<String,Object> updatedFields = fields; + table.getDbDatabaseExt().runBeforeCommit( new Runnable() { + public void run() { + ListenerList<V> list = table.getPostUpdateListeners(); + if( !list.isEmpty() && isInDb ) { + V obj2 = DbUtils.getGoodCopy(obj); + if( obj2==null ) + return; // presumably deleted later in transaction + DbRecordImpl rec2 = (DbRecordImpl)obj2.getDbRecord(); + try { + list.event(obj2); + } finally { + updatedFields.clear(); + } + } + } + } ); + } finally { + fields = new HashMap<String,Object>(); + } + } + + private void store() { + StringBuilder buf = new StringBuilder(); + try { + DbArcana arcana = table.getDbDatabase().arcana(); + int n = 0; + if( isInDb() ) { // update + buf.append( "update " ); + buf.append( table.getTableName() ); + buf.append( " set " ); + boolean first = true; + for( Map.Entry<String,Object> entry : fields.entrySet() ) { + if( first ) { + first = false; + } else { + buf.append( ',' ); + } + buf.append( arcana.quoteIdentifier(entry.getKey()) ); + if( entry.getValue() instanceof DbExpression ) { + buf.append( '=' ); + buf.append( entry.getValue() ); + } else { + buf.append( "=?" ); + n++; + } + } + } else { // insert + buf.append( "insert into " ); + buf.append( table.getTableName() ); + if( fields.isEmpty() ) { + buf.append( " DEFAULT VALUES" ); + } else { + buf.append( " (" ); + boolean first = true; + for( Map.Entry<String,Object> entry : fields.entrySet() ) { + if( first ) { + first = false; + } else { + buf.append( ',' ); + } + buf.append( arcana.quoteIdentifier(entry.getKey()) ); + } + buf.append( ") values (" ); + first = true; + for( Map.Entry<String,Object> entry : fields.entrySet() ) { + if( first ) { + first = false; + } else { + buf.append(','); + } + if( entry.getValue() instanceof DbExpression ) { + buf.append( entry.getValue() ); + } else { + buf.append( '?' ); + n++; + } + } + buf.append( ")" ); + } + } + Connection con = table.getDbDatabaseExt().getConnection(); + try { + DbKeySetter<K> keySetter = table.getKeySetter(); + PreparedStatement stmt; + if( isInDb() ) { + stmt = keySetter.prepareStatement( + con, key, buf.toString(), 1+n + ); + } else { + stmt = con.prepareStatement( buf.toString() ); + } + int idx = 1; + for( Map.Entry<String,Object> entry : fields.entrySet() ) { + Object value = entry.getValue(); + if( value instanceof DbExpression ) + continue; + try { + arcana.setValue(stmt,idx++,value); + } catch(SQLException e) { + throw new SQLRuntimeException("field="+entry.getKey()+" value="+value,e); + } + } + stmt.executeUpdate(); + boolean wasInDb = isInDb; + isInDb = true; + stmt.close(); + if( !wasInDb ) { + key = keySetter.refreshKeyAfterInsert(con,this); + } + } finally { + try { + con.close(); + } catch(SQLException e) { + logger.error("couldn't close connection",e); + throw e; + } catch(RuntimeException e) { + logger.error("couldn't close connection",e); + throw e; + } + } + } catch(SQLException e) { + throw new SQLRuntimeException("key = "+key+" sql = "+buf.toString(),e); + } finally { + table.uncache(key); + } + } + + public void delete() { + checkTrans(); + ListenerList<V> list = table.getPreDeleteListeners(); + list.event(obj); + if( !isInDb() ) + throw new IllegalStateException("not in database"); + try { + Connection con = table.getDbDatabaseExt().getConnection(); + PreparedStatement stmt = table.getKeySetter().prepareStatement( + con, key, "delete from "+table.getTableName(), 1 + ); + stmt.executeUpdate(); + isInDb = false; + stmt.close(); + con.close(); +/* + table.getDbDatabaseExt().runBeforeCommit( new Runnable() { + public void run() { + table.getPostDeleteListeners().event(obj); + } + } ); +*/ + } catch(SQLException e) { + throw new SQLRuntimeException("key = "+key,e); + } finally { + table.uncache(key); + fields.clear(); + } + table.getDbDatabaseExt().runBeforeCommit( new Runnable() { + public void run() { + ListenerList<V> list = table.getPostDeleteListeners(); + list.event(obj); + } + } ); + } + + public boolean isStale() { + return isStale || (isInDb || trans!=null) && trans != table.getDbDatabaseExt().getTransaction(); + } + + public void makeStale() { + isStale = true; + } + + public boolean isInDb() { + return isInDb; + } + +}