/* * JEF - Copyright 2009-2010 Jiyi (mr.jiyi@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jef.database.dialect; import java.sql.Connection; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Properties; import javax.sql.DataSource; import javax.sql.rowset.CachedRowSet; import jef.common.log.LogUtil; import jef.database.ConnectInfo; import jef.database.DbCfg; import jef.database.DbMetaData; import jef.database.DebugUtil; import jef.database.ORMConfig; import jef.database.datasource.DataSourceInfo; import jef.database.datasource.SimpleDataSource; import jef.database.dialect.ColumnType.AutoIncrement; import jef.database.dialect.ColumnType.Varchar; import jef.database.dialect.handler.LimitHandler; import jef.database.dialect.handler.OracleLimitHander; import jef.database.exception.JDBCExceptionHelper; import jef.database.exception.TemplatedViolatedConstraintNameExtracter; import jef.database.exception.ViolatedConstraintNameExtracter; import jef.database.jdbc.result.IResultSet; import jef.database.jsqlparser.expression.BinaryExpression; import jef.database.jsqlparser.expression.Function; import jef.database.jsqlparser.expression.Interval; import jef.database.jsqlparser.expression.LongValue; import jef.database.jsqlparser.expression.Parenthesis; import jef.database.jsqlparser.expression.operators.arithmetic.Division; import jef.database.jsqlparser.expression.operators.arithmetic.Multiplication; import jef.database.jsqlparser.visitor.Expression; import jef.database.meta.DbProperty; import jef.database.meta.Feature; import jef.database.meta.SequenceInfo; import jef.database.query.Func; import jef.database.query.Scientific; import jef.database.query.function.EmuCoalesce_Nvl; import jef.database.query.function.EmuOracleCast; import jef.database.query.function.EmuOracleDateAdd; import jef.database.query.function.EmuOracleDateSub; import jef.database.query.function.EmuOracleExtract; import jef.database.query.function.EmuOracleTime; import jef.database.query.function.EmuOracleTimeStampDiff; import jef.database.query.function.EmuOracleTimestampAdd; import jef.database.query.function.EmuOracleToDate; import jef.database.query.function.NoArgSQLFunction; import jef.database.query.function.StandardSQLFunction; import jef.database.query.function.TemplateFunction; import jef.database.query.function.TransformFunction; import jef.database.query.function.VarArgsSQLFunction; import jef.database.support.RDBMS; import jef.database.wrapper.clause.InsertSqlClause; import jef.database.wrapper.populator.AbstractResultSetTransformer; import jef.jre5support.ProcessUtil; import jef.tools.DateFormats; import jef.tools.DateUtils; import jef.tools.JefConfiguration; import jef.tools.StringUtils; import jef.tools.collection.CollectionUtils; import jef.tools.string.JefStringReader; /** * 修改列名Oracle:lter table bbb rename column nnnnn to hh int; */ public class OracleDialect extends AbstractDialect { public OracleDialect() { features = CollectionUtils.identityHashSet(); features.addAll(Arrays.asList(Feature.AUTOINCREMENT_NEED_SEQUENCE, Feature.USER_AS_SCHEMA, Feature.REMARK_META_FETCH, Feature.BRUKETS_FOR_ALTER_TABLE, Feature.SUPPORT_CONCAT, Feature.SUPPORT_CONNECT_BY, Feature.DROP_CASCADE, Feature.SUPPORT_SEQUENCE, Feature.EMPTY_CHAR_IS_NULL, Feature.SUPPORT_COMMENT, Feature.COLUMN_DEF_ALLOW_NULL)); super.loadKeywords("oracle_keywords.properties"); if (JefConfiguration.getBoolean(DbCfg.DB_ENABLE_ROWID, false)) { features.add(Feature.SELECT_ROW_NUM); } setProperty(DbProperty.ADD_COLUMN, "ADD"); setProperty(DbProperty.MODIFY_COLUMN, "MODIFY"); setProperty(DbProperty.DROP_COLUMN, "DROP"); setProperty(DbProperty.CHECK_SQL, "SELECT 1 FROM DUAL"); setProperty(DbProperty.SEQUENCE_FETCH, "SELECT %s.NEXTVAL FROM DUAL"); setProperty(DbProperty.SELECT_EXPRESSION, "SELECT %s FROM DUAL"); setProperty(DbProperty.WRAP_FOR_KEYWORD, "\"\""); setProperty(DbProperty.OTHER_VERSION_SQL, "select 'USER_LANGUAGE',userenv('language') from dual"); registerNative(Scientific.sinh); registerNative(Scientific.cosh); registerNative(Scientific.tanh); registerNative(Scientific.exp); registerNative(Scientific.ln); registerNative(new StandardSQLFunction("log"));// Oracle can assign a // log param. registerNative(new StandardSQLFunction("stddev")); registerNative(new StandardSQLFunction("variance")); registerNative(Func.round); registerNative(Func.trunc); registerNative(Func.ceil); registerNative(Func.floor); registerNative(new StandardSQLFunction("chr")); registerNative(new StandardSQLFunction("initcap")); registerNative(Func.lower); registerNative(Func.upper); registerNative(Func.trim); registerNative(Func.ltrim); registerNative(Func.rtrim); registerNative(Scientific.soundex); registerNative(new StandardSQLFunction("ascii")); registerNative(Func.add_months); registerAlias("lcase", "lower"); registerAlias("ucase", "upper"); // Oracle的to_char有两种含义,具体如何转化需要根据传入参数来确定,单参数下是类型转换,双参数下是日期格式化 registerNative(new StandardSQLFunction("to_char")); registerNative(new StandardSQLFunction("to_number")); registerNative(new StandardSQLFunction("to_date")); registerAlias(Func.date, "trunc"); registerCompatible(Func.time, new EmuOracleTime()); registerNative(new NoArgSQLFunction("sysdate", false)); registerAlias(Func.current_timestamp, "sysdate"); registerAlias(Func.now, "sysdate"); registerNative(new NoArgSQLFunction("systimestamp", false)); registerCompatible(Func.current_time, new TemplateFunction("current_time", "(systimestamp-trunc(sysdate))")); registerNative(new StandardSQLFunction("last_day")); registerNative(new NoArgSQLFunction("uid", false)); registerNative(new NoArgSQLFunction("user", false)); registerNative(new NoArgSQLFunction("rowid", false)); registerNative(new NoArgSQLFunction("rownum", false)); registerNative(Func.length); registerNative(Func.lengthb); registerNative(new StandardSQLFunction("instr")); registerNative(new StandardSQLFunction("instrb")); registerNative(Func.lpad); registerNative(Func.replace); registerNative(Func.rpad); registerNative(new StandardSQLFunction("substr")); registerAlias(Func.substring, "substr"); registerNative(new StandardSQLFunction("substrb")); registerNative(Func.translate); // Multi-param numeric dialect functions... registerNative(new StandardSQLFunction("atan2")); registerNative(Func.mod); registerNative(Func.nvl); registerNative(Func.nullif); registerNative(Func.decode); registerNative(new StandardSQLFunction("nvl2")); registerNative(Scientific.power); // Multi-param date dialect functions... registerNative(new StandardSQLFunction("add_months")); registerNative(new StandardSQLFunction("months_between")); registerNative(new StandardSQLFunction("next_day")); registerNative(new StandardSQLFunction("rollup")); registerNative(new StandardSQLFunction("cube")); // Oracle的GROUP BY语句除了最基本的语法外,还支持ROLLUP和CUBE语句。如果是ROLLUP(A, B, // C)的话,首先会对(A、B、C)进行GROUP BY,然后对(A、B)进行GROUP BY,然后是(A)进行GROUP BY, // 最后对全表进行GROUP BY操作。如果是GROUP BY CUBE(A, B, C),则首先会对(A、B、C)进行GROUP // BY,然后依次是(A、B),(A、C),(A),(B、C),(B),(C),最后对全表进行GROUP BY操作。 // 用GROUP BY GROUPING SETS来代替GROUP BY // CUBE。你可以应用来指定你感兴趣的总数组合。因为它不必计算它不需要集合(也不会产生太多结果),所以对SQL引擎来说更为高效。 // 其格式为: // GROUP BY GROUPING SETS ((list), (list) ... ) registerCompatible(Func.current_date, new NoArgSQLFunction("trunc(sysdate)", false)); registerCompatible(Func.concat, new VarArgsSQLFunction("", "||", "")); registerCompatible(Func.coalesce, new EmuCoalesce_Nvl()); registerCompatible(Func.locate, new TransformFunction("locate", "instr", new int[] { 2, 1 }));// 用instr来模拟locate参数相反 registerCompatible(Func.year, new EmuOracleExtract("year", false)); registerCompatible(Func.month, new EmuOracleExtract("month", false)); registerCompatible(Func.day, new EmuOracleExtract("day", false)); registerCompatible(Func.hour, new EmuOracleExtract("hour", true)); registerCompatible(Func.minute, new EmuOracleExtract("minute", true)); registerCompatible(Func.second, new EmuOracleExtract("second", true)); registerCompatible(null, EmuOracleToDate.getInstance(), "timestamp"); registerCompatible(Func.adddate, new EmuOracleDateAdd()); registerCompatible(Func.subdate, new EmuOracleDateSub()); registerCompatible(Func.timestampdiff, new EmuOracleTimeStampDiff()); registerCompatible(Func.timestampadd, new EmuOracleTimestampAdd()); registerCompatible(Func.cast, new EmuOracleCast()); registerCompatible(Func.datediff, new TemplateFunction("datediff", "trunc(%1$s-%2$s)")); registerAlias(Func.str, "to_char"); typeNames.put(Types.VARCHAR, 4000, "varchar2($l)", 0); typeNames.put(Types.VARCHAR, 1024 * 1024 * 1024 * 4, "clob", Types.CLOB); typeNames.put(Types.FLOAT, "number($p,$s)", 0); typeNames.put(Types.DOUBLE, "number($p,$s)", 0); typeNames.put(Types.NUMERIC, "number($p,$s)", 0); typeNames.put(Types.TINYINT, "number($p)", 0); typeNames.put(Types.SMALLINT, "number($p)", 0); typeNames.put(Types.INTEGER, "number($p)", 0); typeNames.put(Types.BIGINT, "number($p)", 0); typeNames.put(Types.TIMESTAMP, "date", 0); typeNames.put(Types.TIME, "date", 0); } protected String getComment(AutoIncrement column, boolean flag) { StringBuilder sb = new StringBuilder(); sb.append("number(" + column.precision + ")"); if (flag) { if (column.nullable) { sb.append(" null"); } else { sb.append(" not null"); } } return sb.toString(); } public CachedRowSet newCacheRowSetInstance() throws SQLException { return new oracle.jdbc.rowset.OracleCachedRowSet(); } public ColumnType getProprtMetaFromDbType(jef.database.meta.Column column) { if ("NUMBER".equals(column.getDataType())) { if (column.getDecimalDigit() > 0) {// 小数 return new ColumnType.Double(column.getColumnSize(), column.getDecimalDigit()); } else {// 整数 return new ColumnType.Int(column.getColumnSize()); } } else if ("NVARCHAR2".equals(column.getDataType())) { if ("GUID".equals(column.getColumnDef())) { return new ColumnType.GUID(); } else { return new Varchar(column.getColumnSize()); } } else if ("DATE".equals(column.getDataType())) { return new ColumnType.Date(); } else if (column.getDataType().toUpperCase().startsWith("TIMESTAMP")) { // Oracle // 有 // TimeStamp(6)这种类型 return new ColumnType.TimeStamp(); } else { return super.getProprtMetaFromDbType(column); } } public String getDriverClass(String url) { return "oracle.jdbc.driver.OracleDriver"; } @Override public String generateUrl(String host, int port, String pathOrName) { StringBuilder sb = new StringBuilder("jdbc:"); // jdbc:oracle:thin:@192.168.253.249:1521:ora10 sb.append(getName()).append(":thin:"); sb.append("@").append(host).append(":").append(port <= 0 ? 1521 : port); sb.append(":").append(pathOrName); String url = sb.toString(); if (ORMConfig.getInstance().isDebugMode()) { LogUtil.show(url); } return url; } public RDBMS getName() { return RDBMS.oracle; } /** * 由于暂不考虑支持Oracle TIMESTAMP到毫秒这个特性,因此在查询时需要对TIMESTAMP进行truncate处理, * 以避免因多了几个毫秒而导致查不到数据的问题。 */ @Override public java.sql.Timestamp toTimestampSqlParam(Date timestamp) { Calendar gval = Calendar.getInstance(); gval.setTime(timestamp); int mills = gval.get(Calendar.MILLISECOND); return new java.sql.Timestamp(timestamp.getTime() - mills); } /** * 判断SEQUENCE是否存在时,若schema为小写会存在误判情况(将存在判断为不存在), <br> * 为了避免大小写引起的问题,一律转成大写。 */ @Override public String getSchema(String schema) { return StringUtils.upperCase(schema); } @Override public List<SequenceInfo> getSequenceInfo(DbMetaData conn, String schema, String seqName) { String sql = "select sequence_owner,sequence_name,min_value,max_value,increment_by,cache_size,last_number from all_sequences where sequence_owner like ? and sequence_name like ?"; schema = StringUtils.isBlank(schema) ? "%" : schema.toUpperCase(); seqName = StringUtils.isBlank(seqName) ? "%" : seqName.toUpperCase(); try { return conn.selectBySql(sql, new AbstractResultSetTransformer<List<SequenceInfo>>() { @Override public List<SequenceInfo> transformer(IResultSet rs) throws SQLException { List<SequenceInfo> result = new ArrayList<SequenceInfo>(); if (rs.next()) { SequenceInfo seq = new SequenceInfo(); seq.setCatalog(null); seq.setSchema(rs.getString(1)); seq.setName(rs.getString(2)); seq.setMinValue(rs.getLong(3)); // seq.setMaxValue(rs.getLong(4)); seq.setStep(rs.getInt(5)); seq.setCacheSize(rs.getInt(6)); seq.setCurrentValue(rs.getLong(7)); result.add(seq); } return result; } }, 0, Arrays.asList(schema, seqName)); } catch (SQLException e) { DebugUtil.setSqlState(e, sql); LogUtil.error("Error while getting sequence info [{}.{}].", schema, seqName, e); } return null; } @Override public boolean isIOError(SQLException se) { int code = se.getErrorCode(); if (code == 12007 || code == 17410 || code == 17008 || code == 17002 || code == 28 || (code > 1087 && code < 1093)) { return true; } else if (se.getCause() != null && "NetException".equals(se.getCause().getClass().getSimpleName())) { return true; } else { // 为了跟踪所有Oracle出现网络异常时的错误码,将错误码和异常信息打印出来。 // LogUtil.info("Oracle non-io Err:" + se.getErrorCode() + ":" + // se.getMessage()); return false; } } public Connection createConnection(DataSource ds) throws SQLException { // 设置连接属性 Properties prop = new Properties(); prop.put("v$session.program", "JefOrm@".concat(ProcessUtil.getHostname())); prop.put("v$session.process", String.valueOf(ProcessUtil.getPid())); prop.put(oracle.net.ns.SQLnetDef.TCP_CONNTIMEOUT_STR, "4000"); prop.put(oracle.net.ns.SQLnetDef.TCP_READTIMEOUT_STR, "30000"); if (ds instanceof SimpleDataSource) { return ((SimpleDataSource) ds).getConnectionFromDriver(prop); } else { return ds.getConnection(); } } @Override public void processConnectProperties(DataSourceInfo dsw) { dsw.putProperty("v$session.program", "JefOrm@".concat(ProcessUtil.getHostname())); dsw.putProperty("v$session.process", String.valueOf(ProcessUtil.getPid())); dsw.putProperty(oracle.net.ns.SQLnetDef.TCP_CONNTIMEOUT_STR, "4000"); dsw.putProperty(oracle.net.ns.SQLnetDef.TCP_READTIMEOUT_STR, "30000"); } static final char[] CHAR_OF_END = new char[] { ':' }; static final char[] CHAR_OF_END2 = new char[] { ':', ' ' }; public void parseDbInfo(ConnectInfo connectInfo) { JefStringReader reader = new JefStringReader(connectInfo.getUrl()); reader.setIgnoreChars(' '); reader.consume("jdbc:", ' '); reader.consume("oracle:", ' '); if (reader.matchNext("oci:", ' ') > -1) {// 按OCI处理 reader.consume("oci:@", ' '); reader.omitChars('/'); String token = reader.readToken(CHAR_OF_END2); if (reader.eof()) { connectInfo.setDbname(token); connectInfo.setHost(token); } else { String port = reader.readToken(CHAR_OF_END); if (port.length() > 0) { token = token + ":" + port; } connectInfo.setHost(token); String dbname = reader.readToken(CHAR_OF_END2); connectInfo.setDbname(dbname); } } else {// 按thin处理 reader.consume("thin:@", ' ');// RAC连接串 if (reader.matchNextIgnoreCase("(DESCRIPTION") > -1) {// reader.omitAfterKeyIgnoreCase("(HOST=", ' '); String host = reader.readToken('"', ')'); reader.omitAfterKeyIgnoreCase("(SERVICE_NAME=", ' '); String dbName = reader.readToken('"', ')'); connectInfo.setHost(host.trim()); connectInfo.setDbname(dbName.trim()); } else {// 通常串 reader.omitChars('/'); String host = reader.readToken(CHAR_OF_END); String port = reader.readToken(CHAR_OF_END); String dbname = reader.readToken(CHAR_OF_END2); if (port.length() > 0) host = host + ":" + port; connectInfo.setHost(host); connectInfo.setDbname(dbname); } } reader.close(); } @Override public void processIntervalExpression(BinaryExpression parent, Interval interval) { if (interval.isPostgreMode()) { interval.toMySqlMode(); } String unit = interval.getUnit().toLowerCase(); interval.toMySqlMode(); Expression value = interval.getValue(); if ("day".equals(unit)) { replace(parent, interval, value); } else if ("hour".equals(unit)) { value = new Division(value, new LongValue(24)); value = new Parenthesis(value); replace(parent, interval, value); } else if ("minute".equals(unit)) { value = new Division(value, new LongValue(1440)); value = new Parenthesis(value); replace(parent, interval, value); } else if ("second".equals(unit)) { value = new Division(value, new LongValue(86400)); value = new Parenthesis(value); replace(parent, interval, value); } else if ("month".equals(unit)) { if (parent.getLeftExpression() == interval) { parent.swap();// 交换到右边 } Function func = new Function("add_months", parent.getLeftExpression(), interval.getValue()); parent.rewrite = func; } else if ("year".equals(unit)) { if (parent.getLeftExpression() == interval) { parent.swap();// 交换到右边 } Expression right = new Multiplication(interval.getValue(), new LongValue(12)); Function func = new Function("add_months", parent.getLeftExpression(), right); parent.rewrite = func; } else { throw new UnsupportedOperationException("The Oracle Dialect can't handle datetime unit [" + unit + "] for now."); } } private void replace(BinaryExpression parent, Interval interval, Expression value) { if (parent.getLeftExpression() == interval) { parent.setLeftExpression(value); } else { parent.setRightExpression(value); } } @Override public String getSqlDateExpression(Date value) { return "to_date(" + QUOT + DateUtils.formatDate(value) + QUOT + ",'YYYY-MM-DD')"; } @Override public String getSqlTimeExpression(Date value) { return "to_date(" + QUOT + DateFormats.TIME_ONLY.get().format(value) + QUOT + ",'HH24:MI:SS')"; } @Override public String getSqlTimestampExpression(Date value) { return "to_date(" + QUOT + DateUtils.formatDateTime(value) + QUOT + ",'YYYY-MM-dd HH24:MI:SS')"; } public void addKeyword(String... keys) { for (String s : keys) { this.keywords.add(s.toUpperCase()); } } @Override public void toExtremeInsert(InsertSqlClause sql) { // alter table xxx nologging sql.setInsert("insert /*+ APPEND */ into "); } private LimitHandler limit = new OracleLimitHander(); @Override public LimitHandler getLimitHandler() { return limit; } private static ViolatedConstraintNameExtracter EXTRACTER_8 = new TemplatedViolatedConstraintNameExtracter() { /** * Extract the name of the violated constraint from the given * SQLException. * * @param sqle * The exception that was the result of the constraint * violation. * @return The extracted constraint name. */ public String extractConstraintName(SQLException sqle) { int errorCode = JDBCExceptionHelper.extractErrorCode(sqle); if (errorCode == 1 || errorCode == 2291 || errorCode == 2292) { return extractUsingTemplate("constraint (", ") violated", sqle.getMessage()); } else if (errorCode == 1400) { // simple nullability constraint return null; } else { return null; } } }; private static ViolatedConstraintNameExtracter EXTRACTER_9 = new TemplatedViolatedConstraintNameExtracter() { /** * Extract the name of the violated constraint from the given * SQLException. * * @param sqle * The exception that was the result of the constraint * violation. * @return The extracted constraint name. */ public String extractConstraintName(SQLException sqle) { int errorCode = JDBCExceptionHelper.extractErrorCode(sqle); if (errorCode == 1 || errorCode == 2291 || errorCode == 2292) { return extractUsingTemplate("constraint (", ") violated", sqle.getMessage()); } else if (errorCode == 1400) { // simple nullability constraint return null; } else { return null; } } }; @Override public ViolatedConstraintNameExtracter getViolatedConstraintNameExtracter() { return EXTRACTER_9; } }