Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
Copyright (c) 2005 Health Market Science, Inc.
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
You can contact Health Market Science at [email protected]
or at the following address:
Health Market Science
2700 Horizon Drive
Suite 200
King of Prussia, PA 19406
*/
package com.healthmarketscience.jackcess.impl;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
import com.healthmarketscience.jackcess.ColumnBuilder;
import com.healthmarketscience.jackcess.Cursor;
import com.healthmarketscience.jackcess.CursorBuilder;
import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.Database;
import com.healthmarketscience.jackcess.IndexBuilder;
import com.healthmarketscience.jackcess.IndexCursor;
import com.healthmarketscience.jackcess.PropertyMap;
import com.healthmarketscience.jackcess.Relationship;
import com.healthmarketscience.jackcess.Row;
import com.healthmarketscience.jackcess.RuntimeIOException;
import com.healthmarketscience.jackcess.Table;
import com.healthmarketscience.jackcess.impl.query.QueryImpl;
import com.healthmarketscience.jackcess.query.Query;
import com.healthmarketscience.jackcess.util.CaseInsensitiveColumnMatcher;
import com.healthmarketscience.jackcess.util.ErrorHandler;
import com.healthmarketscience.jackcess.util.LinkResolver;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
*
* @author Tim McCune
* @usage _general_class_
*/
public class DatabaseImpl implements Database
{
private static final Log LOG = LogFactory.getLog(DatabaseImpl.class);
/** this is the default "userId" used if we cannot find existing info. this
seems to be some standard "Admin" userId for access files */
private static final byte[] SYS_DEFAULT_SID = new byte[2];
static {
SYS_DEFAULT_SID[0] = (byte) 0xA6;
SYS_DEFAULT_SID[1] = (byte) 0x33;
}
/** the default value for the resource path used to load classpath
* resources.
*/
public static final String DEFAULT_RESOURCE_PATH =
"com/healthmarketscience/jackcess/";
/** the resource path to be used when loading classpath resources */
static final String RESOURCE_PATH =
System.getProperty(RESOURCE_PATH_PROPERTY, DEFAULT_RESOURCE_PATH);
/** whether or not this jvm has "broken" nio support */
static final boolean BROKEN_NIO = Boolean.TRUE.toString().equalsIgnoreCase(
System.getProperty(BROKEN_NIO_PROPERTY));
/** additional internal details about each FileFormat */
private static final Map FILE_FORMAT_DETAILS =
new EnumMap(Database.FileFormat.class);
static {
addFileFormatDetails(FileFormat.V1997, null, JetFormat.VERSION_3);
addFileFormatDetails(FileFormat.V2000, "empty", JetFormat.VERSION_4);
addFileFormatDetails(FileFormat.V2003, "empty2003", JetFormat.VERSION_4);
addFileFormatDetails(FileFormat.V2007, "empty2007", JetFormat.VERSION_12);
addFileFormatDetails(FileFormat.V2010, "empty2010", JetFormat.VERSION_14);
addFileFormatDetails(FileFormat.MSISAM, null, JetFormat.VERSION_MSISAM);
}
/** System catalog always lives on page 2 */
private static final int PAGE_SYSTEM_CATALOG = 2;
/** Name of the system catalog */
private static final String TABLE_SYSTEM_CATALOG = "MSysObjects";
/** this is the access control bit field for created tables. the value used
is equivalent to full access (Visual Basic DAO PermissionEnum constant:
dbSecFullAccess) */
private static final Integer SYS_FULL_ACCESS_ACM = 1048575;
/** ACE table column name of the actual access control entry */
private static final String ACE_COL_ACM = "ACM";
/** ACE table column name of the inheritable attributes flag */
private static final String ACE_COL_F_INHERITABLE = "FInheritable";
/** ACE table column name of the relevant objectId */
private static final String ACE_COL_OBJECT_ID = "ObjectId";
/** ACE table column name of the relevant userId */
private static final String ACE_COL_SID = "SID";
/** Relationship table column name of the column count */
private static final String REL_COL_COLUMN_COUNT = "ccolumn";
/** Relationship table column name of the flags */
private static final String REL_COL_FLAGS = "grbit";
/** Relationship table column name of the index of the columns */
private static final String REL_COL_COLUMN_INDEX = "icolumn";
/** Relationship table column name of the "to" column name */
private static final String REL_COL_TO_COLUMN = "szColumn";
/** Relationship table column name of the "to" table name */
private static final String REL_COL_TO_TABLE = "szObject";
/** Relationship table column name of the "from" column name */
private static final String REL_COL_FROM_COLUMN = "szReferencedColumn";
/** Relationship table column name of the "from" table name */
private static final String REL_COL_FROM_TABLE = "szReferencedObject";
/** Relationship table column name of the relationship */
private static final String REL_COL_NAME = "szRelationship";
/** System catalog column name of the page on which system object definitions
are stored */
private static final String CAT_COL_ID = "Id";
/** System catalog column name of the name of a system object */
private static final String CAT_COL_NAME = "Name";
private static final String CAT_COL_OWNER = "Owner";
/** System catalog column name of a system object's parent's id */
private static final String CAT_COL_PARENT_ID = "ParentId";
/** System catalog column name of the type of a system object */
private static final String CAT_COL_TYPE = "Type";
/** System catalog column name of the date a system object was created */
private static final String CAT_COL_DATE_CREATE = "DateCreate";
/** System catalog column name of the date a system object was updated */
private static final String CAT_COL_DATE_UPDATE = "DateUpdate";
/** System catalog column name of the flags column */
private static final String CAT_COL_FLAGS = "Flags";
/** System catalog column name of the properties column */
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;
/** the maximum size of any of the included "empty db" resources */
private static final long MAX_EMPTYDB_SIZE = 350000L;
/** this object is a "system" object */
static final int SYSTEM_OBJECT_FLAG = 0x80000000;
/** this object is another type of "system" object */
static final int ALT_SYSTEM_OBJECT_FLAG = 0x02;
/** this object is hidden */
public static final int HIDDEN_OBJECT_FLAG = 0x08;
/** all flags which seem to indicate some type of system object */
static final int SYSTEM_OBJECT_FLAGS =
SYSTEM_OBJECT_FLAG | ALT_SYSTEM_OBJECT_FLAG;
/** read-only channel access mode */
public static final String RO_CHANNEL_MODE = "r";
/** read/write channel access mode */
public static final String RW_CHANNEL_MODE = "rw";
/** Name of the system object that is the parent of all tables */
private static final String SYSTEM_OBJECT_NAME_TABLES = "Tables";
/** Name of the system object that is the parent of all databases */
private static final String SYSTEM_OBJECT_NAME_DATABASES = "Databases";
/** Name of the system object that is the parent of all relationships */
private static final String SYSTEM_OBJECT_NAME_RELATIONSHIPS =
"Relationships";
/** Name of the table that contains system access control entries */
private static final String TABLE_SYSTEM_ACES = "MSysACEs";
/** Name of the table that contains table relationships */
private static final String TABLE_SYSTEM_RELATIONSHIPS = "MSysRelationships";
/** Name of the table that contains queries */
private static final String TABLE_SYSTEM_QUERIES = "MSysQueries";
/** Name of the table that contains complex type information */
private static final String TABLE_SYSTEM_COMPLEX_COLS = "MSysComplexColumns";
/** Name of the main database properties object */
private static final String OBJECT_NAME_DB_PROPS = "MSysDb";
/** Name of the summary properties object */
private static final String OBJECT_NAME_SUMMARY_PROPS = "SummaryInfo";
/** Name of the user-defined properties object */
private static final String OBJECT_NAME_USERDEF_PROPS = "UserDefined";
/** System object type for table definitions */
static final Short TYPE_TABLE = 1;
/** System object type for query definitions */
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;
/** the columns to read when reading system catalog normally */
private static Collection SYSTEM_CATALOG_COLUMNS =
new HashSet(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID,
CAT_COL_FLAGS, CAT_COL_DATABASE,
CAT_COL_FOREIGN_NAME));
/** the columns to read when finding table names */
private static Collection SYSTEM_CATALOG_TABLE_NAME_COLUMNS =
new HashSet(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID,
CAT_COL_FLAGS, CAT_COL_PARENT_ID));
/** the columns to read when getting object propertyes */
private static Collection SYSTEM_CATALOG_PROPS_COLUMNS =
new HashSet(Arrays.asList(CAT_COL_ID, CAT_COL_PROPS));
/** the File of the database */
private final File _file;
/** Buffer to hold database pages */
private ByteBuffer _buffer;
/** ID of the Tables system object */
private Integer _tableParentId;
/** Format that the containing database is in */
private final JetFormat _format;
/**
* Cache map of UPPERCASE table names to page numbers containing their
* definition and their stored table name (max size
* MAX_CACHED_LOOKUP_TABLES).
*/
private final Map _tableLookup =
new LinkedHashMap() {
private static final long serialVersionUID = 0L;
@Override
protected boolean removeEldestEntry(Map.Entry e) {
return(size() > MAX_CACHED_LOOKUP_TABLES);
}
};
/** set of table names as stored in the mdb file, created on demand */
private Set _tableNames;
/** Reads and writes database pages */
private final PageChannel _pageChannel;
/** System catalog table */
private TableImpl _systemCatalog;
/** utility table finder */
private TableFinder _tableFinder;
/** System access control entries table (initialized on first use) */
private TableImpl _accessControlEntries;
/** System relationships table (initialized on first use) */
private TableImpl _relationships;
/** System queries table (initialized on first use) */
private TableImpl _queries;
/** System complex columns table (initialized on first use) */
private TableImpl _complexCols;
/** SIDs to use for the ACEs added for new tables */
private final List _newTableSIDs = new ArrayList();
/** optional error handler to use when row errors are encountered */
private ErrorHandler _dbErrorHandler;
/** the file format of the database */
private FileFormat _fileFormat;
/** charset to use when handling text */
private Charset _charset;
/** timezone to use when handling dates */
private TimeZone _timeZone;
/** language sort order to be used for textual columns */
private ColumnImpl.SortOrder _defaultSortOrder;
/** default code page to be used for textual columns (in some dbs) */
private Short _defaultCodePage;
/** the ordering used for table columns */
private Table.ColumnOrder _columnOrder;
/** whether or not enforcement of foreign-keys is enabled */
private boolean _enforceForeignKeys;
/** cache of in-use tables */
private final TableCache _tableCache = new TableCache();
/** handler for reading/writing properteies */
private PropertyMaps.Handler _propsHandler;
/** ID of the Databases system object */
private Integer _dbParentId;
/** core database properties */
private PropertyMaps _dbPropMaps;
/** summary properties */
private PropertyMaps _summaryPropMaps;
/** user-defined properties */
private PropertyMaps _userDefPropMaps;
/** linked table resolver */
private LinkResolver _linkResolver;
/** any linked databases which have been opened */
private Map _linkedDbs;
/** shared state used when enforcing foreign keys */
private final FKEnforcer.SharedState _fkEnforcerSharedState =
FKEnforcer.initSharedState();
/** Calendar for use interpreting dates/times in Columns */
private Calendar _calendar;
/**
* Open an existing Database. If the existing file is not writeable or the
* readOnly flag is {@code true}, the file will be opened read-only.
* @param mdbFile File containing the database
* @param readOnly iff {@code true}, force opening file in read-only
* mode
* @param channel pre-opened FileChannel. if provided explicitly, it will
* not be closed by this Database instance
* @param autoSync whether or not to enable auto-syncing on write. if
* {@code true}, writes will be immediately flushed to disk.
* This leaves the database in a (fairly) consistent state
* on each write, but can be very inefficient for many
* updates. if {@code false}, flushing to disk happens at
* the jvm's leisure, which can be much faster, but may
* leave the database in an inconsistent state if failures
* are encountered during writing. Writes may be flushed at
* any time using {@link #flush}.
* @param charset Charset to use, if {@code null}, uses default
* @param timeZone TimeZone to use, if {@code null}, uses default
* @param provider CodecProvider for handling page encoding/decoding, may be
* {@code null} if no special encoding is necessary
* @usage _advanced_method_
*/
public static DatabaseImpl open(
File mdbFile, boolean readOnly, FileChannel channel,
boolean autoSync, Charset charset, TimeZone timeZone,
CodecProvider provider)
throws IOException
{
boolean closeChannel = false;
if(channel == null) {
if(!mdbFile.exists() || !mdbFile.canRead()) {
throw new FileNotFoundException("given file does not exist: " +
mdbFile);
}
// force read-only for non-writable files
readOnly |= !mdbFile.canWrite();
// open file channel
channel = openChannel(mdbFile, readOnly);
closeChannel = true;
}
boolean success = false;
try {
if(!readOnly) {
// verify that format supports writing
JetFormat jetFormat = JetFormat.getFormat(channel);
if(jetFormat.READ_ONLY) {
throw new IOException("jet format '" + jetFormat +
"' does not support writing");
}
}
DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync,
null, charset, timeZone, provider);
success = true;
return db;
} finally {
if(!success && closeChannel) {
// something blew up, shutdown the channel (quietly)
ByteUtil.closeQuietly(channel);
}
}
}
/**
* Create a new Database for the given fileFormat
* @param fileFormat version of new database.
* @param mdbFile Location to write the new database to. If this file
* already exists, it will be overwritten.
* @param channel pre-opened FileChannel. if provided explicitly, it will
* not be closed by this Database instance
* @param autoSync whether or not to enable auto-syncing on write. if
* {@code true}, writes will be immediately flushed to disk.
* This leaves the database in a (fairly) consistent state
* on each write, but can be very inefficient for many
* updates. if {@code false}, flushing to disk happens at
* the jvm's leisure, which can be much faster, but may
* leave the database in an inconsistent state if failures
* are encountered during writing. Writes may be flushed at
* any time using {@link #flush}.
* @param charset Charset to use, if {@code null}, uses default
* @param timeZone TimeZone to use, if {@code null}, uses default
* @usage _advanced_method_
*/
public static DatabaseImpl create(FileFormat fileFormat, File mdbFile,
FileChannel channel, boolean autoSync,
Charset charset, TimeZone timeZone)
throws IOException
{
FileFormatDetails details = getFileFormatDetails(fileFormat);
if (details.getFormat().READ_ONLY) {
throw new IOException("file format " + fileFormat +
" does not support writing");
}
boolean closeChannel = false;
if(channel == null) {
channel = openChannel(mdbFile, false);
closeChannel = true;
}
boolean success = false;
try {
channel.truncate(0);
transferFrom(channel, getResourceAsStream(details.getEmptyFilePath()));
channel.force(true);
DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync,
fileFormat, charset, timeZone, null);
success = true;
return db;
} finally {
if(!success && closeChannel) {
// something blew up, shutdown the channel (quietly)
ByteUtil.closeQuietly(channel);
}
}
}
/**
* Package visible only to support unit tests via DatabaseTest.openChannel().
* @param mdbFile file to open
* @param readOnly true if read-only
* @return a FileChannel on the given file.
* @exception FileNotFoundException
* if the mode is "r" but the given file object does
* not denote an existing regular file, or if the mode begins
* with "rw" but the given file object does not denote
* an existing, writable regular file and a new regular file of
* that name cannot be created, or if some other error occurs
* while opening or creating the file
*/
static FileChannel openChannel(final File mdbFile, final boolean readOnly)
throws FileNotFoundException
{
final String mode = (readOnly ? RO_CHANNEL_MODE : RW_CHANNEL_MODE);
return new RandomAccessFile(mdbFile, mode).getChannel();
}
/**
* 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.
* @param autoSync whether or not to enable auto-syncing on write. if
* {@code true}, writes will be immediately flushed to disk.
* This leaves the database in a (fairly) consistent state
* on each write, but can be very inefficient for many
* updates. if {@code false}, flushing to disk happens at
* the jvm's leisure, which can be much faster, but may
* leave the database in an inconsistent state if failures
* are encountered during writing. Writes may be flushed at
* any time using {@link #flush}.
* @param fileFormat version of new database (if known)
* @param charset Charset to use, if {@code null}, uses default
* @param timeZone TimeZone to use, if {@code null}, uses default
*/
protected DatabaseImpl(File file, FileChannel channel, boolean closeChannel,
boolean autoSync, FileFormat fileFormat, Charset charset,
TimeZone timeZone, CodecProvider provider)
throws IOException
{
_file = file;
_format = JetFormat.getFormat(channel);
_charset = ((charset == null) ? getDefaultCharset(_format) : charset);
_columnOrder = getDefaultColumnOrder();
_enforceForeignKeys = getDefaultEnforceForeignKeys();
_fileFormat = fileFormat;
_pageChannel = new PageChannel(channel, closeChannel, _format, autoSync);
_timeZone = ((timeZone == null) ? getDefaultTimeZone() : timeZone);
if(provider == null) {
provider = DefaultCodecProvider.INSTANCE;
}
// note, it's slighly sketchy to pass ourselves along partially
// constructed, but only our _format and _pageChannel refs should be
// needed
_pageChannel.initialize(this, provider);
_buffer = _pageChannel.createPageBuffer();
readSystemCatalog();
}
public File getFile() {
return _file;
}
/**
* @usage _advanced_method_
*/
public PageChannel getPageChannel() {
return _pageChannel;
}
/**
* @usage _advanced_method_
*/
public JetFormat getFormat() {
return _format;
}
/**
* @return The system catalog table
* @usage _advanced_method_
*/
public TableImpl getSystemCatalog() {
return _systemCatalog;
}
/**
* @return The system Access Control Entries table (loaded on demand)
* @usage _advanced_method_
*/
public TableImpl getAccessControlEntries() throws IOException {
if(_accessControlEntries == null) {
_accessControlEntries = getSystemTable(TABLE_SYSTEM_ACES);
if(_accessControlEntries == null) {
throw new IOException("Could not find system table " +
TABLE_SYSTEM_ACES);
}
}
return _accessControlEntries;
}
/**
* @return the complex column system table (loaded on demand)
* @usage _advanced_method_
*/
public TableImpl getSystemComplexColumns() throws IOException {
if(_complexCols == null) {
_complexCols = getSystemTable(TABLE_SYSTEM_COMPLEX_COLS);
if(_complexCols == null) {
throw new IOException("Could not find system table " +
TABLE_SYSTEM_COMPLEX_COLS);
}
}
return _complexCols;
}
public ErrorHandler getErrorHandler() {
return((_dbErrorHandler != null) ? _dbErrorHandler : ErrorHandler.DEFAULT);
}
public void setErrorHandler(ErrorHandler newErrorHandler) {
_dbErrorHandler = newErrorHandler;
}
public LinkResolver getLinkResolver() {
return((_linkResolver != null) ? _linkResolver : LinkResolver.DEFAULT);
}
public void setLinkResolver(LinkResolver newLinkResolver) {
_linkResolver = newLinkResolver;
}
public Map getLinkedDatabases() {
return ((_linkedDbs == null) ? Collections.emptyMap() :
Collections.unmodifiableMap(_linkedDbs));
}
public TimeZone getTimeZone() {
return _timeZone;
}
public void setTimeZone(TimeZone newTimeZone) {
if(newTimeZone == null) {
newTimeZone = getDefaultTimeZone();
}
_timeZone = newTimeZone;
// clear cached calendar when timezone is changed
_calendar = null;
}
public Charset getCharset()
{
return _charset;
}
public void setCharset(Charset newCharset) {
if(newCharset == null) {
newCharset = getDefaultCharset(getFormat());
}
_charset = newCharset;
}
public Table.ColumnOrder getColumnOrder() {
return _columnOrder;
}
public void setColumnOrder(Table.ColumnOrder newColumnOrder) {
if(newColumnOrder == null) {
newColumnOrder = getDefaultColumnOrder();
}
_columnOrder = newColumnOrder;
}
public boolean isEnforceForeignKeys() {
return _enforceForeignKeys;
}
public void setEnforceForeignKeys(Boolean newEnforceForeignKeys) {
if(newEnforceForeignKeys == null) {
newEnforceForeignKeys = getDefaultEnforceForeignKeys();
}
_enforceForeignKeys = newEnforceForeignKeys;
}
/**
* @usage _advanced_method_
*/
FKEnforcer.SharedState getFKEnforcerSharedState() {
return _fkEnforcerSharedState;
}
/**
* @usage _advanced_method_
*/
Calendar getCalendar()
{
if(_calendar == null) {
_calendar = Calendar.getInstance(_timeZone);
}
return _calendar;
}
/**
* @returns the current handler for reading/writing properties, creating if
* necessary
*/
private PropertyMaps.Handler getPropsHandler() {
if(_propsHandler == null) {
_propsHandler = new PropertyMaps.Handler(this);
}
return _propsHandler;
}
public FileFormat getFileFormat() throws IOException {
if(_fileFormat == null) {
Map possibleFileFormats =
getFormat().getPossibleFileFormats();
if(possibleFileFormats.size() == 1) {
// single possible format (null key), easy enough
_fileFormat = possibleFileFormats.get(null);
} else {
// need to check the "AccessVersion" property
String accessVersion = (String)getDatabaseProperties().getValue(
PropertyMap.ACCESS_VERSION_PROP);
_fileFormat = possibleFileFormats.get(accessVersion);
if(_fileFormat == null) {
throw new IllegalStateException("Could not determine FileFormat");
}
}
}
return _fileFormat;
}
/**
* @return a (possibly cached) page ByteBuffer for internal use. the
* returned buffer should be released using
* {@link #releaseSharedBuffer} when no longer in use
*/
private ByteBuffer takeSharedBuffer() {
// we try to re-use a single shared _buffer, but occassionally, it may be
// needed by multiple operations at the same time (e.g. loading a
// secondary table while loading a primary table). this method ensures
// that we don't corrupt the _buffer, but instead force the second caller
// to use a new buffer.
if(_buffer != null) {
ByteBuffer curBuffer = _buffer;
_buffer = null;
return curBuffer;
}
return _pageChannel.createPageBuffer();
}
/**
* Relinquishes use of a page ByteBuffer returned by
* {@link #takeSharedBuffer}.
*/
private void releaseSharedBuffer(ByteBuffer buffer) {
// we always stuff the returned buffer back into _buffer. it doesn't
// really matter if multiple values over-write, at the end of the day, we
// just need one shared buffer
_buffer = buffer;
}
/**
* @return the currently configured database default language sort order for
* textual columns
* @usage _intermediate_method_
*/
public ColumnImpl.SortOrder getDefaultSortOrder() throws IOException {
if(_defaultSortOrder == null) {
initRootPageInfo();
}
return _defaultSortOrder;
}
/**
* @return the currently configured database default code page for textual
* data (may not be relevant to all database versions)
* @usage _intermediate_method_
*/
public short getDefaultCodePage() throws IOException {
if(_defaultCodePage == null) {
initRootPageInfo();
}
return _defaultCodePage;
}
/**
* Reads various config info from the db page 0.
*/
private void initRootPageInfo() throws IOException {
ByteBuffer buffer = takeSharedBuffer();
try {
_pageChannel.readPage(buffer, 0);
_defaultSortOrder = ColumnImpl.readSortOrder(
buffer, _format.OFFSET_SORT_ORDER, _format);
_defaultCodePage = buffer.getShort(_format.OFFSET_CODE_PAGE);
} finally {
releaseSharedBuffer(buffer);
}
}
/**
* @return a PropertyMaps instance decoded from the given bytes (always
* returns non-{@code null} result).
* @usage _intermediate_method_
*/
public PropertyMaps readProperties(byte[] propsBytes, int objectId,
RowIdImpl rowId)
throws IOException
{
return getPropsHandler().read(propsBytes, objectId, rowId);
}
/**
* Read the system catalog
*/
private void readSystemCatalog() throws IOException {
_systemCatalog = readTable(TABLE_SYSTEM_CATALOG, PAGE_SYSTEM_CATALOG,
SYSTEM_OBJECT_FLAGS);
try {
_tableFinder = new DefaultTableFinder(
_systemCatalog.newCursor()
.setIndexByColumnNames(CAT_COL_PARENT_ID, CAT_COL_NAME)
.setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE)
.toIndexCursor());
} catch(IllegalArgumentException e) {
LOG.info("Could not find expected index on table " +
_systemCatalog.getName());
// use table scan instead
_tableFinder = new FallbackTableFinder(
_systemCatalog.newCursor()
.setColumnMatcher(CaseInsensitiveColumnMatcher.INSTANCE)
.toCursor());
}
_tableParentId = _tableFinder.findObjectId(DB_PARENT_ID,
SYSTEM_OBJECT_NAME_TABLES);
if(_tableParentId == null) {
throw new IOException("Did not find required parent table id");
}
if (LOG.isDebugEnabled()) {
LOG.debug("Finished reading system catalog. Tables: " +
getTableNames());
}
}
public Set getTableNames() throws IOException {
if(_tableNames == null) {
Set tableNames =
new TreeSet(String.CASE_INSENSITIVE_ORDER);
_tableFinder.getTableNames(tableNames, false);
_tableNames = tableNames;
}
return _tableNames;
}
public Set getSystemTableNames() throws IOException {
Set sysTableNames =
new TreeSet(String.CASE_INSENSITIVE_ORDER);
_tableFinder.getTableNames(sysTableNames, true);
return sysTableNames;
}
public Iterator
iterator() {
return new TableIterator();
}
public TableImpl getTable(String name) throws IOException {
return getTable(name, false);
}
/**
* @param tableDefPageNumber the page number of a table definition
* @return The table, or null if it doesn't exist
* @usage _advanced_method_
*/
public TableImpl getTable(int tableDefPageNumber) throws IOException {
// first, check for existing table
TableImpl table = _tableCache.get(tableDefPageNumber);
if(table != null) {
return table;
}
// lookup table info from system catalog
Row objectRow = _tableFinder.getObjectRow(
tableDefPageNumber, SYSTEM_CATALOG_COLUMNS);
if(objectRow == null) {
return null;
}
String name = (String)objectRow.get(CAT_COL_NAME);
int flags = (Integer)objectRow.get(CAT_COL_FLAGS);
return readTable(name, tableDefPageNumber, flags);
}
/**
* @param name Table name
* @param includeSystemTables whether to consider returning a system table
* @return The table, or null if it doesn't exist
*/
private TableImpl getTable(String name, boolean includeSystemTables)
throws IOException
{
TableInfo tableInfo = lookupTable(name);
if ((tableInfo == null) || (tableInfo.pageNumber == null)) {
return null;
}
if(!includeSystemTables && isSystemObject(tableInfo.flags)) {
return null;
}
if(tableInfo.isLinked()) {
if(_linkedDbs == null) {
_linkedDbs = new HashMap();
}
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 ((DatabaseImpl)linkedDb).getTable(linkedTableName,
includeSystemTables);
}
return readTable(tableInfo.tableName, tableInfo.pageNumber,
tableInfo.flags);
}
/**
* Create a new table in this database
* @param name Name of the table to create
* @param columns List of Columns in the table
* @usage _general_method_
*/
public void createTable(String name, List columns)
throws IOException
{
createTable(name, columns, null);
}
/**
* Create a new table in this database
* @param name Name of the table to create
* @param columns List of Columns in the table
* @param indexes List of IndexBuilders describing indexes for the table
* @usage _general_method_
*/
public void createTable(String name, List columns,
List indexes)
throws IOException
{
if(lookupTable(name) != null) {
throw new IllegalArgumentException(
"Cannot create table with name of existing table");
}
new TableCreator(this, name, columns, indexes).createTable();
}
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");
getPageChannel().startWrite();
try {
int linkedTableId = _tableFinder.getNextFreeSyntheticId();
addNewTable(name, linkedTableId, TYPE_LINKED_TABLE, linkedDbName,
linkedTableName);
} finally {
getPageChannel().finishWrite();
}
}
/**
* Adds a newly created table to the relevant internal database structures.
*/
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), type, linkedDbName,
linkedTableName);
//Add this table to system tables
addToSystemCatalog(name, tdefPageNumber, type, linkedDbName,
linkedTableName);
addToAccessControlEntries(tdefPageNumber);
}
public List getRelationships(Table table1, Table table2)
throws IOException
{
return getRelationships((TableImpl)table1, (TableImpl)table2);
}
public List getRelationships(
TableImpl table1, TableImpl table2)
throws IOException
{
int nameCmp = table1.getName().compareTo(table2.getName());
if(nameCmp == 0) {
throw new IllegalArgumentException("Must provide two different tables");
}
if(nameCmp > 0) {
// we "order" the two tables given so that we will return a collection
// of relationships in the same order regardless of whether we are given
// (TableFoo, TableBar) or (TableBar, TableFoo).
TableImpl tmp = table1;
table1 = table2;
table2 = tmp;
}
return getRelationshipsImpl(table1, table2, true);
}
public List getRelationships(Table table)
throws IOException
{
if(table == null) {
throw new IllegalArgumentException("Must provide a table");
}
// since we are getting relationships specific to certain table include
// all tables
return getRelationshipsImpl((TableImpl)table, null, true);
}
public List getRelationships()
throws IOException
{
return getRelationshipsImpl(null, null, false);
}
public List getSystemRelationships()
throws IOException
{
return getRelationshipsImpl(null, null, true);
}
private List getRelationshipsImpl(
TableImpl table1, TableImpl table2, boolean includeSystemTables)
throws IOException
{
// the relationships table does not get loaded until first accessed
if(_relationships == null) {
_relationships = getSystemTable(TABLE_SYSTEM_RELATIONSHIPS);
if(_relationships == null) {
throw new IOException("Could not find system relationships table");
}
}
List relationships = new ArrayList();
if(table1 != null) {
Cursor cursor = createCursorWithOptionalIndex(
_relationships, REL_COL_FROM_TABLE, table1.getName());
collectRelationships(cursor, table1, table2, relationships,
includeSystemTables);
cursor = createCursorWithOptionalIndex(
_relationships, REL_COL_TO_TABLE, table1.getName());
collectRelationships(cursor, table2, table1, relationships,
includeSystemTables);
} else {
collectRelationships(new CursorBuilder(_relationships).toCursor(),
null, null, relationships, includeSystemTables);
}
return relationships;
}
public List getQueries() throws IOException
{
// the queries table does not get loaded until first accessed
if(_queries == null) {
_queries = getSystemTable(TABLE_SYSTEM_QUERIES);
if(_queries == null) {
throw new IOException("Could not find system queries table");
}
}
// find all the queries from the system catalog
List queryInfo = new ArrayList();
Map> queryRowMap =
new HashMap>();
for(Row row :
CursorImpl.createCursor(_systemCatalog).newIterable().setColumnNames(
SYSTEM_CATALOG_COLUMNS))
{
String name = (String) row.get(CAT_COL_NAME);
if (name != null && TYPE_QUERY.equals(row.get(CAT_COL_TYPE))) {
queryInfo.add(row);
Integer id = (Integer)row.get(CAT_COL_ID);
queryRowMap.put(id, new ArrayList());
}
}
// find all the query rows
for(Row row : CursorImpl.createCursor(_queries)) {
QueryImpl.Row queryRow = new QueryImpl.Row(row);
List queryRows = queryRowMap.get(queryRow.objectId);
if(queryRows == null) {
LOG.warn("Found rows for query with id " + queryRow.objectId +
" missing from system catalog");
continue;
}
queryRows.add(queryRow);
}
// lastly, generate all the queries
List queries = new ArrayList();
for(Row row : queryInfo) {
String name = (String) row.get(CAT_COL_NAME);
Integer id = (Integer)row.get(CAT_COL_ID);
int flags = (Integer)row.get(CAT_COL_FLAGS);
List queryRows = queryRowMap.get(id);
queries.add(QueryImpl.create(flags, name, queryRows, id));
}
return queries;
}
public TableImpl getSystemTable(String tableName) throws IOException
{
return getTable(tableName, true);
}
public PropertyMap getDatabaseProperties() throws IOException {
if(_dbPropMaps == null) {
_dbPropMaps = getPropertiesForDbObject(OBJECT_NAME_DB_PROPS);
}
return _dbPropMaps.getDefault();
}
public PropertyMap getSummaryProperties() throws IOException {
if(_summaryPropMaps == null) {
_summaryPropMaps = getPropertiesForDbObject(OBJECT_NAME_SUMMARY_PROPS);
}
return _summaryPropMaps.getDefault();
}
public PropertyMap getUserDefinedProperties() throws IOException {
if(_userDefPropMaps == null) {
_userDefPropMaps = getPropertiesForDbObject(OBJECT_NAME_USERDEF_PROPS);
}
return _userDefPropMaps.getDefault();
}
/**
* @return the PropertyMaps for the object with the given id
* @usage _advanced_method_
*/
public PropertyMaps getPropertiesForObject(int objectId)
throws IOException
{
Row objectRow = _tableFinder.getObjectRow(
objectId, SYSTEM_CATALOG_PROPS_COLUMNS);
byte[] propsBytes = null;
RowIdImpl rowId = null;
if(objectRow != null) {
propsBytes = (byte[])objectRow.get(CAT_COL_PROPS);
rowId = (RowIdImpl)objectRow.getId();
}
return readProperties(propsBytes, objectId, rowId);
}
/**
* @return property group for the given "database" object
*/
private PropertyMaps getPropertiesForDbObject(String dbName)
throws IOException
{
if(_dbParentId == null) {
// need the parent if of the databases objects
_dbParentId = _tableFinder.findObjectId(DB_PARENT_ID,
SYSTEM_OBJECT_NAME_DATABASES);
if(_dbParentId == null) {
throw new IOException("Did not find required parent db id");
}
}
Row objectRow = _tableFinder.getObjectRow(
_dbParentId, dbName, SYSTEM_CATALOG_PROPS_COLUMNS);
byte[] propsBytes = null;
int objectId = -1;
RowIdImpl rowId = null;
if(objectRow != null) {
propsBytes = (byte[])objectRow.get(CAT_COL_PROPS);
objectId = (Integer)objectRow.get(CAT_COL_ID);
rowId = (RowIdImpl)objectRow.getId();
}
return readProperties(propsBytes, objectId, rowId);
}
public String getDatabasePassword() throws IOException
{
ByteBuffer buffer = takeSharedBuffer();
try {
_pageChannel.readPage(buffer, 0);
byte[] pwdBytes = new byte[_format.SIZE_PASSWORD];
buffer.position(_format.OFFSET_PASSWORD);
buffer.get(pwdBytes);
// de-mask password using extra password mask if necessary (the extra
// password mask is generated from the database creation date stored in
// the header)
byte[] pwdMask = getPasswordMask(buffer, _format);
if(pwdMask != null) {
for(int i = 0; i < pwdBytes.length; ++i) {
pwdBytes[i] ^= pwdMask[i % pwdMask.length];
}
}
boolean hasPassword = false;
for(int i = 0; i < pwdBytes.length; ++i) {
if(pwdBytes[i] != 0) {
hasPassword = true;
break;
}
}
if(!hasPassword) {
return null;
}
String pwd = ColumnImpl.decodeUncompressedText(pwdBytes, getCharset());
// remove any trailing null chars
int idx = pwd.indexOf('\0');
if(idx >= 0) {
pwd = pwd.substring(0, idx);
}
return pwd;
} finally {
releaseSharedBuffer(buffer);
}
}
/**
* Finds the relationships matching the given from and to tables from the
* given cursor and adds them to the given list.
*/
private void collectRelationships(
Cursor cursor, TableImpl fromTable, TableImpl toTable,
List relationships, boolean includeSystemTables)
throws IOException
{
String fromTableName = ((fromTable != null) ? fromTable.getName() : null);
String toTableName = ((toTable != null) ? toTable.getName() : null);
for(Row row : cursor) {
String fromName = (String)row.get(REL_COL_FROM_TABLE);
String toName = (String)row.get(REL_COL_TO_TABLE);
if(((fromTableName == null) ||
fromTableName.equalsIgnoreCase(fromName)) &&
((toTableName == null) ||
toTableName.equalsIgnoreCase(toName))) {
String relName = (String)row.get(REL_COL_NAME);
// found more info for a relationship. see if we already have some
// info for this relationship
Relationship rel = null;
for(Relationship tmp : relationships) {
if(tmp.getName().equalsIgnoreCase(relName)) {
rel = tmp;
break;
}
}
TableImpl relFromTable = fromTable;
if(relFromTable == null) {
relFromTable = getTable(fromName, includeSystemTables);
if(relFromTable == null) {
// invalid table or ignoring system tables, just ignore
continue;
}
}
TableImpl relToTable = toTable;
if(relToTable == null) {
relToTable = getTable(toName, includeSystemTables);
if(relToTable == null) {
// invalid table or ignoring system tables, just ignore
continue;
}
}
if(rel == null) {
// new relationship
int numCols = (Integer)row.get(REL_COL_COLUMN_COUNT);
int flags = (Integer)row.get(REL_COL_FLAGS);
rel = new RelationshipImpl(relName, relFromTable, relToTable,
flags, numCols);
relationships.add(rel);
}
// add column info
int colIdx = (Integer)row.get(REL_COL_COLUMN_INDEX);
ColumnImpl fromCol = relFromTable.getColumn(
(String)row.get(REL_COL_FROM_COLUMN));
ColumnImpl toCol = relToTable.getColumn(
(String)row.get(REL_COL_TO_COLUMN));
rel.getFromColumns().set(colIdx, fromCol);
rel.getToColumns().set(colIdx, toCol);
}
}
}
/**
* Add a new table to the system catalog
* @param name Table name
* @param pageNumber Page number that contains the table definition
*/
private void addToSystemCatalog(String name, int pageNumber, Short type,
String linkedDbName, String linkedTableName)
throws IOException
{
Object[] catalogRow = new Object[_systemCatalog.getColumnCount()];
int idx = 0;
Date creationTime = new Date();
for (Iterator iter = _systemCatalog.getColumns().iterator();
iter.hasNext(); idx++)
{
ColumnImpl col = iter.next();
if (CAT_COL_ID.equals(col.getName())) {
catalogRow[idx] = Integer.valueOf(pageNumber);
} else if (CAT_COL_NAME.equals(col.getName())) {
catalogRow[idx] = name;
} else if (CAT_COL_TYPE.equals(col.getName())) {
catalogRow[idx] = type;
} else if (CAT_COL_DATE_CREATE.equals(col.getName()) ||
CAT_COL_DATE_UPDATE.equals(col.getName())) {
catalogRow[idx] = creationTime;
} else if (CAT_COL_PARENT_ID.equals(col.getName())) {
catalogRow[idx] = _tableParentId;
} else if (CAT_COL_FLAGS.equals(col.getName())) {
catalogRow[idx] = Integer.valueOf(0);
} else if (CAT_COL_OWNER.equals(col.getName())) {
byte[] owner = new byte[2];
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);
}
/**
* Add a new table to the system's access control entries
* @param pageNumber Page number that contains the table definition
*/
private void addToAccessControlEntries(int pageNumber) throws IOException {
if(_newTableSIDs.isEmpty()) {
initNewTableSIDs();
}
TableImpl acEntries = getAccessControlEntries();
ColumnImpl acmCol = acEntries.getColumn(ACE_COL_ACM);
ColumnImpl inheritCol = acEntries.getColumn(ACE_COL_F_INHERITABLE);
ColumnImpl objIdCol = acEntries.getColumn(ACE_COL_OBJECT_ID);
ColumnImpl sidCol = acEntries.getColumn(ACE_COL_SID);
// construct a collection of ACE entries mimicing those of our parent, the
// "Tables" system object
List