git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1379 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-4.0.2
<body> | <body> | ||||
<release version="4.0.2" date="TBD"> | <release version="4.0.2" date="TBD"> | ||||
<action dev="jahlborn" type="update"> | <action dev="jahlborn" type="update"> | ||||
Add Table methods to access the creation and last modified dates. | |||||
Add Table methods to get the creation and last modified dates. | |||||
</action> | |||||
<action dev="jahlborn" type="update" issue="45"> | |||||
Add support for linked odbc tables. TableMetaData provides access to | |||||
connection string and local TableDefinition if available. | |||||
</action> | </action> | ||||
</release> | </release> | ||||
<release version="4.0.1" date="2021-06-21"> | <release version="4.0.1" date="2021-06-21"> |
* attributes. | * attributes. | ||||
*/ | */ | ||||
public Column addToTable(Table table) throws IOException { | public Column addToTable(Table table) throws IOException { | ||||
return addToTableDefinition(table); | |||||
} | |||||
/** | |||||
* Adds a new Column to the given TableDefinition with the currently | |||||
* configured attributes. | |||||
*/ | |||||
public Column addToTableDefinition(TableDefinition table) throws IOException { | |||||
return new TableUpdater((TableImpl)table).addColumn(this); | return new TableUpdater((TableImpl)table).addColumn(this); | ||||
} | } | ||||
* @see TableBuilder | * @see TableBuilder | ||||
* @usage _general_class_ | * @usage _general_class_ | ||||
*/ | */ | ||||
public class IndexBuilder | |||||
public class IndexBuilder | |||||
{ | { | ||||
/** name typically used by MS Access for the primary key index */ | /** name typically used by MS Access for the primary key index */ | ||||
public static final String PRIMARY_KEY_NAME = "PrimaryKey"; | public static final String PRIMARY_KEY_NAME = "PrimaryKey"; | ||||
public IndexBuilder setUnique() { | public IndexBuilder setUnique() { | ||||
_flags |= IndexData.UNIQUE_INDEX_FLAG; | _flags |= IndexData.UNIQUE_INDEX_FLAG; | ||||
return this; | return this; | ||||
} | |||||
} | |||||
/** | /** | ||||
* Sets this index to encforce required. | * Sets this index to encforce required. | ||||
public IndexBuilder setRequired() { | public IndexBuilder setRequired() { | ||||
_flags |= IndexData.REQUIRED_INDEX_FLAG; | _flags |= IndexData.REQUIRED_INDEX_FLAG; | ||||
return this; | return this; | ||||
} | |||||
} | |||||
/** | /** | ||||
* Sets this index to ignore null values. | * Sets this index to ignore null values. | ||||
public IndexBuilder setIgnoreNulls() { | public IndexBuilder setIgnoreNulls() { | ||||
_flags |= IndexData.IGNORE_NULLS_INDEX_FLAG; | _flags |= IndexData.IGNORE_NULLS_INDEX_FLAG; | ||||
return this; | return this; | ||||
} | |||||
} | |||||
/** | /** | ||||
* @usage _advanced_method_ | * @usage _advanced_method_ | ||||
* attributes. | * attributes. | ||||
*/ | */ | ||||
public Index addToTable(Table table) throws IOException { | public Index addToTable(Table table) throws IOException { | ||||
return addToTableDefinition(table); | |||||
} | |||||
/** | |||||
* Adds a new Index to the given TableDefinition with the currently | |||||
* configured attributes. | |||||
*/ | |||||
public Index addToTableDefinition(TableDefinition table) throws IOException { | |||||
return new TableUpdater((TableImpl)table).addIndex(this); | return new TableUpdater((TableImpl)table).addIndex(this); | ||||
} | } | ||||
* @author James Ahlborn | * @author James Ahlborn | ||||
* @usage _general_class_ | * @usage _general_class_ | ||||
*/ | */ | ||||
public interface Table extends Iterable<Row> | |||||
public interface Table extends Iterable<Row>, TableDefinition | |||||
{ | { | ||||
/** | /** | ||||
* enum which controls the ordering of the columns in a table. | * enum which controls the ordering of the columns in a table. | ||||
* @return The name of the table | * @return The name of the table | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
*/ | */ | ||||
@Override | |||||
public String getName(); | public String getName(); | ||||
/** | /** | ||||
* Whether or not this table has been marked as hidden. | * Whether or not this table has been marked as hidden. | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
*/ | */ | ||||
@Override | |||||
public boolean isHidden(); | public boolean isHidden(); | ||||
/** | /** | ||||
* Whether or not this table is a system (internal) table. | * Whether or not this table is a system (internal) table. | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
*/ | */ | ||||
@Override | |||||
public boolean isSystem(); | public boolean isSystem(); | ||||
/** | /** | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
*/ | */ | ||||
@Override | |||||
public int getColumnCount(); | public int getColumnCount(); | ||||
/** | /** | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
*/ | */ | ||||
@Override | |||||
public Database getDatabase(); | public Database getDatabase(); | ||||
/** | /** | ||||
* @return All of the columns in this table (unmodifiable List) | * @return All of the columns in this table (unmodifiable List) | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
*/ | */ | ||||
@Override | |||||
public List<? extends Column> getColumns(); | public List<? extends Column> getColumns(); | ||||
/** | /** | ||||
* @return the column with the given name | * @return the column with the given name | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
*/ | */ | ||||
@Override | |||||
public Column getColumn(String name); | public Column getColumn(String name); | ||||
/** | /** | ||||
* @return the properties for this table | * @return the properties for this table | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
*/ | */ | ||||
@Override | |||||
public PropertyMap getProperties() throws IOException; | public PropertyMap getProperties() throws IOException; | ||||
/** | /** | ||||
* @return the created date for this table if available | * @return the created date for this table if available | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
*/ | */ | ||||
@Override | |||||
public LocalDateTime getCreatedDate() throws IOException; | public LocalDateTime getCreatedDate() throws IOException; | ||||
/** | /** | ||||
* @return the last updated date for this table if available | * @return the last updated date for this table if available | ||||
* @usage _general_method_ | * @usage _general_method_ | ||||
*/ | */ | ||||
@Override | |||||
public LocalDateTime getUpdatedDate() throws IOException; | public LocalDateTime getUpdatedDate() throws IOException; | ||||
/** | /** | ||||
* @return All of the Indexes on this table (unmodifiable List) | * @return All of the Indexes on this table (unmodifiable List) | ||||
* @usage _intermediate_method_ | * @usage _intermediate_method_ | ||||
*/ | */ | ||||
@Override | |||||
public List<? extends Index> getIndexes(); | public List<? extends Index> getIndexes(); | ||||
/** | /** | ||||
* @throws IllegalArgumentException if there is no index with the given name | * @throws IllegalArgumentException if there is no index with the given name | ||||
* @usage _intermediate_method_ | * @usage _intermediate_method_ | ||||
*/ | */ | ||||
@Override | |||||
public Index getIndex(String name); | public Index getIndex(String name); | ||||
/** | /** | ||||
* table | * table | ||||
* @usage _intermediate_method_ | * @usage _intermediate_method_ | ||||
*/ | */ | ||||
@Override | |||||
public Index getPrimaryKeyIndex(); | public Index getPrimaryKeyIndex(); | ||||
/** | /** | ||||
* table and the given table | * table and the given table | ||||
* @usage _intermediate_method_ | * @usage _intermediate_method_ | ||||
*/ | */ | ||||
@Override | |||||
public Index getForeignKeyIndex(Table otherTable); | public Index getForeignKeyIndex(Table otherTable); | ||||
/** | /** |
/* | |||||
Copyright (c) 2022 James Ahlborn | |||||
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 com.healthmarketscience.jackcess; | |||||
import java.io.IOException; | |||||
import java.time.LocalDateTime; | |||||
import java.util.List; | |||||
/** | |||||
* The definition of a single database table. A TableDefinition instance is | |||||
* retrieved from a {@link TableMetaData} instance. The TableDefinition | |||||
* instance only provides access to the table metadata, but no table data. | |||||
* <p> | |||||
* A TableDefinition instance is not thread-safe (see {@link Database} for | |||||
* more thread-safety details). | |||||
* | |||||
* @author James Ahlborn | |||||
* @usage _intermediate_class_ | |||||
*/ | |||||
public interface TableDefinition | |||||
{ | |||||
/** | |||||
* @return The name of the table | |||||
* @usage _general_method_ | |||||
*/ | |||||
public String getName(); | |||||
/** | |||||
* Whether or not this table has been marked as hidden. | |||||
* @usage _general_method_ | |||||
*/ | |||||
public boolean isHidden(); | |||||
/** | |||||
* Whether or not this table is a system (internal) table. | |||||
* @usage _general_method_ | |||||
*/ | |||||
public boolean isSystem(); | |||||
/** | |||||
* @usage _general_method_ | |||||
*/ | |||||
public int getColumnCount(); | |||||
/** | |||||
* @usage _general_method_ | |||||
*/ | |||||
public Database getDatabase(); | |||||
/** | |||||
* @return All of the columns in this table (unmodifiable List) | |||||
* @usage _general_method_ | |||||
*/ | |||||
public List<? extends Column> getColumns(); | |||||
/** | |||||
* @return the column with the given name | |||||
* @usage _general_method_ | |||||
*/ | |||||
public Column getColumn(String name); | |||||
/** | |||||
* @return the properties for this table | |||||
* @usage _general_method_ | |||||
*/ | |||||
public PropertyMap getProperties() throws IOException; | |||||
/** | |||||
* @return the created date for this table if available | |||||
* @usage _general_method_ | |||||
*/ | |||||
public LocalDateTime getCreatedDate() throws IOException; | |||||
/** | |||||
* Note: jackcess <i>does not automatically update the modified date of a | |||||
* Table</i>. | |||||
* | |||||
* @return the last updated date for this table if available | |||||
* @usage _general_method_ | |||||
*/ | |||||
public LocalDateTime getUpdatedDate() throws IOException; | |||||
/** | |||||
* @return All of the Indexes on this table (unmodifiable List) | |||||
* @usage _intermediate_method_ | |||||
*/ | |||||
public List<? extends Index> getIndexes(); | |||||
/** | |||||
* @return the index with the given name | |||||
* @throws IllegalArgumentException if there is no index with the given name | |||||
* @usage _intermediate_method_ | |||||
*/ | |||||
public Index getIndex(String name); | |||||
/** | |||||
* @return the primary key index for this table | |||||
* @throws IllegalArgumentException if there is no primary key index on this | |||||
* table | |||||
* @usage _intermediate_method_ | |||||
*/ | |||||
public Index getPrimaryKeyIndex(); | |||||
/** | |||||
* @return the foreign key index joining this table to the given other table | |||||
* @throws IllegalArgumentException if there is no relationship between this | |||||
* table and the given table | |||||
* @usage _intermediate_method_ | |||||
*/ | |||||
public Index getForeignKeyIndex(Table otherTable); | |||||
} |
* @author James Ahlborn | * @author James Ahlborn | ||||
* @usage _intermediate_class_ | * @usage _intermediate_class_ | ||||
*/ | */ | ||||
public interface TableMetaData | |||||
public interface TableMetaData | |||||
{ | { | ||||
public enum Type { | |||||
LOCAL, LINKED, LINKED_ODBC; | |||||
} | |||||
/** | |||||
* The type of table | |||||
*/ | |||||
public Type getType(); | |||||
/** | /** | ||||
* The name of the table (as it is stored in the database) | * The name of the table (as it is stored in the database) | ||||
*/ | */ | ||||
* {@code true} if this is a linked table, {@code false} otherwise. | * {@code true} if this is a linked table, {@code false} otherwise. | ||||
*/ | */ | ||||
public boolean isLinked(); | public boolean isLinked(); | ||||
/** | /** | ||||
* {@code true} if this is a system table, {@code false} otherwise. | * {@code true} if this is a system table, {@code false} otherwise. | ||||
*/ | */ | ||||
*/ | */ | ||||
public String getLinkedDbName(); | public String getLinkedDbName(); | ||||
/** | |||||
* The connection of this the linked database if this is a linked ODBC | |||||
* table, {@code null} otherwise. | |||||
*/ | |||||
public String getConnectionName(); | |||||
/** | /** | ||||
* Opens this table from the given Database instance. | * Opens this table from the given Database instance. | ||||
*/ | */ | ||||
public Table open(Database db) throws IOException; | public Table open(Database db) throws IOException; | ||||
/** | |||||
* Gets the local table definition from the given Database instance if | |||||
* available. Only useful for linked ODBC tables. | |||||
*/ | |||||
public TableDefinition getTableDefinition(Database db) throws IOException; | |||||
} | } |
import com.healthmarketscience.jackcess.RuntimeIOException; | import com.healthmarketscience.jackcess.RuntimeIOException; | ||||
import com.healthmarketscience.jackcess.Table; | import com.healthmarketscience.jackcess.Table; | ||||
import com.healthmarketscience.jackcess.TableBuilder; | import com.healthmarketscience.jackcess.TableBuilder; | ||||
import com.healthmarketscience.jackcess.TableDefinition; | |||||
import com.healthmarketscience.jackcess.TableMetaData; | import com.healthmarketscience.jackcess.TableMetaData; | ||||
import com.healthmarketscience.jackcess.expr.EvalConfig; | import com.healthmarketscience.jackcess.expr.EvalConfig; | ||||
import com.healthmarketscience.jackcess.impl.query.QueryImpl; | import com.healthmarketscience.jackcess.impl.query.QueryImpl; | ||||
private static final String CAT_COL_DATABASE = "Database"; | private static final String CAT_COL_DATABASE = "Database"; | ||||
/** System catalog column name of the remote table name */ | /** System catalog column name of the remote table name */ | ||||
private static final String CAT_COL_FOREIGN_NAME = "ForeignName"; | private static final String CAT_COL_FOREIGN_NAME = "ForeignName"; | ||||
/** System catalog column name of the remote connection name */ | |||||
private static final String CAT_COL_CONNECT_NAME = "Connect"; | |||||
/** top-level parentid for a database */ | /** top-level parentid for a database */ | ||||
private static final int DB_PARENT_ID = 0xF000000; | private static final int DB_PARENT_ID = 0xF000000; | ||||
private static final String OBJECT_NAME_USERDEF_PROPS = "UserDefined"; | private static final String OBJECT_NAME_USERDEF_PROPS = "UserDefined"; | ||||
/** System object type for table definitions */ | /** System object type for table definitions */ | ||||
static final Short TYPE_TABLE = 1; | static final Short TYPE_TABLE = 1; | ||||
/** System object type for linked odbc tables */ | |||||
private static final Short TYPE_LINKED_ODBC_TABLE = 4; | |||||
/** System object type for query definitions */ | /** System object type for query definitions */ | ||||
private static final Short TYPE_QUERY = 5; | private static final Short TYPE_QUERY = 5; | ||||
/** System object type for linked table definitions */ | /** System object type for linked table definitions */ | ||||
private static Collection<String> SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS = | private static Collection<String> SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS = | ||||
new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID, | new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID, | ||||
CAT_COL_FLAGS, CAT_COL_PARENT_ID, | CAT_COL_FLAGS, CAT_COL_PARENT_ID, | ||||
CAT_COL_DATABASE, CAT_COL_FOREIGN_NAME)); | |||||
CAT_COL_DATABASE, CAT_COL_FOREIGN_NAME, | |||||
CAT_COL_CONNECT_NAME)); | |||||
/** the columns to read when getting object propertyes */ | /** the columns to read when getting object propertyes */ | ||||
private static Collection<String> SYSTEM_CATALOG_PROPS_COLUMNS = | private static Collection<String> SYSTEM_CATALOG_PROPS_COLUMNS = | ||||
new HashSet<String>(Arrays.asList(CAT_COL_ID, CAT_COL_PROPS)); | new HashSet<String>(Arrays.asList(CAT_COL_ID, CAT_COL_PROPS)); | ||||
private static final Pattern INVALID_IDENTIFIER_CHARS = | private static final Pattern INVALID_IDENTIFIER_CHARS = | ||||
Pattern.compile("[\\p{Cntrl}.!`\\]\\[]"); | Pattern.compile("[\\p{Cntrl}.!`\\]\\[]"); | ||||
/** regex to match a password in an ODBC string */ | |||||
private static final Pattern ODBC_PWD_PATTERN = Pattern.compile("\\bPWD=[^;]+"); | |||||
/** the File of the database */ | /** the File of the database */ | ||||
private final Path _file; | private final Path _file; | ||||
/** the simple name of the database */ | /** the simple name of the database */ | ||||
private boolean _evaluateExpressions; | private boolean _evaluateExpressions; | ||||
/** factory for ColumnValidators */ | /** factory for ColumnValidators */ | ||||
private ColumnValidatorFactory _validatorFactory = SimpleColumnValidatorFactory.INSTANCE; | private ColumnValidatorFactory _validatorFactory = SimpleColumnValidatorFactory.INSTANCE; | ||||
/** cache of in-use tables */ | |||||
/** cache of in-use tables (or table definitions) */ | |||||
private final TableCache _tableCache = new TableCache(); | private final TableCache _tableCache = new TableCache(); | ||||
/** handler for reading/writing properteies */ | /** handler for reading/writing properteies */ | ||||
private PropertyMaps.Handler _propsHandler; | private PropertyMaps.Handler _propsHandler; | ||||
// common case, local table name == remote table name | // common case, local table name == remote table name | ||||
TableInfo tableInfo = lookupTable(table.getName()); | TableInfo tableInfo = lookupTable(table.getName()); | ||||
if((tableInfo != null) && tableInfo.isLinked() && | |||||
matchesLinkedTable(table, ((LinkedTableInfo)tableInfo).linkedTableName, | |||||
((LinkedTableInfo)tableInfo).linkedDbName)) { | |||||
if((tableInfo != null) && | |||||
(tableInfo.getType() == TableMetaData.Type.LINKED) && | |||||
matchesLinkedTable(table, tableInfo.getLinkedTableName(), | |||||
tableInfo.getLinkedDbName())) { | |||||
return true; | return true; | ||||
} | } | ||||
* Read the system catalog | * Read the system catalog | ||||
*/ | */ | ||||
private void readSystemCatalog() throws IOException { | private void readSystemCatalog() throws IOException { | ||||
_systemCatalog = readTable(TABLE_SYSTEM_CATALOG, PAGE_SYSTEM_CATALOG, | |||||
SYSTEM_OBJECT_FLAGS); | |||||
_systemCatalog = loadTable(TABLE_SYSTEM_CATALOG, PAGE_SYSTEM_CATALOG, | |||||
SYSTEM_OBJECT_FLAGS, TYPE_TABLE); | |||||
try { | try { | ||||
_tableFinder = new DefaultTableFinder( | _tableFinder = new DefaultTableFinder( | ||||
* @usage _advanced_method_ | * @usage _advanced_method_ | ||||
*/ | */ | ||||
public TableImpl getTable(int tableDefPageNumber) throws IOException { | 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 = objectRow.getString(CAT_COL_NAME); | |||||
int flags = objectRow.getInt(CAT_COL_FLAGS); | |||||
return readTable(name, tableDefPageNumber, flags); | |||||
return loadTable(null, tableDefPageNumber, 0, null); | |||||
} | } | ||||
/** | /** | ||||
private TableImpl getTable(TableInfo tableInfo, boolean includeSystemTables) | private TableImpl getTable(TableInfo tableInfo, boolean includeSystemTables) | ||||
throws IOException | throws IOException | ||||
{ | { | ||||
if(tableInfo.isLinked()) { | |||||
if(tableInfo.getType() == TableMetaData.Type.LINKED) { | |||||
if(_linkedDbs == null) { | if(_linkedDbs == null) { | ||||
_linkedDbs = new HashMap<String,Database>(); | _linkedDbs = new HashMap<String,Database>(); | ||||
} | } | ||||
String linkedDbName = ((LinkedTableInfo)tableInfo).linkedDbName; | |||||
String linkedTableName = ((LinkedTableInfo)tableInfo).linkedTableName; | |||||
String linkedDbName = tableInfo.getLinkedDbName(); | |||||
String linkedTableName = tableInfo.getLinkedTableName(); | |||||
Database linkedDb = _linkedDbs.get(linkedDbName); | Database linkedDb = _linkedDbs.get(linkedDbName); | ||||
if(linkedDb == null) { | if(linkedDb == null) { | ||||
linkedDb = getLinkResolver().resolveLinkedDatabase(this, linkedDbName); | linkedDb = getLinkResolver().resolveLinkedDatabase(this, linkedDbName); | ||||
includeSystemTables); | includeSystemTables); | ||||
} | } | ||||
return readTable(tableInfo.tableName, tableInfo.pageNumber, | |||||
tableInfo.flags); | |||||
return loadTable(tableInfo.tableName, tableInfo.pageNumber, | |||||
tableInfo.flags, tableInfo.tableType); | |||||
} | } | ||||
/** | /** | ||||
} | } | ||||
} | } | ||||
private String createRelationshipName(RelationshipCreator creator) | |||||
throws IOException | |||||
{ | |||||
private String createRelationshipName(RelationshipCreator creator) { | |||||
// ensure that the final identifier name does not get too long | // ensure that the final identifier name does not get too long | ||||
// - the primary name is limited to ((max / 2) - 3) | // - the primary name is limited to ((max / 2) - 3) | ||||
// - the total name is limited to (max - 3) | // - the total name is limited to (max - 3) | ||||
/** | /** | ||||
* Reads a table with the given name from the given pageNumber. | * Reads a table with the given name from the given pageNumber. | ||||
*/ | */ | ||||
private TableImpl readTable(String name, int pageNumber, int flags) | |||||
private TableImpl loadTable(String name, int pageNumber, int flags, Short type) | |||||
throws IOException | throws IOException | ||||
{ | { | ||||
// first, check for existing table | // first, check for existing table | ||||
return table; | return table; | ||||
} | } | ||||
if(name == null) { | |||||
// lookup table info from system catalog | |||||
Row objectRow = _tableFinder.getObjectRow( | |||||
pageNumber, SYSTEM_CATALOG_COLUMNS); | |||||
if(objectRow == null) { | |||||
return null; | |||||
} | |||||
name = objectRow.getString(CAT_COL_NAME); | |||||
flags = objectRow.getInt(CAT_COL_FLAGS); | |||||
type = objectRow.getShort(CAT_COL_TYPE); | |||||
} | |||||
// need to load table from db | |||||
return _tableCache.put(readTable(name, pageNumber, flags, type)); | |||||
} | |||||
/** | |||||
* Reads a table with the given name from the given pageNumber. | |||||
*/ | |||||
private TableImpl readTable( | |||||
String name, int pageNumber, int flags, Short type) | |||||
throws IOException | |||||
{ | |||||
ByteBuffer buffer = takeSharedBuffer(); | ByteBuffer buffer = takeSharedBuffer(); | ||||
try { | try { | ||||
// need to load table from db | // need to load table from db | ||||
"Looking for " + name + " at page " + pageNumber + | "Looking for " + name + " at page " + pageNumber + | ||||
", but page type is " + pageType)); | ", but page type is " + pageType)); | ||||
} | } | ||||
return _tableCache.put( | |||||
new TableImpl(this, buffer, pageNumber, name, flags)); | |||||
return (!TYPE_LINKED_ODBC_TABLE.equals(type) ? | |||||
new TableImpl(this, buffer, pageNumber, name, flags) : | |||||
new TableDefinitionImpl(this, buffer, pageNumber, name, flags)); | |||||
} finally { | } finally { | ||||
releaseSharedBuffer(buffer); | releaseSharedBuffer(buffer); | ||||
} | } | ||||
{ | { | ||||
_tableLookup.put(toLookupName(tableName), | _tableLookup.put(toLookupName(tableName), | ||||
createTableInfo(tableName, pageNumber, 0, type, | createTableInfo(tableName, pageNumber, 0, type, | ||||
linkedDbName, linkedTableName)); | |||||
linkedDbName, linkedTableName, null)); | |||||
// clear this, will be created next time needed | // clear this, will be created next time needed | ||||
_tableNames = null; | _tableNames = null; | ||||
} | } | ||||
private static TableInfo createTableInfo( | |||||
String tableName, Short type, Row row) { | |||||
Integer pageNumber = row.getInt(CAT_COL_ID); | |||||
int flags = row.getInt(CAT_COL_FLAGS); | |||||
String linkedDbName = row.getString(CAT_COL_DATABASE); | |||||
String linkedTableName = row.getString(CAT_COL_FOREIGN_NAME); | |||||
String connectName = row.getString(CAT_COL_CONNECT_NAME); | |||||
return createTableInfo(tableName, pageNumber, flags, type, linkedDbName, | |||||
linkedTableName, connectName); | |||||
} | |||||
/** | /** | ||||
* Creates a TableInfo instance appropriate for the given table data. | * Creates a TableInfo instance appropriate for the given table data. | ||||
*/ | */ | ||||
private static TableInfo createTableInfo( | private static TableInfo createTableInfo( | ||||
String tableName, Integer pageNumber, int flags, Short type, | String tableName, Integer pageNumber, int flags, Short type, | ||||
String linkedDbName, String linkedTableName) | |||||
String linkedDbName, String linkedTableName, String connectName) | |||||
{ | { | ||||
if(TYPE_LINKED_TABLE.equals(type)) { | if(TYPE_LINKED_TABLE.equals(type)) { | ||||
return new LinkedTableInfo(pageNumber, tableName, flags, linkedDbName, | |||||
linkedTableName); | |||||
return new LinkedTableInfo(pageNumber, tableName, flags, type, | |||||
linkedDbName, linkedTableName); | |||||
} else if(TYPE_LINKED_ODBC_TABLE.equals(type)) { | |||||
return new LinkedODBCTableInfo(pageNumber, tableName, flags, type, | |||||
connectName, linkedTableName); | |||||
} | } | ||||
return new TableInfo(pageNumber, tableName, flags); | |||||
return new TableInfo(pageNumber, tableName, flags, type); | |||||
} | } | ||||
/** | /** | ||||
} | } | ||||
private static boolean isTableType(Short objType) { | private static boolean isTableType(Short objType) { | ||||
return(TYPE_TABLE.equals(objType) || TYPE_LINKED_TABLE.equals(objType)); | |||||
return(TYPE_TABLE.equals(objType) || isAnyLinkedTableType(objType)); | |||||
} | } | ||||
public static FileFormatDetails getFileFormatDetails(FileFormat fileFormat) { | public static FileFormatDetails getFileFormatDetails(FileFormat fileFormat) { | ||||
return defaultValue; | return defaultValue; | ||||
} | } | ||||
private static boolean isAnyLinkedTableType(Short type) { | |||||
return (TYPE_LINKED_TABLE.equals(type) || | |||||
TYPE_LINKED_ODBC_TABLE.equals(type)); | |||||
} | |||||
/** | /** | ||||
* Utility class for storing table page number and actual name. | * Utility class for storing table page number and actual name. | ||||
*/ | */ | ||||
public final Integer pageNumber; | public final Integer pageNumber; | ||||
public final String tableName; | public final String tableName; | ||||
public final int flags; | public final int flags; | ||||
public final Short tableType; | |||||
private TableInfo(Integer newPageNumber, String newTableName, int newFlags) { | |||||
private TableInfo(Integer newPageNumber, String newTableName, int newFlags, | |||||
Short newTableType) { | |||||
pageNumber = newPageNumber; | pageNumber = newPageNumber; | ||||
tableName = newTableName; | tableName = newTableName; | ||||
flags = newFlags; | flags = newFlags; | ||||
tableType = newTableType; | |||||
} | |||||
@Override | |||||
public Type getType() { | |||||
return Type.LOCAL; | |||||
} | } | ||||
@Override | @Override | ||||
return null; | return null; | ||||
} | } | ||||
@Override | |||||
public String getConnectionName() { | |||||
return null; | |||||
} | |||||
@Override | @Override | ||||
public Table open(Database db) throws IOException { | public Table open(Database db) throws IOException { | ||||
return ((DatabaseImpl)db).getTable(this, true); | return ((DatabaseImpl)db).getTable(this, true); | ||||
} | } | ||||
@Override | |||||
public TableDefinition getTableDefinition(Database db) throws IOException { | |||||
return null; | |||||
} | |||||
@Override | @Override | ||||
public String toString() { | public String toString() { | ||||
ToStringBuilder sb = CustomToStringStyle.valueBuilder("TableMetaData") | ToStringBuilder sb = CustomToStringStyle.valueBuilder("TableMetaData") | ||||
if(isLinked()) { | if(isLinked()) { | ||||
sb.append("isLinked", isLinked()) | sb.append("isLinked", isLinked()) | ||||
.append("linkedTableName", getLinkedTableName()) | .append("linkedTableName", getLinkedTableName()) | ||||
.append("linkedDbName", getLinkedDbName()); | |||||
.append("linkedDbName", getLinkedDbName()) | |||||
.append("connectionName", maskPassword(getConnectionName())); | |||||
} | } | ||||
return sb.toString(); | return sb.toString(); | ||||
} | } | ||||
private static String maskPassword(String connectionName) { | |||||
return ((connectionName != null) ? | |||||
ODBC_PWD_PATTERN.matcher(connectionName).replaceAll("PWD=XXXXXX") : | |||||
null); | |||||
} | |||||
} | } | ||||
/** | /** | ||||
*/ | */ | ||||
private static class LinkedTableInfo extends TableInfo | private static class LinkedTableInfo extends TableInfo | ||||
{ | { | ||||
private final String linkedDbName; | |||||
private final String linkedTableName; | |||||
private final String _linkedDbName; | |||||
private final String _linkedTableName; | |||||
private LinkedTableInfo(Integer newPageNumber, String newTableName, | private LinkedTableInfo(Integer newPageNumber, String newTableName, | ||||
int newFlags, String newLinkedDbName, | |||||
int newFlags, Short newTableType, | |||||
String newLinkedDbName, | |||||
String newLinkedTableName) { | String newLinkedTableName) { | ||||
super(newPageNumber, newTableName, newFlags); | |||||
linkedDbName = newLinkedDbName; | |||||
linkedTableName = newLinkedTableName; | |||||
super(newPageNumber, newTableName, newFlags, newTableType); | |||||
_linkedDbName = newLinkedDbName; | |||||
_linkedTableName = newLinkedTableName; | |||||
} | |||||
@Override | |||||
public Type getType() { | |||||
return Type.LINKED; | |||||
} | } | ||||
@Override | @Override | ||||
@Override | @Override | ||||
public String getLinkedTableName() { | public String getLinkedTableName() { | ||||
return linkedTableName; | |||||
return _linkedTableName; | |||||
} | } | ||||
@Override | @Override | ||||
public String getLinkedDbName() { | public String getLinkedDbName() { | ||||
return linkedDbName; | |||||
return _linkedDbName; | |||||
} | |||||
} | |||||
/** | |||||
* Utility class for storing linked ODBC table info | |||||
*/ | |||||
private static class LinkedODBCTableInfo extends TableInfo | |||||
{ | |||||
private final String _linkedTableName; | |||||
private final String _connectionName; | |||||
private LinkedODBCTableInfo(Integer newPageNumber, String newTableName, | |||||
int newFlags, Short newTableType, | |||||
String connectName, | |||||
String newLinkedTableName) { | |||||
super(newPageNumber, newTableName, newFlags, newTableType); | |||||
_linkedTableName = newLinkedTableName; | |||||
_connectionName = connectName; | |||||
} | |||||
@Override | |||||
public Type getType() { | |||||
return Type.LINKED_ODBC; | |||||
} | |||||
@Override | |||||
public boolean isLinked() { | |||||
return true; | |||||
} | |||||
@Override | |||||
public String getLinkedTableName() { | |||||
return _linkedTableName; | |||||
} | |||||
@Override | |||||
public String getConnectionName() { | |||||
return _connectionName; | |||||
} | |||||
@Override | |||||
public Table open(Database db) throws IOException { | |||||
return null; | |||||
} | |||||
@Override | |||||
public TableDefinition getTableDefinition(Database db) throws IOException { | |||||
return (((pageNumber != null) && (pageNumber > 0)) ? | |||||
((DatabaseImpl)db).getTable(this, true) : | |||||
null); | |||||
} | } | ||||
} | } | ||||
} else if(systemTables) { | } else if(systemTables) { | ||||
tableNames.add(tableName); | tableNames.add(tableName); | ||||
} | } | ||||
} else if(TYPE_LINKED_TABLE.equals(type) && linkedTables) { | |||||
} else if(linkedTables && isAnyLinkedTableType(type)) { | |||||
tableNames.add(tableName); | tableNames.add(tableName); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
String realName = row.getString(CAT_COL_NAME); | String realName = row.getString(CAT_COL_NAME); | ||||
Integer pageNumber = row.getInt(CAT_COL_ID); | |||||
int flags = row.getInt(CAT_COL_FLAGS); | |||||
String linkedDbName = row.getString(CAT_COL_DATABASE); | |||||
String linkedTableName = row.getString(CAT_COL_FOREIGN_NAME); | |||||
return createTableInfo(realName, pageNumber, flags, type, linkedDbName, | |||||
linkedTableName); | |||||
return createTableInfo(realName, type, row); | |||||
} | } | ||||
return null; | return null; | ||||
Row row = _systemCatalogCursor.getCurrentRow( | Row row = _systemCatalogCursor.getCurrentRow( | ||||
SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS); | SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS); | ||||
Integer pageNumber = row.getInt(CAT_COL_ID); | |||||
String realName = row.getString(CAT_COL_NAME); | |||||
int flags = row.getInt(CAT_COL_FLAGS); | |||||
Short type = row.getShort(CAT_COL_TYPE); | Short type = row.getShort(CAT_COL_TYPE); | ||||
if(!isTableType(type)) { | if(!isTableType(type)) { | ||||
return null; | return null; | ||||
} | } | ||||
String linkedDbName = row.getString(CAT_COL_DATABASE); | |||||
String linkedTableName = row.getString(CAT_COL_FOREIGN_NAME); | |||||
String realName = row.getString(CAT_COL_NAME); | |||||
return createTableInfo(realName, pageNumber, flags, type, linkedDbName, | |||||
linkedTableName); | |||||
return createTableInfo(realName, type, row); | |||||
} | } | ||||
@Override | @Override | ||||
continue; | continue; | ||||
} | } | ||||
Integer pageNumber = row.getInt(CAT_COL_ID); | |||||
int flags = row.getInt(CAT_COL_FLAGS); | |||||
String linkedDbName = row.getString(CAT_COL_DATABASE); | |||||
String linkedTableName = row.getString(CAT_COL_FOREIGN_NAME); | |||||
return createTableInfo(realName, pageNumber, flags, type, linkedDbName, | |||||
linkedTableName); | |||||
return createTableInfo(realName, type, row); | |||||
} | } | ||||
return null; | return null; |
/* | |||||
Copyright (c) 2022 James Ahlborn | |||||
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 com.healthmarketscience.jackcess.impl; | |||||
import java.io.IOException; | |||||
import java.nio.ByteBuffer; | |||||
import java.util.List; | |||||
/** | |||||
* A database table definition which does not allow any actual data | |||||
* interaction (read or write). | |||||
* <p> | |||||
* Note, in an ideal world, TableImpl would extend TableDefinitionImpl. | |||||
* However, since TableDefinitionImpl came later, it was easier to do it this | |||||
* way and avoid a lot of unnecessary code shuffling. | |||||
* <p> | |||||
* Is not thread-safe. | |||||
* | |||||
* @author James Ahlborn | |||||
* @usage _advanced_class_ | |||||
*/ | |||||
public class TableDefinitionImpl extends TableImpl | |||||
{ | |||||
protected TableDefinitionImpl(DatabaseImpl database, ByteBuffer tableBuffer, | |||||
int pageNumber, String name, int flags) | |||||
throws IOException { | |||||
super(database, tableBuffer, pageNumber, name, flags); | |||||
} | |||||
@Override | |||||
protected List<? extends Object[]> addRows(List<? extends Object[]> rows, | |||||
final boolean isBatchWrite) | |||||
throws IOException { | |||||
// all row additions eventually flow through this method | |||||
throw new UnsupportedOperationException( | |||||
withErrorContext("TableDefinition has no data access")); | |||||
} | |||||
@Override | |||||
public RowState createRowState() { | |||||
// RowState is needed for all traversal operations, so this kills any data | |||||
// reading as well as update/delete methods | |||||
throw new UnsupportedOperationException( | |||||
withErrorContext("TableDefinition has no data access")); | |||||
} | |||||
} |
* rows have been written, and every time a data page is filled. | * rows have been written, and every time a data page is filled. | ||||
* @param rows List of Object[] row values | * @param rows List of Object[] row values | ||||
*/ | */ | ||||
private List<? extends Object[]> addRows(List<? extends Object[]> rows, | |||||
final boolean isBatchWrite) | |||||
protected List<? extends Object[]> addRows(List<? extends Object[]> rows, | |||||
final boolean isBatchWrite) | |||||
throws IOException | throws IOException | ||||
{ | { | ||||
if(rows.isEmpty()) { | if(rows.isEmpty()) { |
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import static com.healthmarketscience.jackcess.Database.*; | import static com.healthmarketscience.jackcess.Database.*; | ||||
import static com.healthmarketscience.jackcess.DatabaseBuilder.*; | |||||
import static com.healthmarketscience.jackcess.TestUtil.*; | |||||
import com.healthmarketscience.jackcess.impl.ColumnImpl; | import com.healthmarketscience.jackcess.impl.ColumnImpl; | ||||
import com.healthmarketscience.jackcess.impl.DatabaseImpl; | import com.healthmarketscience.jackcess.impl.DatabaseImpl; | ||||
import static com.healthmarketscience.jackcess.impl.JetFormatTest.*; | import static com.healthmarketscience.jackcess.impl.JetFormatTest.*; | ||||
import com.healthmarketscience.jackcess.impl.RowIdImpl; | import com.healthmarketscience.jackcess.impl.RowIdImpl; | ||||
import com.healthmarketscience.jackcess.impl.RowImpl; | import com.healthmarketscience.jackcess.impl.RowImpl; | ||||
import com.healthmarketscience.jackcess.impl.TableImpl; | import com.healthmarketscience.jackcess.impl.TableImpl; | ||||
import com.healthmarketscience.jackcess.util.LinkResolver; | |||||
import com.healthmarketscience.jackcess.util.RowFilterTest; | import com.healthmarketscience.jackcess.util.RowFilterTest; | ||||
import junit.framework.TestCase; | import junit.framework.TestCase; | ||||
import static com.healthmarketscience.jackcess.TestUtil.*; | |||||
import static com.healthmarketscience.jackcess.DatabaseBuilder.*; | |||||
/** | /** | ||||
} | } | ||||
} | } | ||||
public void testLinkedTables() throws Exception { | |||||
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.LINKED)) { | |||||
Database db = openCopy(testDB); | |||||
try { | |||||
db.getTable("Table2"); | |||||
fail("FileNotFoundException should have been thrown"); | |||||
} catch(FileNotFoundException e) { | |||||
// success | |||||
} | |||||
TableMetaData tmd = db.getTableMetaData("Table2"); | |||||
assertEquals("Table2", tmd.getName()); | |||||
assertTrue(tmd.isLinked()); | |||||
assertFalse(tmd.isSystem()); | |||||
assertEquals("Table1", tmd.getLinkedTableName()); | |||||
assertEquals("Z:\\jackcess_test\\linkeeTest.accdb", tmd.getLinkedDbName()); | |||||
tmd = db.getTableMetaData("FooTable"); | |||||
assertNull(tmd); | |||||
assertTrue(db.getLinkedDatabases().isEmpty()); | |||||
final String linkeeDbName = "Z:\\jackcess_test\\linkeeTest.accdb"; | |||||
final File linkeeFile = new File("src/test/data/linkeeTest.accdb"); | |||||
db.setLinkResolver(new LinkResolver() { | |||||
public Database resolveLinkedDatabase(Database linkerdb, String dbName) | |||||
throws IOException { | |||||
assertEquals(linkeeDbName, dbName); | |||||
return DatabaseBuilder.open(linkeeFile); | |||||
} | |||||
}); | |||||
Table t2 = db.getTable("Table2"); | |||||
assertEquals(1, db.getLinkedDatabases().size()); | |||||
Database linkeeDb = db.getLinkedDatabases().get(linkeeDbName); | |||||
assertNotNull(linkeeDb); | |||||
assertEquals(linkeeFile, linkeeDb.getFile()); | |||||
assertEquals("linkeeTest.accdb", ((DatabaseImpl)linkeeDb).getName()); | |||||
List<? extends Map<String, Object>> expectedRows = | |||||
createExpectedTable( | |||||
createExpectedRow( | |||||
"ID", 1, | |||||
"Field1", "bar")); | |||||
assertTable(expectedRows, t2); | |||||
db.createLinkedTable("FooTable", linkeeDbName, "Table2"); | |||||
tmd = db.getTableMetaData("FooTable"); | |||||
assertEquals("FooTable", tmd.getName()); | |||||
assertTrue(tmd.isLinked()); | |||||
assertFalse(tmd.isSystem()); | |||||
assertEquals("Table2", tmd.getLinkedTableName()); | |||||
assertEquals("Z:\\jackcess_test\\linkeeTest.accdb", tmd.getLinkedDbName()); | |||||
Table t3 = db.getTable("FooTable"); | |||||
assertEquals(1, db.getLinkedDatabases().size()); | |||||
expectedRows = | |||||
createExpectedTable( | |||||
createExpectedRow( | |||||
"ID", 1, | |||||
"Field1", "buzz")); | |||||
assertTable(expectedRows, t3); | |||||
tmd = db.getTableMetaData("Table1"); | |||||
assertEquals("Table1", tmd.getName()); | |||||
assertFalse(tmd.isLinked()); | |||||
assertFalse(tmd.isSystem()); | |||||
assertNull(tmd.getLinkedTableName()); | |||||
assertNull(tmd.getLinkedDbName()); | |||||
Table t1 = tmd.open(db); | |||||
assertFalse(db.isLinkedTable(null)); | |||||
assertTrue(db.isLinkedTable(t2)); | |||||
assertTrue(db.isLinkedTable(t3)); | |||||
assertFalse(db.isLinkedTable(t1)); | |||||
List<Table> tables = getTables(db.newIterable()); | |||||
assertEquals(3, tables.size()); | |||||
assertTrue(tables.contains(t1)); | |||||
assertTrue(tables.contains(t2)); | |||||
assertTrue(tables.contains(t3)); | |||||
assertFalse(tables.contains(((DatabaseImpl)db).getSystemCatalog())); | |||||
tables = getTables(db.newIterable().setIncludeNormalTables(false)); | |||||
assertEquals(2, tables.size()); | |||||
assertFalse(tables.contains(t1)); | |||||
assertTrue(tables.contains(t2)); | |||||
assertTrue(tables.contains(t3)); | |||||
assertFalse(tables.contains(((DatabaseImpl)db).getSystemCatalog())); | |||||
tables = getTables(db.newIterable().withLocalUserTablesOnly()); | |||||
assertEquals(1, tables.size()); | |||||
assertTrue(tables.contains(t1)); | |||||
assertFalse(tables.contains(t2)); | |||||
assertFalse(tables.contains(t3)); | |||||
assertFalse(tables.contains(((DatabaseImpl)db).getSystemCatalog())); | |||||
tables = getTables(db.newIterable().withSystemTablesOnly()); | |||||
assertTrue(tables.size() > 5); | |||||
assertFalse(tables.contains(t1)); | |||||
assertFalse(tables.contains(t2)); | |||||
assertFalse(tables.contains(t3)); | |||||
assertTrue(tables.contains(((DatabaseImpl)db).getSystemCatalog())); | |||||
db.close(); | |||||
} | |||||
} | |||||
private static List<Table> getTables(Iterable<Table> tableIter) | |||||
static List<Table> getTables(Iterable<Table> tableIter) | |||||
{ | { | ||||
List<Table> tableList = new ArrayList<Table>(); | List<Table> tableList = new ArrayList<Table>(); | ||||
for(Table t : tableIter) { | for(Table t : tableIter) { | ||||
expectedCreateDate = "2004-05-28T17:51:48.701"; | expectedCreateDate = "2004-05-28T17:51:48.701"; | ||||
expectedUpdateDate = "2006-07-24T09:56:19.701"; | expectedUpdateDate = "2006-07-24T09:56:19.701"; | ||||
} | } | ||||
System.out.println("FOO " + testDB.getExpectedFileFormat() + " " + | |||||
table.getCreatedDate() + " " + | |||||
table.getUpdatedDate()); | |||||
assertEquals(expectedCreateDate, table.getCreatedDate().toString()); | assertEquals(expectedCreateDate, table.getCreatedDate().toString()); | ||||
assertEquals(expectedUpdateDate, table.getUpdatedDate().toString()); | assertEquals(expectedUpdateDate, table.getUpdatedDate().toString()); | ||||
} | } |
/* | |||||
Copyright (c) 2016 James Ahlborn | |||||
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 com.healthmarketscience.jackcess; | |||||
import java.io.File; | |||||
import java.io.FileNotFoundException; | |||||
import java.io.IOException; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import static com.healthmarketscience.jackcess.TestUtil.*; | |||||
import com.healthmarketscience.jackcess.impl.DatabaseImpl; | |||||
import static com.healthmarketscience.jackcess.impl.JetFormatTest.*; | |||||
import com.healthmarketscience.jackcess.util.LinkResolver; | |||||
import junit.framework.TestCase; | |||||
/** | |||||
* | |||||
* @author James Ahlborn | |||||
*/ | |||||
public class LinkedTableTest extends TestCase | |||||
{ | |||||
public void testLinkedTables() throws Exception { | |||||
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.LINKED)) { | |||||
Database db = openCopy(testDB); | |||||
try { | |||||
db.getTable("Table2"); | |||||
fail("FileNotFoundException should have been thrown"); | |||||
} catch(FileNotFoundException e) { | |||||
// success | |||||
} | |||||
TableMetaData tmd = db.getTableMetaData("Table2"); | |||||
assertEquals("Table2", tmd.getName()); | |||||
assertTrue(tmd.isLinked()); | |||||
assertFalse(tmd.isSystem()); | |||||
assertEquals("Table1", tmd.getLinkedTableName()); | |||||
assertNull(tmd.getConnectionName()); | |||||
assertEquals(TableMetaData.Type.LINKED, tmd.getType()); | |||||
assertEquals("Z:\\jackcess_test\\linkeeTest.accdb", tmd.getLinkedDbName()); | |||||
assertNull(tmd.getTableDefinition(db)); | |||||
tmd = db.getTableMetaData("FooTable"); | |||||
assertNull(tmd); | |||||
assertTrue(db.getLinkedDatabases().isEmpty()); | |||||
final String linkeeDbName = "Z:\\jackcess_test\\linkeeTest.accdb"; | |||||
final File linkeeFile = new File("src/test/data/linkeeTest.accdb"); | |||||
db.setLinkResolver(new LinkResolver() { | |||||
@Override | |||||
public Database resolveLinkedDatabase(Database linkerdb, String dbName) | |||||
throws IOException { | |||||
assertEquals(linkeeDbName, dbName); | |||||
return DatabaseBuilder.open(linkeeFile); | |||||
} | |||||
}); | |||||
Table t2 = db.getTable("Table2"); | |||||
assertEquals(1, db.getLinkedDatabases().size()); | |||||
Database linkeeDb = db.getLinkedDatabases().get(linkeeDbName); | |||||
assertNotNull(linkeeDb); | |||||
assertEquals(linkeeFile, linkeeDb.getFile()); | |||||
assertEquals("linkeeTest.accdb", ((DatabaseImpl)linkeeDb).getName()); | |||||
List<? extends Map<String, Object>> expectedRows = | |||||
createExpectedTable( | |||||
createExpectedRow( | |||||
"ID", 1, | |||||
"Field1", "bar")); | |||||
assertTable(expectedRows, t2); | |||||
db.createLinkedTable("FooTable", linkeeDbName, "Table2"); | |||||
tmd = db.getTableMetaData("FooTable"); | |||||
assertEquals("FooTable", tmd.getName()); | |||||
assertTrue(tmd.isLinked()); | |||||
assertFalse(tmd.isSystem()); | |||||
assertEquals("Table2", tmd.getLinkedTableName()); | |||||
assertEquals("Z:\\jackcess_test\\linkeeTest.accdb", tmd.getLinkedDbName()); | |||||
Table t3 = db.getTable("FooTable"); | |||||
assertEquals(1, db.getLinkedDatabases().size()); | |||||
expectedRows = | |||||
createExpectedTable( | |||||
createExpectedRow( | |||||
"ID", 1, | |||||
"Field1", "buzz")); | |||||
assertTable(expectedRows, t3); | |||||
tmd = db.getTableMetaData("Table1"); | |||||
assertEquals("Table1", tmd.getName()); | |||||
assertFalse(tmd.isLinked()); | |||||
assertFalse(tmd.isSystem()); | |||||
assertNull(tmd.getLinkedTableName()); | |||||
assertNull(tmd.getLinkedDbName()); | |||||
Table t1 = tmd.open(db); | |||||
assertFalse(db.isLinkedTable(null)); | |||||
assertTrue(db.isLinkedTable(t2)); | |||||
assertTrue(db.isLinkedTable(t3)); | |||||
assertFalse(db.isLinkedTable(t1)); | |||||
List<Table> tables = DatabaseTest.getTables(db.newIterable()); | |||||
assertEquals(3, tables.size()); | |||||
assertTrue(tables.contains(t1)); | |||||
assertTrue(tables.contains(t2)); | |||||
assertTrue(tables.contains(t3)); | |||||
assertFalse(tables.contains(((DatabaseImpl)db).getSystemCatalog())); | |||||
tables = DatabaseTest.getTables(db.newIterable().setIncludeNormalTables(false)); | |||||
assertEquals(2, tables.size()); | |||||
assertFalse(tables.contains(t1)); | |||||
assertTrue(tables.contains(t2)); | |||||
assertTrue(tables.contains(t3)); | |||||
assertFalse(tables.contains(((DatabaseImpl)db).getSystemCatalog())); | |||||
tables = DatabaseTest.getTables(db.newIterable().withLocalUserTablesOnly()); | |||||
assertEquals(1, tables.size()); | |||||
assertTrue(tables.contains(t1)); | |||||
assertFalse(tables.contains(t2)); | |||||
assertFalse(tables.contains(t3)); | |||||
assertFalse(tables.contains(((DatabaseImpl)db).getSystemCatalog())); | |||||
tables = DatabaseTest.getTables(db.newIterable().withSystemTablesOnly()); | |||||
assertTrue(tables.size() > 5); | |||||
assertFalse(tables.contains(t1)); | |||||
assertFalse(tables.contains(t2)); | |||||
assertFalse(tables.contains(t3)); | |||||
assertTrue(tables.contains(((DatabaseImpl)db).getSystemCatalog())); | |||||
db.close(); | |||||
} | |||||
} | |||||
public void testOdbcLinkedTables() throws Exception { | |||||
for (final TestDB testDB : | |||||
TestDB.getSupportedForBasename(Basename.LINKED_ODBC)) { | |||||
Database db = openCopy(testDB); | |||||
TableMetaData tmd = db.getTableMetaData("Ordrar"); | |||||
assertEquals(TableMetaData.Type.LINKED_ODBC, tmd.getType()); | |||||
assertEquals("dbo.Ordrar", tmd.getLinkedTableName()); | |||||
assertNull(tmd.getLinkedDbName()); | |||||
assertEquals("DSN=Magnapinna;Description=Safexit;UID=safexit;PWD=DummyPassword;APP=Microsoft Office;DATABASE=safexit", tmd.getConnectionName()); | |||||
assertFalse(tmd.toString().contains("DummyPassword")); | |||||
TableDefinition t = tmd.getTableDefinition(db); | |||||
List<? extends Column> cols = t.getColumns(); | |||||
assertEquals(20, cols.size()); | |||||
List<? extends Index> idxs = t.getIndexes(); | |||||
assertEquals(5, idxs.size()); | |||||
Table tbl = db.getTable("Ordrar"); | |||||
try { | |||||
tbl.iterator(); | |||||
fail("UnsupportedOperationException should have been thrown"); | |||||
} catch(UnsupportedOperationException expected) { | |||||
// expected | |||||
} | |||||
try { | |||||
tbl.addRow(1L,"bar"); | |||||
fail("UnsupportedOperationException should have been thrown"); | |||||
} catch(UnsupportedOperationException expected) { | |||||
// expected | |||||
} | |||||
db.close(); | |||||
} | |||||
} | |||||
} |
COMPLEX("complexDataTest"), | COMPLEX("complexDataTest"), | ||||
UNSUPPORTED("unsupportedFieldsTest"), | UNSUPPORTED("unsupportedFieldsTest"), | ||||
LINKED("linkerTest"), | LINKED("linkerTest"), | ||||
LINKED_ODBC("odbcLinkerTest"), | |||||
BLOB("testOle"), | BLOB("testOle"), | ||||
CALC_FIELD("calcFieldTest"), | CALC_FIELD("calcFieldTest"), | ||||
BINARY_INDEX("binIdxTest"), | BINARY_INDEX("binIdxTest"), |