diff options
author | bvosburgh | 2008-09-12 05:20:22 +0000 |
---|---|---|
committer | bvosburgh | 2008-09-12 05:20:22 +0000 |
commit | 9a4a93e9c623edf8b2fa8f12eda4af78e94c96ac (patch) | |
tree | a91e196bd31cdf40278555bdf4ce5b739dc6d68f /jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db | |
parent | 3c751de5e716860007a8c030215d623a1897ca40 (diff) | |
download | webtools.dali-9a4a93e9c623edf8b2fa8f12eda4af78e94c96ac.tar.gz webtools.dali-9a4a93e9c623edf8b2fa8f12eda4af78e94c96ac.tar.xz webtools.dali-9a4a93e9c623edf8b2fa8f12eda4af78e94c96ac.zip |
[242321] database case-sensitivity
Diffstat (limited to 'jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db')
16 files changed, 1044 insertions, 427 deletions
diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/ConnectionProfile.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/ConnectionProfile.java index 95a9ad4460..6dd625a783 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/ConnectionProfile.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/ConnectionProfile.java @@ -99,7 +99,7 @@ public interface ConnectionProfile /** * Return the name of the Driver instance */ - public String getDriverName(); + String getDriverName(); // ********** connection ********** diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/ConnectionProfileFactory.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/ConnectionProfileFactory.java index 4bff72eae2..e44773953a 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/ConnectionProfileFactory.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/ConnectionProfileFactory.java @@ -48,7 +48,8 @@ public interface ConnectionProfileFactory { * Clients should use this method when a JPA platform is unavailable * (e.g. during project creation). The returned connection profile will * not be able to search for database objects by any names other than - * those supplied by the factory. + * those supplied by the factory (i.e. lookups cannot be performed with + * "identifiers"). */ ConnectionProfile buildConnectionProfile(String name); diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/Database.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/Database.java index d4f3c1b9e1..c101d40f59 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/Database.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/Database.java @@ -29,22 +29,15 @@ public interface Database // ********** properties ********** /** - * Return the database's vendor. + * Return the name of the database's vendor. */ - String getVendor(); + String getVendorName(); /** * Return the database's version. */ String getVersion(); - /** - * Return the database's default schema. - * In most cases the default schema's name will match the user name. - * It may be null. - */ - Schema getDefaultSchema(); - // ********** catalogs ********** @@ -55,40 +48,59 @@ public interface Database /** * Return the database's catalogs. + * @see #supportsCatalogs() */ Iterator<Catalog> catalogs(); /** * Return the number of catalogs the database contains. + * @see #supportsCatalogs() */ int catalogsSize(); /** - * Return the names of the database's catalogs. + * Return the catalog with specified name. The name must be an exact match + * of the catalog's name. + * @see #supportsCatalogs() + * @see #getCatalogForIdentifier(String) + */ + Catalog getCatalogNamed(String name); + + /** + * Return the database's catalog identifiers, sorted by name. */ - Iterator<String> catalogNames(); + Iterator<String> sortedCatalogIdentifiers(); + + /** + * Return the catalog for the specified identifier. The identifier should + * be an SQL identifier (i.e. quoted when case-sensitive or containing + * special characters, unquoted otherwise). + * @see #supportsCatalogs() + * @see #getCatalogNamed(String) + */ + Catalog getCatalogForIdentifier(String identifier); /** * Return the database's "default" catalog. * Return null if the database does not support catalogs. + * @see #supportsCatalogs() */ Catalog getDefaultCatalog(); - /** - * Return the catalog with specified name. The name should be an SQL - * identifier (i.e. quoted when case-sensitive, unquoted when - * case-insensitive). - */ - Catalog getCatalogNamed(String name); + // ********** utility methods ********** - // ********** search utility ********** + /** + * Select and return from the specified list of database objects the + * database object identified by the specified identifier. + * The identifier should be an SQL identifier (i.e. delimited when + * non-"normal"). + */ + <T extends DatabaseObject> T selectDatabaseObjectForIdentifier(T[] databaseObjects, String identifier); /** - * Return the database object from the specified list with specified name. - * The name should be an SQL identifier (i.e. quoted when case-sensitive, - * unquoted when case-insensitive). + * Convert the specified name to a database-appropriate SQL identifier. */ - <T extends DatabaseObject> T getDatabaseObjectNamed(T[] databaseObjects, String name); + String convertNameToIdentifier(String name); } diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/DatabaseFinder.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/DatabaseFinder.java index d8cf20e36f..e7806e7933 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/DatabaseFinder.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/DatabaseFinder.java @@ -25,9 +25,10 @@ package org.eclipse.jpt.db; public interface DatabaseFinder { /** - * Return the database object with the specified name from the specified list. + * Select and return from the specified list of database objects the + * database object identified by the specified identifier. */ - <T extends DatabaseObject> T getDatabaseObjectNamed(T[] databaseObjects, String name, DefaultCallback defaultCallback); + <T extends DatabaseObject> T selectDatabaseObjectForIdentifier(T[] databaseObjects, String identifier, DefaultCallback defaultCallback); /** * The platform-provided finder is passed a "default" callback that can be @@ -36,9 +37,10 @@ public interface DatabaseFinder { interface DefaultCallback { /** - * Return the database object with the specified name from the specified list. + * Select and return from the specified list of database objects the + * database object identified by the specified identifier. */ - <T extends DatabaseObject> T getDatabaseObjectNamed(T[] databaseObjects, String name); + <T extends DatabaseObject> T selectDatabaseObjectForIdentifier(T[] databaseObjects, String identifier); } @@ -55,9 +57,9 @@ public interface DatabaseFinder { super(); } // search for an exact match on the name - public <T extends DatabaseObject> T getDatabaseObjectNamed(T[] databaseObjects, String name, DefaultCallback defaultCallback) { + public <T extends DatabaseObject> T selectDatabaseObjectForIdentifier(T[] databaseObjects, String identifier, DefaultCallback defaultCallback) { for (T databaseObject : databaseObjects) { - if (databaseObject.getName().equals(name)) { + if (databaseObject.getName().equals(identifier)) { return databaseObject; } } @@ -82,8 +84,8 @@ public interface DatabaseFinder { super(); } // simply use the callback - public <T extends DatabaseObject> T getDatabaseObjectNamed(T[] databaseObjects, String name, DefaultCallback defaultCallback) { - return defaultCallback.getDatabaseObjectNamed(databaseObjects, name); + public <T extends DatabaseObject> T selectDatabaseObjectForIdentifier(T[] databaseObjects, String identifier, DefaultCallback defaultCallback) { + return defaultCallback.selectDatabaseObjectForIdentifier(databaseObjects, identifier); } @Override public String toString() { diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/DatabaseObject.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/DatabaseObject.java index 5b25256f3a..e9f755dbb0 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/DatabaseObject.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/DatabaseObject.java @@ -28,18 +28,27 @@ public interface DatabaseObject { String getName(); /** - * Given the name of a Java member and the database object to which it is - * mapped, build and return a string to be used as the value for the member's - * database object annotation's 'name' element. Return null if the member - * maps to the database object by default. + * Return the database object's "identifier", which is the object's name + * modified so it can be used in an SQL statement (e.g. if the name contains + * special characters or is mixed case, it will be delimited, typically by + * double-quotes). + * Return null if the database object's identifier matches the specified + * "default name". */ - String getAnnotationIdentifier(String javaIdentifier); + String getIdentifier(String defaultName); /** - * Return a string to be used as the value for the member's - * database object annotation's 'name' element. + * Return the database object's "identifier", which is the object's name + * modified so it can be used in an SQL statement (e.g. if the name contains + * special characters or is mixed case, it will be delimited, typically by + * double-quotes). */ - String getAnnotationIdentifier(); + String getIdentifier(); + + /** + * Return the database object's database. + */ + Database getDatabase(); /** * Return the database object's connection profile. diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/Schema.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/Schema.java index 7d82c9e0ce..984dfb537a 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/Schema.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/Schema.java @@ -27,10 +27,9 @@ public interface Schema extends DatabaseObject, Comparable<Schema> { /** - * Return the schema's catalog. Return null if the schema's database does - * not support catalogs. + * Return the schema's container; either a catalog or a database. */ - Catalog getCatalog(); + SchemaContainer getContainer(); // ********** tables ********** @@ -46,16 +45,24 @@ public interface Schema int tablesSize(); /** - * Return the names of the schema's tables. + * Return the table with specified name. The name must be an exact match + * of the table's name. + * @see #getTableForIdentifier(String) */ - Iterator<String> tableNames(); + Table getTableNamed(String name); /** - * Return the table with specified name. The name should be an SQL - * identifier (i.e. quoted when case-sensitive, unquoted when - * case-insensitive). + * Return the schema's table identifiers, sorted by name. */ - Table getTableNamed(String name); + Iterator<String> sortedTableIdentifiers(); + + /** + * Return the table for the specified identifier. The identifier should + * be an SQL identifier (i.e. quoted when case-sensitive or containing + * special characters, unquoted otherwise). + * @see #getTableNamed(String) + */ + Table getTableForIdentifier(String identifier); // ********** sequences ********** @@ -71,15 +78,23 @@ public interface Schema int sequencesSize(); /** - * Return the names of the schema's sequences. + * Return the sequence with specified name. The name must be an exact match + * of the sequence's name. + * @see #getSequenceForIdentifier(String) + */ + Sequence getSequenceNamed(String name); + + /** + * Return the schema's sequence identifers, sorted by name. */ - Iterator<String> sequenceNames(); + Iterator<String> sortedSequenceIdentifiers(); /** - * Return the sequence with specified name. The name should be an SQL - * identifier (i.e. quoted when case-sensitive, unquoted when - * case-insensitive). + * Return the sequence for the specified identifier. The identifier should + * be an SQL identifier (i.e. quoted when case-sensitive or containing + * special characters, unquoted otherwise). + * @see #getSequenceNamed(String) */ - Sequence getSequenceNamed(String name); + Sequence getSequenceForIdentifier(String identifier); } diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/SchemaContainer.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/SchemaContainer.java index c814945bc2..26372f4a03 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/SchemaContainer.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/SchemaContainer.java @@ -37,15 +37,31 @@ public interface SchemaContainer int schemataSize(); /** - * Return the names of the container's schemata. + * Return the schema with specified name. The name must be an exact match + * of the schema's name. + * @see #schemaNames() + * @see #getSchemaForIdentifier(String) */ - Iterator<String> schemaNames(); + Schema getSchemaNamed(String name); /** - * Return the schema with specified name. The name should be an SQL - * identifier (i.e. quoted when case-sensitive, unquoted when - * case-insensitive). + * Return the container's schema identifiers, sorted by name. */ - Schema getSchemaNamed(String name); + Iterator<String> sortedSchemaIdentifiers(); + + /** + * Return the schema for the specified identifier. The identifier should + * be an SQL identifier (i.e. quoted when case-sensitive or containing + * special characters, unquoted otherwise). + * @see #schemaIdentifiers() + * @see #getSchemaNamed(String) + */ + Schema getSchemaForIdentifier(String identifier); + + /** + * Return the container's default schema, as defined by the database vendor. + * In most cases the default schema's name will match the user name. + */ + Schema getDefaultSchema(); } diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/Table.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/Table.java index 4ea223d068..ef4d334af0 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/Table.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/Table.java @@ -45,16 +45,24 @@ public interface Table int columnsSize(); /** - * Return the names of the table's columns. + * Return the column with specified name. The name must be an exact match + * of the column's name. + * @see #getColumnForIdentifier(String) */ - Iterator<String> columnNames(); + Column getColumnNamed(String name); /** - * Return the column with specified name. The name should be an SQL - * identifier (i.e. quoted when case-sensitive, unquoted when - * case-insensitive). + * Return the table's column identifers, sorted by name. */ - Column getColumnNamed(String name); + Iterator<String> sortedColumnIdentifiers(); + + /** + * Return the column for the specified identifier. The identifier should + * be an SQL identifier (i.e. quoted when case-sensitive or containing + * special characters, unquoted otherwise). + * @see #getColumnNamed(String) + */ + Column getColumnForIdentifier(String identifier); // ********** primary key columns ********** diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPCatalogWrapper.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPCatalogWrapper.java index 63414ca0f9..d1182e5f11 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPCatalogWrapper.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPCatalogWrapper.java @@ -51,11 +51,6 @@ final class DTPCatalogWrapper } @Override - DTPCatalogWrapper getCatalog() { - return this; - } - - @Override DTPSchemaWrapper getSchema(org.eclipse.datatools.modelbase.sql.schema.Schema dtpSchema) { return this.wraps(dtpSchema.getCatalog()) ? this.getSchema_(dtpSchema) diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPConnectionProfileWrapper.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPConnectionProfileWrapper.java index 10332afe88..ef0b0eec35 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPConnectionProfileWrapper.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPConnectionProfileWrapper.java @@ -90,13 +90,13 @@ final class DTPConnectionProfileWrapper return this.dtpConnectionProfile.getName(); } - public String getAnnotationIdentifier(String javaIdentifier) { - // connection profiles should not be in Java annotations + public String getIdentifier(String javaIdentifier) { + // connection profiles do not have "identifiers" throw new UnsupportedOperationException(); } - public String getAnnotationIdentifier() { - // connection profiles should not be in Java annotations + public String getIdentifier() { + // connection profiles do not have "identifiers" throw new UnsupportedOperationException(); } @@ -307,22 +307,22 @@ final class DTPConnectionProfileWrapper } /** - * This is called by whenever we need to find a component by name - * (e.g. Table.getColumnNamed(String)). We channel all the calls to here + * This is called whenever we need to find a component by identifier + * (e.g. Table.getColumnForIdentifier(String)). We channel all the calls to here * and then we delegate to the JPA platform-supplied "database finder". */ - <T extends DatabaseObject> T getDatabaseObjectNamed(T[] databaseObjects, String name) { - return this.finder.getDatabaseObjectNamed(databaseObjects, name, this.databaseFinderCallback); + <T extends DatabaseObject> T selectDatabaseObjectForIdentifier(T[] databaseObjects, String identifier) { + return this.finder.selectDatabaseObjectForIdentifier(databaseObjects, identifier, this.databaseFinderCallback); } /** * The default "database finder" calls back to here so we can delegate to - * the database, which contains all the state necessary to properly match - * identifiers. + * the database, which contains all the information necessary to properly + * match identifiers. */ - <T extends DatabaseObject> T getDatabaseObjectNamed_(T[] databaseObjects, String name) { + <T extends DatabaseObject> T selectDatabaseObjectForIdentifier_(T[] databaseObjects, String identifier) { // the database should not be null here - call its internal method - return this.database.getDatabaseObjectNamed_(databaseObjects, name); + return this.database.selectDatabaseObjectForIdentifier_(databaseObjects, identifier); } void databaseChanged(DTPDatabaseWrapper db) { @@ -540,9 +540,9 @@ final class DTPConnectionProfileWrapper // ********** default DatabaseFinder ********** class DatabaseFinderCallback implements DatabaseFinder.DefaultCallback { - public <T extends DatabaseObject> T getDatabaseObjectNamed(T[] databaseObjects, String name) { + public <T extends DatabaseObject> T selectDatabaseObjectForIdentifier(T[] databaseObjects, String identifier) { // call back to the internal method - return DTPConnectionProfileWrapper.this.getDatabaseObjectNamed_(databaseObjects, name); + return DTPConnectionProfileWrapper.this.selectDatabaseObjectForIdentifier_(databaseObjects, identifier); } } diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPDatabaseObjectWrapper.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPDatabaseObjectWrapper.java index bed57df93b..a4cfae644c 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPDatabaseObjectWrapper.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPDatabaseObjectWrapper.java @@ -88,13 +88,25 @@ abstract class DTPDatabaseObjectWrapper } /** - * This is called by whenever we need to find a component by name - * (e.g. Table.getColumnNamed(String)). We channel all the calls to the - * connection profile, which then delegates to the JPA platform- - * supplied "database finder". + * This is called by whenever we need to find a component by identifier + * (e.g. Table.getColumnForIdentifier(String)). We channel all the calls to the + * connection profile, which then delegates to the JPA platform-supplied + * "database finder". */ - <T extends DatabaseObject> T getDatabaseObjectNamed(T[] databaseObjects, String name) { - return this.getConnectionProfile().getDatabaseObjectNamed(databaseObjects, name); + <T extends DatabaseObject> T selectDatabaseObjectForIdentifier(T[] databaseObjects, String identifier) { + return this.getConnectionProfile().selectDatabaseObjectForIdentifier(databaseObjects, identifier); + } + + /** + * Convenience method. + */ + <T extends DatabaseObject> T selectDatabaseObjectNamed(T[] databaseObjects, String name) { + for (T dbObject : databaseObjects) { + if (dbObject.getName().equals(name)) { + return dbObject; + } + } + return null; } /** @@ -117,27 +129,8 @@ abstract class DTPDatabaseObjectWrapper * Table(bar) vs. "Foo" => "bar" * Table(Bar) vs. "Foo" => "Bar" */ - public String getAnnotationIdentifier(String javaIdentifier) { - return this.getAnnotationIdentifier(javaIdentifier, this.getName()); - } - - String getAnnotationIdentifier(String javaIdentifier, String dbIdentifier) { - if (this.getDatabase().vendorFoldsToUppercase()) { - if (StringTools.stringIsUppercase(dbIdentifier)) { - return dbIdentifier.equalsIgnoreCase(javaIdentifier) ? null : dbIdentifier; - } - return this.getDatabase().delimitIdentifier(dbIdentifier); - } - if (this.getDatabase().vendorFoldsToLowercase()) { - if (StringTools.stringIsLowercase(dbIdentifier)) { - return dbIdentifier.equalsIgnoreCase(javaIdentifier) ? null : dbIdentifier; - } - return this.getDatabase().delimitIdentifier(dbIdentifier); - } - if (this.getDatabase().vendorDoesNotFold()) { - return dbIdentifier.equals(javaIdentifier) ? null : dbIdentifier; - } - throw new IllegalStateException("unknown vendor folding: " + this.getDatabase().getVendor()); //$NON-NLS-1$ + public String getIdentifier(String defaultName) { + return this.getDatabase().convertNameToIdentifier(this.getName(), defaultName); } /** @@ -146,29 +139,25 @@ abstract class DTPDatabaseObjectWrapper * Table(FOO) => "FOO" * Table(Foo) => "\"Foo\"" * Table(foo) => "\"foo\"" + * Table(foo++) => "\"foo++\"" + * Table(f"o) => "\"f\"\"o++\"" (i.e. "f""o") * * PostgreSQL etc. - * Table(foo) => "foo" - * Table(Foo) => "\"Foo\"" * Table(FOO) => "\"FOO\"" + * Table(Foo) => "\"Foo\"" + * Table(foo) => "foo" + * Table(foo++) => "\"foo++\"" + * Table(f"o) => "\"f\"\"o++\"" (i.e. "f""o") * * SQL Server etc. + * Table(FOO) => "FOO" * Table(Foo) => "Foo" * Table(foo) => "foo" - * Table(FOO) => "FOO" + * Table(foo++) => "\"foo++\"" + * Table(f"o) => "\"f\"\"o++\"" (i.e. "f""o") */ - public String getAnnotationIdentifier() { - String name = this.getName(); - if (this.getDatabase().vendorFoldsToUppercase()) { - return StringTools.stringIsUppercase(name) ? name : this.getDatabase().delimitIdentifier(name); - } - if (this.getDatabase().vendorFoldsToLowercase()) { - return StringTools.stringIsLowercase(name) ? name : this.getDatabase().delimitIdentifier(name); - } - if (this.getDatabase().vendorDoesNotFold()) { - return name; - } - throw new IllegalStateException("unknown vendor folding: " + this.getDatabase().getVendor()); //$NON-NLS-1$ + public String getIdentifier() { + return this.getDatabase().convertNameToIdentifier(this.getName()); } @Override diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPDatabaseWrapper.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPDatabaseWrapper.java index e7da680987..04b9870ab0 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPDatabaseWrapper.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPDatabaseWrapper.java @@ -10,6 +10,7 @@ package org.eclipse.jpt.db.internal; import java.text.Collator; +import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -18,15 +19,25 @@ import org.eclipse.datatools.connectivity.sqm.internal.core.RDBCorePlugin; import org.eclipse.jpt.db.Catalog; import org.eclipse.jpt.db.Database; import org.eclipse.jpt.db.DatabaseObject; +import org.eclipse.jpt.utility.internal.CollectionTools; import org.eclipse.jpt.utility.internal.StringTools; import org.eclipse.jpt.utility.internal.iterators.ArrayIterator; import org.eclipse.jpt.utility.internal.iterators.TransformationIterator; /** * Wrap a DTP Database. + * * Sometimes the database will directly hold schemata; but if the database * supports catalogs, it will not hold the schemata directly, but will delegate * to the "default" catalog. + * + * Note: + * We use "name" when dealing with the unmodified name of a database object + * as supplied by the database itself (i.e. it is not delimited and it is always + * case-sensitive). + * We use "identifier" when dealing with a string representation of a database + * object name (i.e. it may be delimited and, depending on the vendor, it may + * be case-insensitive). */ final class DTPDatabaseWrapper extends DTPSchemaContainerWrapper @@ -35,7 +46,7 @@ final class DTPDatabaseWrapper // the wrapped DTP database private final org.eclipse.datatools.modelbase.sql.schema.Database dtpDatabase; - // lazy-initialized + // lazy-initialized, sorted private DTPCatalogWrapper[] catalogs; // lazy-initialized - but it can be 'null' so we use a flag @@ -46,28 +57,6 @@ final class DTPDatabaseWrapper private static final DTPCatalogWrapper[] EMPTY_CATALOGS = new DTPCatalogWrapper[0]; - // ********** constants ********** - - private static final String POSTGRESQL_PUBLIC_SCHEMA_NAME = "public"; //$NON-NLS-1$ - - - // ********** vendors ********** - - static final String DERBY_VENDOR = "Derby"; //$NON-NLS-1$ - static final String HSQLDB_VENDOR = "HSQLDB"; //$NON-NLS-1$ - static final String DB2_UDB_I_SERIES_VENDOR = "DB2 UDB iSeries"; //$NON-NLS-1$ - static final String DB2_UDB_VENDOR = "DB2 UDB"; //$NON-NLS-1$ - static final String DB2_UDB_Z_SERIES_VENDOR = "DB2 UDB zSeries"; //$NON-NLS-1$ - static final String INFORMIX_VENDOR = "Informix"; //$NON-NLS-1$ - static final String SQL_SERVER_VENDOR = "SQL Server"; //$NON-NLS-1$ - static final String MYSQL_VENDOR = "MySql"; //$NON-NLS-1$ - static final String ORACLE_VENDOR = "Oracle"; //$NON-NLS-1$ - static final String POSTGRES_VENDOR = "postgres"; //$NON-NLS-1$ - static final String MAXDB_VENDOR = "MaxDB"; //$NON-NLS-1$ - static final String SYBASE_ASA_VENDOR = "Sybase_ASA"; //$NON-NLS-1$ - static final String SYBASE_ASE_VENDOR = "Sybase_ASE"; //$NON-NLS-1$ - - // ********** constructor ********** DTPDatabaseWrapper(DTPConnectionProfileWrapper connectionProfile, org.eclipse.datatools.modelbase.sql.schema.Database dtpDatabase) { @@ -99,11 +88,6 @@ final class DTPDatabaseWrapper } @Override - DTPCatalogWrapper getCatalog() { - return null; // catalog not supported - } - - @Override DTPSchemaWrapper getSchema(org.eclipse.datatools.modelbase.sql.schema.Schema dtpSchema) { return this.getSchema_(dtpSchema); } @@ -128,7 +112,7 @@ final class DTPDatabaseWrapper // ********** Database implementation ********** - public String getVendor() { + public String getVendorName() { return this.dtpDatabase.getVendor(); } @@ -138,8 +122,8 @@ final class DTPDatabaseWrapper // override to make method public since it's in the Database interface @Override - public <T extends DatabaseObject> T getDatabaseObjectNamed(T[] databaseObjects, String name) { - return super.getDatabaseObjectNamed(databaseObjects, name); + public <T extends DatabaseObject> T selectDatabaseObjectForIdentifier(T[] databaseObjects, String identifier) { + return super.selectDatabaseObjectForIdentifier(databaseObjects, identifier); } // ***** catalogs @@ -154,6 +138,10 @@ final class DTPDatabaseWrapper return new ArrayIterator<Catalog>(this.getCatalogs()); } + private Iterator<DTPCatalogWrapper> catalogWrappers() { + return new ArrayIterator<DTPCatalogWrapper>(this.getCatalogs()); + } + private synchronized DTPCatalogWrapper[] getCatalogs() { if (this.catalogs == null) { this.catalogs = this.buildCatalogs(); @@ -170,7 +158,7 @@ final class DTPDatabaseWrapper for (int i = result.length; i-- > 0;) { result[i] = new DTPCatalogWrapper(this, dtpCatalogs.get(i)); } - return result; + return CollectionTools.sort(result); } // minimize scope of suppressed warnings @@ -183,42 +171,6 @@ final class DTPDatabaseWrapper return this.getCatalogs().length; } - public Iterator<String> catalogNames() { - return new TransformationIterator<Catalog, String>(this.catalogs()) { - @Override - protected String transform(Catalog catalog) { - return catalog.getName(); - } - }; - } - - public synchronized DTPCatalogWrapper getDefaultCatalog() { - if ( ! this.defaultCatalogCalculated) { - this.defaultCatalogCalculated = true; - this.defaultCatalog = this.buildDefaultCatalog(); - } - return this.defaultCatalog; - } - - private DTPCatalogWrapper buildDefaultCatalog() { - if ( ! this.supportsCatalogs()) { - return null; - } - for (DTPCatalogWrapper catalog : this.getCatalogs()) { - String catalogName = catalog.getName(); - if (catalogName.length() == 0) { - return catalog; // special catalog that contains all schemata (Derby) - } - if (catalogName.equals(this.getConnectionProfile().getUserName())) { - return catalog; // user name is default catalog - } - if (catalogName.equals(this.getName())) { - return catalog; // special catalog with same name as DB (PostgreSQL) - } - } - throw new IllegalStateException("unknown default catalog"); //$NON-NLS-1$ - } - /** * return the catalog for the specified DTP catalog */ @@ -232,223 +184,84 @@ final class DTPDatabaseWrapper } public DTPCatalogWrapper getCatalogNamed(String name) { - return this.getDatabaseObjectNamed(this.getCatalogs(), name); + return this.selectDatabaseObjectNamed(this.getCatalogs(), name); } - // ***** schemata - - @Override - synchronized DTPSchemaWrapper[] getSchemata() { - DTPCatalogWrapper cat = this.getDefaultCatalog(); - return (cat == null) ? super.getSchemata() : cat.getSchemata(); - } - - public DTPSchemaWrapper getDefaultSchema() { - DTPSchemaWrapper schema = this.getSchemaNamed(this.getConnectionProfile().getUserName()); - if (schema != null) { - return schema; - } - // PostgreSQL has a "schema search path" - the default is: - // "$user",public - // so if "$user" is not found, return public - if (this.getVendor().equals(POSTGRES_VENDOR)) { - return this.getSchemaNamed(POSTGRESQL_PUBLIC_SCHEMA_NAME); - } - // MySQL database has a single schema with the same name as the database - if (this.getVendor().equals(MYSQL_VENDOR)) { - return this.getSchemaNamed(this.getName()); - } - return null; - } - - - // ********** identifiers ********** - - /** - * Return the database object with the specified name. If the name is - * "delimited" (typically with double-quotes), it will be used without any - * folding. If the name is "normal" (i.e. not delimited), it will be - * folded to the appropriate case (typically uppercase). - * @see #identifierIsDelimited(String) - * @see #foldIdentifier(String) - * - * Since the database has the appropriate state to compare identifiers, - * the connection profile delegates to here when using the default - * "database finder". - */ - // TODO convert embedded quotes? - <T extends DatabaseObject> T getDatabaseObjectNamed_(T[] databaseObjects, String name) { - name = this.normalizeIdentifier(name); - for (T dbObject : databaseObjects) { - if (dbObject.getName().equals(name)) { - return dbObject; + public Iterator<String> sortedCatalogIdentifiers() { + // the catalogs are already sorted + return new TransformationIterator<DTPCatalogWrapper, String>(this.catalogWrappers()) { + @Override + protected String transform(DTPCatalogWrapper next) { + return next.getIdentifier(); } - } - return null; + }; } - private String normalizeIdentifier(String identifier) { - if (this.identifierIsDelimited(identifier)) { - return StringTools.unwrap(identifier); - } - return this.foldIdentifier(identifier); + public DTPCatalogWrapper getCatalogForIdentifier(String identifier) { + return this.selectDatabaseObjectForIdentifier(this.getCatalogs(), identifier); } - /** - * Return whether the specified identifier is "delimited" for the current - * database (typically with double-quotes). - */ - private boolean identifierIsDelimited(String identifier) { - if (this.vendorAllowsQuoteDelimiters() - && StringTools.stringIsQuoted(identifier)) { - return true; - } - if (this.vendorAllowsBracketDelimiters() - && StringTools.stringIsBracketed(identifier)) { - return true; - } - if (this.vendorAllowsBacktickDelimiters() - && StringTools.stringIsDelimited(identifier, BACKTICK)) { - return true; + public synchronized DTPCatalogWrapper getDefaultCatalog() { + if ( ! this.defaultCatalogCalculated) { + this.defaultCatalogCalculated = true; + this.defaultCatalog = this.buildDefaultCatalog(); } - return false; - } - - /** - * Return whether the database allows identifiers to delimited with - * quotes: "FOO". - */ - boolean vendorAllowsQuoteDelimiters() { - // all platforms allow identifiers to be delimited by quotes - return true; - } - - /** - * Return whether the database allows identifiers to delimited with - * brackets: [FOO]. - */ - boolean vendorAllowsBracketDelimiters() { - String vendor = this.getVendor(); - return vendor.equals(SQL_SERVER_VENDOR) - || vendor.equals(SYBASE_ASE_VENDOR) - || vendor.equals(SYBASE_ASA_VENDOR); + return this.defaultCatalog; } - /** - * Return whether the database allows identifiers to delimited with - * backticks: `FOO`. - */ - boolean vendorAllowsBacktickDelimiters() { - String vendor = this.getVendor(); - return vendor.equals(MYSQL_VENDOR); + private DTPCatalogWrapper buildDefaultCatalog() { + return this.supportsCatalogs() ? this.getVendor().getDefaultCatalog(this) : null; } - private static final char BACKTICK = '`'; + // ***** schemata - /** - * Fold the specified identifier to the appropriate case. - * The SQL-92 spec says a "normal" (non-delimited) identifier should be - * folded to uppercase; but some databases do otherwise (e.g. PostgreSQL). - * - * According to on-line documentation I could find: ~bjv - * The following databases fold to uppercase: - * Derby - * Oracle - * DB2 - * HSQLDB - * MaxDB - * The following databases fold to lowercase: - * PostgreSQL - * Informix - * MySQL (sorta - depends on underlying O/S file system and env var) - * The following databases do not fold: - * MS SQL Server (might depend on collation setting...) - * Sybase (might depend on collation setting...) - */ - private String foldIdentifier(String identifier) { - if (this.vendorFoldsToLowercase()) { - return identifier.toLowerCase(); - } - if (this.vendorFoldsToUppercase()) { - return identifier.toUpperCase(); - } - if (this.vendorDoesNotFold()) { - return identifier; - } - throw new IllegalStateException("unknown vendor folding: " + this.getVendor()); //$NON-NLS-1$ + @Override + synchronized DTPSchemaWrapper[] getSchemata() { + DTPCatalogWrapper cat = this.getDefaultCatalog(); + return (cat == null) ? super.getSchemata() : cat.getSchemata(); } - /** - * Return whether the database folds non-delimited identifiers to lowercase. - */ - boolean vendorFoldsToLowercase() { - String vendor = this.getVendor(); - return vendor.equals(POSTGRES_VENDOR) - || vendor.equals(INFORMIX_VENDOR); + DTPSchemaWrapper getDefaultSchema(DTPSchemaContainerWrapper schemaContainer) { + return this.getVendor().getDefaultSchema(schemaContainer); } - /** - * Return whether the database does not fold non-delimited identifiers to - * lowercase. - */ - boolean vendorDoesNotFoldToLowercase() { - return ! this.vendorFoldsToLowercase(); - } - /** - * Return whether the database folds non-delimited identifiers to uppercase. - */ - boolean vendorFoldsToUppercase() { - return this.vendorFolds() - && this.vendorDoesNotFoldToLowercase(); - } + // ********** names vs. identifiers ********** /** - * Return whether the database does not fold non-delimited identifiers to - * uppercase. + * Convert the specified name to an identifier. Return null if the resulting + * identifier matches the specified default name. */ - boolean vendorDoesNotFoldToUppercase() { - return ! this.vendorFoldsToUppercase(); + String convertNameToIdentifier(String name, String defaultName) { + return this.getVendor().convertNameToIdentifier(name, defaultName); } /** - * Return whether the database folds non-delimited identifiers to either - * uppercase or lowercase. + * Convert the specified name to an identifier. */ - boolean vendorFolds() { - return ! this.vendorDoesNotFold(); + public String convertNameToIdentifier(String name) { + return this.getVendor().convertNameToIdentifier(name); } /** - * Return whether the database does not fold non-delimited identifiers to - * either uppercase or lowercase (i.e. the identifier is used unmodified). - * These guys are bit flaky, so we force an exact match. - * (e.g. MySQL folds database and table names to lowercase on Windows - * by default; but that default can be changed by the - * 'lower_case_table_names' system variable. This because databases are - * stored as directories and tables are stored as files in the underlying - * O/S, and the case-sensitivity of the names is determined by the behavior - * of filenames on the O/S. Then, to complicate things, - * none of the other identifiers, like table and column names, are folded; - * but they are case-insensitive, unless delimited. See - * http://dev.mysql.com/doc/refman/6.0/en/identifier-case-sensitivity.html.) + * Return the database object identified by the specified identifier. If + * the identifier is "delimited" (typically with double-quotes), it will be + * used without any folding. If the name is "normal" (i.e. not delimited), + * it will be folded to the appropriate case (typically uppercase). + * + * Since the database has the appropriate state to compare identifiers and + * names, the connection profile delegates to here when using the default + * "database finder". */ - boolean vendorDoesNotFold() { - String vendor = this.getVendor(); - return vendor.equals(SQL_SERVER_VENDOR) - || vendor.equals(SYBASE_ASE_VENDOR) - || vendor.equals(SYBASE_ASA_VENDOR) - || vendor.equals(MYSQL_VENDOR); + <T extends DatabaseObject> T selectDatabaseObjectForIdentifier_(T[] databaseObjects, String identifier) { + return this.selectDatabaseObjectNamed(databaseObjects, this.convertIdentifierToName(identifier)); } /** - * Delimit the specified identifier in a vendor-appropriate fashion. + * Convert the specified identifier to a name. */ - String delimitIdentifier(String identifier) { - if (this.vendorAllowsQuoteDelimiters()) { - return StringTools.quote(identifier); - } - throw new IllegalStateException("unknown vendor delimiters: " + this.getVendor()); //$NON-NLS-1$ + String convertIdentifierToName(String identifier) { + return this.getVendor().convertIdentifierToName(identifier); } @@ -465,6 +278,10 @@ final class DTPDatabaseWrapper return RDBCorePlugin.getDefault().getDatabaseDefinitionRegistry().getDefinition(this.dtpDatabase); } + private Vendor getVendor() { + return getVendor(this.getVendorName()); + } + // ********** listening ********** @@ -517,4 +334,742 @@ final class DTPDatabaseWrapper this.catalogs = null; } + + // ********** vendors ********** + + private static Vendor getVendor(String name) { + Vendor vendor = getVendors().get(name); + return (vendor != null) ? vendor : Default.INSTANCE; + } + + /** + * keyed by vendor name + */ + private static HashMap<String, Vendor> Vendors; + + private static synchronized HashMap<String, Vendor> getVendors() { + if (Vendors == null) { + Vendors = buildVendors(); + } + return Vendors; + } + + private static HashMap<String, Vendor> buildVendors() { + HashMap<String, Vendor> map = new HashMap<String, Vendor>(20); + putVendor(map, Derby.INSTANCE); + putVendor(map, HSQLDB.INSTANCE); + putVendor(map, DB2.UDB); + putVendor(map, DB2.UDB_I_SERIES); + putVendor(map, DB2.UDB_Z_SERIES); + putVendor(map, Informix.INSTANCE); + putVendor(map, SQLServer.INSTANCE); + putVendor(map, MySQL.INSTANCE); + putVendor(map, Oracle.INSTANCE); + putVendor(map, Postgres.INSTANCE); + putVendor(map, MaxDB.INSTANCE); + putVendor(map, Sybase.ASA); + putVendor(map, Sybase.ASE); + return map; + } + + private static void putVendor(HashMap<String, Vendor> map, Vendor vendor) { + String name = vendor.getName(); + if (map.put(name, vendor) != null) { + throw new IllegalArgumentException("Duplicate vendor: " + name); //$NON-NLS-1$ + } + } + + + // ********** vendor classes ********** + + /** + * Delegate vendor-specific behavior to implementations of this class" + * - default catalog and schema + * - converting names to identifiers and vice-versa + * + * Note: + * We use "name" when dealing with the unmodified name of a database object + * as supplied by the database itself (i.e. it is not delimited and it is always + * case-sensitive). + * We use "identifier" when dealing with a string representation of a database + * object name (i.e. it may be delimited and, depending on the vendor, it may + * be case-insensitive). + */ + private abstract static class Vendor { + + Vendor() { + super(); + } + + /** + * Return the vendor's name. This must match the name specified by the + * DTP connection profile. + */ + abstract String getName(); + + /** + * The SQL spec says a "normal" (non-delimited) identifier should be + * folded to uppercase; but some databases do otherwise (e.g. Sybase). + */ + Folder getFolder() { + return Folder.UPPER; + } + + + // ********** default catalog and schema ********** + + DTPCatalogWrapper getDefaultCatalog(DTPDatabaseWrapper database) { + return database.getCatalogForIdentifier(this.getDefaultCatalogIdentifier(database)); + } + + /** + * Typically, the name of the default catalog is the user name. + */ + String getDefaultCatalogIdentifier(DTPDatabaseWrapper database) { + return database.getConnectionProfile().getUserName(); + } + + DTPSchemaWrapper getDefaultSchema(DTPSchemaContainerWrapper sc) { + return sc.getSchemaForIdentifier(this.getDefaultSchemaIdentifier(sc)); + } + + /** + * Typically, the name of the default schema is the user name. + */ + String getDefaultSchemaIdentifier(DTPSchemaContainerWrapper sc) { + return sc.getDatabase().getConnectionProfile().getUserName(); + } + + + // ********** name -> identifier ********** + + /** + * @see DTPDatabaseWrapper#convertNameToIdentifier(String, String) + */ + final String convertNameToIdentifier(String name, String defaultName) { + return this.nameRequiresDelimiters(name) ? this.delimitName(name) + : this.normalNamesMatch(name, defaultName) ? null : name; + } + + /** + * @see DTPDatabaseWrapper#convertNameToIdentifier(String) + */ + final String convertNameToIdentifier(String name) { + return this.nameRequiresDelimiters(name) ? this.delimitName(name) : name; + } + + /** + * Return whether the specified database object name must be delimited + * when used in an SQL statement. + * If the name has any "special" characters (as opposed to letters, + * digits, and other allowed characters [e.g. underscores]), it requires + * delimiters. + * If the name is mixed case and the database folds undelimited names + * (to either uppercase or lowercase), it requires delimiters. + */ + final boolean nameRequiresDelimiters(String name) { + return (name.length() == 0) // an empty string must be delimited(?) + || this.nameContainsAnySpecialCharacters(name) + || this.nameIsNotFolded(name); + } + + /** + * Return whether the specified name contains any "special" characters + * that require the name to be delimited. + * Pre-condition: the specified name is not empty + */ + final boolean nameContainsAnySpecialCharacters(String name) { + char[] string = name.toCharArray(); + if (this.characterIsSpecialNameStart(string[0])) { + return true; + } + for (int i = string.length; i-- > 1; ) { // note: stop at 1 + if (this.characterIsSpecialNamePart(string[i])) { + return true; + } + } + return false; + } + + /** + * Return whether the specified character is "special" for the first + * character of a name. + * Typically, databases are more restrictive about what characters can + * be used to *start* an identifier (as opposed to the characters + * allowed for the remainder of the identifier). + */ + final boolean characterIsSpecialNameStart(char c) { + return ! this.characterIsNormalNameStart(c); + } + + /** + * Return whether the specified character is "normal" for the first + * character of a name. + * The first character of an identifier can be: + * - a letter + * - any of the other, vendor-specific, "normal" start characters + */ + boolean characterIsNormalNameStart(char c) { + // all vendors allow a letter + return Character.isLetter(c) + || this.characterIsNormalNameStart_(c); + } + + private boolean characterIsNormalNameStart_(char c) { + return arrayContains(this.getNormalNameStartCharacters(), c); + } + + /** + * Return the "normal" characters, beyond letters, for the + * first character of a name. + * Return null if there are no additional characters. + */ + char[] getNormalNameStartCharacters() { + return null; + } + + /** + * Return whether the specified character is "special" for the second and + * subsequent characters of a name. + */ + final boolean characterIsSpecialNamePart(char c) { + return ! this.characterIsNormalNamePart(c); + } + + /** + * Return whether the specified character is "normal" for the second and + * subsequent characters of a name. + * The second and subsequent characters of a "normal" name can be: + * - a letter + * - a digit + * - any of the other, vendor-specific, "normal" start characters + * - any of the other, vendor-specific, "normal" part characters + */ + boolean characterIsNormalNamePart(char c) { + // all vendors allow a letter or digit + return Character.isLetterOrDigit(c) + || this.characterIsNormalNameStart_(c) + || this.characterIsNormalNamePart_(c); + } + + private boolean characterIsNormalNamePart_(char c) { + return arrayContains(this.getNormalNamePartCharacters(), c); + } + + /** + * Return the "normal" characters, beyond letters and digits and the + * "normal" first characters, for the second and subsequent characters + * of an identifier. Return null if there are no additional characters. + */ + char[] getNormalNamePartCharacters() { + return null; + } + + /** + * Return whether the specified name is not folded to the database's + * case, requiring it to be delimited. + */ + final boolean nameIsNotFolded(String name) { + return ! this.getFolder().stringIsFolded(name); + } + + /** + * Return whether the specified "normal" names match. + */ + final boolean normalNamesMatch(String name1, String name2) { + return this.normalIdentifiersAreCaseSensitive() ? + name1.equals(name2) + : + name1.equalsIgnoreCase(name2); + } + + /** + * Typically, "normal" identifiers are case-insensitive. + */ + final boolean normalIdentifiersAreCaseSensitive() { + return this.getFolder().isCaseSensitive(); + } + + /** + * Wrap the specified name in delimiters (typically double-quotes), + * converting it to an identifier. + */ + String delimitName(String name) { + return StringTools.quote(name); + } + + + // ********** identifier -> name ********** + + /** + * @see DTPDatabaseWrapper#selectDatabaseObjectForIdentifier_(DatabaseObject[], String) + */ + // not sure how to handle an empty string: + // both "" and "\"\"" are converted to "" ... + // convert "" to 'null' since empty strings must be delimited? + final String convertIdentifierToName(String identifier) { + return (identifier == null) ? null : + this.identifierIsDelimited(identifier) ? + StringTools.undelimit(identifier) + : + this.getFolder().fold(identifier); + } + + /** + * Return whether the specified identifier is "delimited". + * The SQL-92 spec says identifiers should be delimited by + * double-quotes; but some databases allow otherwise (e.g. Sybase). + */ + boolean identifierIsDelimited(String identifier) { + return StringTools.stringIsQuoted(identifier); + } + + + // ********** misc ********** + + @Override + public String toString() { + return this.getName(); + } + + /** + * static convenience method - array null check + */ + static boolean arrayContains(char[] array, char c) { + return (array != null) && CollectionTools.contains(array, c); + } + + /** + * Handle database-specific case-folding issues. + */ + enum Folder { + UPPER { + @Override String fold(String string) { return string.toUpperCase(); } + @Override boolean stringIsFolded(String string) { return StringTools.stringIsUppercase(string); } + @Override boolean isCaseSensitive() { return false; } + }, + LOWER { + @Override String fold(String string) { return string.toLowerCase(); } + @Override boolean stringIsFolded(String string) { return StringTools.stringIsLowercase(string); } + @Override boolean isCaseSensitive() { return false; } + }, + NONE { + @Override String fold(String string) { return string; } + @Override boolean stringIsFolded(String string) { return true; } + @Override boolean isCaseSensitive() { return true; } + }; + abstract String fold(String string); + abstract boolean stringIsFolded(String string); + abstract boolean isCaseSensitive(); + } + + } + + private static class Default extends Vendor { + static final Vendor INSTANCE = new Default(); + + private Default() { + super(); + } + + @Override + String getName() { + return "Default Vendor"; //$NON-NLS-1$ + } + + } + + private static class Derby extends Vendor { + static final Vendor INSTANCE = new Derby(); + + private Derby() { + super(); + } + + @Override + String getName() { + return "Derby"; //$NON-NLS-1$ + } + + /** + * Derby has a single, unnamed catalog that contains all the schemata. + */ + @Override + String getDefaultCatalogIdentifier(DTPDatabaseWrapper database) { + return ""; //$NON-NLS-1$ + } + + /** + * The default user name on Derby is "APP". + */ + @Override + String getDefaultSchemaIdentifier(DTPSchemaContainerWrapper sc) { + String user = super.getDefaultSchemaIdentifier(sc); + return ((user == null) || (user.length() == 0)) ? + DEFAULT_USER_NAME + : + user; + } + private static final String DEFAULT_USER_NAME = "APP"; //$NON-NLS-1$ + + @Override + char[] getNormalNamePartCharacters() { + return NORMAL_NAME_PART_CHARACTERS; + } + private static final char[] NORMAL_NAME_PART_CHARACTERS = new char[] { '_' }; + + } + + private static class HSQLDB extends Vendor { + static final Vendor INSTANCE = new HSQLDB(); + + private HSQLDB() { + super(); + } + + @Override + String getName() { + return "HSQLDB"; //$NON-NLS-1$ + } + + } + + private static class DB2 extends Vendor { + static final Vendor UDB_I_SERIES = new DB2("DB2 UDB iSeries"); //$NON-NLS-1$ + static final Vendor UDB = new DB2("DB2 UDB"); //$NON-NLS-1$ + static final Vendor UDB_Z_SERIES = new DB2("DB2 UDB zSeries"); //$NON-NLS-1$ + + private final String name; + + private DB2(String name) { + super(); + this.name = name; + } + + @Override + String getName() { + return this.name; + } + + @Override + char[] getNormalNamePartCharacters() { + return NORMAL_NAME_PART_CHARACTERS; + } + private static final char[] NORMAL_NAME_PART_CHARACTERS = new char[] { '_' }; + + } + + private static class Informix extends Vendor { + static final Vendor INSTANCE = new Informix(); + + private Informix() { + super(); + } + + @Override + String getName() { + return "Informix"; //$NON-NLS-1$ + } + + @Override + Folder getFolder() { + return Folder.LOWER; + } + + @Override + char[] getNormalNameStartCharacters() { + return NORMAL_NAME_START_CHARACTERS; + } + private static final char[] NORMAL_NAME_START_CHARACTERS = new char[] { '_' }; + + @Override + char[] getNormalNamePartCharacters() { + return NORMAL_NAME_PART_CHARACTERS; + } + private static final char[] NORMAL_NAME_PART_CHARACTERS = new char[] { '$' }; + + } + + private static class SQLServer extends Vendor { + static final Vendor INSTANCE = new SQLServer(); + + private SQLServer() { + super(); + } + + @Override + String getName() { + return "SQL Server"; //$NON-NLS-1$ + } + + /** + * The default schema on SQL Server for any database (catalog) is 'dbo'. + */ + @Override + String getDefaultSchemaIdentifier(DTPSchemaContainerWrapper sc) { + return DEFAULT_SCHEMA_NAME; + } + private static final String DEFAULT_SCHEMA_NAME = "dbo"; //$NON-NLS-1$ + + /** + * By default, SQL Server identifiers are case-sensitive, even without + * delimiters. This can depend on the collation setting.... + */ + @Override + Folder getFolder() { + return Folder.NONE; + } + + @Override + char[] getNormalNameStartCharacters() { + return NORMAL_NAME_START_CHARACTERS; + } + private static final char[] NORMAL_NAME_START_CHARACTERS = new char[] { '_', '@', '#' }; + + @Override + char[] getNormalNamePartCharacters() { + return NORMAL_NAME_PART_CHARACTERS; + } + private static final char[] NORMAL_NAME_PART_CHARACTERS = new char[] { '$' }; + + /** + * By default, SQL Server delimits identifiers with brackets ([]); but it + * can also be configured to use double-quotes. + */ + @Override + boolean identifierIsDelimited(String identifier) { + return StringTools.stringIsBracketed(identifier) + || super.identifierIsDelimited(identifier); + } + + } + + private static class MySQL extends Vendor { + static final Vendor INSTANCE = new MySQL(); + + private MySQL() { + super(); + } + + @Override + String getName() { + return "MySql"; //$NON-NLS-1$ + } + + /** + * MySQL is a bit unusual, so we force exact matches. + * (e.g. MySQL folds database and table names to lowercase on Windows + * by default; but that default can be changed by the + * 'lower_case_table_names' system variable. This is because databases are + * stored as directories and tables are stored as files in the underlying + * O/S; and the case-sensitivity of the names is determined by the behavior + * of filenames on the O/S. Then, to complicate things, + * none of the other identifiers, like table and column names, are folded; + * but they are case-insensitive, unless delimited. See + * http://dev.mysql.com/doc/refman/6.0/en/identifier-case-sensitivity.html.) + */ + @Override + Folder getFolder() { + return Folder.NONE; + } + + /** + * MySQL has a single schema with the same name as the database. + */ + @Override + String getDefaultSchemaIdentifier(DTPSchemaContainerWrapper sc) { + return sc.getDatabase().getName(); + } + + /** + * MySQL is the only vendor that allows a digit. + * Although, the name cannnot be *all* digits. + */ + @Override + boolean characterIsNormalNameStart(char c) { + return Character.isDigit(c) || super.characterIsNormalNameStart(c); + } + + @Override + char[] getNormalNameStartCharacters() { + return NORMAL_NAME_START_CHARACTERS; + } + private static final char[] NORMAL_NAME_START_CHARACTERS = new char[] { '_', '$' }; + + /** + * By default, MySQL delimits identifiers with backticks (`); but it + * can also be configured to use double-quotes. + */ + @Override + boolean identifierIsDelimited(String identifier) { + return StringTools.stringIsDelimited(identifier, BACKTICK) + || super.identifierIsDelimited(identifier); + } + private static final char BACKTICK = '`'; + + } + + private static class Oracle extends Vendor { + static final Vendor INSTANCE = new Oracle(); + + private Oracle() { + super(); + } + + @Override + String getName() { + return "Oracle"; //$NON-NLS-1$ + } + + /** + * Oracle has a single, unnamed catalog that contains all the schemata. + */ + @Override + String getDefaultCatalogIdentifier(DTPDatabaseWrapper database) { + return ""; //$NON-NLS-1$ + } + + @Override + char[] getNormalNamePartCharacters() { + return NORMAL_NAME_PART_CHARACTERS; + } + private static final char[] NORMAL_NAME_PART_CHARACTERS = new char[] { '_', '$', '#' }; + + } + + private static class Postgres extends Vendor { + static final Vendor INSTANCE = new Postgres(); + + private Postgres() { + super(); + } + + @Override + String getName() { + return "postgres"; //$NON-NLS-1$ + } + + @Override + Folder getFolder() { + return Folder.LOWER; + } + + /** + * PostgreSQL has a single, unnamed catalog that contains all the schemata. + */ + @Override + String getDefaultCatalogIdentifier(DTPDatabaseWrapper database) { + return ""; //$NON-NLS-1$ + } + + /** + * PostgreSQL has a "schema search path". The default is: + * "$user",public + * If "$user" is not found, return "public". + */ + @Override + DTPSchemaWrapper getDefaultSchema(DTPSchemaContainerWrapper sc) { + DTPSchemaWrapper schema = super.getDefaultSchema(sc); + return (schema != null) ? schema : sc.getSchemaNamed(PUBLIC_SCHEMA_NAME); + } + private static final String PUBLIC_SCHEMA_NAME = "public"; //$NON-NLS-1$ + + @Override + char[] getNormalNameStartCharacters() { + return NORMAL_NAME_START_CHARACTERS; + } + private static final char[] NORMAL_NAME_START_CHARACTERS = new char[] { '_' }; + + @Override + char[] getNormalNamePartCharacters() { + return NORMAL_NAME_PART_CHARACTERS; + } + private static final char[] NORMAL_NAME_PART_CHARACTERS = new char[] { '$' }; + + } + + private static class MaxDB extends Vendor { + static final Vendor INSTANCE = new MaxDB(); + + private MaxDB() { + super(); + } + + @Override + String getName() { + return "MaxDB"; //$NON-NLS-1$ + } + + @Override + char[] getNormalNameStartCharacters() { + return NORMAL_NAME_START_CHARACTERS; + } + private static final char[] NORMAL_NAME_START_CHARACTERS = new char[] { '#', '@', '$' }; + + @Override + char[] getNormalNamePartCharacters() { + return NORMAL_NAME_PART_CHARACTERS; + } + private static final char[] NORMAL_NAME_PART_CHARACTERS = new char[] { '_' }; + + } + + private static class Sybase extends Vendor { + static final Vendor ASA = new Sybase("Sybase_ASA"); //$NON-NLS-1$ + static final Vendor ASE = new Sybase("Sybase_ASE"); //$NON-NLS-1$ + + private final String name; + + private Sybase(String name) { + super(); + this.name = name; + } + + @Override + String getName() { + return this.name; + } + + /** + * The default schema on Sybase for any database (catalog) is 'dbo'. + */ + @Override + String getDefaultSchemaIdentifier(DTPSchemaContainerWrapper sc) { + return DEFAULT_SCHEMA_NAME; + } + private static final String DEFAULT_SCHEMA_NAME = "dbo"; //$NON-NLS-1$ + + /** + * By default, Sybase identifiers are case-sensitive, even without + * delimiters. This can depend on the collation setting.... + */ + @Override + Folder getFolder() { + return Folder.NONE; + } + + @Override + char[] getNormalNameStartCharacters() { + return NORMAL_NAME_START_CHARACTERS; + } + private static final char[] NORMAL_NAME_START_CHARACTERS = new char[] { '_', '@' }; + + @Override + char[] getNormalNamePartCharacters() { + return NORMAL_NAME_PART_CHARACTERS; + } + private static final char[] NORMAL_NAME_PART_CHARACTERS = new char[] { '$', '¥', '£', '#' }; + + /** + * By default, Sybase delimits identifiers with brackets ([]); but it + * can also be configured to use double-quotes. + */ + @Override + boolean identifierIsDelimited(String identifier) { + return StringTools.stringIsBracketed(identifier) + || super.identifierIsDelimited(identifier); + } + + } + } diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPForeignKeyWrapper.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPForeignKeyWrapper.java index a605046f62..8317bb926e 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPForeignKeyWrapper.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPForeignKeyWrapper.java @@ -273,9 +273,9 @@ final class DTPForeignKeyWrapper * ForeignKey(Foo_ID => ID) vs. "foo" => "Foo_ID" */ public String getJoinColumnAnnotationIdentifier(String attributeName) { - String defaultBaseColumnName = attributeName + '_' + this.getReferencedTable().getPrimaryKeyColumn().getName(); String baseColumnName = this.getColumnPair().getBaseColumn().getName(); - return this.getAnnotationIdentifier(defaultBaseColumnName, baseColumnName); + String defaultBaseColumnName = attributeName + '_' + this.getReferencedTable().getPrimaryKeyColumn().getName(); + return this.getDatabase().convertNameToIdentifier(baseColumnName, defaultBaseColumnName); } diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPSchemaContainerWrapper.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPSchemaContainerWrapper.java index 0323456f89..3ac1595aee 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPSchemaContainerWrapper.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPSchemaContainerWrapper.java @@ -14,6 +14,7 @@ import java.util.List; import org.eclipse.jpt.db.Schema; import org.eclipse.jpt.db.SchemaContainer; +import org.eclipse.jpt.utility.internal.CollectionTools; import org.eclipse.jpt.utility.internal.iterators.ArrayIterator; import org.eclipse.jpt.utility.internal.iterators.TransformationIterator; @@ -51,12 +52,6 @@ abstract class DTPSchemaContainerWrapper abstract List<org.eclipse.datatools.modelbase.sql.schema.Schema> getDTPSchemata(); /** - * return the schema container's catalog, - * null if the database does not support catalogs - */ - abstract DTPCatalogWrapper getCatalog(); - - /** * return the schema for the specified DTP schema */ abstract DTPSchemaWrapper getSchema(org.eclipse.datatools.modelbase.sql.schema.Schema dtpSchema); @@ -122,24 +117,33 @@ abstract class DTPSchemaContainerWrapper for (int i = result.length; i-- > 0;) { result[i] = new DTPSchemaWrapper(this, dtpSchemata.get(i)); } - return result; + return CollectionTools.sort(result); } public int schemataSize() { return this.getSchemata().length; } - public Iterator<String> schemaNames() { + public DTPSchemaWrapper getSchemaNamed(String name) { + return this.selectDatabaseObjectNamed(this.getSchemata(), name); + } + + public Iterator<String> sortedSchemaIdentifiers() { + // the schemata are already sorted return new TransformationIterator<DTPSchemaWrapper, String>(this.schemaWrappers()) { @Override - protected String transform(DTPSchemaWrapper schema) { - return schema.getName(); + protected String transform(DTPSchemaWrapper next) { + return next.getIdentifier(); } }; } - public DTPSchemaWrapper getSchemaNamed(String name) { - return this.getDatabaseObjectNamed(this.getSchemata(), name); + public DTPSchemaWrapper getSchemaForIdentifier(String identifier) { + return this.selectDatabaseObjectForIdentifier(this.getSchemata(), identifier); + } + + public DTPSchemaWrapper getDefaultSchema() { + return this.getDatabase().getDefaultSchema(this); } diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPSchemaWrapper.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPSchemaWrapper.java index 3d3a2938d3..08382c69d7 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPSchemaWrapper.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPSchemaWrapper.java @@ -13,10 +13,10 @@ import java.text.Collator; import java.util.Iterator; import java.util.List; -import org.eclipse.jpt.db.Catalog; import org.eclipse.jpt.db.Schema; import org.eclipse.jpt.db.Sequence; import org.eclipse.jpt.db.Table; +import org.eclipse.jpt.utility.internal.CollectionTools; import org.eclipse.jpt.utility.internal.iterators.ArrayIterator; import org.eclipse.jpt.utility.internal.iterators.TransformationIterator; @@ -60,8 +60,8 @@ final class DTPSchemaWrapper return this.dtpSchema.getName(); } - public Catalog getCatalog() { - return this.getContainer().getCatalog(); + public DTPSchemaContainerWrapper getContainer() { + return (DTPSchemaContainerWrapper) this.getParent(); } // ***** tables @@ -87,7 +87,7 @@ final class DTPSchemaWrapper for (int i = result.length; i-- > 0;) { result[i] = new DTPTableWrapper(this, dtpTables.get(i)); } - return result; + return CollectionTools.sort(result); } // minimize scope of suppressed warnings @@ -100,15 +100,6 @@ final class DTPSchemaWrapper return this.getTables().length; } - public Iterator<String> tableNames() { - return new TransformationIterator<DTPTableWrapper, String>(this.tableWrappers()) { - @Override - protected String transform(DTPTableWrapper table) { - return table.getName(); - } - }; - } - /** * return the table for the specified DTP table */ @@ -133,7 +124,21 @@ final class DTPSchemaWrapper } public DTPTableWrapper getTableNamed(String name) { - return this.getDatabaseObjectNamed(this.getTables(), name); + return this.selectDatabaseObjectNamed(this.getTables(), name); + } + + public Iterator<String> sortedTableIdentifiers() { + // the tables are already sorted + return new TransformationIterator<DTPTableWrapper, String>(this.tableWrappers()) { + @Override + protected String transform(DTPTableWrapper table) { + return table.getIdentifier(); + } + }; + } + + public DTPTableWrapper getTableForIdentifier(String identifier) { + return this.selectDatabaseObjectForIdentifier(this.getTables(), identifier); } // ***** sequences @@ -159,7 +164,7 @@ final class DTPSchemaWrapper for (int i = result.length; i-- > 0;) { result[i] = new DTPSequenceWrapper(this, dtpSequences.get(i)); } - return result; + return CollectionTools.sort(result); } // minimize scope of suppressed warnings @@ -172,17 +177,22 @@ final class DTPSchemaWrapper return this.getSequences().length; } - public Iterator<String> sequenceNames() { + public DTPSequenceWrapper getSequenceNamed(String name) { + return this.selectDatabaseObjectNamed(this.getSequences(), name); + } + + public Iterator<String> sortedSequenceIdentifiers() { + // the sequences are already sorted return new TransformationIterator<DTPSequenceWrapper, String>(this.sequenceWrappers()) { @Override protected String transform(DTPSequenceWrapper sequence) { - return sequence.getName(); + return sequence.getIdentifier(); } }; } - public DTPSequenceWrapper getSequenceNamed(String name) { - return this.getDatabaseObjectNamed(this.getSequences(), name); + public DTPSequenceWrapper getSequenceForIdentifier(String identifier) { + return this.selectDatabaseObjectForIdentifier(this.getSequences(), identifier); } @@ -216,10 +226,6 @@ final class DTPSchemaWrapper return this.getTable_(dtpColumn.getTable()).getColumn_(dtpColumn); } - private DTPSchemaContainerWrapper getContainer() { - return (DTPSchemaContainerWrapper) this.getParent(); - } - // ********** listening ********** diff --git a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPTableWrapper.java b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPTableWrapper.java index fc50bc4913..3dab7266df 100644 --- a/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPTableWrapper.java +++ b/jpa/plugins/org.eclipse.jpt.db/src/org/eclipse/jpt/db/internal/DTPTableWrapper.java @@ -96,7 +96,7 @@ final class DTPTableWrapper for (int i = result.length; i-- > 0;) { result[i] = new DTPColumnWrapper(this, dtpColumns.get(i)); } - return result; + return CollectionTools.sort(result); } // minimize scope of suppressed warnings @@ -109,13 +109,8 @@ final class DTPTableWrapper return this.getColumns().length; } - public Iterator<String> columnNames() { - return new TransformationIterator<DTPColumnWrapper, String>(this.columnWrappers()) { - @Override - protected String transform(DTPColumnWrapper next) { - return next.getName(); - } - }; + public DTPColumnWrapper getColumnNamed(String name) { + return this.selectDatabaseObjectNamed(this.getColumns(), name); } /** @@ -141,8 +136,18 @@ final class DTPTableWrapper throw new IllegalArgumentException("invalid DTP column: " + dtpColumn); //$NON-NLS-1$ } - public DTPColumnWrapper getColumnNamed(String name) { - return this.getDatabaseObjectNamed(this.getColumns(), name); + public Iterator<String> sortedColumnIdentifiers() { + // the columns are already sorted + return new TransformationIterator<DTPColumnWrapper, String>(this.columnWrappers()) { + @Override + protected String transform(DTPColumnWrapper next) { + return next.getIdentifier(); + } + }; + } + + public DTPColumnWrapper getColumnForIdentifier(String identifier) { + return this.selectDatabaseObjectForIdentifier(this.getColumns(), identifier); } // ***** primaryKeyColumns |