| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.empire.dbms.oracle; |
| |
| import java.sql.Connection; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| |
| import org.apache.empire.commons.ObjectUtils; |
| import org.apache.empire.commons.StringUtils; |
| import org.apache.empire.data.DataType; |
| import org.apache.empire.db.DBColumn; |
| import org.apache.empire.db.DBColumnExpr; |
| import org.apache.empire.db.DBCommand; |
| import org.apache.empire.db.DBContext; |
| import org.apache.empire.db.DBDDLGenerator; |
| import org.apache.empire.db.DBDDLGenerator.DDLActionType; |
| import org.apache.empire.db.DBDatabase; |
| import org.apache.empire.db.DBExpr; |
| import org.apache.empire.db.DBObject; |
| import org.apache.empire.db.DBReader; |
| import org.apache.empire.db.DBRelation; |
| import org.apache.empire.db.DBSQLScript; |
| import org.apache.empire.db.DBTable; |
| import org.apache.empire.db.DBTableColumn; |
| import org.apache.empire.db.DBView; |
| import org.apache.empire.db.context.DBContextStatic; |
| import org.apache.empire.db.exceptions.EmpireSQLException; |
| import org.apache.empire.db.exceptions.QueryNoResultException; |
| import org.apache.empire.db.expr.column.DBValueExpr; |
| import org.apache.empire.db.validation.DBModelChecker; |
| import org.apache.empire.dbms.DBMSFeature; |
| import org.apache.empire.dbms.DBMSHandler; |
| import org.apache.empire.dbms.DBMSHandlerBase; |
| import org.apache.empire.dbms.DBSqlPhrase; |
| import org.apache.empire.exceptions.InvalidArgumentException; |
| import org.apache.empire.exceptions.InvalidPropertyException; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * This class provides support for the Oracle database system.<br> |
| * Oracle Version 9 or higher is required. |
| */ |
| public class DBMSHandlerOracle extends DBMSHandlerBase |
| { |
| // *Deprecated* private static final long serialVersionUID = 1L; |
| private static final Logger log = LoggerFactory.getLogger(DBMSHandlerOracle.class); |
| |
| // Implementation of boolean types |
| public enum BooleanType |
| { |
| CHAR, // as CHAR(1) with 'Y' for true and 'N' for false |
| NUMBER // as NUMBER(1) with 1 for true and 0 for false |
| } |
| |
| private boolean oracle8Compatibilty = false; |
| |
| private BooleanType booleanType = BooleanType.NUMBER; |
| |
| private DBDDLGenerator<?> ddlGenerator = null; // lazy creation |
| |
| private String schemaName; |
| |
| /** |
| * Constructor for the Oracle database dbms.<br> |
| * |
| */ |
| public DBMSHandlerOracle() |
| { |
| // Info |
| log.info("DBMSHandlerOracle created. Boolean Type is " + booleanType); |
| // Additional reserved names |
| this.reservedSQLKeywords.add("date"); |
| this.reservedSQLKeywords.add("number"); |
| } |
| |
| public boolean isOracle8Compatibilty() |
| { |
| return oracle8Compatibilty; |
| } |
| |
| public void setOracle8Compatibilty(boolean oracle8Compatibilty) |
| { |
| this.oracle8Compatibilty = oracle8Compatibilty; |
| } |
| |
| public BooleanType getBooleanType() |
| { |
| return booleanType; |
| } |
| |
| public void setBooleanType(BooleanType booleanType) |
| { |
| this.booleanType = booleanType; |
| log.info("DBDatabaseDriverOracle Boolean Type set to " + booleanType); |
| } |
| |
| public String getSchemaName() |
| { |
| return schemaName; |
| } |
| |
| public void setSchemaName(String schemaName) |
| { |
| this.schemaName = schemaName; |
| } |
| |
| /** |
| * Returns whether or not a particular feature is supported by this dbms |
| * @param type type of requested feature. @see DBMSFeature |
| * @return true if the features is supported or false otherwise |
| */ |
| @Override |
| public boolean isSupported(DBMSFeature type) |
| { |
| switch (type) |
| { // return support info |
| case CREATE_SCHEMA: return false; |
| case SEQUENCES: return true; |
| case SEQUENCE_NEXTVAL: return true; |
| case QUERY_LIMIT_ROWS: return true; |
| case QUERY_SKIP_ROWS: return true; |
| default: |
| // All other features are not supported by default |
| return false; |
| } |
| } |
| |
| /** |
| * Creates a new Oracle command object. |
| * |
| * @return the new DBCommandOracle object |
| */ |
| @Override |
| public DBCommand createCommand(boolean autoPrepareStmt) |
| { |
| // create oracle command |
| return new DBCommandOracle(autoPrepareStmt); |
| } |
| |
| /** |
| * Gets an sql phrase template for this database system.<br> |
| * @see DBMSHandler#getSQLPhrase(int) |
| * @return the phrase template |
| */ |
| @Override |
| public String getSQLPhrase(DBSqlPhrase phrase) |
| { |
| switch (phrase) |
| { |
| // sql-phrases |
| case SQL_NULL: return "null"; |
| case SQL_PARAMETER: return " ? "; |
| case SQL_RENAME_TABLE: return " "; |
| case SQL_RENAME_COLUMN: return " AS "; |
| case SQL_DATABASE_LINK: return "@"; |
| case SQL_QUOTES_OPEN: return "\""; |
| case SQL_QUOTES_CLOSE: return "\""; |
| case SQL_CONCAT_EXPR: return " || "; |
| case SQL_PSEUDO_TABLE: return "DUAL"; |
| // data types |
| case SQL_BOOLEAN_TRUE: return (booleanType==BooleanType.CHAR) ? "'Y'" : "1"; |
| case SQL_BOOLEAN_FALSE: return (booleanType==BooleanType.CHAR) ? "'N'" : "0"; |
| case SQL_CURRENT_DATE: return "sysdate"; |
| case SQL_DATE_PATTERN: return "yyyy-MM-dd"; |
| case SQL_DATE_TEMPLATE: return "TO_DATE('{0}', 'YYYY-MM-DD')"; |
| case SQL_CURRENT_TIMESTAMP: return "systimestamp"; |
| case SQL_DATETIME_PATTERN: |
| case SQL_TIMESTAMP_PATTERN: return "yyyy-MM-dd HH:mm:ss.SSS"; |
| case SQL_DATETIME_TEMPLATE: |
| case SQL_TIMESTAMP_TEMPLATE: return "TO_TIMESTAMP('{0}', 'YYYY.MM.DD HH24:MI:SS.FF')"; |
| // functions |
| case SQL_FUNC_COALESCE: return "nvl(?, {0})"; |
| case SQL_FUNC_SUBSTRING: return "substr(?, {0})"; |
| case SQL_FUNC_SUBSTRINGEX: return "substr(?, {0}, {1})"; |
| case SQL_FUNC_REPLACE: return "replace(?, {0}, {1})"; |
| case SQL_FUNC_REVERSE: return "reverse(?)"; |
| case SQL_FUNC_STRINDEX: return "instr(?, {0})"; |
| case SQL_FUNC_STRINDEXFROM: return "instr(?, {0}, {1})"; |
| case SQL_FUNC_LENGTH: return "length(?)"; |
| case SQL_FUNC_UPPER: return "upper(?)"; |
| case SQL_FUNC_LOWER: return "lower(?)"; |
| case SQL_FUNC_TRIM: return "trim(?)"; |
| case SQL_FUNC_LTRIM: return "ltrim(?)"; |
| case SQL_FUNC_RTRIM: return "rtrim(?)"; |
| case SQL_FUNC_ESCAPE: return "? escape '{0}'"; |
| case SQL_FUNC_CONCAT: return " || "; |
| // Numeric |
| case SQL_FUNC_ABS: return "abs(?)"; |
| case SQL_FUNC_ROUND: return "round(?,{0})"; |
| case SQL_FUNC_TRUNC: return "trunc(?,{0})"; |
| case SQL_FUNC_CEILING: return "ceil(?)"; |
| case SQL_FUNC_FLOOR: return "floor(?)"; |
| case SQL_FUNC_MODULO: return "mod(?,{0})"; |
| case SQL_FUNC_FORMAT: return "TO_CHAR(?, {0:VARCHAR})"; |
| // Date |
| case SQL_FUNC_DAY: return oracle8Compatibilty ? "to_number(to_char(?,'DD'))" : "extract(day from ?)"; |
| case SQL_FUNC_MONTH: return oracle8Compatibilty ? "to_number(to_char(?,'MM'))" : "extract(month from ?)"; |
| case SQL_FUNC_YEAR: return oracle8Compatibilty ? "to_number(to_char(?,'YYYY'))" : "extract(year from ?)"; |
| // Aggregation |
| case SQL_FUNC_SUM: return "sum(?)"; |
| case SQL_FUNC_MAX: return "max(?)"; |
| case SQL_FUNC_MIN: return "min(?)"; |
| case SQL_FUNC_AVG: return "avg(?)"; |
| case SQL_FUNC_STRAGG: return "listagg(? {0})"; |
| // Others |
| case SQL_FUNC_DECODE: return "decode(? {0})"; |
| case SQL_FUNC_DECODE_SEP: return ","; |
| case SQL_FUNC_DECODE_PART: return "{0}, {1}"; |
| case SQL_FUNC_DECODE_ELSE: return "{0}"; |
| // Not defined |
| default: |
| // log.warn("SQL phrase " + phrase.name() + " is not defined!"); |
| return phrase.getSqlDefault(); |
| } |
| } |
| |
| /** |
| * @see DBMSHandler#getConvertPhrase(DataType, DataType, Object) |
| */ |
| @Override |
| public String getConvertPhrase(DataType destType, DataType srcType, Object format) |
| { |
| switch (destType) |
| { |
| /* |
| * case DBExpr.DT_BOOL: return "convert(bit, ?)"; case DBExpr.DT_INTEGER: return "convert(int, ?)"; case |
| * DBExpr.DT_DECIMAL: return "convert(decimal, ?)"; case DBExpr.DT_NUMBER: return "convert(float, ?)"; case |
| * DBExpr.DT_DATE: return "convert(datetime, ?, 111)"; case DBExpr.DT_DATETIME: return "convert(datetime, ?, 120)"; |
| */ |
| // Convert to text |
| case VARCHAR: |
| case CHAR: |
| case CLOB: |
| if (format instanceof String) |
| { // Convert using a format string |
| return "to_char(?, '"+format.toString()+"')"; |
| } |
| return "to_char(?)"; |
| // Convert to number |
| case INTEGER: |
| case FLOAT: |
| case DECIMAL: |
| if (format instanceof String) |
| { // Convert using a format string |
| return "to_number(?, '"+format.toString()+"')"; |
| } |
| return "to_number(?)"; |
| // Convert to date |
| case DATE: |
| case DATETIME: |
| if (format instanceof String) |
| { // Convert using a format string |
| return "to_date(?, '"+format.toString()+"')"; |
| } |
| return "to_date(?)"; |
| case TIMESTAMP: |
| if (format instanceof String) |
| { // Convert using a format string |
| return "to_timestamp(?, '"+format.toString()+"')"; |
| } |
| return "to_timestamp(?)"; |
| // Unknown Type |
| default: |
| log.error("getConvertPhrase: unknown type " + destType); |
| return "?"; |
| } |
| } |
| |
| /** |
| * Extracts native error message of an sqlExeption. |
| * |
| * @param sqle the SQLException |
| * @return the error message of the database |
| */ |
| @Override |
| public String extractErrorMessage(SQLException sqle) |
| { |
| String msg = sqle.getMessage(); |
| msg = msg.substring(msg.indexOf(':') + 1); |
| // Find End |
| int end = msg.indexOf("ORA"); |
| if (end >= 0) |
| msg = msg.substring(0, end - 1); |
| return msg; |
| } |
| |
| /** |
| * Gets the value of a sql ResultSet. |
| * Gives the dbms the oportunity to change the value |
| * i.e. to simulate missing data types with other types. |
| * |
| * @param rset the sql Resultset with the current data row |
| * @param columnIndex one based column Index of the desired column |
| * @param dataType the desired data type |
| * @return the value of the column |
| */ |
| @Override |
| public Object getResultValue(ResultSet rset, int columnIndex, DataType dataType) |
| throws SQLException |
| { |
| // Check for character large object |
| if (dataType == DataType.BOOL) |
| { // Get character large object |
| String val = rset.getString(columnIndex); |
| if (val==null || rset.wasNull()) |
| return null; |
| // Check Value |
| if (val.equalsIgnoreCase("Y") || val.equals("1")) |
| return Boolean.TRUE; |
| return Boolean.FALSE; |
| } |
| // Default |
| return super.getResultValue(rset, columnIndex, dataType); |
| } |
| |
| /** |
| * @see DBMSHandler#getNextSequenceValue(DBDatabase, String, int, Connection) |
| */ |
| @Override |
| public Object getNextSequenceValue(DBDatabase db, String seqName, int minValue, Connection conn) |
| { // Use Oracle Sequences |
| StringBuilder sql = new StringBuilder(80); |
| sql.append("SELECT "); |
| db.appendQualifiedName(sql, seqName, null); |
| sql.append(".NEXTVAL FROM DUAL"); |
| // Query next sequence value |
| String sqlCmd = sql.toString(); |
| if (log.isDebugEnabled()) |
| log.debug("Executing: " + sqlCmd); |
| Object val = querySingleValue(sqlCmd, null, DataType.UNKNOWN, conn); |
| if (ObjectUtils.isEmpty(val)) |
| { // Error! |
| log.error("getNextSequenceValue: Invalid sequence value for sequence " + seqName); |
| throw new QueryNoResultException(sqlCmd); |
| } |
| // Done |
| return val; |
| } |
| |
| /** |
| * @see DBMSHandler#getNextSequenceValueExpr(DBTableColumn col) |
| */ |
| @Override |
| public DBColumnExpr getNextSequenceValueExpr(DBTableColumn column) |
| { |
| String seqName = StringUtils.toString(column.getDefaultValue()); |
| if (StringUtils.isEmpty(seqName)) |
| throw new InvalidArgumentException("column", column); |
| StringBuilder sql = new StringBuilder(80); |
| column.getDatabase().appendQualifiedName(sql, seqName, null); |
| sql.append(".NEXTVAL"); |
| return new DBValueExpr(column.getDatabase(), sql.toString(), DataType.UNKNOWN); |
| } |
| |
| /** |
| * Overridden. Returns a timestamp that is used for record updates created by the database server. |
| * |
| * @return the current date and time of the database server. |
| */ |
| @Override |
| public java.sql.Timestamp getUpdateTimestamp(Connection conn) |
| { |
| // Default implementation |
| ResultSet rs = null; |
| try |
| { // Oracle Timestamp query |
| rs = executeQuery("SELECT systimestamp FROM DUAL", null, false, conn); |
| return (rs.next() ? rs.getTimestamp(1) : null); |
| } catch (SQLException e) { |
| // throw exception |
| throw new EmpireSQLException(this, e); |
| } finally |
| { // Cleanup |
| try |
| { // ResultSet close |
| Statement stmt = (rs!=null) ? rs.getStatement() : null; |
| if (rs != null) |
| rs.close(); |
| if (stmt != null) |
| stmt.close(); |
| } catch (SQLException e) { |
| // throw exception |
| throw new EmpireSQLException(this, e); |
| } |
| } |
| } |
| |
| /** |
| * @see DBMSHandler#getDDLScript(DDLActionType, DBObject, DBSQLScript) |
| */ |
| @Override |
| public void getDDLScript(DDLActionType type, DBObject dbo, DBSQLScript script) |
| { |
| if (ddlGenerator==null) |
| ddlGenerator = new OracleDDLGenerator(this); |
| // forward request |
| ddlGenerator.getDDLScript(type, dbo, script); |
| } |
| |
| /** |
| * @see DBMSHandler#appendEnableRelationStmt(DBRelation, boolean, DBSQLScript) |
| */ |
| @Override |
| public void appendEnableRelationStmt(DBRelation r, boolean enable, DBSQLScript script) |
| { |
| // ALTER TABLE {table.name} {ENABLE|DISABLE} CONSTRAINT {relation.name} |
| StringBuilder b = new StringBuilder(); |
| b.append("ALTER TABLE "); |
| r.getForeignKeyTable().addSQL(b, DBExpr.CTX_FULLNAME); |
| b.append(enable ? " ENABLE " : " DISABLE "); |
| b.append("CONSTRAINT "); |
| b.append(r.getName()); |
| // add |
| script.addStmt(b); |
| } |
| |
| /** |
| * Checks whether the database definition matches the real database structure. |
| */ |
| public void checkDatabase(DBDatabase db, String owner, Connection conn) |
| { |
| DBContext context = new DBContextStatic(this, conn); |
| // Check Params |
| if (owner==null || owner.length()==0) |
| throw new InvalidArgumentException("owner", owner); |
| // Database definition |
| OracleSYSDatabase sysDB = new OracleSYSDatabase(this); |
| // Check Columns |
| DBCommand sysDBCommand = createCommand(false); |
| sysDBCommand.select(sysDB.CI.getColumns()); |
| sysDBCommand.where (sysDB.CI.C_OWNER.is(owner)); |
| |
| OracleDataDictionnary dataDictionnary = new OracleDataDictionnary(); |
| DBReader rd = new DBReader(context); |
| try |
| { |
| rd.open(sysDBCommand); |
| // read all |
| log.info("---------------------------------------------------------------------------------"); |
| log.info("checkDatabase start: " + db.getClass().getName()); |
| String skipTable = ""; |
| while (rd.moveNext()) |
| { |
| String tableName = rd.getString(sysDB.CI.C_TABLE_NAME); |
| |
| // if a table wasn't found before, skip it |
| if (tableName.equals(skipTable)) |
| continue; |
| |
| // check if the found table exists in the DBDatabase object |
| String columnName = rd.getString(sysDB.CI.C_COLUMN_NAME); |
| DBTable dbTable = db.getTable(tableName); |
| DBView dbView = db.getView(tableName); |
| |
| String dataType = rd.getString(sysDB.CI.C_DATA_TYPE); |
| int charLength = rd.getInt(sysDB.CI.C_CHAR_LENGTH); |
| int dataLength = rd.getInt(sysDB.CI.C_DATA_LENGTH); |
| int dataPrecision = rd.getInt(sysDB.CI.C_DATA_PRECISION); |
| int dataScale = rd.getInt(sysDB.CI.C_DATA_SCALE); |
| String nullable = rd.getString(sysDB.CI.C_NULLABLE); |
| |
| dataDictionnary.fillDataDictionnary(tableName, columnName, dataType, |
| charLength, dataLength, dataPrecision, dataScale, nullable); |
| |
| if (dbTable != null) |
| { |
| |
| // check if the found column exists in the found DBTable |
| DBColumn col = dbTable.getColumn(columnName); |
| if (col == null) |
| { |
| log.warn("COLUMN NOT FOUND IN " + db.getClass().getName() + "\t: [" + tableName + "][" |
| + columnName + "][" + dataType + "][" + dataLength + "]"); |
| continue; |
| } |
| /* |
| else |
| { // check the DBTableColumn definition |
| int length = (charLength>0) ? charLength : dataLength; |
| dataDictionnary.checkColumnDefinition(col, dataType, length, dataPrecision, dataScale, nullable.equals("N")); |
| } |
| */ |
| } |
| else if (dbView!=null) |
| { |
| log.debug("Column check for view " + tableName + " not yet implemented."); |
| } |
| else |
| { |
| log.debug("TABLE OR VIEW NOT FOUND IN " + db.getClass().getName() + "\t: [" + tableName + "]"); |
| // skip this table |
| skipTable = tableName; |
| continue; |
| } |
| } |
| // check Tables |
| dataDictionnary.checkDBTableDefinition(db.getTables()); |
| // check Views |
| dataDictionnary.checkDBViewDefinition (db.getViews()); |
| // done |
| log.info("checkDatabase end: " + db.getClass().getName()); |
| log.info("---------------------------------------------------------------------------------"); |
| } finally { |
| // close |
| rd.close(); |
| } |
| } |
| |
| @Override |
| public OracleDBModelParser createModelParser(String catalog, String schema) |
| { |
| // Check schema |
| String schemaPattern = StringUtils.coalesce(schema, this.schemaName); |
| if (StringUtils.isEmpty(schemaPattern)) |
| throw new InvalidPropertyException("schemaName", null); |
| // create parser |
| return new OracleDBModelParser(schemaPattern); |
| } |
| |
| /** |
| * Creates a DataModelChecker instance of this DBMSHandler |
| * @return |
| */ |
| @Override |
| public DBModelChecker createModelChecker(DBDatabase db) |
| { // the default model checker |
| OracleDBModelParser modelParser = createModelParser(null, (db!=null ? db.getSchema() : null)); |
| return new OracleDBModelChecker(modelParser, getBooleanType()); |
| } |
| |
| } |
| |