/* * Copyright 2004-2015 the Seasar Foundation and the Others. * * 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 org.seasar.extension.jdbc.gen.internal.dialect; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.util.Arrays; import java.util.Map; import javax.persistence.GenerationType; import javax.persistence.TemporalType; import org.seasar.extension.jdbc.PropertyMeta; import org.seasar.extension.jdbc.ValueType; import org.seasar.extension.jdbc.gen.internal.sqltype.BigIntType; import org.seasar.extension.jdbc.gen.internal.sqltype.BinaryType; import org.seasar.extension.jdbc.gen.internal.sqltype.BlobType; import org.seasar.extension.jdbc.gen.internal.sqltype.BooleanType; import org.seasar.extension.jdbc.gen.internal.sqltype.ClobType; import org.seasar.extension.jdbc.gen.internal.sqltype.DecimalType; import org.seasar.extension.jdbc.gen.internal.sqltype.DoubleType; import org.seasar.extension.jdbc.gen.internal.sqltype.IntegerType; import org.seasar.extension.jdbc.gen.internal.sqltype.SmallIntType; import org.seasar.extension.jdbc.gen.internal.sqltype.TimeType; import org.seasar.extension.jdbc.gen.internal.sqltype.TimestampType; import org.seasar.extension.jdbc.gen.internal.sqltype.VarcharType; import org.seasar.extension.jdbc.gen.internal.util.CharUtil; import org.seasar.extension.jdbc.gen.provider.ValueTypeProvider; import org.seasar.extension.jdbc.gen.sqltype.SqlType; import org.seasar.extension.jdbc.util.ConnectionUtil; import org.seasar.framework.util.CaseInsensitiveMap; import org.seasar.framework.util.PreparedStatementUtil; import org.seasar.framework.util.ResultSetUtil; import org.seasar.framework.util.StatementUtil; import org.seasar.framework.util.StringUtil; /** * Oracleの方言を扱うクラスです。 * * @author taedium */ public class OracleGenDialect extends StandardGenDialect { /** テーブルが見つからないことを示すエラーコード */ protected static int TABLE_NOT_FOUND_ERROR_CODE = 942; /** カラムが見つからないことを示すエラーコード */ protected static int COLUMN_NOT_FOUND_ERROR_CODE = 904; /** シーケンスが見つからないことを示すエラーコード */ protected static int SEQUENCE_NOT_FOUND_ERROR_CODE = 2289; /** Oracle固有の{@literal DATE}型を使用する場合は{@literal true} */ protected boolean useOracleDate = true; /** * インスタンスを構築します。 */ public OracleGenDialect() { sqlTypeMap.put(Types.BINARY, new BinaryType("raw($l)")); sqlTypeMap.put(Types.BIGINT, new BigIntType("number($p,0)")); sqlTypeMap.put(Types.BLOB, new BlobType("blob")); sqlTypeMap.put(Types.BOOLEAN, new BooleanType("number(1,0)")); sqlTypeMap.put(Types.CLOB, new ClobType("clob")); sqlTypeMap.put(Types.DECIMAL, new DecimalType("number($p,$s)")); sqlTypeMap.put(Types.DOUBLE, new DoubleType("double precision")); sqlTypeMap.put(Types.INTEGER, new IntegerType("number($p,0)")); sqlTypeMap.put(Types.SMALLINT, new SmallIntType("number($p,0)")); sqlTypeMap.put(Types.TIME, new TimeType("date")); sqlTypeMap.put(Types.VARCHAR, new VarcharType("varchar2($l)")); columnTypeMap.put("binary_double", OracleColumnType.BINARY_DOUBLE); columnTypeMap.put("binary_float", OracleColumnType.BINARY_FLOAT); columnTypeMap.put("blob", OracleColumnType.BLOB); columnTypeMap.put("clob", OracleColumnType.CLOB); columnTypeMap.put("long", OracleColumnType.LONG); columnTypeMap.put("long raw", OracleColumnType.LONG_RAW); columnTypeMap.put("nchar", OracleColumnType.NCHAR); columnTypeMap.put("nclob", OracleColumnType.NCLOB); columnTypeMap.put("number", OracleColumnType.NUMBER); columnTypeMap.put("nvarchar2", OracleColumnType.NVARCHAR2); columnTypeMap.put("raw", OracleColumnType.RAW); columnTypeMap.put("timestamp", OracleColumnType.TIMESTAMP); columnTypeMap.put("varchar2", OracleColumnType.VARCHAR2); } /** * Oracle固有の{@literal DATE}型を使用する場合は{@literal true}を返します。 * * @return Oracle固有の{@literal DATE}型を使用する場合は{@literal true} */ public boolean isUseOracleDate() { return useOracleDate; } /** * Oracle固有の{@literal DATE}型を使用する場合は{@literal true}を設定します。 * * @param useOracleDate * Oracle固有の{@literal DATE}型を使用する場合は{@literal true} */ public void setUseOracleDate(boolean useOracleDate) { this.useOracleDate = useOracleDate; } @Override public String getName() { return "oracle"; } @Override public GenerationType getDefaultGenerationType() { return GenerationType.SEQUENCE; } @Override public boolean supportsSequence() { return true; } @Override public boolean supportsGetIndexInfo(String catalogName, String schemaName, String tableName) { if (tableName == null) { throw new NullPointerException("tableName"); } for (int i = 0; i < tableName.length(); i++) { if (!CharUtil.isAscii(tableName.charAt(i))) { return false; } } return true; } @Override public String getSequenceDefinitionFragment(String dataType, long initialValue, int allocationSize) { return "increment by " + allocationSize + " start with " + initialValue; } @Override public String getSqlBlockDelimiter() { return "/"; } @Override public boolean isTableNotFound(Throwable throwable) { Integer errorCode = getErrorCode(throwable); return errorCode != null && errorCode.intValue() == TABLE_NOT_FOUND_ERROR_CODE; } @Override public boolean isColumnNotFound(Throwable throwable) { Integer errorCode = getErrorCode(throwable); return errorCode != null && errorCode.intValue() == COLUMN_NOT_FOUND_ERROR_CODE; } @Override public boolean isSequenceNotFound(Throwable throwable) { Integer errorCode = getErrorCode(throwable); return errorCode != null && errorCode.intValue() == SEQUENCE_NOT_FOUND_ERROR_CODE; } @Override public ColumnType getColumnType(String typeName, int sqlType) { if (useOracleDate && StringUtil.equalsIgnoreCase(typeName, "date")) { return OracleColumnType.DATE; } ColumnType columnType = columnTypeMap.get(typeName); if (columnType != null) { return columnType; } if (StringUtil.startsWithIgnoreCase(typeName, "timestamp")) { typeName = "timestamp"; } return super.getColumnType(typeName, sqlType); } @Override public SqlBlockContext createSqlBlockContext() { return new OracleSqlBlockContext(); } @Override public String getSequenceNextValString(String sequenceName, int allocationSize) { return "select " + sequenceName + ".nextval from dual"; } @Override public boolean supportsCommentInCreateTable() { return false; } @Override public boolean supportsCommentOn() { return true; } @Override public boolean isJdbcCommentAvailable() { return false; } @Override public String getTableComment(Connection connection, String catalogName, String schemaName, String tableName) throws SQLException { String sql = "select comments from all_tab_comments where owner = ? and table_name = ? and table_type = 'TABLE'"; logger.debug(String.format(sql.replace("?", "'%s'"), schemaName, tableName)); PreparedStatement ps = ConnectionUtil.prepareStatement(connection, sql); try { ps.setString(1, schemaName); ps.setString(2, tableName); ResultSet rs = PreparedStatementUtil.executeQuery(ps); try { if (rs.next()) { return rs.getString(1); } return null; } finally { ResultSetUtil.close(rs); } } finally { StatementUtil.close(ps); } } @Override public Map<String, String> getColumnCommentMap(Connection connection, String catalogName, String schemaName, String tableName) throws SQLException { String sql = "select column_name, comments from all_col_comments where owner = ? and table_name = ?"; logger.debug(String.format(sql.replace("?", "'%s'"), schemaName, tableName)); PreparedStatement ps = ConnectionUtil.prepareStatement(connection, sql); try { ps.setString(1, schemaName); ps.setString(2, tableName); ResultSet rs = PreparedStatementUtil.executeQuery(ps); try { @SuppressWarnings("unchecked") Map<String, String> commentMap = new CaseInsensitiveMap(); while (rs.next()) { commentMap.put(rs.getString(1), rs.getString(2)); } return commentMap; } finally { ResultSetUtil.close(rs); } } finally { StatementUtil.close(ps); } } @Override public boolean supportsReferentialUpdateRule() { return false; } @Override public SqlType getSqlType(ValueTypeProvider valueTypeProvider, PropertyMeta propertyMeta) { if (useOracleDate) { ValueType valueType = valueTypeProvider.provide(propertyMeta); if (valueType instanceof org.seasar.extension.jdbc.types.OracleDateType || valueType instanceof org.seasar.extension.jdbc.types.OracleDateCalendarType) { return new TimestampType("date"); } } return super.getSqlType(valueTypeProvider, propertyMeta); } /** * Oracle用の{@link ColumnType}の実装クラスです。 * * @author taedium */ public static class OracleColumnType extends StandardColumnType { private static OracleColumnType BINARY_DOUBLE = new OracleColumnType( "binary_double", Double.class); private static OracleColumnType BINARY_FLOAT = new OracleColumnType( "binary_float", Float.class); private static OracleColumnType BLOB = new OracleColumnType("blob", byte[].class, true); private static OracleColumnType CLOB = new OracleColumnType("clob", String.class, true); private static OracleColumnType DATE = new OracleColumnType("date", Timestamp.class, false, TemporalType.TIMESTAMP); private static OracleColumnType LONG_RAW = new OracleColumnType( "long raw", byte[].class); private static OracleColumnType LONG = new OracleColumnType("long", String.class); private static OracleColumnType NCHAR = new OracleColumnType( "nchar($l)", String.class) { @Override public String getColumnDefinition(int length, int precision, int scale, String defaultValue) { return super.getColumnDefinition(length / 2, precision, scale, defaultValue); } }; private static OracleColumnType NCLOB = new OracleColumnType("nclob", String.class, true); private static OracleColumnType NUMBER = new OracleColumnType( "number($p,$s)", BigDecimal.class) { @Override public Class<?> getAttributeClass(int length, int precision, int scale) { if (scale != 0) { return BigDecimal.class; } if (precision < 5) { return Short.class; } if (precision < 10) { return Integer.class; } if (precision < 19) { return Long.class; } return BigInteger.class; } }; private static OracleColumnType NVARCHAR2 = new OracleColumnType( "nvarchar2($l)", String.class) { @Override public String getColumnDefinition(int length, int precision, int scale, String defaultValue) { return super.getColumnDefinition(length / 2, precision, scale, defaultValue); } }; private static OracleColumnType RAW = new OracleColumnType("raw($l)", byte[].class); private static OracleColumnType TIMESTAMP = new OracleColumnType( "timestamp($s)", Timestamp.class); private static OracleColumnType VARCHAR2 = new OracleColumnType( "varchar2($l)", String.class); /** * インスタンスを構築します。 * * @param dataType * データ型 * @param attributeClass * 属性のクラス */ public OracleColumnType(String dataType, Class<?> attributeClass) { super(dataType, attributeClass); } /** * インスタンスを構築します。 * * @param dataType * データ型 * @param attributeClass * 属性のクラス * @param lob * LOBの場合{@code true} */ public OracleColumnType(String dataType, Class<?> attributeClass, boolean lob) { super(dataType, attributeClass, lob); } /** * インスタンスを構築します。 * * @param dataType * データ型 * @param attributeClass * 属性のクラス * @param lob * LOBの場合{@code true} * @param temporalType * 時制の種別 */ public OracleColumnType(String dataType, Class<?> attributeClass, boolean lob, TemporalType temporalType) { super(dataType, attributeClass, lob, temporalType); } } /** * Oracle用の{@link StandardColumnType}の実装クラスです。 * * @author taedium */ public static class OracleSqlBlockContext extends StandardSqlBlockContext { /** * インスタンスを構築します。 */ protected OracleSqlBlockContext() { sqlBlockStartKeywordsList.add(Arrays.asList("create", "or", "replace", "procedure")); sqlBlockStartKeywordsList.add(Arrays.asList("create", "or", "replace", "function")); sqlBlockStartKeywordsList.add(Arrays.asList("create", "or", "replace", "triger")); sqlBlockStartKeywordsList.add(Arrays.asList("create", "procedure")); sqlBlockStartKeywordsList.add(Arrays.asList("create", "function")); sqlBlockStartKeywordsList.add(Arrays.asList("create", "trigger")); sqlBlockStartKeywordsList.add(Arrays.asList("declare")); sqlBlockStartKeywordsList.add(Arrays.asList("begin")); } } }