diff src/fschmidt/db/cache/DbTableImpl.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/cache/DbTableImpl.java	Sun Oct 05 17:24:15 2025 -0600
@@ -0,0 +1,261 @@
+/*
+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.cache;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Collections;
+import java.util.Collection;
+import java.util.NoSuchElementException;
+import fschmidt.db.util.Unique;
+import fschmidt.db.util.WeakCacheMap;
+import fschmidt.db.DbObject;
+import fschmidt.db.DbKey;
+import fschmidt.db.DbRecord;
+import fschmidt.db.extend.DbRecordExt;
+import fschmidt.db.extend.DbTableExt;
+import fschmidt.db.extend.DbDatabaseExt;
+import fschmidt.db.extend.DbTransaction;
+import fschmidt.db.extend.FilterTable;
+
+
+final class DbTableImpl<K extends DbKey,V extends DbObject<K,V>> extends FilterTable<K,V> {
+	private static final DbObject NULL0 = new DbObject(){
+		public DbRecord getDbRecord() { throw new RuntimeException(); }
+	};
+
+	@SuppressWarnings("unchecked")
+	private final V NULL = (V)NULL0;  // hack
+
+	private static class CacheKey {
+		private final DbTableImpl table;
+		final Object key;
+
+		CacheKey(DbTableImpl table,Object key) {
+			this.table = table;
+			this.key = key;
+		}
+
+		@Override public boolean equals(Object obj) {
+			if( obj == this )
+				return true;
+			if( !(obj instanceof DbTableImpl.CacheKey) )
+				return false;
+			CacheKey ck = (CacheKey)obj;
+			return ck.table == table && ck.key.equals(key);
+		}
+
+		@Override public int hashCode() {
+			return table.hashCode() + key.hashCode();
+		}
+
+	}
+
+	private static final Map<CacheKey,DbObject> cache0 = Collections.synchronizedMap(new WeakCacheMap<CacheKey,DbObject>());
+	private final Unique<K> unique = new Unique<K>();
+	private final DbDatabaseImpl database;
+
+	DbTableImpl(DbTableExt<K,V> table,DbDatabaseImpl database) {
+		super(database,table);
+		this.database = database;
+	}
+
+	private Map<CacheKey,DbObject> transCache() {
+		Map<String,Object> transMap = database.transactionMap();
+		@SuppressWarnings("unchecked")
+		Map<CacheKey,DbObject> transCache = (Map<CacheKey,DbObject>)transMap.get("transCache");
+		if( transCache == null ) {
+			transCache = new WeakCacheMap<CacheKey,DbObject>();
+			transMap.put( "transCache", transCache );
+		}
+		return transCache;
+	}
+
+	private final boolean isStale(DbObject obj) {
+		if( obj==null )
+			return true;
+		if( obj==NULL )
+			return false;
+/*
+		if( database.isInTransaction() ) {
+			return transCache().containsKey(obj.getDbRecord().getPrimaryKey());
+		} else {
+			return obj.getDbRecord().isStale();
+		}
+*/
+		return obj.getDbRecord().isStale();
+	}
+
+	public final V findByPrimaryKey(K key) {
+		CacheKey ck = new CacheKey(this,key);
+		if( database.isInTransaction() ) {
+			Map<CacheKey,DbObject> cache = transCache();
+			@SuppressWarnings("unchecked")
+			V obj = (V)cache.get(ck);
+			if( obj==null ) {
+				obj = table.findByPrimaryKey(key);
+				if( obj==null )
+					obj = NULL;
+				cache.put(ck,obj);
+			}
+			return obj==NULL ? null : obj;
+		}
+		Map<CacheKey,DbObject> cache = cache0;
+		key = unique.get(key);
+		try {
+			synchronized(key) {
+				@SuppressWarnings("unchecked")
+				V obj = (V)cache.get(ck);
+				if( !isStale(obj) )
+					return obj==NULL ? null : obj;
+				obj = table.findByPrimaryKey(key);
+				if( !isStale( cache.put( ck, obj==null ? NULL : obj ) ) )
+					throw new RuntimeException();
+				return obj;
+			}
+		} finally {
+			unique.free(key);
+		}
+	}
+
+	public final Map<K,V> findByPrimaryKey(Collection<K> keys) {
+		Map<K,V> map = new HashMap<K,V>();
+		Set<K> set = new HashSet<K>();
+		Map<CacheKey,DbObject> cache = database.isInTransaction() ? transCache() : cache0;
+		boolean inTrans = database.isInTransaction();
+		for( K key : keys ) {
+			CacheKey ck = new CacheKey(this,key);
+			@SuppressWarnings("unchecked")
+			V obj = (V)cache.get(ck);
+			if( obj==null || !inTrans && isStale(obj) ) {
+				set.add(key);
+			} else {
+				map.put( key, obj==NULL ? null : obj );
+			}
+		}
+		if( !set.isEmpty() ) {
+			Map<K,V> objs = table.findByPrimaryKey(set);
+			for( K key : set ) {
+				CacheKey ck = new CacheKey(this,key);
+				if( inTrans ) {
+					if( cache.get(ck) != null )
+						throw new RuntimeException();
+					V obj = table.findByPrimaryKey(key);
+					if( obj==null )
+						obj = NULL;
+					cache.put(ck,obj);
+					map.put( key, obj==NULL ? null : obj );
+				} else {
+					key = unique.get(key);
+					try {
+						synchronized(key) {
+							@SuppressWarnings("unchecked")
+							V obj = (V)cache.get(ck);
+							if( isStale(obj) ) {
+								obj = objs.get(key);
+								if( obj == null )
+									obj = NULL;
+								if( !isStale( cache.put(ck,obj) ) )
+									throw new RuntimeException();
+							}
+							map.put( key, obj==NULL ? null : obj );
+						}
+					} finally {
+						unique.free(key);
+					}
+				}
+			}
+		}
+		return map;
+	}
+
+	public V getDbObject(K key,ResultSet rs,String tableName)
+		throws SQLException
+	{
+		CacheKey ck = new CacheKey(this,key);
+		if( database.isInTransaction() ) {
+			Map<CacheKey,DbObject> cache = transCache();
+			@SuppressWarnings("unchecked")
+			V obj = (V)cache.get(ck);
+			if( obj==null ) {
+				obj = table.getDbObject(key,rs,tableName);
+				if( obj==null )
+					obj = NULL;
+				cache.put(ck,obj);
+			}
+			return obj==NULL ? null : obj;
+		}
+		Map<CacheKey,DbObject> cache = cache0;
+		key = unique.get(key);
+		try {
+			synchronized(key) {
+				@SuppressWarnings("unchecked")
+				V obj = (V)cache.get(ck);
+				if( !isStale(obj) )
+					return obj==NULL ? null : obj;
+				obj = table.getDbObject(key,rs,tableName);
+				if( !isStale( cache.put( ck, obj==null ? NULL : obj ) ) )
+					throw new RuntimeException();
+				return obj;
+			}
+		} finally {
+			unique.free(key);
+		}
+	}
+
+	public void uncache(final K key) {
+		if( key == null )
+			return;
+		final CacheKey ck = new CacheKey(this,key);
+		if( database.isInTransaction() ) {
+			doUncache(ck,transCache());
+		}
+		database.runJustAfterCommit( new Runnable() {
+			public void run() {
+				doUncache(ck,cache0);
+			}
+		} );
+	}
+
+	private static void doUncache(CacheKey ck,Map<CacheKey,DbObject> cache) {
+		DbObject obj = cache.remove(ck);
+		if( obj!=null && obj!=NULL0 ) {
+			DbRecordExt rec = (DbRecordExt)obj.getDbRecord();
+			rec.makeStale();
+		}
+	}
+
+	static void clearCache() {
+		while( !cache0.isEmpty() ) {
+			CacheKey[] a = cache0.keySet().toArray(new CacheKey[0]);
+			for( CacheKey ck : a ) {
+				doUncache( ck, cache0 );
+			}
+		}
+	}
+
+}