/**
* 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.metamodel.jdbc.dialects;
import java.io.InputStream;
import java.io.Reader;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.apache.metamodel.jdbc.JdbcDataContext;
import org.apache.metamodel.query.AbstractQueryClause;
import org.apache.metamodel.query.FilterClause;
import org.apache.metamodel.query.FilterItem;
import org.apache.metamodel.query.FromClause;
import org.apache.metamodel.query.FromItem;
import org.apache.metamodel.query.GroupByClause;
import org.apache.metamodel.query.GroupByItem;
import org.apache.metamodel.query.OperatorType;
import org.apache.metamodel.query.OrderByClause;
import org.apache.metamodel.query.OrderByItem;
import org.apache.metamodel.query.Query;
import org.apache.metamodel.query.ScalarFunction;
import org.apache.metamodel.query.SelectClause;
import org.apache.metamodel.query.SelectItem;
import org.apache.metamodel.schema.Column;
import org.apache.metamodel.schema.ColumnType;
import org.apache.metamodel.schema.ColumnTypeImpl;
import org.apache.metamodel.util.FileHelper;
import org.apache.metamodel.util.FormatHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract implementation of query rewriter. This implementation delegates the
* rewriting of the Query into several subtasks according to the query items to
* be rendered. This makes it easy to overload single methods in order to
* correct syntax quirks.
*/
public abstract class AbstractQueryRewriter implements IQueryRewriter {
private static final Logger logger = LoggerFactory.getLogger(AbstractQueryRewriter.class);
private final JdbcDataContext _dataContext;
public AbstractQueryRewriter(JdbcDataContext dataContext) {
_dataContext = dataContext;
}
public JdbcDataContext getDataContext() {
return _dataContext;
}
@Override
public boolean isTransactional() {
return true;
}
@Override
public ColumnType getColumnType(int jdbcType, String nativeType, Integer columnSize) {
return ColumnTypeImpl.convertColumnType(jdbcType);
}
public String rewriteQuery(Query query) {
query = beforeRewrite(query);
final StringBuilder sb = new StringBuilder();
sb.append(rewriteSelectClause(query, query.getSelectClause()));
sb.append(rewriteFromClause(query, query.getFromClause()));
sb.append(rewriteWhereClause(query, query.getWhereClause()));
sb.append(rewriteGroupByClause(query, query.getGroupByClause()));
sb.append(rewriteHavingClause(query, query.getHavingClause()));
sb.append(rewriteOrderByClause(query, query.getOrderByClause()));
return sb.toString();
}
public boolean isSchemaIncludedInColumnPaths() {
return false;
}
/**
* Method to modify query before rewriting begins. Overwrite this method if
* you want to change parts of the query that are not just rendering
* related. Cloning the query before modifying is recommended in order to
* not violate referential integrity of clients (the query is mutable).
*
* @param query
* @return the modified query
*/
protected Query beforeRewrite(Query query) {
return query;
}
@Override
public String rewriteColumnType(ColumnType columnType, Integer columnSize) {
return rewriteColumnTypeInternal(columnType.toString(), columnSize);
}
protected String rewriteColumnTypeInternal(String columnType, Object columnParameter) {
final StringBuilder sb = new StringBuilder();
sb.append(columnType);
if (columnParameter != null) {
sb.append('(');
sb.append(columnParameter);
sb.append(')');
}
return sb.toString();
}
protected String rewriteOrderByClause(Query query, OrderByClause orderByClause) {
StringBuilder sb = new StringBuilder();
if (orderByClause.getItemCount() > 0) {
sb.append(AbstractQueryClause.PREFIX_ORDER_BY);
List<OrderByItem> items = orderByClause.getItems();
for (int i = 0; i < items.size(); i++) {
OrderByItem item = items.get(i);
if (i != 0) {
sb.append(AbstractQueryClause.DELIM_COMMA);
}
sb.append(rewriteOrderByItem(query, item));
}
}
return sb.toString();
}
@Override
public String rewriteFromItem(FromItem item) {
return rewriteFromItem(item.getQuery(), item);
}
protected String rewriteOrderByItem(Query query, OrderByItem item) {
return item.toSql(isSchemaIncludedInColumnPaths());
}
protected String rewriteHavingClause(Query query, FilterClause havingClause) {
StringBuilder sb = new StringBuilder();
if (havingClause.getItemCount() > 0) {
sb.append(AbstractQueryClause.PREFIX_HAVING);
List<FilterItem> items = havingClause.getItems();
for (int i = 0; i < items.size(); i++) {
FilterItem item = items.get(i);
if (i != 0) {
sb.append(AbstractQueryClause.DELIM_AND);
}
sb.append(rewriteFilterItem(item));
}
}
return sb.toString();
}
protected String rewriteGroupByClause(Query query, GroupByClause groupByClause) {
StringBuilder sb = new StringBuilder();
if (groupByClause.getItemCount() > 0) {
sb.append(AbstractQueryClause.PREFIX_GROUP_BY);
List<GroupByItem> items = groupByClause.getItems();
for (int i = 0; i < items.size(); i++) {
GroupByItem item = items.get(i);
if (i != 0) {
sb.append(AbstractQueryClause.DELIM_COMMA);
}
sb.append(rewriteGroupByItem(query, item));
}
}
return sb.toString();
}
protected String rewriteGroupByItem(Query query, GroupByItem item) {
return item.toSql(isSchemaIncludedInColumnPaths());
}
protected String rewriteWhereClause(Query query, FilterClause whereClause) {
StringBuilder sb = new StringBuilder();
if (whereClause.getItemCount() > 0) {
sb.append(AbstractQueryClause.PREFIX_WHERE);
List<FilterItem> items = whereClause.getItems();
for (int i = 0; i < items.size(); i++) {
FilterItem item = items.get(i);
if (i != 0) {
sb.append(AbstractQueryClause.DELIM_AND);
}
sb.append(rewriteFilterItem(item));
}
}
return sb.toString();
}
@Override
public String rewriteFilterItem(FilterItem item) {
if (item.isCompoundFilter()) {
FilterItem[] childItems = item.getChildItems();
StringBuilder sb = new StringBuilder();
sb.append('(');
for (int i = 0; i < childItems.length; i++) {
FilterItem child = childItems[i];
if (i != 0) {
sb.append(' ');
sb.append(item.getLogicalOperator().toString());
sb.append(' ');
}
sb.append(rewriteFilterItem(child));
}
sb.append(')');
return sb.toString();
}
final String primaryFilterSql = item.toSql(isSchemaIncludedInColumnPaths());
final OperatorType operator = item.getOperator();
if (OperatorType.DIFFERENT_FROM.equals(operator)) {
final Object operand = item.getOperand();
if (operand != null) {
// special case in SQL where NULL is not treated as a value -
// see Ticket #1058
FilterItem isNullFilter = new FilterItem(item.getSelectItem(), OperatorType.EQUALS_TO, null);
final String secondaryFilterSql = rewriteFilterItem(isNullFilter);
return '(' + primaryFilterSql + " OR " + secondaryFilterSql + ')';
}
}
return primaryFilterSql;
}
protected String rewriteFromClause(Query query, FromClause fromClause) {
StringBuilder sb = new StringBuilder();
if (fromClause.getItemCount() > 0) {
sb.append(AbstractQueryClause.PREFIX_FROM);
List<FromItem> items = fromClause.getItems();
for (int i = 0; i < items.size(); i++) {
FromItem item = items.get(i);
if (i != 0) {
sb.append(AbstractQueryClause.DELIM_COMMA);
}
sb.append(rewriteFromItem(query, item));
}
}
return sb.toString();
}
protected String rewriteFromItem(Query query, FromItem item) {
return item.toSql(isSchemaIncludedInColumnPaths());
}
protected String rewriteSelectClause(Query query, SelectClause selectClause) {
StringBuilder sb = new StringBuilder();
if (selectClause.getItemCount() > 0) {
sb.append(AbstractQueryClause.PREFIX_SELECT);
if (selectClause.isDistinct()) {
sb.append("DISTINCT ");
}
List<SelectItem> items = selectClause.getItems();
for (int i = 0; i < items.size(); i++) {
SelectItem item = items.get(i);
if (i != 0) {
sb.append(AbstractQueryClause.DELIM_COMMA);
}
final ScalarFunction scalarFunction = item.getScalarFunction();
if (scalarFunction != null && !isScalarFunctionSupported(scalarFunction)) {
// replace with a SelectItem without the function - the
// function will be applied in post-processing.
item = item.replaceFunction(null);
}
sb.append(rewriteSelectItem(query, item));
}
}
return sb.toString();
}
protected String rewriteSelectItem(Query query, SelectItem item) {
if (item.isFunctionApproximationAllowed()) {
// function approximation is not included in any standard SQL
// constructions - will have to be overridden by subclasses if there
// are specialized dialects for it.
item = item.replaceFunctionApproximationAllowed(false);
}
return item.toSql(isSchemaIncludedInColumnPaths());
}
@Override
public void setStatementParameter(PreparedStatement st, int valueIndex, Column column, Object value)
throws SQLException {
final ColumnType type = (column == null ? null : column.getType());
if (type == null || type == ColumnType.OTHER) {
// type is not known - nothing more we can do to narrow the type
st.setObject(valueIndex, value);
return;
}
if (value == null && type != null) {
try {
final int jdbcType = type.getJdbcType();
st.setNull(valueIndex, jdbcType);
return;
} catch (Exception e) {
logger.warn("Exception occurred while calling setNull(...) for value index " + valueIndex
+ ". Attempting value-based setter method instead.", e);
}
}
if (type == ColumnType.VARCHAR && value instanceof Date) {
// some drivers (SQLite and JTDS for MS SQL server) treat dates as
// VARCHARS. In that case we need to convert the dates to the
// correct format
String nativeType = column.getNativeType();
Date date = (Date) value;
if ("DATE".equalsIgnoreCase(nativeType)) {
value = FormatHelper.formatSqlTime(ColumnType.DATE, date, false);
} else if ("TIME".equalsIgnoreCase(nativeType)) {
value = FormatHelper.formatSqlTime(ColumnType.TIME, date, false);
} else if ("TIMESTAMP".equalsIgnoreCase(nativeType) || "DATETIME".equalsIgnoreCase(nativeType)) {
value = FormatHelper.formatSqlTime(ColumnType.TIMESTAMP, date, false);
}
}
if (type != null && type.isTimeBased() && value instanceof String) {
value = FormatHelper.parseSqlTime(type, (String) value);
}
try {
if (type == ColumnType.DATE && value instanceof Date) {
Calendar cal = Calendar.getInstance();
cal.setTime((Date) value);
st.setDate(valueIndex, new java.sql.Date(cal.getTimeInMillis()), cal);
} else if (type == ColumnType.TIME && value instanceof Date) {
final Time time = toTime((Date) value);
st.setTime(valueIndex, time);
} else if (type == ColumnType.TIMESTAMP && value instanceof Date) {
final Timestamp ts = toTimestamp((Date) value);
st.setTimestamp(valueIndex, ts);
} else if (type == ColumnType.CLOB || type == ColumnType.NCLOB) {
if (value instanceof InputStream) {
InputStream inputStream = (InputStream) value;
st.setAsciiStream(valueIndex, inputStream);
} else if (value instanceof Reader) {
Reader reader = (Reader) value;
st.setCharacterStream(valueIndex, reader);
} else if (value instanceof NClob) {
NClob nclob = (NClob) value;
st.setNClob(valueIndex, nclob);
} else if (value instanceof Clob) {
Clob clob = (Clob) value;
st.setClob(valueIndex, clob);
} else if (value instanceof String) {
st.setString(valueIndex, (String) value);
} else {
st.setObject(valueIndex, value);
}
} else if (type == ColumnType.BLOB || type == ColumnType.BINARY) {
if (value instanceof byte[]) {
byte[] bytes = (byte[]) value;
st.setBytes(valueIndex, bytes);
} else if (value instanceof InputStream) {
InputStream inputStream = (InputStream) value;
st.setBinaryStream(valueIndex, inputStream);
} else if (value instanceof Blob) {
Blob blob = (Blob) value;
st.setBlob(valueIndex, blob);
} else {
st.setObject(valueIndex, value);
}
} else if (type.isLiteral()) {
final String str;
if (value instanceof Reader) {
Reader reader = (Reader) value;
str = FileHelper.readAsString(reader);
} else {
str = value.toString();
}
st.setString(valueIndex, str);
} else {
st.setObject(valueIndex, value);
}
} catch (SQLException e) {
logger.error("Failed to set parameter {} to value: {}", valueIndex, value);
throw e;
}
}
protected Time toTime(Date value) {
if (value instanceof Time) {
return (Time) value;
}
final Calendar cal = Calendar.getInstance();
cal.setTime((Date) value);
return new java.sql.Time(cal.getTimeInMillis());
}
protected Timestamp toTimestamp(Date value) {
if (value instanceof Timestamp) {
return (Timestamp) value;
}
final Calendar cal = Calendar.getInstance();
cal.setTime((Date) value);
return new Timestamp(cal.getTimeInMillis());
}
@Override
public Object getResultSetValue(ResultSet resultSet, int columnIndex, Column column) throws SQLException {
final ColumnType type = column.getType();
try {
if (type == ColumnType.TIME) {
return resultSet.getTime(columnIndex);
} else if (type == ColumnType.DATE) {
return resultSet.getDate(columnIndex);
} else if (type == ColumnType.TIMESTAMP) {
return resultSet.getTimestamp(columnIndex);
} else if (type == ColumnType.BLOB) {
final Blob blob = resultSet.getBlob(columnIndex);
return blob;
} else if (type == JdbcDataContext.COLUMN_TYPE_BLOB_AS_BYTES) {
final Blob blob = resultSet.getBlob(columnIndex);
final InputStream inputStream = blob.getBinaryStream();
final byte[] bytes = FileHelper.readAsBytes(inputStream);
return bytes;
} else if (type.isBinary()) {
return resultSet.getBytes(columnIndex);
} else if (type == ColumnType.CLOB || type == ColumnType.NCLOB) {
final Clob clob = resultSet.getClob(columnIndex);
return clob;
} else if (type == JdbcDataContext.COLUMN_TYPE_CLOB_AS_STRING) {
final Clob clob = resultSet.getClob(columnIndex);
final Reader reader = clob.getCharacterStream();
final String result = FileHelper.readAsString(reader);
return result;
} else if (type.isBoolean()) {
return resultSet.getBoolean(columnIndex);
}
} catch (Exception e) {
logger.warn("Failed to retrieve " + type
+ " value using type-specific getter, retrying with generic getObject(...) method", e);
}
return resultSet.getObject(columnIndex);
}
protected boolean isSupportedVersion(String databaseProductName, int databaseVersion) {
if(databaseProductName.equals(_dataContext.getDatabaseProductName())
&& databaseVersion <= getDatabaseMajorVersion(_dataContext.getDatabaseVersion())) {
return true;
}
return false;
}
private int getDatabaseMajorVersion(String version) {
int firstDot = -1;
if(version != null) {
version = version.replaceAll("[^0-9.]+", "");
firstDot = version.indexOf('.');
}
if(firstDot >= 0) {
return Integer.valueOf(version.substring(0, firstDot));
} else {
return 0;
}
}
}