| /* |
| * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| |
| package org.eclipse.persistence.tools.dbws.jdbc; |
| |
| //javase imports |
| import java.sql.Connection; |
| import java.sql.DatabaseMetaData; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| import static java.sql.DatabaseMetaData.columnNullable; |
| import static java.sql.DatabaseMetaData.procedureReturnsResult; |
| import static java.sql.DatabaseMetaData.tableIndexStatistic; |
| import static java.sql.DatabaseMetaData.procedureColumnInOut; |
| import static java.sql.DatabaseMetaData.procedureColumnOut; |
| import static java.sql.DatabaseMetaData.procedureColumnReturn; |
| import static java.sql.Types.ARRAY; |
| import static java.sql.Types.OTHER; |
| import static java.sql.Types.STRUCT; |
| import static java.util.logging.Level.FINEST; |
| |
| //java eXtension imports |
| import javax.xml.namespace.QName; |
| |
| // EclipseLink imports |
| import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform; |
| import org.eclipse.persistence.internal.xr.Attachment; |
| import org.eclipse.persistence.internal.xr.CollectionResult; |
| import org.eclipse.persistence.internal.xr.NamedQueryHandler; |
| import org.eclipse.persistence.internal.xr.Parameter; |
| import org.eclipse.persistence.internal.xr.ProcedureArgument; |
| import org.eclipse.persistence.internal.xr.ProcedureOutputArgument; |
| import org.eclipse.persistence.internal.xr.QueryHandler; |
| import org.eclipse.persistence.internal.xr.QueryOperation; |
| import org.eclipse.persistence.internal.xr.Result; |
| import org.eclipse.persistence.internal.xr.StoredFunctionQueryHandler; |
| import org.eclipse.persistence.internal.xr.StoredProcedureQueryHandler; |
| import org.eclipse.persistence.platform.database.DerbyPlatform; |
| import org.eclipse.persistence.platform.database.MySQLPlatform; |
| import org.eclipse.persistence.platform.database.PostgreSQLPlatform; |
| import org.eclipse.persistence.queries.DatabaseQuery; |
| import org.eclipse.persistence.sessions.Project; |
| import org.eclipse.persistence.tools.dbws.BaseDBWSBuilderHelper; |
| import org.eclipse.persistence.tools.dbws.DBWSBuilder; |
| import org.eclipse.persistence.tools.dbws.DBWSBuilderHelper; |
| import org.eclipse.persistence.tools.dbws.ProcedureOperationModel; |
| import org.eclipse.persistence.tools.dbws.Util; |
| |
| import static org.eclipse.persistence.internal.xr.Util.SXF_QNAME; |
| import static org.eclipse.persistence.oxm.XMLConstants.ANY_QNAME; |
| import static org.eclipse.persistence.tools.dbws.Util.SXF_QNAME_CURSOR; |
| import static org.eclipse.persistence.tools.dbws.Util.buildCustomQName; |
| import static org.eclipse.persistence.tools.dbws.Util.escapePunctuation; |
| import static org.eclipse.persistence.tools.dbws.Util.buildTypeForJDBCType; |
| import static org.eclipse.persistence.tools.dbws.Util.getXMLTypeFromJDBCType; |
| import static org.eclipse.persistence.tools.dbws.Util.qNameFromString; |
| |
| //DDL parser imports |
| import org.eclipse.persistence.tools.oracleddl.metadata.ArgumentType; |
| import org.eclipse.persistence.tools.oracleddl.metadata.ArgumentTypeDirection; |
| import org.eclipse.persistence.tools.oracleddl.metadata.CompositeDatabaseType; |
| import org.eclipse.persistence.tools.oracleddl.metadata.DatabaseType; |
| import org.eclipse.persistence.tools.oracleddl.metadata.FunctionType; |
| import org.eclipse.persistence.tools.oracleddl.metadata.ProcedureType; |
| import org.eclipse.persistence.tools.oracleddl.metadata.TableType; |
| import static org.eclipse.persistence.tools.oracleddl.metadata.ArgumentTypeDirection.IN; |
| import static org.eclipse.persistence.tools.oracleddl.metadata.ArgumentTypeDirection.INOUT; |
| import static org.eclipse.persistence.tools.oracleddl.metadata.ArgumentTypeDirection.OUT; |
| import static org.eclipse.persistence.tools.oracleddl.metadata.ArgumentTypeDirection.RETURN; |
| |
| /* |
| * Known Problems/Limitations: Oracle |
| * |
| * 1) Oracle driver returns wrong value of column size |
| * Driver versions: |
| * - 10.0.1.4 |
| * - 9.2.0.6 |
| * When tables are created with NLS_LENGTH_SEMANTICS='CHAR' |
| * As per JavaDOC for the method ResultSet DatabaseMetaData.getColumns(....) : |
| * 7. COLUMN_SIZE int => column size. For char or date types this is the maximum |
| * number of characters, for numeric or decimal types this is precision. |
| * Suppose you create a table : |
| * create table test (col_char varchar2(8)); |
| * Now Retrieve the MetaData for the column "col_char" : |
| * ... |
| * ResultSet columnsInfo = databaseMetaData.getColumns(null, null, "test", "%"); |
| * if (columnsInfo != null) { |
| * while (columnsInfo.next()) { |
| * dbColumn.setName(columnsInfo.getString(4)); // 4 COLUMN_NAME String => column name |
| * dbColumn.setPrecision(columnsInfo.getInt(7)); // 7 COLUMN_SIZE int => column size. |
| * // For char or date types this is the |
| * // maximum number of characters, for |
| * // numeric or decimal types this is |
| * // precision. |
| * } |
| * } |
| * the return value is 24 which is number of bytes and not char_length |
| * as per the JavaDoc for the column col_char the return value should be 8. |
| * But it returns the value 24 which is the number of bytes and not the char_length. |
| * solved by 4485954 - updated version of oracle.jdbc.driver.OracleDatabaseMetaData |
| * in 10.2.0.2.0 |
| * |
| * 3) DatabaseMetaData.getColumns: |
| * The driver doesn't return INTEGER data type, instead it returns 3 ("NUMBER") |
| * with column precision of 22 and scale 0. |
| * |
| * 4) databaseMetaData.getTables() does not work for SYNONYMS |
| * |
| * 5) databaseMetaData.getProcedureColumns will not return the 'null' column for Stored Procedures |
| * that have no arguments, while USER_PROCEDURES (since 9.x) will |
| * |
| * 6) When making a call java method to a pl/sql stored procedure, register an out parameter of |
| * type java.sql.Types.VARCHAR (statement.registerOutParameter(i, Types.VARCHAR). The procedure |
| * has an OUT parameter of type VARCHAR2 with a NOCOPY hint (parameter1 OUT NOCOPY VARCHAR2). |
| * The pl/sql procedure throws an exception as soon as the size of 'parameter1' exceeds ~2000 |
| * characters (~4000 bytes). |
| * |
| * workaround : use oracle.jdbc.OracleTypes.VARCHAR |
| * |
| * 7) databaseMetaData.getIndexInfo() does not work for VIEWS, a SQLException with the following |
| * "ORA-01702: a view is not appropriate here: can't retrieve indexInfo" is thrown |
| * (other platforms just ignore). |
| * |
| * For IBM AS/400 via JTOpen |
| * 1) In the result set returned by DatabaseMetaData.getColumns(), the CHAR_OCTET_LENGTH and |
| * ORDINAL_POSITION columns are not supported in Toolbox versions prior to V5R1 (JTOpen 2.x). |
| * The following workarounds are available: |
| * For CHAR_OCTET_LENGTH, which is supposed to indicate the maximum number of bytes in the column, |
| * use the value in the COLUMN_SIZE. Multiply this value by 2 if the column holds DBCS characters. |
| * For ORDINAL_POSITION, which is supposed to indicate the index of the column in a table, |
| * run a query for all columns in the table ("SELECT * FROM table"), and then issue |
| * ResultSet.findColumn() on the result set to determine the column index from the column name. |
| * In the ResultSetMetaData object, the getTableName() and getSchema() name methods are not |
| * supported in Toolbox versions prior to V5R2 (JTOpen 3.x). In supported Toolbox versions, |
| * these methods work only if the "extended metadata" connection property has been set to True |
| * and only when the target server is at the V5R2 operating system or later. Because retrieving |
| * the additional information may incur a performance penalty, this property is set to False by |
| * default. |
| * |
| * ODBC-JDBC Bridge |
| * 1) for MS-ACCESS getPrimaryKeys not supported |
| * |
| * Apache Derby (a.k.a. JavaDB) |
| * 1) metadata for storedFunctions not available until JDK 6/JDBC 4 |
| * (Oracle returns metadata for storedFunctions via databaseMetaData.getProcedures()) |
| * |
| */ |
| public class JDBCHelper extends BaseDBWSBuilderHelper implements DBWSBuilderHelper { |
| |
| // useful fields in databaseMetaData.getTables() ResultSet |
| public static final int TABLESINFO_CATALOG = 1; // String ==> table catalog (may be null) |
| public static final int TABLESINFO_SCHEMA = 2; // String => table schema (may be null) |
| public static final int TABLESINFO_NAME = 3; // String => table name |
| public static final int TABLESINFO_TYPE = 4; // String => table type. Typical types are: |
| // "TABLE", "VIEW", "SYSTEM TABLE", |
| // "GLOBAL TEMPORARY", "LOCAL TEMPORARY", |
| // "ALIAS", "SYNONYM". |
| // useful fields in databaseMetaData.getColumns() ResultSet |
| public static final int COLUMNSINFO_COLUMN_NAME = 4; // String => table name |
| public static final int COLUMNSINFO_DATA_TYPE = 5; // int => SQL type from java.sql.Types |
| public static final int COLUMNSINFO_TYPE_NAME = 6; // String => Data source dependent type name, |
| // for a UDT the type name is fully qualified |
| public static final int COLUMNSINFO_COLUMN_SIZE = 7; // int => column size. For char or date |
| // types, this is the maximum number of |
| // characters, for numeric or decimal types |
| // this is precision. |
| public static final int COLUMNSINFO_DECIMAL_DIGITS = 9; // int => the number of fractional digits |
| public static final int COLUMNSINFO_NULLABLE = 11; // int => is NULL allowed? |
| // columnNoNulls - might not allow |
| // columnNullable - definitely allows |
| // columnNullableUnknown - unknown |
| public static final int COLUMNSINFO_ORDINAL_POSITION = 17; // int => index of column in table |
| // (1-based) |
| // useful fields in databaseMetaData.getPrimaryKeys() ResultSet |
| public static final int PKSINFO_KEY_SEQ = 5; // KEY_SEQ short => sequence number within primary |
| // key |
| public static final int PKSINFO_PK_NAME = 6; // PK_NAME String => primary key name (may be null) |
| // useful fields in databaseMetaData.getIndexInfo() ResultSet |
| public static final int INDEXINFO_NON_UNIQUE = 4; // boolean => Can index values be non-unique. |
| // false when TYPE is tableIndexStatistic |
| public static final int INDEXINFO_TYPE = 7; // short => index type: |
| // tableIndexStatistic - this identifies |
| // table statistics that are returned in |
| // conjuction with a table's index |
| // descriptions |
| // tableIndexClustered - this is a |
| // clustered index |
| // tableIndexHashed - this is a hashed |
| // index |
| // tableIndexOther - this is some other |
| // style of index |
| public static final int INDEXINFO_ORDINAL_POSITION = 8; // short => column sequence number |
| // within index; zero when TYPE is |
| // tableIndexStatistic |
| // useful fields in databaseMetaData.getProcedures() ResultSet |
| public static final int PROCS_INFO_CATALOG = 1; // String => procedure catalog (may be null) |
| public static final int PROCS_INFO_SCHEMA = 2; // String => procedure schema (may be null) |
| public static final int PROCS_INFO_NAME = 3; // String ==> procedure name |
| public static final int PROCS_INFO_TYPE = 8; // short => kind of procedure: |
| // procedureResultUnknown - May return a result |
| // procedureNoResult - Does not return a result |
| // procedureReturnsResult - Returns a result |
| // useful fields in databaseMetaData.getProcedureColumns() ResultSet |
| public static final int PROC_COLS_INFO_CATALOG = 1; // String ==> procedure catalog (may be null) |
| public static final int PROC_COLS_INFO_SCHEMA = 2; // String ==> procedure schema (may be null) |
| public static final int PROC_COLS_INFO_NAME = 3; // String ==> procedure name |
| public static final int PROC_COLS_INFO_COLNAME = 4; // String => column/parameter name |
| public static final int PROC_COLS_INFO_TYPE = 5; // Short => kind of column/parameter: |
| // procedureColumnUnknown - nobody knows |
| // procedureColumnIn - IN parameter |
| // procedureColumnInOut - INOUT parameter |
| // procedureColumnOut - OUT parameter |
| // procedureColumnReturn - procedure return value |
| // procedureColumnResult - result column in ResultSet |
| public static final int PROC_COLS_INFO_DATA_TYPE = 6; // int => SQL type from java.sql.Types |
| public static final int PROC_COLS_INFO_TYPE_NAME = 7; // String => SQL type name, for a UDT type |
| // the type name is fully qualified |
| public static final int PROC_COLS_INFO_PRECISION = 8; // int => precision |
| public static final int PROC_COLS_INFO_LENGTH = 9; // int => length in bytes of data |
| public static final int PROC_COLS_INFO_SCALE = 10; // short => scale |
| public static final int PROC_COLS_INFO_RADIX = 11; // short => radix |
| public static final int PROC_COLS_INFO_NULLABLE = 12; // short => can it contain NULL. |
| // procedureNoNulls - does not allow NULL values |
| // procedureNullable - allows NULL values |
| // procedureNullableUnknown - nullability unknown |
| public static final int PROC_COLS_INFO_ORA_SEQUENCE = 14; // Oracle-specific |
| // for package-qualified procedures, |
| // Oracle allows overloading of the |
| // procedure name. This field indicates |
| // the position of the argument (as |
| // argument names may be reused |
| public static final int PROC_COLS_INFO_ORA_OVERLOAD = 15; // Oracle-specific |
| // for package-qualified procedures, |
| // Oracle allows overloading of the |
| // procedure name. This field indicates |
| // the 'overload level' - i.e. which |
| // procedure we are dealing with |
| |
| public JDBCHelper(DBWSBuilder dbwsBuilder) { |
| super(dbwsBuilder); |
| } |
| |
| /** |
| * Indicates if one or more database tables were discovered. |
| * |
| */ |
| @Override |
| public boolean hasTables() { |
| return dbTables.size() != 0; |
| } |
| |
| @Override |
| public void buildProcedureOperation(ProcedureOperationModel procedureOperationModel) { |
| String name = procedureOperationModel.getName(); |
| boolean isMySQL = dbwsBuilder.getDatabasePlatform().getClass().getName().contains("MySQL"); |
| for (ProcedureType storedProcedure : procedureOperationModel.getDbStoredProcedures()) { |
| StringBuilder sb = new StringBuilder(); |
| if (name == null || name.length() == 0) { |
| if (storedProcedure.getOverload() > 0) { |
| sb.append(storedProcedure.getOverload()); |
| sb.append('_'); |
| } |
| if (storedProcedure.getCatalogName() != null && storedProcedure.getCatalogName().length() > 0) { |
| sb.append(storedProcedure.getCatalogName()); |
| sb.append('_'); |
| } |
| if (storedProcedure.getSchema() != null && storedProcedure.getSchema().length() > 0) { |
| sb.append(storedProcedure.getSchema()); |
| sb.append('_'); |
| } |
| sb.append(storedProcedure.getProcedureName()); |
| } |
| else { |
| sb.append(name); |
| } |
| QueryOperation qo = new QueryOperation(); |
| qo.setName(sb.toString()); |
| QueryHandler qh; |
| if (storedProcedure.isFunctionType()) { |
| qh = new StoredFunctionQueryHandler(); |
| } |
| else { |
| qh = new StoredProcedureQueryHandler(); |
| } |
| sb = new StringBuilder(); |
| if (!isMySQL) { |
| if (storedProcedure.getCatalogName() != null && storedProcedure.getCatalogName().length() > 0) { |
| sb.append(storedProcedure.getCatalogName()); |
| sb.append('.'); |
| } |
| } |
| if (storedProcedure.getSchema() != null && storedProcedure.getSchema().length() > 0) { |
| sb.append(storedProcedure.getSchema()); |
| sb.append('.'); |
| } |
| sb.append(storedProcedure.getProcedureName()); |
| ((StoredProcedureQueryHandler)qh).setName(sb.toString()); |
| dbwsBuilder.logMessage(FINEST, "Building QueryOperation for " + sb.toString()); |
| // before assigning queryHandler, check for named query in OR project |
| List<DatabaseQuery> queries = dbwsBuilder.getOrProject().getQueries(); |
| if (queries.size() > 0) { |
| for (DatabaseQuery q : queries) { |
| if (q.getName().equals(qo.getName())) { |
| qh = new NamedQueryHandler(); |
| ((NamedQueryHandler)qh).setName(qo.getName()); |
| } |
| } |
| } |
| qo.setQueryHandler(qh); |
| String returnType = procedureOperationModel.getReturnType(); |
| boolean isCollection = procedureOperationModel.isCollection(); |
| boolean isSimpleXMLFormat = procedureOperationModel.isSimpleXMLFormat(); |
| Result result = null; |
| if (storedProcedure.isFunctionType()) { |
| FunctionType storedFunction = (FunctionType) storedProcedure; |
| DatabaseType rarg = storedFunction.getReturnArgument(); |
| if (rarg.getTypeName().contains("CURSOR")) { |
| result = new CollectionResult(); |
| result.setType(SXF_QNAME_CURSOR); |
| } |
| else { |
| result = new Result(); |
| int rargJdbcType = Util.getJDBCTypeFromTypeName(rarg.getTypeName()); |
| switch (rargJdbcType) { |
| case STRUCT: |
| case ARRAY: |
| case OTHER: |
| if (returnType != null) { |
| result.setType(buildCustomQName(returnType, dbwsBuilder)); |
| } |
| else { |
| result.setType(ANY_QNAME); |
| } |
| break; |
| default : |
| result.setType(getXMLTypeFromJDBCType(rargJdbcType)); |
| break; |
| } |
| } |
| } |
| else { |
| // if user overrides returnType, assume they're right |
| if (returnType != null) { |
| result = new Result(); |
| result.setType(buildCustomQName(returnType, dbwsBuilder)); |
| } |
| else { |
| if (isCollection) { |
| result = new CollectionResult(); |
| if (isSimpleXMLFormat) { |
| result.setType(SXF_QNAME_CURSOR); |
| } |
| } |
| else { |
| result = new Result(); |
| result.setType(SXF_QNAME); |
| } |
| } |
| } |
| if (procedureOperationModel.getBinaryAttachment()) { |
| Attachment attachment = new Attachment(); |
| attachment.setMimeType("application/octet-stream"); |
| result.setAttachment(attachment); |
| } |
| for (ArgumentType arg : storedProcedure.getArguments()) { |
| String argName = arg.getArgumentName(); |
| if (argName != null) { |
| ProcedureArgument pa = null; |
| Parameter parm = null; |
| ArgumentTypeDirection direction = arg.getDirection(); |
| QName xmlType = null; |
| switch (Util.getJDBCTypeFromTypeName(arg.getTypeName())) { |
| case STRUCT: |
| case ARRAY: |
| case OTHER: |
| String typeString = nct.generateSchemaAlias(arg.getTypeName()); |
| xmlType = buildCustomQName(typeString, dbwsBuilder); |
| break; |
| default : |
| xmlType = getXMLTypeFromJDBCType(Util.getJDBCTypeFromTypeName(arg.getTypeName())); |
| break; |
| } |
| if (direction == IN) { |
| parm = new Parameter(); |
| parm.setName(argName); |
| parm.setType(xmlType); |
| pa = new ProcedureArgument(); |
| pa.setName(argName); |
| pa.setParameterName(argName); |
| if (qh instanceof StoredProcedureQueryHandler) { |
| ((StoredProcedureQueryHandler)qh).getInArguments().add(pa); |
| } |
| } |
| else { |
| // the first OUT/INOUT arg determines singleResult vs. collectionResult |
| pa = new ProcedureOutputArgument(); |
| ProcedureOutputArgument pao = (ProcedureOutputArgument)pa; |
| pao.setName(argName); |
| pao.setParameterName(argName); |
| if (arg.getTypeName().contains("CURSOR") && |
| returnType == null) { // if user overrides returnType, assume they're right |
| pao.setResultType(SXF_QNAME_CURSOR); |
| if (result == null) { |
| result = new CollectionResult(); |
| result.setType(SXF_QNAME_CURSOR); |
| } |
| } |
| else { |
| // if user overrides returnType, assume they're right |
| // Hmm, multiple OUT's gonna be a problem - later! |
| if (returnType != null && !isSimpleXMLFormat) { |
| xmlType = qNameFromString("{" + dbwsBuilder.getTargetNamespace() + "}" + |
| returnType, dbwsBuilder.getSchema()); |
| } |
| pao.setResultType(xmlType); |
| if (result == null) { |
| if (isCollection) { |
| result = new CollectionResult(); |
| } |
| else { |
| result = new Result(); |
| } |
| result.setType(xmlType); |
| } |
| } |
| if (direction == INOUT) { |
| parm = new Parameter(); |
| parm.setName(argName); |
| parm.setType(xmlType); |
| result.setType(xmlType); |
| // use of INOUT precludes SimpleXMLFormat |
| isSimpleXMLFormat = false; |
| |
| if (qh instanceof StoredProcedureQueryHandler) { |
| ((StoredProcedureQueryHandler)qh).getInOutArguments().add(pao); |
| } |
| } |
| else { |
| if (qh instanceof StoredProcedureQueryHandler) { |
| ((StoredProcedureQueryHandler)qh).getOutArguments().add(pao); |
| } |
| } |
| } |
| if (parm != null) { |
| qo.getParameters().add(parm); |
| } |
| } |
| } |
| |
| handleSimpleXMLFormat(isSimpleXMLFormat, result, procedureOperationModel); |
| qo.setResult(result); |
| dbwsBuilder.getXrServiceModel().getOperations().put(qo.getName(), qo); |
| } |
| finishProcedureOperation(); |
| } |
| |
| @Override |
| protected List<TableType> loadTables(List<String> catalogPatterns, List<String> schemaPatterns, |
| List<String> tableNamePatterns) { |
| List<TableType> tables = new ArrayList<TableType>(); |
| for (int i = 0, len = catalogPatterns.size(); i < len; i++) { |
| String catalogPattern = catalogPatterns.get(i); |
| String schemaPattern = schemaPatterns.get(i); |
| String tablePattern = tableNamePatterns.get(i); |
| tables.addAll(loadTables(catalogPattern, schemaPattern, tablePattern)); |
| } |
| return tables; |
| } |
| protected List<TableType> loadTables(String originalCatalogPattern, String originalSchemaPattern, |
| String originalTablePattern) { |
| List<TableType> dbTables = null; |
| String schemaPattern = escapePunctuation(originalSchemaPattern); |
| String tablePattern = escapePunctuation(originalTablePattern); |
| DatabaseMetaData databaseMetaData = getDatabaseMetaData(dbwsBuilder.getConnection()); |
| boolean supportsCatalogsInTableDefinitions = true; |
| try { |
| supportsCatalogsInTableDefinitions = |
| databaseMetaData.supportsCatalogsInTableDefinitions(); |
| } |
| catch (SQLException sqlException) { /* ignore*/ } |
| String catalogPattern = escapePunctuation( |
| supportsCatalogsInTableDefinitions ? originalCatalogPattern : ""); |
| // Make sure table(s) is/are available |
| ResultSet tablesInfo = null; |
| try { |
| tablesInfo = databaseMetaData.getTables(catalogPattern, schemaPattern, tablePattern, null); |
| } |
| catch (SQLException sqlException) { |
| throw new IllegalStateException("failure retrieving JDBC table metadata", sqlException); |
| } |
| // did we get a hit? |
| if (tablesInfo != null) { |
| dbTables = new ArrayList<TableType>(); |
| try { |
| while (tablesInfo.next()) { |
| String actualTableCatalog = null; |
| try { |
| actualTableCatalog = tablesInfo.getString(TABLESINFO_CATALOG); |
| } |
| catch (SQLException sqlException) { |
| // we can live without catalog info |
| } |
| String actualTableSchema = null; |
| try { |
| actualTableSchema = tablesInfo.getString(TABLESINFO_SCHEMA); |
| } |
| catch (SQLException sqlException) { |
| // we can live without schema info |
| } |
| String actualTableName = tablesInfo.getString(TABLESINFO_NAME); |
| String tableType = null; |
| try { |
| tableType = tablesInfo.getString(TABLESINFO_TYPE); |
| } |
| catch (SQLException sqlException) { |
| // we can live without table type - assume TABLE |
| } |
| DbTable dbTable = new DbTable(); |
| dbTable.setCatalog(actualTableCatalog); |
| dbTable.setSchema(actualTableSchema); |
| dbTable.setTableName(actualTableName); |
| if (tableType != null) { |
| dbTable.setType(tableType); |
| } |
| dbTables.add(dbTable); |
| ResultSet columnInfo = databaseMetaData.getColumns(actualTableCatalog, |
| actualTableSchema, actualTableName, "%"); |
| while (columnInfo.next()) { |
| String columnName = columnInfo.getString(COLUMNSINFO_COLUMN_NAME); |
| DbColumn dbColumn = new DbColumn(columnName); |
| int dbPrecision = -1; |
| try { |
| dbPrecision = columnInfo.getInt(COLUMNSINFO_COLUMN_SIZE); |
| } |
| catch (NumberFormatException nfe) { |
| // ignore |
| } |
| int dbScale = columnInfo.getInt(COLUMNSINFO_DECIMAL_DIGITS); |
| if (columnInfo.getInt(COLUMNSINFO_NULLABLE) == columnNullable) { |
| dbColumn.unSetNotNull(); |
| } |
| else { |
| dbColumn.setNotNull(); |
| } |
| dbColumn.setJDBCType(columnInfo.getInt(COLUMNSINFO_DATA_TYPE)); |
| dbColumn.setJDBCTypeName(columnInfo.getString(COLUMNSINFO_TYPE_NAME)); |
| dbColumn.setEnclosedType(buildTypeForJDBCType(dbColumn.getJDBCType(), |
| dbPrecision, dbScale)); |
| dbTable.getColumns().add(dbColumn); |
| } |
| columnInfo.close(); |
| ResultSet pksInfo = databaseMetaData.getPrimaryKeys(actualTableCatalog, |
| actualTableSchema, actualTableName); |
| if (pksInfo != null) { |
| while (pksInfo.next()) { |
| short keySeq = pksInfo.getShort(PKSINFO_KEY_SEQ); |
| String pkConstraintName = pksInfo.getString(PKSINFO_PK_NAME); |
| DbColumn dbColumn = (DbColumn)dbTable.getColumns().get(keySeq - 1); |
| dbColumn.setPk(); |
| dbColumn.setPkConstraintName(pkConstraintName); |
| } |
| } |
| pksInfo.close(); |
| try { |
| ResultSet indexInfo = databaseMetaData.getIndexInfo(actualTableCatalog, |
| actualTableSchema, actualTableName, true, false); |
| if (indexInfo != null) { |
| while (indexInfo.next()) { |
| boolean nonUnique = indexInfo.getBoolean(INDEXINFO_NON_UNIQUE); |
| if (!nonUnique) { // reverse logic! |
| short type = indexInfo.getShort(INDEXINFO_TYPE); |
| if (type != tableIndexStatistic) { |
| short pos = indexInfo.getShort(INDEXINFO_ORDINAL_POSITION); |
| DbColumn dbColumn = (DbColumn)dbTable.getColumns().get(pos - 1); |
| if (!dbColumn.pk()) { |
| dbColumn.setUnique(true); |
| } |
| } |
| } |
| } |
| } |
| indexInfo.close(); |
| } |
| catch (SQLException sqlException) { |
| // ignore |
| } |
| } |
| tablesInfo.close(); |
| } |
| catch (SQLException sqlException) { |
| throw new IllegalStateException("failure retrieving JDBC table metadata", |
| sqlException); |
| } |
| } |
| return dbTables; |
| } |
| |
| @Override |
| protected List<ProcedureType> loadProcedures(List<String> catalogPatterns, |
| List<String> schemaPatterns, List<String> procedureNamePatterns) { |
| List<ProcedureType> procedures = new ArrayList<ProcedureType>(); |
| for (int i = 0, len = catalogPatterns.size(); i < len; i++) { |
| String catalogPattern = catalogPatterns.get(i); |
| String schemaPattern = schemaPatterns.get(i); |
| String tablePattern = procedureNamePatterns.get(i); |
| List<ProcedureType> procs = loadProcedures(catalogPattern, schemaPattern, tablePattern); |
| if (procs != null) { |
| procedures.addAll(loadProcedures(catalogPattern, schemaPattern, tablePattern)); |
| } |
| } |
| return procedures; |
| } |
| protected List<ProcedureType> loadProcedures(String originalCatalogPattern, |
| String originalSchemaPattern, String originalProcedurePattern) { |
| List<ProcedureType> dbStoredProcedures = null; |
| boolean catalogMatchDontCare = false; |
| DatabasePlatform platform = dbwsBuilder.getDatabasePlatform(); |
| if (platform instanceof MySQLPlatform || |
| platform instanceof DerbyPlatform || |
| platform instanceof PostgreSQLPlatform ) { |
| // note that get info on other platforms that also require catalogMatchDontCare = true |
| catalogMatchDontCare = true; |
| } |
| String catalogPattern = escapePunctuation(originalCatalogPattern); |
| String schemaPattern = escapePunctuation(originalSchemaPattern); |
| String procedurePattern = escapePunctuation(originalProcedurePattern); |
| // Make sure procedure(s) is/are available |
| ResultSet procsInfo = null; |
| try { |
| DatabaseMetaData databaseMetaData = getDatabaseMetaData(dbwsBuilder.getConnection()); |
| procsInfo = databaseMetaData.getProcedures(catalogPattern, schemaPattern, procedurePattern); |
| // did we get a hit? |
| if (procsInfo != null) { |
| List<ProcedureType> tmpProcs = new ArrayList<ProcedureType>(); |
| while (procsInfo.next()) { |
| String actualCatalogName = procsInfo.getString(PROCS_INFO_CATALOG); |
| String actualSchemaName = procsInfo.getString(PROCS_INFO_SCHEMA); |
| String actualProcedureName = procsInfo.getString(PROCS_INFO_NAME); |
| short procedureType = procsInfo.getShort(PROCS_INFO_TYPE); |
| ProcedureType dbStoredProcedure; |
| if (procedureType == procedureReturnsResult) { |
| dbStoredProcedure = new FunctionType(actualProcedureName); |
| } |
| else { |
| dbStoredProcedure = new ProcedureType(actualProcedureName); |
| } |
| if (actualCatalogName != null && actualCatalogName.length() > 0) { |
| dbStoredProcedure.setCatalogName(actualCatalogName); |
| } |
| if (actualSchemaName != null && actualSchemaName.length() > 0) { |
| dbStoredProcedure.setSchema(actualSchemaName); |
| } |
| tmpProcs.add(dbStoredProcedure); |
| } |
| procsInfo.close(); |
| /* new a temp bucket to hold DbStoredArgs until they can be sorted out with respect |
| * to which DbStoredProcedure owns which args; this has to be done because Oracle can |
| * return multiple hits across multiple packages for the same procedureName. |
| */ |
| int numProcs = tmpProcs.size(); |
| if (numProcs > 0) { |
| dbStoredProcedures = new ArrayList<ProcedureType>(numProcs); |
| ResultSet procedureColumnsInfo = null; |
| procedureColumnsInfo = databaseMetaData.getProcedureColumns(catalogPattern, |
| schemaPattern, procedurePattern, "%"); |
| while (procedureColumnsInfo.next()) { |
| String actualCatalogName = procedureColumnsInfo.getString(PROC_COLS_INFO_CATALOG); |
| String actualSchemaName = procedureColumnsInfo.getString(PROC_COLS_INFO_SCHEMA); |
| String actualProcedureName = procedureColumnsInfo.getString(PROC_COLS_INFO_NAME); |
| String argName = procedureColumnsInfo.getString(PROC_COLS_INFO_COLNAME); |
| // some MySql drivers return empty string, some return null: set to emptyString regardless |
| if (argName == null) { |
| argName = ""; |
| } |
| ArgumentType dbStoredArgument = new ArgumentType(argName); |
| short inOut = procedureColumnsInfo.getShort(PROC_COLS_INFO_TYPE); |
| if (inOut == procedureColumnInOut) { |
| dbStoredArgument.setDirection(INOUT); |
| } |
| else if (inOut == procedureColumnOut) { |
| dbStoredArgument.setDirection(OUT); |
| } |
| else if (inOut == procedureColumnReturn) { |
| dbStoredArgument.setDirection(RETURN); |
| } else { |
| // default to ArgumentTypeDirection.IN |
| dbStoredArgument.setDirection(IN); |
| } |
| |
| int jdbcType = procedureColumnsInfo.getInt(PROC_COLS_INFO_DATA_TYPE); |
| int precision = procedureColumnsInfo.getInt(PROC_COLS_INFO_PRECISION); |
| int scale = procedureColumnsInfo.getInt(PROC_COLS_INFO_SCALE); |
| |
| dbStoredArgument.setEnclosedType(buildTypeForJDBCType(jdbcType, precision, scale)); |
| |
| // find matching DbStoredProcedure |
| // this dbStoredArgument belongs to a 'regular' procedure |
| ProcedureType matchingProc = null; |
| for (int i = 0; i < tmpProcs.size();) { |
| ProcedureType tmpProc = tmpProcs.get(i); |
| if (matches(tmpProc, actualCatalogName, actualSchemaName, |
| actualProcedureName, false, catalogMatchDontCare)) { |
| matchingProc = tmpProc; |
| dbStoredProcedures.add(matchingProc); |
| break; |
| } |
| i++; |
| } |
| if (matchingProc == null) { |
| // look in dbStoredProcedures - matching proc already moved over ? |
| for (ProcedureType dbStoredProcedure: dbStoredProcedures) { |
| if (matches(dbStoredProcedure, actualCatalogName, actualSchemaName, |
| actualProcedureName, false, catalogMatchDontCare)) { |
| matchingProc = dbStoredProcedure; |
| break; |
| } |
| } |
| } |
| if (matchingProc != null) { |
| if (matchingProc.isFunctionType() && dbStoredArgument.getArgumentName().equalsIgnoreCase("")) { |
| ((FunctionType)matchingProc).setReturnArgument(dbStoredArgument); |
| } |
| else { |
| matchingProc.getArguments().add(dbStoredArgument); |
| } |
| tmpProcs.remove(matchingProc); |
| } |
| // else some argument that doesn't have a matching proc? ignore for now |
| } |
| procedureColumnsInfo.close(); |
| if (!tmpProcs.isEmpty()) { |
| // leftovers are the no-arg procedures |
| dbStoredProcedures.addAll(tmpProcs); |
| } |
| } |
| } |
| } |
| catch (SQLException sqlException) { |
| throw new IllegalStateException("failure retrieving Stored Procedure metadata", |
| sqlException); |
| } |
| if (dbStoredProcedures != null && !dbStoredProcedures.isEmpty()) { |
| Collections.sort(dbStoredProcedures, new Comparator<ProcedureType>() { |
| @Override |
| public int compare(ProcedureType o1, ProcedureType o2) { |
| String name1 = o1.getProcedureName(); |
| String name2 = o2.getProcedureName(); |
| if (!name1.equals(name2)) { |
| return name1.compareTo(name2); |
| } |
| else { |
| return o1.getOverload() - o2.getOverload(); |
| } |
| } |
| }); |
| } |
| return dbStoredProcedures; |
| } |
| |
| static DatabaseMetaData getDatabaseMetaData(Connection connection) { |
| if (connection == null) { |
| // without a connection we cannot retrieve the metadata |
| throw new IllegalStateException("Connection is null - cannot retrieve JDBC metadata"); |
| } |
| DatabaseMetaData databaseMetaData = null; |
| try { |
| databaseMetaData = connection.getMetaData(); |
| } catch (SQLException sqlException) { |
| // without metadata, there is nothing to do here |
| throw new IllegalStateException("failure retrieving JDBC metadata", sqlException); |
| } |
| return databaseMetaData; |
| } |
| |
| public static boolean matches(ProcedureType proc, String catalog, String schema, String name, boolean isOracle, |
| boolean catalogMatchDontCare) { |
| |
| // return true if all 3 match, sorta |
| |
| boolean catalogMatch = |
| proc.getCatalogName() == null ? |
| // for Oracle, catalog matching is 'dont-care' only if null |
| (isOracle ? true : |
| // other platforms: null has to match null |
| (catalog == null)) |
| : proc.getCatalogName().equals(catalog); |
| // but catalogDontCare trumps! |
| if (catalogMatchDontCare) { |
| catalogMatch = true; |
| } |
| boolean schemaMatch = |
| // either they are both null or they match |
| proc.getSchema() == null ? (schema == null) |
| : proc.getSchema().equals(schema); |
| boolean nameMatch = |
| // either they are both null or they match |
| proc.getProcedureName() == null ? (name == null) |
| : proc.getProcedureName().equals(name); |
| return catalogMatch && schemaMatch && nameMatch; |
| } |
| |
| @Override |
| public void addToOROXProjectsForComplexTypes(List<CompositeDatabaseType> types, Project orProject, Project oxProject) { |
| } |
| @Override |
| protected void buildQueryForProcedureType(ProcedureType procType, Project orProject, Project oxProject, ProcedureOperationModel opModel, boolean hasComplexArgs) { |
| } |
| } |