aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/com
diff options
context:
space:
mode:
authorJames Ahlborn <jtahlborn@yahoo.com>2012-02-01 04:25:23 +0000
committerJames Ahlborn <jtahlborn@yahoo.com>2012-02-01 04:25:23 +0000
commitae2ffd397026c7eac5210d343934ab15eb00bb4a (patch)
tree0216889dd61d74e7a335a6716bdacd4bb47b8390 /src/java/com
parent012405a81e1bc5a29f991ec03345803f0677d9c0 (diff)
downloadjackcess-ae2ffd397026c7eac5210d343934ab15eb00bb4a.tar.gz
jackcess-ae2ffd397026c7eac5210d343934ab15eb00bb4a.zip
add initial support for linked tables (fixes issue 3479560)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@610 f203690c-595d-4dc9-a70b-905162fa7fd2
Diffstat (limited to 'src/java/com')
-rw-r--r--src/java/com/healthmarketscience/jackcess/Database.java294
-rw-r--r--src/java/com/healthmarketscience/jackcess/LinkResolver.java37
-rw-r--r--src/java/com/healthmarketscience/jackcess/PageChannel.java4
-rw-r--r--src/java/com/healthmarketscience/jackcess/PropertyMaps.java2
-rw-r--r--src/java/com/healthmarketscience/jackcess/Table.java2
-rw-r--r--src/java/com/healthmarketscience/jackcess/TableCreator.java2
6 files changed, 304 insertions, 37 deletions
diff --git a/src/java/com/healthmarketscience/jackcess/Database.java b/src/java/com/healthmarketscience/jackcess/Database.java
index 174f95f..4ef116a 100644
--- a/src/java/com/healthmarketscience/jackcess/Database.java
+++ b/src/java/com/healthmarketscience/jackcess/Database.java
@@ -47,6 +47,7 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.HashMap;
@@ -182,6 +183,19 @@ public class Database
}
};
+ /**
+ * default link resolver used if none provided
+ * @usage _general_field_
+ */
+ public static final LinkResolver DEFAULT_LINK_RESOLVER = new LinkResolver() {
+ public Database resolveLinkedDatabase(Database linkerDb,
+ String linkeeFileName)
+ throws IOException
+ {
+ return Database.open(new File(linkeeFileName));
+ }
+ };
+
/** the resource path to be used when loading classpath resources */
static final String RESOURCE_PATH =
System.getProperty(RESOURCE_PATH_PROPERTY, DEFAULT_RESOURCE_PATH);
@@ -244,6 +258,10 @@ public class Database
private static final String CAT_COL_FLAGS = "Flags";
/** System catalog column name of the properties column */
private static final String CAT_COL_PROPS = "LvProp";
+ /** System catalog column name of the remote database */
+ private static final String CAT_COL_DATABASE = "Database";
+ /** System catalog column name of the remote table name */
+ private static final String CAT_COL_FOREIGN_NAME = "ForeignName";
/** top-level parentid for a database */
private static final int DB_PARENT_ID = 0xF000000;
@@ -320,9 +338,11 @@ public class Database
/** Name of the user-defined properties object */
private static final String OBJECT_NAME_USERDEF_PROPS = "UserDefined";
/** System object type for table definitions */
- private static final Short TYPE_TABLE = (short) 1;
+ static final Short TYPE_TABLE = 1;
/** System object type for query definitions */
- private static final Short TYPE_QUERY = (short) 5;
+ private static final Short TYPE_QUERY = 5;
+ /** System object type for linked table definitions */
+ private static final Short TYPE_LINKED_TABLE = 6;
/** max number of table lookups to cache */
private static final int MAX_CACHED_LOOKUP_TABLES = 50;
@@ -330,7 +350,8 @@ public class Database
/** the columns to read when reading system catalog normally */
private static Collection<String> SYSTEM_CATALOG_COLUMNS =
new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID,
- CAT_COL_FLAGS));
+ CAT_COL_FLAGS, CAT_COL_DATABASE,
+ CAT_COL_FOREIGN_NAME));
/** the columns to read when finding table names */
private static Collection<String> SYSTEM_CATALOG_TABLE_NAME_COLUMNS =
new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID,
@@ -380,7 +401,9 @@ public class Database
"yesno"
));
}
-
+
+ /** the File of the database */
+ private final File _file;
/** Buffer to hold database pages */
private ByteBuffer _buffer;
/** ID of the Tables system object */
@@ -446,6 +469,10 @@ public class Database
private PropertyMaps _summaryPropMaps;
/** user-defined properties */
private PropertyMaps _userDefPropMaps;
+ /** linked table resolver */
+ private LinkResolver _linkResolver;
+ /** any linked databases which have been opened */
+ private Map<String,Database> _linkedDbs;
/**
@@ -584,7 +611,8 @@ public class Database
}
}
- return new Database(channel, autoSync, null, charset, timeZone, provider);
+ return new Database(mdbFile, channel, autoSync, null, charset, timeZone,
+ provider);
}
/**
@@ -698,8 +726,8 @@ public class Database
channel.truncate(0);
transferFrom(channel, getResourceAsStream(fileFormat._emptyFile));
channel.force(true);
- return new Database(channel, autoSync, fileFormat, charset, timeZone,
- null);
+ return new Database(mdbFile, channel, autoSync, fileFormat, charset,
+ timeZone, null);
}
/**
@@ -724,6 +752,7 @@ public class Database
/**
* Create a new database by reading it in from a FileChannel.
+ * @param file the File to which the channel is connected
* @param channel File channel of the database. This needs to be a
* FileChannel instead of a ReadableByteChannel because we need to
* randomly jump around to various points in the file.
@@ -739,14 +768,14 @@ public class Database
* @param charset Charset to use, if {@code null}, uses default
* @param timeZone TimeZone to use, if {@code null}, uses default
*/
- protected Database(FileChannel channel, boolean autoSync,
+ protected Database(File file, FileChannel channel, boolean autoSync,
FileFormat fileFormat, Charset charset, TimeZone timeZone,
CodecProvider provider)
throws IOException
{
boolean success = false;
try {
-
+ _file = file;
_format = JetFormat.getFormat(channel);
_charset = ((charset == null) ? getDefaultCharset(_format) : charset);
_columnOrder = getDefaultColumnOrder();
@@ -777,6 +806,13 @@ public class Database
}
/**
+ * Returns the File underlying this Database
+ */
+ public File getFile() {
+ return _file;
+ }
+
+ /**
* @usage _advanced_method_
*/
public PageChannel getPageChannel() {
@@ -866,6 +902,35 @@ public class Database
}
/**
+ * Gets the currently configured LinkResolver (always non-{@code null}).
+ * This will be used to handle all linked database loading.
+ * @usage _intermediate_method_
+ */
+ public LinkResolver getLinkResolver() {
+ return((_linkResolver != null) ? _linkResolver : DEFAULT_LINK_RESOLVER);
+ }
+
+ /**
+ * Sets a new LinkResolver. If {@code null}, resets to the
+ * {@link #DEFAULT_LINK_RESOLVER}.
+ * @usage _intermediate_method_
+ */
+ public void setLinkResolver(LinkResolver newLinkResolver) {
+ _linkResolver = newLinkResolver;
+ }
+
+ /**
+ * Returns an unmodifiable view of the currently loaded linked databases,
+ * mapped from the linked database file name to the linked database. This
+ * information may be useful for implementing a LinkResolver.
+ * @usage _intermediate_method_
+ */
+ public Map<String,Database> getLinkedDatabases() {
+ return ((_linkedDbs == null) ? Collections.<String,Database>emptyMap() :
+ Collections.unmodifiableMap(_linkedDbs));
+ }
+
+ /**
* Gets currently configured TimeZone (always non-{@code null}).
* @usage _intermediate_method_
*/
@@ -1198,6 +1263,24 @@ public class Database
return null;
}
+ if(tableInfo.isLinked()) {
+
+ if(_linkedDbs == null) {
+ _linkedDbs = new HashMap<String,Database>();
+ }
+
+ String linkedDbName = ((LinkedTableInfo)tableInfo).linkedDbName;
+ String linkedTableName = ((LinkedTableInfo)tableInfo).linkedTableName;
+ Database linkedDb = _linkedDbs.get(linkedDbName);
+ if(linkedDb == null) {
+ linkedDb = getLinkResolver().resolveLinkedDatabase(this, linkedDbName);
+ _linkedDbs.put(linkedDbName, linkedDb);
+ }
+
+ return linkedDb.getTable(linkedTableName, includeSystemTables,
+ useBigIndex);
+ }
+
return readTable(tableInfo.tableName, tableInfo.pageNumber,
tableInfo.flags, useBigIndex);
}
@@ -1234,14 +1317,45 @@ public class Database
}
/**
+ * Create a new table in this database
+ * @param name Name of the table to create
+ * @usage _general_method_
+ */
+ public void createLinkedTable(String name, String linkedDbName,
+ String linkedTableName)
+ throws IOException
+ {
+ if(lookupTable(name) != null) {
+ throw new IllegalArgumentException(
+ "Cannot create linked table with name of existing table");
+ }
+
+ validateIdentifierName(name, getFormat().MAX_TABLE_NAME_LENGTH, "table");
+ validateIdentifierName(linkedDbName, DataType.MEMO.getMaxSize(),
+ "linked database");
+ validateIdentifierName(linkedTableName, getFormat().MAX_TABLE_NAME_LENGTH,
+ "linked table");
+
+ int linkedTableId = _tableFinder.getNextFreeSyntheticId();
+
+ addNewTable(name, linkedTableId, TYPE_LINKED_TABLE, linkedDbName,
+ linkedTableName);
+ }
+
+ /**
* Adds a newly created table to the relevant internal database structures.
*/
- void addNewTable(String name, int tdefPageNumber) throws IOException {
+ void addNewTable(String name, int tdefPageNumber, Short type,
+ String linkedDbName, String linkedTableName)
+ throws IOException
+ {
//Add this table to our internal list.
- addTable(name, Integer.valueOf(tdefPageNumber));
+ addTable(name, Integer.valueOf(tdefPageNumber), type, linkedDbName,
+ linkedTableName);
//Add this table to system tables
- addToSystemCatalog(name, tdefPageNumber);
+ addToSystemCatalog(name, tdefPageNumber, type, linkedDbName,
+ linkedTableName);
addToAccessControlEntries(tdefPageNumber);
}
@@ -1539,7 +1653,8 @@ public class Database
* @param name Table name
* @param pageNumber Page number that contains the table definition
*/
- private void addToSystemCatalog(String name, int pageNumber)
+ private void addToSystemCatalog(String name, int pageNumber, Short type,
+ String linkedDbName, String linkedTableName)
throws IOException
{
Object[] catalogRow = new Object[_systemCatalog.getColumnCount()];
@@ -1554,7 +1669,7 @@ public class Database
} else if (CAT_COL_NAME.equals(col.getName())) {
catalogRow[idx] = name;
} else if (CAT_COL_TYPE.equals(col.getName())) {
- catalogRow[idx] = TYPE_TABLE;
+ catalogRow[idx] = type;
} else if (CAT_COL_DATE_CREATE.equals(col.getName()) ||
CAT_COL_DATE_UPDATE.equals(col.getName())) {
catalogRow[idx] = creationTime;
@@ -1567,6 +1682,10 @@ public class Database
catalogRow[idx] = owner;
owner[0] = (byte) 0xcf;
owner[1] = (byte) 0x5f;
+ } else if (CAT_COL_DATABASE.equals(col.getName())) {
+ catalogRow[idx] = linkedDbName;
+ } else if (CAT_COL_FOREIGN_NAME.equals(col.getName())) {
+ catalogRow[idx] = linkedTableName;
}
}
_systemCatalog.addRow(catalogRow);
@@ -1786,18 +1905,29 @@ public class Database
}
/**
- * Flushes any current changes to the database file to disk.
+ * Flushes any current changes to the database file (and any linked
+ * databases) to disk.
* @usage _general_method_
*/
public void flush() throws IOException {
+ if(_linkedDbs != null) {
+ for(Database linkedDb : _linkedDbs.values()) {
+ linkedDb.flush();
+ }
+ }
_pageChannel.flush();
}
/**
- * Close the database file
+ * Close the database file (and any linked databases)
* @usage _general_method_
*/
public void close() throws IOException {
+ if(_linkedDbs != null) {
+ for(Database linkedDb : _linkedDbs.values()) {
+ linkedDb.close();
+ }
+ }
_pageChannel.close();
}
@@ -1848,15 +1978,31 @@ public class Database
/**
* Adds a table to the _tableLookup and resets the _tableNames set
*/
- private void addTable(String tableName, Integer pageNumber)
+ private void addTable(String tableName, Integer pageNumber, Short type,
+ String linkedDbName, String linkedTableName)
{
_tableLookup.put(toLookupName(tableName),
- new TableInfo(pageNumber, tableName, 0));
+ createTableInfo(tableName, pageNumber, 0, type,
+ linkedDbName, linkedTableName));
// clear this, will be created next time needed
_tableNames = null;
}
/**
+ * Creates a TableInfo instance appropriate for the given table data.
+ */
+ private static TableInfo createTableInfo(
+ String tableName, Integer pageNumber, int flags, Short type,
+ String linkedDbName, String linkedTableName)
+ {
+ if(TYPE_LINKED_TABLE.equals(type)) {
+ return new LinkedTableInfo(pageNumber, tableName, flags, linkedDbName,
+ linkedTableName);
+ }
+ return new TableInfo(pageNumber, tableName, flags);
+ }
+
+ /**
* @return the tableInfo of the given table, if any
*/
private TableInfo lookupTable(String tableName) throws IOException {
@@ -2036,6 +2182,10 @@ public class Database
return stream;
}
+ private static boolean isTableType(Short objType) {
+ return(TYPE_TABLE.equals(objType) || TYPE_LINKED_TABLE.equals(objType));
+ }
+
/**
* Utility class for storing table page number and actual name.
*/
@@ -2045,13 +2195,37 @@ public class Database
public final String tableName;
public final int flags;
- private TableInfo(Integer newPageNumber,
- String newTableName,
- int newFlags) {
+ private TableInfo(Integer newPageNumber, String newTableName, int newFlags) {
pageNumber = newPageNumber;
tableName = newTableName;
flags = newFlags;
}
+
+ public boolean isLinked() {
+ return false;
+ }
+ }
+
+ /**
+ * Utility class for storing linked table info
+ */
+ private static class LinkedTableInfo extends TableInfo
+ {
+ private final String linkedDbName;
+ private final String linkedTableName;
+
+ private LinkedTableInfo(Integer newPageNumber, String newTableName,
+ int newFlags, String newLinkedDbName,
+ String newLinkedTableName) {
+ super(newPageNumber, newTableName, newFlags);
+ linkedDbName = newLinkedDbName;
+ linkedTableName = newLinkedTableName;
+ }
+
+ @Override
+ public boolean isLinked() {
+ return true;
+ }
}
/**
@@ -2133,7 +2307,7 @@ public class Database
Short type = (Short)row.get(CAT_COL_TYPE);
int parentId = (Integer)row.get(CAT_COL_PARENT_ID);
- if((parentId == _tableParentId) && TYPE_TABLE.equals(type) &&
+ if((parentId == _tableParentId) && isTableType(type) &&
(isSystemObject(flags) == systemTables)) {
tableNames.add(tableName);
}
@@ -2150,6 +2324,18 @@ public class Database
public abstract TableInfo lookupTable(String tableName)
throws IOException;
+
+ protected abstract int findMaxSyntheticId() throws IOException;
+
+ public int getNextFreeSyntheticId() throws IOException
+ {
+ int maxSynthId = findMaxSyntheticId();
+ if(maxSynthId >= -1) {
+ // bummer, no more ids available
+ throw new IllegalStateException("Too many database objects!");
+ }
+ return maxSynthId + 1;
+ }
}
/**
@@ -2163,6 +2349,14 @@ public class Database
private DefaultTableFinder(IndexCursor systemCatalogCursor) {
_systemCatalogCursor = systemCatalogCursor;
}
+
+ private void initIdCursor() throws IOException {
+ if(_systemCatalogIdCursor == null) {
+ _systemCatalogIdCursor = new CursorBuilder(_systemCatalog)
+ .setIndexByColumnNames(CAT_COL_ID)
+ .toIndexCursor();
+ }
+ }
@Override
protected Cursor findRow(Integer parentId, String name)
@@ -2175,12 +2369,7 @@ public class Database
@Override
protected Cursor findRow(Integer objectId) throws IOException
{
- if(_systemCatalogIdCursor == null) {
- _systemCatalogIdCursor = new CursorBuilder(_systemCatalog)
- .setIndexByColumnNames(CAT_COL_ID)
- .toIndexCursor();
- }
-
+ initIdCursor();
return (_systemCatalogIdCursor.findFirstRowByEntry(objectId) ?
_systemCatalogIdCursor : null);
}
@@ -2199,11 +2388,15 @@ public class Database
int flags = (Integer)row.get(CAT_COL_FLAGS);
Short type = (Short)row.get(CAT_COL_TYPE);
- if(!TYPE_TABLE.equals(type)) {
+ if(!isTableType(type)) {
return null;
}
- return new TableInfo(pageNumber, realName, flags);
+ String linkedDbName = (String)row.get(CAT_COL_DATABASE);
+ String linkedTableName = (String)row.get(CAT_COL_FOREIGN_NAME);
+
+ return createTableInfo(realName, pageNumber, flags, type, linkedDbName,
+ linkedTableName);
}
@Override
@@ -2214,6 +2407,21 @@ public class Database
.setEndEntry(_tableParentId, IndexData.MAX_VALUE)
.toIndexCursor();
}
+
+ @Override
+ protected int findMaxSyntheticId() throws IOException {
+ initIdCursor();
+ _systemCatalogIdCursor.reset();
+
+ // synthetic ids count up from min integer. so the current, highest,
+ // in-use synthetic id is the max id < 0.
+ _systemCatalogIdCursor.findClosestRowByEntry(0);
+ if(!_systemCatalogIdCursor.moveToPreviousRow()) {
+ return Integer.MIN_VALUE;
+ }
+ Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
+ return (Integer)_systemCatalogIdCursor.getCurrentRowValue(idCol);
+ }
}
/**
@@ -2253,7 +2461,7 @@ public class Database
SYSTEM_CATALOG_TABLE_NAME_COLUMNS)) {
Short type = (Short)row.get(CAT_COL_TYPE);
- if(!TYPE_TABLE.equals(type)) {
+ if(!isTableType(type)) {
continue;
}
@@ -2269,7 +2477,11 @@ public class Database
Integer pageNumber = (Integer)row.get(CAT_COL_ID);
int flags = (Integer)row.get(CAT_COL_FLAGS);
- return new TableInfo(pageNumber, realName, flags);
+ String linkedDbName = (String)row.get(CAT_COL_DATABASE);
+ String linkedTableName = (String)row.get(CAT_COL_FOREIGN_NAME);
+
+ return createTableInfo(realName, pageNumber, flags, type, linkedDbName,
+ linkedTableName);
}
return null;
@@ -2279,6 +2491,21 @@ public class Database
protected Cursor getTableNamesCursor() throws IOException {
return _systemCatalogCursor;
}
+
+ @Override
+ protected int findMaxSyntheticId() throws IOException {
+ // find max id < 0
+ Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
+ _systemCatalogCursor.reset();
+ int curMaxSynthId = Integer.MIN_VALUE;
+ while(_systemCatalogCursor.moveToNextRow()) {
+ int id = (Integer)_systemCatalogCursor.getCurrentRowValue(idCol);
+ if((id > curMaxSynthId) && (id < 0)) {
+ curMaxSynthId = id;
+ }
+ }
+ return curMaxSynthId;
+ }
}
/**
@@ -2331,6 +2558,5 @@ public class Database
_tables.remove(oldRef.getPageNumber());
}
}
- }
-
+ }
}
diff --git a/src/java/com/healthmarketscience/jackcess/LinkResolver.java b/src/java/com/healthmarketscience/jackcess/LinkResolver.java
new file mode 100644
index 0000000..3ce7315
--- /dev/null
+++ b/src/java/com/healthmarketscience/jackcess/LinkResolver.java
@@ -0,0 +1,37 @@
+/*
+Copyright (c) 2011 James Ahlborn
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+*/
+
+package com.healthmarketscience.jackcess;
+
+import java.io.IOException;
+
+/**
+ * Resolver for linked databases.
+ *
+ * @author James Ahlborn
+ */
+public interface LinkResolver
+{
+ /**
+ * Returns the appropriate Database instance for the linkeeFileName from the
+ * given linkerDb.
+ */
+ public Database resolveLinkedDatabase(Database linkerDb, String linkeeFileName)
+ throws IOException;
+}
diff --git a/src/java/com/healthmarketscience/jackcess/PageChannel.java b/src/java/com/healthmarketscience/jackcess/PageChannel.java
index 083d608..68ba5cd 100644
--- a/src/java/com/healthmarketscience/jackcess/PageChannel.java
+++ b/src/java/com/healthmarketscience/jackcess/PageChannel.java
@@ -120,6 +120,10 @@ public class PageChannel implements Channel, Flushable {
return _format;
}
+ public boolean isAutoSync() {
+ return _autoSync;
+ }
+
/**
* Returns the next page number based on the given file size.
*/
diff --git a/src/java/com/healthmarketscience/jackcess/PropertyMaps.java b/src/java/com/healthmarketscience/jackcess/PropertyMaps.java
index 272e8d9..2120b56 100644
--- a/src/java/com/healthmarketscience/jackcess/PropertyMaps.java
+++ b/src/java/com/healthmarketscience/jackcess/PropertyMaps.java
@@ -159,7 +159,7 @@ public class PropertyMaps implements Iterable<PropertyMap>
}
if(!knownType) {
- throw new IOException("Uknown property map type " +
+ throw new IOException("Unknown property map type " +
ByteUtil.toHexString(bb, 4));
}
diff --git a/src/java/com/healthmarketscience/jackcess/Table.java b/src/java/com/healthmarketscience/jackcess/Table.java
index 5819bcd..df53a88 100644
--- a/src/java/com/healthmarketscience/jackcess/Table.java
+++ b/src/java/com/healthmarketscience/jackcess/Table.java
@@ -209,7 +209,7 @@ public class Table
*/
protected Table(Database database, ByteBuffer tableBuffer,
int pageNumber, String name, int flags, boolean useBigIndex)
- throws IOException
+ throws IOException
{
_database = database;
_tableDefPageNumber = pageNumber;
diff --git a/src/java/com/healthmarketscience/jackcess/TableCreator.java b/src/java/com/healthmarketscience/jackcess/TableCreator.java
index 556227c..75aab7c 100644
--- a/src/java/com/healthmarketscience/jackcess/TableCreator.java
+++ b/src/java/com/healthmarketscience/jackcess/TableCreator.java
@@ -132,7 +132,7 @@ class TableCreator
Table.writeTableDefinition(this);
// update the database with the new table info
- _database.addNewTable(_name, _tdefPageNumber);
+ _database.addNewTable(_name, _tdefPageNumber, Database.TYPE_TABLE, null, null);
}
/**