| /* |
| * 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.openjpa.jdbc.sql; |
| |
| import java.io.Serializable; |
| import java.sql.CallableStatement; |
| import java.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Objects; |
| |
| import org.apache.openjpa.jdbc.identifier.DBIdentifier; |
| import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration; |
| import org.apache.openjpa.jdbc.kernel.exps.CollectionParam; |
| import org.apache.openjpa.jdbc.kernel.exps.Val; |
| import org.apache.openjpa.jdbc.schema.Column; |
| import org.apache.openjpa.jdbc.schema.Sequence; |
| import org.apache.openjpa.jdbc.schema.Table; |
| import org.apache.openjpa.kernel.Filters; |
| import org.apache.openjpa.kernel.exps.Parameter; |
| |
| |
| /** |
| * Buffer for SQL statements that can be used to create |
| * java.sql.PreparedStatements. |
| * This buffer holds the SQL statement parameters and their corresponding |
| * columns. The parameters introduced by the runtime system are distinguished |
| * from the parameters set by the user. |
| * |
| * @author Marc Prud'hommeaux |
| * @author Abe White |
| * @author Pinaki Poddar |
| * |
| * @since 0.2.4 |
| */ |
| public final class SQLBuffer |
| implements Serializable, Cloneable { |
| |
| |
| private static final long serialVersionUID = 1L; |
| |
| private static final String PARAMETER_TOKEN = "?"; |
| |
| private final DBDictionary _dict; |
| private final StringBuilder _sql = new StringBuilder(); |
| private List _subsels = null; |
| private List _params = null; |
| private List _cols = null; |
| |
| // Even element refers to an index of the _params list |
| // Odd element refers to the user parameter |
| private List _userIndex = null; |
| private List _userParams = null; |
| |
| /** |
| * Default constructor. |
| */ |
| public SQLBuffer(DBDictionary dict) { |
| _dict = dict; |
| } |
| |
| /** |
| * Copy constructor. |
| */ |
| public SQLBuffer(SQLBuffer buf) { |
| _dict = buf._dict; |
| append(buf); |
| } |
| |
| /** |
| * Perform a shallow clone of this SQL Buffer. |
| */ |
| @Override |
| public Object clone() { |
| return new SQLBuffer(this); |
| } |
| |
| /** |
| * Return true if the buffer is emtpy. |
| */ |
| public boolean isEmpty() { |
| return _sql.length() == 0; |
| } |
| |
| /** |
| * Append all SQL and parameters of the given buffer. |
| */ |
| public SQLBuffer append(SQLBuffer buf) { |
| append(buf, _sql.length(), (_params == null) ? 0 : _params.size(), true, false); |
| return this; |
| } |
| |
| /** |
| * Append parameters only if the given buffer at the given positions. |
| */ |
| public SQLBuffer appendParamOnly(SQLBuffer buf) { |
| append(buf, _sql.length(), (_params == null) ? 0 : _params.size(), true, true); |
| return this; |
| } |
| |
| /** |
| * Append parameters and/or SQL of the given buffer at the given positions. |
| */ |
| private void append(SQLBuffer buf, int sqlIndex, int paramIndex, |
| boolean subsels, boolean paramOnly) { |
| if (subsels) { |
| // only allow appending of buffers with subselects, not insertion |
| if (_subsels != null && !_subsels.isEmpty() |
| && sqlIndex != _sql.length()) |
| throw new IllegalStateException(); |
| if (buf._subsels != null && !buf._subsels.isEmpty()) { |
| if (sqlIndex != _sql.length()) |
| throw new IllegalStateException(); |
| if (_subsels == null) |
| _subsels = new ArrayList(buf._subsels.size()); |
| for (int i = 0; i < buf._subsels.size(); i++) |
| _subsels.add(((Subselect) buf._subsels.get(i)). |
| clone(sqlIndex, paramIndex)); |
| } |
| } |
| |
| if (!paramOnly) { |
| if (sqlIndex == _sql.length()) |
| _sql.append(buf._sql.toString()); |
| else |
| _sql.insert(sqlIndex, buf._sql.toString()); |
| } |
| |
| if (buf._params != null) { |
| if (_params == null) |
| _params = new ArrayList(); |
| if (_cols == null && buf._cols != null) { |
| _cols = new ArrayList(); |
| while (_cols.size() < _params.size()) |
| _cols.add(null); |
| } |
| |
| if (paramIndex == _params.size()) { |
| _params.addAll(buf._params); |
| if (buf._userParams != null) { |
| if (_userParams == null) |
| _userParams = new ArrayList(); |
| _userParams.addAll(paramIndex, buf._userParams); |
| } |
| if (buf._userIndex != null) { |
| if (_userIndex == null) |
| _userIndex = new ArrayList(); |
| _userIndex.addAll(buf._userIndex); |
| } |
| if (buf._cols != null) |
| _cols.addAll(buf._cols); |
| else if (_cols != null) |
| while (_cols.size() < _params.size()) |
| _cols.add(null); |
| } else { |
| _params.addAll(paramIndex, buf._params); |
| if ( buf._userParams != null) { |
| if (_userParams == null) |
| _userParams = new ArrayList(); |
| _userParams.addAll(paramIndex, buf._userParams); |
| } |
| if (buf._userIndex != null) { |
| if (_userIndex == null) |
| _userIndex = new ArrayList(); |
| _userIndex.addAll(buf._userIndex); |
| } |
| if (buf._cols != null) |
| _cols.addAll(paramIndex, buf._cols); |
| else if (_cols != null) |
| while (_cols.size() < _params.size()) |
| _cols.add(paramIndex, null); |
| } |
| } |
| |
| if (_userIndex != null) { |
| // fix up user parameter index(s) |
| for (int i = 0; i < _userIndex.size(); i+=2) { |
| Object param = _userIndex.get(i+1); |
| |
| Object previousParam = (i > 0) ? _userIndex.get(i-1) : null; |
| if ( param != previousParam) { |
| _userIndex.set(i, _userParams.indexOf(param)); |
| }else{ |
| //if there are multiple parameters for the in clause or the combined PK field, |
| //we got duplicate param objects in _userParams list. |
| //In order to find the right index, we have to skip params that's checked already. |
| int previousUserIndex = (Integer)_userIndex.get(i-2); |
| int userParamindex = 0; |
| |
| for(Object next : _userParams){ |
| if (next == param && userParamindex > previousUserIndex) { |
| _userIndex.set(i, userParamindex); |
| break; |
| } |
| userParamindex++; |
| } |
| } |
| } |
| } |
| } |
| |
| public SQLBuffer append(DBIdentifier name) { |
| _sql.append(_dict.toDBName(name)); |
| return this; |
| } |
| |
| public SQLBuffer append(Table table) { |
| _sql.append(_dict.getFullName(table, false)); |
| return this; |
| } |
| |
| public SQLBuffer append(Sequence seq) { |
| _sql.append(_dict.getFullName(seq)); |
| return this; |
| } |
| |
| public SQLBuffer append(Column col) { |
| _sql.append(_dict.getColumnDBName(col)); |
| return this; |
| } |
| |
| public SQLBuffer append(String s) { |
| _sql.append(s); |
| return this; |
| } |
| |
| /** |
| * Append a subselect. This delays resolution of the select SQL. |
| */ |
| public SQLBuffer append(Select sel, JDBCFetchConfiguration fetch) { |
| return append(sel, fetch, false); |
| } |
| |
| /** |
| * Append a subselect count. This delays resolution of the select SQL. |
| */ |
| public SQLBuffer appendCount(Select sel, JDBCFetchConfiguration fetch) { |
| return append(sel, fetch, true); |
| } |
| |
| /** |
| * Append a subselect. This delays resolution of the select SQL. |
| */ |
| private SQLBuffer append(Select sel, JDBCFetchConfiguration fetch, |
| boolean count) { |
| _sql.append("("); |
| Subselect sub = new Subselect(); |
| sub.select = sel; |
| sub.fetch = fetch; |
| sub.count = count; |
| sub.sqlIndex = _sql.length(); |
| sub.paramIndex = (_params == null) ? 0 : _params.size(); |
| _sql.append(")"); |
| |
| if (_subsels == null) |
| _subsels = new ArrayList(2); |
| _subsels.add(sub); |
| return this; |
| } |
| |
| /** |
| * Replace a subselect. |
| */ |
| public boolean replace(Select old, Select sel) { |
| if (_subsels == null) |
| return false; |
| Subselect sub; |
| for (int i = 0; i < _subsels.size(); i++) { |
| sub = (Subselect) _subsels.get(i); |
| if (sub.select == old) { |
| sub.select = sel; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Append a parameter value. |
| */ |
| public SQLBuffer appendValue(Object o) { |
| return appendValue(o, null); |
| } |
| |
| /** |
| * Append a system inserted parameter value for a specific column. |
| */ |
| public SQLBuffer appendValue(Object o, Column col) { |
| return appendValue(o, col, null); |
| } |
| |
| /** |
| * Append a user parameter value for a specific column. User parameters |
| * are marked as opposed to the parameters inserted by the internal runtime |
| * system. This helps to reuse the buffer by reparmeterizing it with new |
| * set of user parameters while the 'internal' parameters remain unchanged. |
| * |
| * @param userParam if non-null, designates a 'user' parameter. |
| */ |
| public SQLBuffer appendValue(Object o, Column col, Parameter userParam) { |
| return appendValue(o, col, userParam, true); |
| } |
| |
| public SQLBuffer appendValue(Object o, Column col, Parameter userParam, boolean useParamToken) { |
| if (o == null) |
| _sql.append("NULL"); |
| else if (o instanceof Raw) |
| _sql.append(o.toString()); |
| else { |
| Class<?> type = Filters.wrap(o.getClass()); |
| if (useParamToken || !validParamLiteralType(type)) { |
| _sql.append(PARAMETER_TOKEN); |
| |
| // initialize param and col lists; we hold off on col list until |
| // we get the first non-null col |
| if (_params == null) |
| _params = new ArrayList(); |
| if (_userParams == null) |
| _userParams = new ArrayList(); |
| if (col != null && _cols == null) { |
| _cols = new ArrayList(); |
| while (_cols.size() < _params.size()) |
| _cols.add(null); |
| } |
| |
| _params.add(o); |
| if (userParam != null) { |
| Object param = userParam; |
| if (userParam instanceof CollectionParam) |
| param = ((CollectionParam) userParam).clone(); |
| _userParams.add(param); |
| if (_userIndex == null) |
| _userIndex = new ArrayList(); |
| int index = _params.size()-1; |
| _userIndex.add(index); |
| _userIndex.add(param); |
| } |
| else |
| _userParams.add(o); |
| if (_cols != null) |
| _cols.add(col); |
| } else { |
| if (type == String.class) { |
| _sql.append("'" + o.toString().replace("'", "''") + "'"); |
| |
| } else if ( type == Character.class ) { |
| if (_dict.storeCharsAsNumbers) { |
| _sql.append(Integer.toString((Character) o)); |
| } else { |
| _sql.append("'" + o.toString().replace("'", "''") + "'"); |
| } |
| } else if (type == Boolean.class) { |
| Boolean b = (Boolean) o; |
| // We store B(b)ooleans as ints. Convert |
| _sql.append(_dict.getBooleanRepresentation().getRepresentation(b)); |
| } else { |
| _sql.append(o.toString()); |
| } |
| } |
| } |
| return this; |
| } |
| |
| private boolean validParamLiteralType(Class<?> type) { |
| boolean ret = type == String.class |
| || type == Integer.class |
| || type == Character.class |
| || type == Boolean.class |
| || type == Short.class |
| || type == Long.class |
| || type == Byte.class; |
| return ret; |
| } |
| |
| /** |
| * Return the list of parameter values. |
| */ |
| public List getParameters() { |
| return (_params == null) ? Collections.EMPTY_LIST : _params; |
| } |
| |
| /** |
| * Get the user parameter positions in the list of parameters. The odd |
| * element of the returned list contains an integer index that refers |
| * to the position in the {@link #getParameters()} list. The even element |
| * of the returned list refers to the user parameter key. |
| * This structure is preferred over a normal map because a user parameter |
| * may occur more than one in the parameters. |
| */ |
| public List getUserParameters() { |
| if (_userIndex == null) |
| return Collections.EMPTY_LIST; |
| return _userIndex; |
| } |
| |
| /** |
| * Return the SQL for this buffer. |
| */ |
| public String getSQL() { |
| return getSQL(false); |
| } |
| |
| /** |
| * Returns the SQL for this buffer. |
| * |
| * @param replaceParams if true, then replace parameters with the |
| * actual parameter values |
| */ |
| public String getSQL(boolean replaceParams) { |
| resolveSubselects(); |
| String sql = _sql.toString(); |
| if (!replaceParams || _params == null || _params.isEmpty()) |
| return sql; |
| |
| StringBuilder buf = new StringBuilder(); |
| Iterator pi = _params.iterator(); |
| for (int i = 0; i < sql.length(); i++) { |
| if (sql.charAt(i) != '?') { |
| buf.append(sql.charAt(i)); |
| continue; |
| } |
| |
| Object param = pi.hasNext() ? pi.next() : null; |
| if (param == null) |
| buf.append("NULL"); |
| else if (param instanceof Number || param instanceof Boolean) |
| buf.append(param); |
| else if (param instanceof String || param instanceof Character) |
| buf.append("'").append(param).append("'"); |
| else |
| buf.append("?"); |
| } |
| return buf.toString(); |
| } |
| |
| /** |
| * Resolve our delayed subselects. |
| */ |
| private void resolveSubselects() { |
| if (_subsels == null || _subsels.isEmpty()) |
| return; |
| |
| // add subsels backwards so that the stored insertion points of |
| // later subsels remain valid |
| Subselect sub; |
| SQLBuffer buf; |
| for (int i = _subsels.size() - 1; i >= 0; i--) { |
| sub = (Subselect) _subsels.get(i); |
| if (sub.count) |
| buf = sub.select.toSelectCount(); |
| else |
| buf = sub.select.toSelect(false, sub.fetch); |
| buf.resolveSubselects(); |
| append(buf, sub.sqlIndex, sub.paramIndex, false, false); |
| } |
| _subsels.clear(); |
| } |
| |
| /** |
| * Create and populate the parameters of a prepared statement using |
| * the SQL in this buffer. |
| */ |
| public PreparedStatement prepareStatement(Connection conn) |
| throws SQLException { |
| return prepareStatement(conn, ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY); |
| } |
| |
| /** |
| * Create and populate the parameters of a prepared statement using |
| * the SQL in this buffer. |
| */ |
| public PreparedStatement prepareStatement(Connection conn, int rsType, |
| int rsConcur) |
| throws SQLException { |
| return prepareStatement(conn, null, rsType, rsConcur); |
| } |
| |
| /** |
| * Create and populate the parameters of a prepred statement using the |
| * SQL in this buffer and the given fetch configuration. |
| */ |
| public PreparedStatement prepareStatement(Connection conn, |
| JDBCFetchConfiguration fetch, int rsType, int rsConcur) |
| throws SQLException { |
| if (rsType == -1 && fetch == null) |
| rsType = ResultSet.TYPE_FORWARD_ONLY; |
| else if (rsType == -1) |
| rsType = fetch.getResultSetType(); |
| if (rsConcur == -1) |
| rsConcur = ResultSet.CONCUR_READ_ONLY; |
| |
| PreparedStatement stmnt; |
| if (rsType == ResultSet.TYPE_FORWARD_ONLY |
| && rsConcur == ResultSet.CONCUR_READ_ONLY) |
| stmnt = conn.prepareStatement(getSQL()); |
| else |
| stmnt = conn.prepareStatement(getSQL(), rsType, rsConcur); |
| try { |
| setParameters(stmnt); |
| if (fetch != null) { |
| if (fetch.getFetchBatchSize() > 0) |
| stmnt.setFetchSize( |
| _dict.getBatchFetchSize(fetch.getFetchBatchSize())); |
| if (rsType != ResultSet.TYPE_FORWARD_ONLY |
| && fetch.getFetchDirection() != ResultSet.FETCH_FORWARD) |
| stmnt.setFetchDirection(fetch.getFetchDirection()); |
| } |
| return stmnt; |
| } catch (SQLException se) { |
| try { |
| stmnt.close(); |
| } catch (SQLException se2) { |
| } |
| throw se; |
| } |
| } |
| |
| /** |
| * Create and populate the parameters of a prepared statement using |
| * the SQL in this buffer. |
| */ |
| public CallableStatement prepareCall(Connection conn) |
| throws SQLException { |
| return prepareCall(conn, ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_READ_ONLY); |
| } |
| |
| /** |
| * Create and populate the parameters of a prepared statement using |
| * the SQL in this buffer. |
| */ |
| public CallableStatement prepareCall(Connection conn, int rsType, |
| int rsConcur) |
| throws SQLException { |
| return prepareCall(conn, null, rsType, rsConcur); |
| } |
| |
| /** |
| * Create and populate the parameters of a prepred statement using the |
| * SQL in this buffer and the given fetch configuration. |
| */ |
| public CallableStatement prepareCall(Connection conn, |
| JDBCFetchConfiguration fetch, int rsType, int rsConcur) |
| throws SQLException { |
| if (rsType == -1 && fetch == null) |
| rsType = ResultSet.TYPE_FORWARD_ONLY; |
| else if (rsType == -1) |
| rsType = fetch.getResultSetType(); |
| if (rsConcur == -1) |
| rsConcur = ResultSet.CONCUR_READ_ONLY; |
| |
| CallableStatement stmnt; |
| if (rsType == ResultSet.TYPE_FORWARD_ONLY |
| && rsConcur == ResultSet.CONCUR_READ_ONLY) |
| stmnt = conn.prepareCall(getSQL()); |
| else |
| stmnt = conn.prepareCall(getSQL(), rsType, rsConcur); |
| try { |
| setParameters(stmnt); |
| if (fetch != null) { |
| if (fetch.getFetchBatchSize() > 0) |
| stmnt.setFetchSize( |
| _dict.getBatchFetchSize(fetch.getFetchBatchSize())); |
| if (rsType != ResultSet.TYPE_FORWARD_ONLY |
| && fetch.getFetchDirection() != ResultSet.FETCH_FORWARD) |
| stmnt.setFetchDirection(fetch.getFetchDirection()); |
| } |
| return stmnt; |
| } catch (SQLException se) { |
| try { |
| stmnt.close(); |
| } catch (SQLException se2) { |
| } |
| throw se; |
| } |
| } |
| |
| /** |
| * Populate the parameters of an existing PreparedStatement |
| * with values from this buffer. |
| */ |
| public void setParameters(PreparedStatement ps) |
| throws SQLException { |
| if (_params == null) |
| return; |
| |
| Column col; |
| for (int i = 0; i < _params.size(); i++) { |
| col = (_cols == null) ? null : (Column) _cols.get(i); |
| _dict.setUnknown(ps, i + 1, _params.get(i), col); |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| int hash = _sql.hashCode(); |
| return (_params == null) ? hash : hash ^ _params.hashCode(); |
| } |
| |
| /** |
| * Compare internal SQL without resolving subselects or stringifying |
| * parameters. |
| */ |
| public boolean sqlEquals(String sql) { |
| return _sql.toString().equals(sql); |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (other == this) |
| return true; |
| if (!(other instanceof SQLBuffer)) |
| return false; |
| |
| SQLBuffer buf = (SQLBuffer) other; |
| return _sql.equals(buf._sql) |
| && Objects.equals(_params, buf._params); |
| } |
| |
| /** |
| * Replace SQL '?' with CAST string if required by DB platform |
| * @param oper |
| * @param val |
| */ |
| public void addCastForParam(String oper, Val val) { |
| if (_sql.charAt(_sql.length() - 1) == '?') { |
| String castString = _dict.addCastAsType(oper, val); |
| if (castString != null) |
| _sql.replace(_sql.length() - 1, _sql.length(), castString); |
| } |
| } |
| |
| /** |
| * Replace current buffer string with the new string |
| * |
| * @param start replace start position |
| * @param end replace end position |
| * @param newString |
| */ |
| public void replaceSqlString(int start, int end, String newString) { |
| _sql.replace(start, end, newString); |
| } |
| |
| /** |
| * Represents a subselect. |
| */ |
| private static class Subselect { |
| |
| public Select select; |
| public JDBCFetchConfiguration fetch; |
| public boolean count; |
| public int sqlIndex; |
| public int paramIndex; |
| |
| public Subselect clone(int sqlIndex, int paramIndex) { |
| if (sqlIndex == 0 && paramIndex == 0) |
| return this; |
| |
| Subselect sub = new Subselect(); |
| sub.select = select; |
| sub.fetch = fetch; |
| sub.count = count; |
| sub.sqlIndex = this.sqlIndex + sqlIndex; |
| sub.paramIndex = this.paramIndex + paramIndex; |
| return sub; |
| } |
| } |
| |
| public void setParameters(List params) { |
| _params = params; |
| } |
| |
| public List getColumns() { |
| return _cols; |
| } |
| } |