All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.metamodel.jdbc.dialects.AbstractQueryRewriter Maven / Gradle / Ivy

The newest version!
/**
 * 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 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 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 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 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 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 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 <= VersionParser.getMajorVersion(_dataContext.getDatabaseVersion())) {
            return true;
        }
        return false;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy