/*
* 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.dialect;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.persistence.EntityExistsException;
import javax.persistence.GenerationType;
import javax.persistence.TemporalType;
import org.seasar.extension.jdbc.DbmsDialect;
import org.seasar.extension.jdbc.FromClause;
import org.seasar.extension.jdbc.JoinColumnMeta;
import org.seasar.extension.jdbc.JoinType;
import org.seasar.extension.jdbc.PropertyMeta;
import org.seasar.extension.jdbc.SelectForUpdateType;
import org.seasar.extension.jdbc.ValueType;
import org.seasar.extension.jdbc.WhereClause;
import org.seasar.extension.jdbc.exception.OrderByNotFoundRuntimeException;
import org.seasar.extension.jdbc.types.ValueTypes;
import org.seasar.extension.jdbc.util.QueryTokenizer;
import org.seasar.framework.util.StringUtil;
import org.seasar.framework.util.tiger.CollectionsUtil;
import org.seasar.framework.util.tiger.Pair;
/**
* 標準的な方言をあつかうクラスです
*
* @author higa
*
*/
public class StandardDialect implements DbmsDialect {
/**
* {@link EntityExistsException}に該当するSQLステートです。
*/
protected static final Set<String> entityExistsExceptionStateCode = CollectionsUtil
.newHashSet(Arrays.asList("23", "27", "44"));
public String getName() {
return null;
}
public boolean supportsLimit() {
return false;
}
public boolean supportsOffset() {
return supportsLimit();
}
public boolean supportsOffsetWithoutLimit() {
return supportsOffset();
}
public boolean supportsCursor() {
return true;
}
public boolean needsParameterForResultSet() {
return false;
}
public String convertLimitSql(String sql, int offset, int limit) {
return sql;
}
public String convertGetCountSql(String sql) {
return "select count(*) from ( " + sql + " ) COUNT_";
}
public String getCountSqlSelectList(List<PropertyMeta> idPropertyMeta) {
return "count(*)";
}
public ValueType getValueType(PropertyMeta propertyMeta) {
return propertyMeta.getValueType();
}
public ValueType getValueType(Class<?> clazz, boolean lob,
TemporalType temporalType) {
if (lob) {
if (clazz == String.class) {
return ValueTypes.CLOB;
} else if (clazz == byte[].class) {
return ValueTypes.BLOB;
} else if (Serializable.class.isAssignableFrom(clazz)) {
return ValueTypes.SERIALIZABLE_BLOB;
}
} else if (clazz == byte[].class) {
return ValueTypes.BYTE_ARRAY;
}
if (temporalType != null) {
if (Date.class == clazz) {
switch (temporalType) {
case DATE:
return ValueTypes.DATE_SQLDATE;
case TIME:
return ValueTypes.DATE_TIME;
case TIMESTAMP:
return ValueTypes.DATE_TIMESTAMP;
}
}
if (Calendar.class == clazz) {
switch (temporalType) {
case DATE:
return ValueTypes.CALENDAR_SQLDATE;
case TIME:
return ValueTypes.CALENDAR_TIME;
case TIMESTAMP:
return ValueTypes.CALENDAR_TIMESTAMP;
}
}
}
ValueType valueType = getValueTypeInternal(clazz);
if (valueType == null) {
valueType = ValueTypes.getValueType(clazz);
}
if (valueType == ValueTypes.OBJECT
&& Serializable.class.isAssignableFrom(clazz)) {
return ValueTypes.SERIALIZABLE_BYTE_ARRAY;
}
return valueType;
}
/**
* 値タイプを返します。
*
* @param clazz
* クラス
* @return 値タイプ
*/
protected ValueType getValueTypeInternal(@SuppressWarnings("unused")
Class<?> clazz) {
return null;
}
public void setupJoin(FromClause fromClause, WhereClause whereClause,
JoinType joinType, String tableName, String tableAlias,
String fkTableAlias, String pkTableAlias,
List<JoinColumnMeta> joinColumnMetaList, String lockHint,
String condition) {
fromClause.addSql(joinType, tableName, tableAlias, fkTableAlias,
pkTableAlias, joinColumnMetaList, lockHint, condition);
}
public GenerationType getDefaultGenerationType() {
return GenerationType.TABLE;
}
public boolean supportsIdentity() {
return false;
}
public boolean isInsertIdentityColumn() {
return false;
}
public boolean supportsGetGeneratedKeys() {
return false;
}
public String getIdentitySelectString(final String tableName,
final String columnName) {
return null;
}
public boolean supportsSequence() {
return false;
}
public String getSequenceNextValString(final String sequenceName,
final int allocationSize) {
return null;
}
public int getDefaultBatchSize() {
return 0;
}
public boolean supportsBatchUpdateResults() {
return true;
}
/**
* 行番号ファンクション名を返します。
*
* @return 行番号ファンクション名
*/
protected String getRowNumberFunctionName() {
return "row_number()";
}
/**
* 行番号を使ったSQLに変換します。
*
* @param sql
* SQL
* @param offset
* オフセット
* @param limit
* リミット
* @return offset、limitつきのSQL
* @throws OrderByNotFoundRuntimeException
* <code>order by</code>が見つからない場合。
*/
protected String convertLimitSqlByRowNumber(String sql, int offset,
int limit) throws OrderByNotFoundRuntimeException {
StringBuilder buf = new StringBuilder(sql.length() + 150);
String lowerSql = sql.toLowerCase();
int startOfSelect = lowerSql.indexOf("select");
buf.append(sql.substring(0, startOfSelect));
buf.append("select * from ( select temp_.*, ");
buf.append(getRowNumberFunctionName()).append(" over(");
int orderByIndex = lowerSql.lastIndexOf("order by");
if (orderByIndex > 0) {
String orderBy = sql.substring(orderByIndex);
buf.append(convertOrderBy(orderBy));
sql = StringUtil.rtrim(sql.substring(0, orderByIndex));
} else {
throw new OrderByNotFoundRuntimeException(sql);
}
buf.append(") as rownumber_ from ( ");
buf.append(sql.substring(startOfSelect));
buf.append(" ) as temp_");
buf.append(" ) as temp2_ where rownumber_ >= ");
buf.append(offset + 1);
if (limit > 0) {
buf.append(" and rownumber_ <= ");
buf.append(offset + limit);
}
return buf.toString();
}
/**
* order by句のテーブルのエイリアスを一時的なテーブル名に変換します。
*
* @param orderBy
* order by句
* @return 変換後のorder by句
*/
protected String convertOrderBy(String orderBy) {
StringBuilder sb = new StringBuilder(10 + orderBy.length());
QueryTokenizer tokenizer = new QueryTokenizer(orderBy);
for (int type = tokenizer.nextToken(); type != QueryTokenizer.TT_EOF; type = tokenizer
.nextToken()) {
String token = tokenizer.getToken();
if (type == QueryTokenizer.TT_WORD) {
String[] names = StringUtil.split(token, ".");
if (names.length == 2) {
sb.append("temp_.").append(names[1]);
} else {
sb.append(token);
}
} else {
sb.append(token);
}
}
return sb.toString();
}
public boolean isUniqueConstraintViolation(Throwable t) {
final String state = getSQLState(t);
if (state != null && state.length() >= 2) {
return entityExistsExceptionStateCode.contains(state
.substring(0, 2));
}
return false;
}
/**
* 例外チェーンをたどって原因となった{@link SQLException#getSQLState() SQLステート}を返します。
* <p>
* 例外チェーンに{@link SQLException SQL例外}が存在しない場合や、SQLステートが設定されていない場合は<code>null</code>を返します。
* </p>
*
* @param t
* 例外
* @return 原因となった{@link SQLException#getSQLState() SQLステート}
*/
protected String getSQLState(Throwable t) {
SQLException cause = getCauseSQLException(t);
if (cause != null && !StringUtil.isEmpty(cause.getSQLState())) {
return cause.getSQLState();
}
return null;
}
/**
* 例外チェーンをたどって原因となった{@link SQLException#getErrorCode() ベンダー固有の例外コード}を返します。
* <p>
* 例外チェーンに{@link SQLException SQL例外}が存在しない場合や、例外コードが設定されていない場合は<code>null</code>を返します。
* </p>
*
* @param t
* 例外
* @return 原因となった{@link SQLException#getErrorCode() ベンダー固有の例外コード}
*/
protected Integer getErrorCode(Throwable t) {
SQLException cause = getCauseSQLException(t);
if (cause != null) {
return cause.getErrorCode();
}
return null;
}
/**
* 例外チェーンをたどって原因となった{@link SQLException SQL例外}を返します。
* <p>
* 例外チェーンにSQL例外が存在しない場合は<code>null</code>を返します。
* </p>
*
* @param t
* 例外
* @return 原因となった{@link SQLException SQL例外}
*/
protected SQLException getCauseSQLException(Throwable t) {
SQLException cause = null;
while (t != null) {
if (t instanceof SQLException) {
cause = SQLException.class.cast(t);
if (cause.getNextException() != null) {
cause = cause.getNextException();
t = cause;
continue;
}
}
t = t.getCause();
}
return cause;
}
public boolean supportsForUpdate(final SelectForUpdateType type,
boolean withTarget) {
return type == SelectForUpdateType.NORMAL && !withTarget;
}
public String getForUpdateString(final SelectForUpdateType type,
final int waitSeconds, final Pair<String, String>... aliases) {
return " for update";
}
public boolean supportsLockHint() {
return false;
}
public String getLockHintString(final SelectForUpdateType type,
final int waitSeconds) {
return "";
}
public boolean supportsInnerJoinForUpdate() {
return true;
}
public boolean supportsOuterJoinForUpdate() {
return true;
}
public String getHintComment(String hint) {
return "";
}
}