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;
+	}
+
+}