mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
Candidate release of source code.
This commit is contained in:
parent
db81e6b3b0
commit
79d8f164f8
12449 changed files with 2800756 additions and 16 deletions
962
Ghidra/Framework/DB/src/main/java/db/DBHandle.java
Normal file
962
Ghidra/Framework/DB/src/main/java/db/DBHandle.java
Normal file
|
@ -0,0 +1,962 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package db;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Iterator;
|
||||
|
||||
import db.buffers.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.UniversalIdGenerator;
|
||||
import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||
import ghidra.util.datastruct.WeakSet;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* <code>DBHandle</code> provides access to an open database.
|
||||
*/
|
||||
public class DBHandle {
|
||||
|
||||
protected BufferMgr bufferMgr;
|
||||
private DBParms dbParms;
|
||||
private MasterTable masterTable;
|
||||
private Hashtable<String, Table> tables;
|
||||
|
||||
private long databaseId; // Universal database identifier introduced with Ghidra 2.3
|
||||
|
||||
private DBHandle scratchPad;
|
||||
|
||||
private WeakSet<DBListener> listenerList = WeakDataStructureFactory.createCopyOnReadWeakSet();
|
||||
|
||||
private long lastTransactionID;
|
||||
private boolean txStarted = false;
|
||||
private boolean waitingForNewTransaction = false;
|
||||
|
||||
private long checkpointNum;
|
||||
private long lastRecoverySnapshotId;
|
||||
|
||||
/**
|
||||
* Construct a temporary database handle.
|
||||
* The saveAs method must be used to save the database.
|
||||
*/
|
||||
public DBHandle() throws IOException {
|
||||
this(BufferMgr.DEFAULT_BUFFER_SIZE, BufferMgr.DEFAULT_CACHE_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a temporary database handle.
|
||||
* The saveAs method must be used to save the database.
|
||||
*/
|
||||
public DBHandle(int requestBufferSize) throws IOException {
|
||||
this(requestBufferSize, BufferMgr.DEFAULT_CACHE_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a temporary database handle.
|
||||
* The saveAs method must be used to save the database.
|
||||
*/
|
||||
public DBHandle(int requestBufferSize, long approxCacheSize) throws IOException {
|
||||
bufferMgr =
|
||||
new BufferMgr(requestBufferSize, approxCacheSize, BufferMgr.DEFAULT_CHECKPOINT_COUNT);
|
||||
dbParms = new DBParms(bufferMgr, true);
|
||||
dbParms.set(DBParms.MASTER_TABLE_ROOT_BUFFER_ID_PARM, -1);
|
||||
masterTable = new MasterTable(this);
|
||||
initDatabaseId();
|
||||
bufferMgr.clearCheckpoints();
|
||||
tables = new Hashtable<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the database contained within the specified
|
||||
* bufferFile. The update mode is determined by the buffer file.
|
||||
* @param bufferFile database buffer file
|
||||
* @throws IOException if IO error occurs
|
||||
*/
|
||||
public DBHandle(BufferFile bufferFile) throws IOException {
|
||||
bufferMgr = new BufferMgr(bufferFile);
|
||||
dbParms = new DBParms(bufferMgr, false);
|
||||
readDatabaseId();
|
||||
if (databaseId == 0 && bufferMgr.canSave()) {
|
||||
// Database is updatable - establish missing databaseId
|
||||
initDatabaseId();
|
||||
bufferMgr.clearCheckpoints();
|
||||
}
|
||||
masterTable = new MasterTable(this);
|
||||
loadTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the database contained within the specified
|
||||
* bufferFile. The update mode is determined by the buffer file.
|
||||
* @param bufferFile database buffer file
|
||||
* @param recover if true an attempt will be made to recover unsaved data if the file is open for update
|
||||
* @param monitor recovery monitor
|
||||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException
|
||||
*/
|
||||
public DBHandle(BufferFile bufferFile, boolean recover, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
bufferMgr = new BufferMgr(bufferFile);
|
||||
if (bufferMgr.canSave()) {
|
||||
if (recover) {
|
||||
bufferMgr.recover(monitor);
|
||||
}
|
||||
else {
|
||||
bufferMgr.clearRecoveryFiles();
|
||||
}
|
||||
}
|
||||
dbParms = new DBParms(bufferMgr, false);
|
||||
readDatabaseId();
|
||||
if (databaseId == 0 && bufferMgr.canSave()) {
|
||||
// Database is updatable - establish missing databaseId
|
||||
initDatabaseId();
|
||||
}
|
||||
bufferMgr.clearCheckpoints();
|
||||
masterTable = new MasterTable(this);
|
||||
loadTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a specific buffer file containing a database
|
||||
* for non-update use. This method is provided primarily
|
||||
* for testing.
|
||||
* @param file buffer file
|
||||
* @throws IOException
|
||||
*/
|
||||
public DBHandle(File file) throws IOException {
|
||||
BufferFile bfile = new LocalBufferFile(file, true);
|
||||
boolean success = false;
|
||||
try {
|
||||
bufferMgr = new BufferMgr(bfile);
|
||||
dbParms = new DBParms(bufferMgr, false);
|
||||
readDatabaseId();
|
||||
masterTable = new MasterTable(this);
|
||||
loadTables();
|
||||
success = true;
|
||||
}
|
||||
finally {
|
||||
if (!success) {
|
||||
bfile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the consistency of this database.
|
||||
* @return true if consistency check passed, else false
|
||||
* @throws CancelledException
|
||||
*/
|
||||
public boolean isConsistent(TaskMonitor monitor) throws CancelledException {
|
||||
int consistentCount = 0;
|
||||
for (Table table : getTables()) {
|
||||
try {
|
||||
if (table.isConsistent(monitor)) {
|
||||
++consistentCount;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this,
|
||||
"Consistency check error while processing table: " + table.getName(), e);
|
||||
}
|
||||
}
|
||||
return consistentCount == tables.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild database tables to resolve certain consistency problems. Use of this
|
||||
* method does not recover lost data which may have occurred during original
|
||||
* database corruption.
|
||||
* @return true if rebuild succeeded, else false
|
||||
* @throws CancelledException
|
||||
*/
|
||||
public boolean rebuild(TaskMonitor monitor) throws CancelledException {
|
||||
for (Table table : getTables()) {
|
||||
try {
|
||||
table.rebuild(monitor);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this, "Rebuild failed while processing table: " + table.getName(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the database ID contained within the specified database file.
|
||||
* This method is intended to be used when unpacking a packed database
|
||||
* to ensure that a duplicate database ID does not exist within the project.
|
||||
* WARNING! Use with extreme caution since this modifies
|
||||
* the original file and could destroy data if used
|
||||
* improperly.
|
||||
* @param file
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void resetDatabaseId(File file) throws IOException {
|
||||
long databaseId = UniversalIdGenerator.nextID().getValue();
|
||||
DBParms.poke(file, DBParms.DATABASE_ID_HIGH_PARM, (int) (databaseId >> 32));
|
||||
DBParms.poke(file, DBParms.DATABASE_ID_LOW_PARM, (int) databaseId);
|
||||
}
|
||||
|
||||
private void setDatabaseId(long id) throws IOException {
|
||||
databaseId = id;
|
||||
dbParms.set(DBParms.DATABASE_ID_HIGH_PARM, (int) (databaseId >> 32));
|
||||
dbParms.set(DBParms.DATABASE_ID_LOW_PARM, (int) databaseId);
|
||||
}
|
||||
|
||||
private void initDatabaseId() throws IOException {
|
||||
setDatabaseId(UniversalIdGenerator.nextID().getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read current databaseId
|
||||
* @throws IOException
|
||||
*/
|
||||
private void readDatabaseId() throws IOException {
|
||||
try {
|
||||
databaseId = ((long) dbParms.get(DBParms.DATABASE_ID_HIGH_PARM) << 32) +
|
||||
(dbParms.get(DBParms.DATABASE_ID_LOW_PARM) & 0x0ffffffffL);
|
||||
}
|
||||
catch (ArrayIndexOutOfBoundsException e) {
|
||||
// DBParams is still at version 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns unique database ID or 0 if this is an older read-only database.
|
||||
*/
|
||||
public long getDatabaseId() {
|
||||
return databaseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the recovery changeSet data file for reading or null if one is not available.
|
||||
* The caller must dispose of the returned file before peforming generating any new
|
||||
* recovery snapshots.
|
||||
* @throws IOException
|
||||
*/
|
||||
public LocalBufferFile getRecoveryChangeSetFile() throws IOException {
|
||||
return bufferMgr.getRecoveryChangeSetFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a recovery snapshot be taken of any unsaved changes;
|
||||
* @param changeSet an optional database-backed change set which reflects changes
|
||||
* made since the last version.
|
||||
* @param monitor task monitor
|
||||
* @return true if snapshot successful or not needed, false if an active transaction prevented snapshot
|
||||
* @throws CancelledException if cancelled by monitor
|
||||
* @throws IOException
|
||||
*/
|
||||
public boolean takeRecoverySnapshot(DBChangeSet changeSet, TaskMonitor monitor)
|
||||
throws CancelledException, IOException {
|
||||
long cpNum;
|
||||
synchronized (this) {
|
||||
if (!bufferMgr.modifiedSinceSnapshot()) {
|
||||
return true;
|
||||
}
|
||||
if (txStarted) {
|
||||
return false;
|
||||
}
|
||||
if (lastRecoverySnapshotId == checkpointNum) {
|
||||
return true;
|
||||
}
|
||||
cpNum = checkpointNum;
|
||||
}
|
||||
if (!bufferMgr.takeRecoverySnapshot(changeSet, monitor)) {
|
||||
return false;
|
||||
}
|
||||
synchronized (this) {
|
||||
lastRecoverySnapshotId = cpNum;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a shared temporary database handle.
|
||||
* This temporary handle will remain open unitl either this
|
||||
* handle is closed or closeScratchPad is invoked.
|
||||
* @throws IOException
|
||||
*/
|
||||
public DBHandle getScratchPad() throws IOException {
|
||||
if (scratchPad == null) {
|
||||
scratchPad = new DBHandle();
|
||||
scratchPad.startTransaction();
|
||||
}
|
||||
return scratchPad;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the scratch-pad database handle if it open.
|
||||
*/
|
||||
public void closeScratchPad() {
|
||||
if (scratchPad != null) {
|
||||
scratchPad.close();
|
||||
scratchPad = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Database listener
|
||||
* @param listener
|
||||
*/
|
||||
public void addListener(DBListener listener) {
|
||||
listenerList.add(listener);
|
||||
}
|
||||
|
||||
private void dbRestored() {
|
||||
for (DBListener listener : listenerList) {
|
||||
listener.dbRestored(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void dbClosed() {
|
||||
for (DBListener listener : listenerList) {
|
||||
listener.dbClosed(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void tableAdded(Table table) {
|
||||
for (DBListener listener : listenerList) {
|
||||
listener.tableAdded(this, table);
|
||||
}
|
||||
}
|
||||
|
||||
void tableDeleted(Table table) {
|
||||
for (DBListener listener : listenerList) {
|
||||
listener.tableDeleted(this, table);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the master database table
|
||||
*/
|
||||
MasterTable getMasterTable() {
|
||||
return masterTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the buffer manager
|
||||
*/
|
||||
BufferMgr getBufferMgr() {
|
||||
return bufferMgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable and start source file pre-cache if appropriate.
|
||||
* WARNING! EXPERIMENTAL !!!
|
||||
*/
|
||||
public void enablePreCache() {
|
||||
bufferMgr.enablePreCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the database parameters
|
||||
*/
|
||||
DBParms getDBParms() {
|
||||
return dbParms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a valid transaction has been started.
|
||||
* @throws NoTransactionException if transaction has not been started
|
||||
* @throws TerminatedTransactionException transaction was prematurely terminated
|
||||
*/
|
||||
public void checkTransaction() {
|
||||
if (!txStarted) {
|
||||
if (waitingForNewTransaction) {
|
||||
throw new TerminatedTransactionException();
|
||||
}
|
||||
throw new NoTransactionException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if transaction is currently active
|
||||
*/
|
||||
protected boolean isTransactionActive() {
|
||||
return txStarted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new transaction
|
||||
* @return transaction ID
|
||||
*/
|
||||
public synchronized long startTransaction() {
|
||||
if (txStarted) {
|
||||
throw new IllegalStateException("Transaction already started");
|
||||
}
|
||||
waitingForNewTransaction = false;
|
||||
txStarted = true;
|
||||
return ++lastTransactionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate transaction. If commit is false, Table instances may be added
|
||||
* or removed/invalidated.
|
||||
* @param id transaction ID
|
||||
* @param commit if true a new checkpoint will be established, if
|
||||
* false all changes since the previous checkpoint will be discarded.
|
||||
* @return true if new checkpoint established.
|
||||
* @throws IOException
|
||||
*/
|
||||
public synchronized boolean endTransaction(long id, boolean commit) throws IOException {
|
||||
if (id != lastTransactionID) {
|
||||
throw new IllegalStateException("Transaction id is not active");
|
||||
}
|
||||
try {
|
||||
if (bufferMgr != null && !bufferMgr.atCheckpoint()) {
|
||||
if (commit) {
|
||||
masterTable.flush();
|
||||
if (bufferMgr.checkpoint()) {
|
||||
++checkpointNum;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// rollback
|
||||
bufferMgr.undo(false);
|
||||
reloadTables();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
txStarted = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are uncommitted changes to the database.
|
||||
* @return true if there are uncommitted changes to the database.
|
||||
*/
|
||||
public synchronized boolean hasUncommittedChanges() {
|
||||
return (bufferMgr != null && !bufferMgr.atCheckpoint());
|
||||
}
|
||||
|
||||
public synchronized void terminateTransaction(long id, boolean commit) throws IOException {
|
||||
endTransaction(id, commit);
|
||||
waitingForNewTransaction = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if there are any changes which can be undone.
|
||||
* @return true if an undo can be performed.
|
||||
*/
|
||||
public boolean canUndo() {
|
||||
return !txStarted && bufferMgr != null && bufferMgr.hasUndoCheckpoints();
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo changes made during the previous transaction checkpoint.
|
||||
* All upper-levels must clear table-based cached data prior to
|
||||
* invoking this method.
|
||||
* @return true if an undo was successful
|
||||
* @throws IOException
|
||||
*/
|
||||
public synchronized boolean undo() throws IOException {
|
||||
if (canUndo() && bufferMgr.undo(true)) {
|
||||
++checkpointNum;
|
||||
reloadTables();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of undo-able transactions
|
||||
*/
|
||||
public int getAvailableUndoCount() {
|
||||
return bufferMgr != null ? bufferMgr.getAvailableUndoCount() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of redo-able transactions
|
||||
*/
|
||||
public int getAvailableRedoCount() {
|
||||
return bufferMgr != null ? bufferMgr.getAvailableRedoCount() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if there are any changes which can be redone
|
||||
* @return true if a redo can be performed.
|
||||
*/
|
||||
public boolean canRedo() {
|
||||
return !txStarted && bufferMgr != null && bufferMgr.hasRedoCheckpoints();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redo previously undone transaction checkpoint.
|
||||
* Moves forward by one checkpoint only.
|
||||
* All upper-levels must clear table-based cached data prior to
|
||||
* invoking this method.
|
||||
* @return boolean
|
||||
* @throws IOException
|
||||
*/
|
||||
public synchronized boolean redo() throws IOException {
|
||||
if (canRedo() && bufferMgr.redo()) {
|
||||
++checkpointNum;
|
||||
reloadTables();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of undo transaction checkpoints maintained by the
|
||||
* underlying buffer manager.
|
||||
* @param maxUndos maximum number of undo checkpoints. An illegal
|
||||
* value restores the default value.
|
||||
*/
|
||||
public synchronized void setMaxUndos(int maxUndos) {
|
||||
bufferMgr.setMaxUndos(maxUndos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of tables defined within the master table.
|
||||
* @return int number of tables.
|
||||
*/
|
||||
public int getTableCount() {
|
||||
return tables.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert the current database version to an older version.
|
||||
* @param oldVersion
|
||||
* @param monitor
|
||||
* @return boolean
|
||||
* @throws IllegalStateException if the database has modified prior to
|
||||
* invoking this method.
|
||||
* @throws IllegalArgumentException if this method is invoked more than
|
||||
* once or the version file(s) are corrupt.
|
||||
*/
|
||||
// boolean revert(int oldVersion, TaskMonitor monitor) throws IOException {
|
||||
// for (int v = (version-1); v >= oldVersion; --v) {
|
||||
// monitor.setMessage("Processing Version " + v);
|
||||
// bufferMgr.applyVersionFile(db.getVersionFile(v), monitor);
|
||||
// if (monitor.isCancelled())
|
||||
// return false;
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Close the database and dispose of the underlying buffer manager.
|
||||
* Any existing recovery data will be discarded.
|
||||
*/
|
||||
public synchronized void close() {
|
||||
close(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database and dispose of the underlying buffer manager.
|
||||
* @param keepRecoveryData
|
||||
*/
|
||||
public synchronized void close(boolean keepRecoveryData) {
|
||||
closeScratchPad();
|
||||
if (bufferMgr != null) {
|
||||
dbClosed();
|
||||
bufferMgr.dispose(keepRecoveryData);
|
||||
bufferMgr = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if unsaved changes have been made.
|
||||
*/
|
||||
public synchronized boolean isChanged() {
|
||||
return bufferMgr != null && bufferMgr.isChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this database handle has been closed.
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return bufferMgr == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save this database to a new version.
|
||||
* @param comment if version history is maintained, this comment will be
|
||||
* associated with the new version.
|
||||
* @param changeSet an optional database-backed change set which reflects changes
|
||||
* made since the last version.
|
||||
* @param monitor progress monitor
|
||||
* @throws CancelledException if task monitor cancelled operation.
|
||||
* @throws IOException thrown if an IO error occurs.
|
||||
*/
|
||||
public synchronized void save(String comment, DBChangeSet changeSet, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
||||
//TODO: Does not throw ReadOnlyException - should it?
|
||||
|
||||
if (txStarted)
|
||||
throw new AssertException("Can't save during transaction");
|
||||
|
||||
long txId = startTransaction();
|
||||
try {
|
||||
masterTable.flush();
|
||||
}
|
||||
finally {
|
||||
endTransaction(txId, true); // saved file may be corrupt on IOException
|
||||
}
|
||||
|
||||
bufferMgr.save(comment, changeSet, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the database to the specified buffer file.
|
||||
* @param outFile buffer file open for writing
|
||||
* @param associateWithNewFile if true the outFile will be associated with this DBHandle as the
|
||||
* current source file, if false no change will be made to this DBHandle's state and the outFile
|
||||
* will be written and set as read-only. The caller is responsbile for disposing the outFile if
|
||||
* this parameter is false.
|
||||
* @param monitor progress monitor
|
||||
* @param associateWithNewFile if true this handle will be associated with the new file
|
||||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException if monitor cancels operation
|
||||
*/
|
||||
public synchronized void saveAs(BufferFile outFile, boolean associateWithNewFile,
|
||||
TaskMonitor monitor) throws IOException, CancelledException {
|
||||
|
||||
if (txStarted)
|
||||
throw new AssertException("Can't save during transaction");
|
||||
|
||||
long txId = startTransaction();
|
||||
boolean addedTx = false;
|
||||
try {
|
||||
// About to create copy of existing file - assign new databaseId
|
||||
if (bufferMgr.getSourceFile() != null) {
|
||||
initDatabaseId();
|
||||
}
|
||||
masterTable.flush();
|
||||
}
|
||||
finally {
|
||||
addedTx = endTransaction(txId, true); // saved file may be corrupt on IOException
|
||||
}
|
||||
|
||||
bufferMgr.saveAs(outFile, associateWithNewFile, monitor);
|
||||
|
||||
if (addedTx && !associateWithNewFile) {
|
||||
// Restore state and original databaseId
|
||||
undo();
|
||||
readDatabaseId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the database to the specified buffer file and a newDatabaseId.
|
||||
* Open handle will always be associated with the new file.
|
||||
* NOTE: This method is intended for use in transforming one database to
|
||||
* match another existing database.
|
||||
* @param outFile buffer file open for writing
|
||||
* @param newDatabaseId database ID to be forced for new database or null to generate
|
||||
* new database ID
|
||||
* @param monitor progress monitor
|
||||
* @param associateWithNewFile if true this handle will be associated with the new file
|
||||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException if monitor cancels operation
|
||||
*/
|
||||
protected synchronized void saveAs(BufferFile outFile, Long newDatabaseId, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
||||
if (txStarted)
|
||||
throw new IllegalStateException("Can't save during transaction");
|
||||
|
||||
long txId = startTransaction();
|
||||
try {
|
||||
if (newDatabaseId == null) {
|
||||
initDatabaseId();
|
||||
}
|
||||
else if (databaseId != newDatabaseId.longValue()) {
|
||||
setDatabaseId(newDatabaseId);
|
||||
}
|
||||
masterTable.flush();
|
||||
}
|
||||
finally {
|
||||
endTransaction(txId, true); // saved file may be corrupt on IOException
|
||||
}
|
||||
|
||||
bufferMgr.saveAs(outFile, true, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the database to the specified buffer file.
|
||||
* @param file buffer file to be created
|
||||
* @param associateWithNewFile if true the outFile will be associated with this DBHandle as the
|
||||
* current source file, if false no change will be made to this DBHandle's state and the outFile
|
||||
* will be written and set as read-only. The caller is responsbile for disposing the outFile if
|
||||
* this parameter is false.
|
||||
* @param monitor progress monitor
|
||||
* @throws DuplicateFileException if file already exists.
|
||||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException if monitor cancels operation
|
||||
*/
|
||||
public synchronized void saveAs(File file, boolean associateWithNewFile, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
||||
if (file.exists()) {
|
||||
throw new DuplicateFileException("File already exists: " + file);
|
||||
}
|
||||
|
||||
LocalBufferFile outFile = new LocalBufferFile(file, bufferMgr.getBufferSize());
|
||||
boolean success = false;
|
||||
try {
|
||||
saveAs(outFile, associateWithNewFile, monitor);
|
||||
success = true;
|
||||
}
|
||||
finally {
|
||||
if (!success) {
|
||||
outFile.delete();
|
||||
}
|
||||
else if (!associateWithNewFile) {
|
||||
outFile.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new buffer with the specified length.
|
||||
* This method may only be invoked while a database transaction
|
||||
* is in progress. A database transaction must also be in progress
|
||||
* when invoking the various put, delete and setSize methods on the returned buffer.
|
||||
* @param length
|
||||
* @return Buffer
|
||||
* @throws IOException if an I/O error occurs while creating the buffer.
|
||||
*/
|
||||
public DBBuffer createBuffer(int length) throws IOException {
|
||||
checkTransaction();
|
||||
return new DBBuffer(this, new ChainedBuffer(length, bufferMgr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an existing buffer. This method should be used with care to avoid
|
||||
* providing an improper id. A database transaction must be in progress
|
||||
* when invoking the various put, delete and setSize methods on the returned buffer.
|
||||
* @param id the buffer id.
|
||||
* @return Buffer
|
||||
* @throws IOException if an I/O error occurs while getting the buffer.
|
||||
*/
|
||||
public DBBuffer getBuffer(int id) throws IOException {
|
||||
return new DBBuffer(this, new ChainedBuffer(bufferMgr, id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this database can be updated.
|
||||
* @return true if this database handle is intended for update
|
||||
*/
|
||||
public boolean canUpdate() {
|
||||
try {
|
||||
return bufferMgr != null && bufferMgr.canSave();
|
||||
}
|
||||
catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load existing tables from database.
|
||||
* @throws IOException thrown if IO error occurs.
|
||||
*/
|
||||
private void loadTables() {
|
||||
|
||||
tables = new Hashtable<>();
|
||||
TableRecord[] tableRecords = masterTable.getTableRecords();
|
||||
for (int i = 0; i < tableRecords.length; i++) {
|
||||
|
||||
// Process each primary tables
|
||||
if (tableRecords[i].getIndexedColumn() < 0) {
|
||||
Table table = new Table(this, tableRecords[i]);
|
||||
tables.put(table.getName(), table);
|
||||
}
|
||||
else { //secondary table indexes
|
||||
IndexTable.getIndexTable(this, tableRecords[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload tables from database following an undo or redo.
|
||||
* @throws IOException thrown if IO error occurs.
|
||||
*/
|
||||
private void reloadTables() throws IOException {
|
||||
|
||||
dbParms.refresh();
|
||||
|
||||
Hashtable<String, Table> oldTables = tables;
|
||||
tables = new Hashtable<>();
|
||||
TableRecord[] tableRecords = masterTable.refreshTableRecords();
|
||||
for (int i = 0; i < tableRecords.length; i++) {
|
||||
|
||||
String tableName = tableRecords[i].getName();
|
||||
|
||||
// Process each primary tables
|
||||
if (tableRecords[i].getIndexedColumn() < 0) {
|
||||
Table t = oldTables.get(tableName);
|
||||
if (t == null || t.isInvalid()) {
|
||||
oldTables.remove(tableName);
|
||||
t = new Table(this, tableRecords[i]);
|
||||
tableAdded(t);
|
||||
}
|
||||
tables.put(tableName, t);
|
||||
}
|
||||
|
||||
// secondary table indexes
|
||||
else if (!oldTables.containsKey(tableName)) {
|
||||
IndexTable.getIndexTable(this, tableRecords[i]);
|
||||
}
|
||||
}
|
||||
dbRestored();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Table that was created with the given name or null if
|
||||
* no such table exists.
|
||||
*/
|
||||
public Table getTable(String name) {
|
||||
return tables.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tables defined within the database.
|
||||
* @return Table[] tables
|
||||
*/
|
||||
public Table[] getTables() {
|
||||
Table[] t = new Table[tables.size()];
|
||||
|
||||
Iterator<Table> it = tables.values().iterator();
|
||||
int i = 0;
|
||||
while (it.hasNext()) {
|
||||
t[i++] = it.next();
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new table with the given name, version and type.
|
||||
*/
|
||||
public synchronized Table createTable(String name, Schema schema) throws IOException {
|
||||
|
||||
if (tables.containsKey(name))
|
||||
throw new IOException("Table already exists");
|
||||
Table table = new Table(this, masterTable.createTableRecord(name, schema, -1));
|
||||
tables.put(name, table);
|
||||
tableAdded(table);
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new table with the given name, version and type.
|
||||
* Create secondary indexes as specified by the array of column indexes.
|
||||
*/
|
||||
public synchronized Table createTable(String name, Schema schema, int[] indexedColumns)
|
||||
throws IOException {
|
||||
|
||||
if (tables.containsKey(name))
|
||||
throw new IOException("Table already exists");
|
||||
Table table = new Table(this, masterTable.createTableRecord(name, schema, -1));
|
||||
tables.put(name, table);
|
||||
for (int i = 0; i < indexedColumns.length; i++) {
|
||||
IndexTable.createIndexTable(table, indexedColumns[i]);
|
||||
}
|
||||
tableAdded(table);
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param table
|
||||
* @param id
|
||||
* @throws DuplicateNameException
|
||||
*/
|
||||
public synchronized boolean setTableName(String oldName, String newName)
|
||||
throws DuplicateNameException {
|
||||
if (!tables.containsKey(oldName)) {
|
||||
return false;
|
||||
}
|
||||
checkTransaction();
|
||||
if (tables.containsKey(newName))
|
||||
throw new DuplicateNameException("Table already exists");
|
||||
Table table = tables.remove(oldName);
|
||||
if (table == null) {
|
||||
return false;
|
||||
}
|
||||
masterTable.changeTableName(oldName, newName);
|
||||
tables.put(newName, table);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specified table from the database.
|
||||
* @param name table name
|
||||
* @throws IOException if there is an I/O error or the table does not exist
|
||||
*/
|
||||
public synchronized void deleteTable(String name) throws IOException {
|
||||
Table table = tables.get(name);
|
||||
if (table == null)
|
||||
return;
|
||||
int[] indexedColumns = table.getIndexedColumns();
|
||||
for (int i = 0; i < indexedColumns.length; i++) {
|
||||
table.removeIndex(indexedColumns[i]);
|
||||
}
|
||||
table.deleteAll();
|
||||
masterTable.deleteTableRecord(table.getTableNum());
|
||||
tables.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of buffer cache hits
|
||||
*/
|
||||
public long getCacheHits() {
|
||||
return bufferMgr.getCacheHits();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of buffer cache misses
|
||||
*/
|
||||
public long getCacheMisses() {
|
||||
return bufferMgr.getCacheMisses();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return low water mark (minimum buffer pool size)
|
||||
*/
|
||||
public int getLowBufferCount() {
|
||||
return bufferMgr.getLowBufferCount();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see java.lang.Object#finalize()
|
||||
*/
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
close(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns size of buffers utilized within the underlying
|
||||
* buffer file. This may be larger than than the requested
|
||||
* buffer size. This value may be used to instatiate a
|
||||
* new BufferFile which is compatible with this database
|
||||
* when using the saveAs method.
|
||||
*/
|
||||
public int getBufferSize() {
|
||||
return bufferMgr.getBufferSize();
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue