View Javadoc

1   package org.apache.torque.adapter;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.sql.Connection;
23  import java.sql.SQLException;
24  import java.sql.Statement;
25  import java.util.HashSet;
26  import java.util.ListIterator;
27  import java.util.Set;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.torque.sql.Query;
32  import org.apache.torque.util.UniqueList;
33  
34  /**
35   * This code should be used for an Oracle database pool.
36   *
37   * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a>
38   * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
39   * @author <a href="mailto:bschneider@vecna.com">Bill Schneider</a>
40   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
41   * @version $Id: OracleAdapter.java 1355228 2012-06-29 03:38:08Z tfischer $
42   */
43  public class OracleAdapter extends AbstractAdapter
44  {
45      /**
46       * Serial version
47       */
48      private static final long serialVersionUID = 8966976210230241194L;
49  
50      /** The class logger. */
51      private static final Log log = LogFactory.getLog(OracleAdapter.class);
52  
53      /**
54       * Empty constructor.
55       */
56      protected OracleAdapter()
57      {
58          // empty
59      }
60  
61      /**
62       * This method is used to ignore case.
63       *
64       * @param in The string to transform to upper case.
65       * @return The upper case string.
66       */
67      @Override
68      public String toUpperCase(String in)
69      {
70          return new StringBuffer("UPPER(").append(in).append(")").toString();
71      }
72  
73      /**
74       * This method is used to ignore case.
75       *
76       * @param in The string whose case to ignore.
77       * @return The string in a case that can be ignored.
78       */
79      @Override
80      public String ignoreCase(String in)
81      {
82          return new StringBuffer("UPPER(").append(in).append(")").toString();
83      }
84  
85      /**
86       * @see org.apache.torque.adapter.Adapter#getIDMethodType()
87       */
88      @Override
89      public IDMethod getIDMethodType()
90      {
91          return IDMethod.SEQUENCE;
92      }
93  
94      /**
95       * Returns the next key from a sequence.  Uses the following
96       * implementation:
97       *
98       * <blockquote><code><pre>
99       * select sequenceName.nextval from dual
100      * </pre></code></blockquote>
101      *
102      * @param sequenceName The name of the sequence (should be of type
103      * <code>String</code>).
104      * @return SQL to retreive the next database key.
105      * @see org.apache.torque.adapter.Adapter#getIDMethodSQL(Object)
106      */
107     @Override
108     public String getIDMethodSQL(Object sequenceName)
109     {
110         return ("select " + sequenceName + ".nextval from dual");
111     }
112 
113     /**
114      * Locks the specified table.
115      *
116      * @param con The JDBC connection to use.
117      * @param table The name of the table to lock.
118      * @exception SQLException No Statement could be created or executed.
119      */
120     @Override
121     public void lockTable(Connection con, String table) throws SQLException
122     {
123         Statement statement = null;
124         try
125         {
126             statement = con.createStatement();
127             statement.execute("SELECT * FROM " +  table + " FOR UPDATE");
128         }
129         finally
130         {
131             if (statement != null)
132             {
133                 try
134                 {
135                     statement.close();
136                 }
137                 catch (Exception e)
138                 {
139                     log.warn("Colud not close lock statement", e);
140                 }
141             }
142         }
143     }
144 
145     /**
146      * Unlocks the specified table.
147      *
148      * @param con The JDBC connection to use.
149      * @param table The name of the table to unlock.
150      * @exception SQLException No Statement could be created or executed.
151      */
152     @Override
153     public void unlockTable(Connection con, String table) throws SQLException
154     {
155         // Tables in Oracle are unlocked when a commit is issued.  The
156         // user may have issued a commit but do it here to be sure.
157         con.commit();
158     }
159 
160     /**
161      * Return true for Oracle
162      * @see org.apache.torque.adapter.AbstractAdapter#supportsNativeLimit()
163      */
164     @Override
165     public boolean supportsNativeLimit()
166     {
167         return true;
168     }
169 
170     /**
171      * Return true for Oracle
172      * @see org.apache.torque.adapter.AbstractAdapter#supportsNativeOffset()
173      */
174     @Override
175     public boolean supportsNativeOffset()
176     {
177         return true;
178     }
179 
180     /**
181      * Build Oracle-style query with limit or offset.
182      * If the original SQL is in variable: query then the requlting
183      * SQL looks like this:
184      * <pre>
185      * SELECT B.* FROM (
186      *          SELECT A.*, rownum as TORQUE$ROWNUM FROM (
187      *                  query
188      *          ) A
189      *     ) B WHERE B.TORQUE$ROWNUM > offset AND B.TORQUE$ROWNUM
190      *     <= offset + limit
191      * </pre>
192      *
193      * @param query The query to modify
194      * @param offset the offset Value
195      * @param limit the limit Value
196      */
197     @Override
198     public void generateLimits(Query query, long offset, int limit)
199     {
200         StringBuffer preLimit = new StringBuffer()
201         .append("SELECT B.* FROM ( ")
202         .append("SELECT A.*, rownum AS TORQUE$ROWNUM FROM ( ");
203 
204         StringBuffer postLimit = new StringBuffer()
205                 .append(" ) A ")
206                 .append(" ) B WHERE ");
207 
208         if (offset > 0)
209         {
210             postLimit.append(" B.TORQUE$ROWNUM > ")
211                     .append(offset);
212 
213             if (limit >= 0)
214             {
215                 postLimit.append(" AND B.TORQUE$ROWNUM <= ")
216                         .append(offset + limit);
217             }
218         }
219         else
220         {
221             postLimit.append(" B.TORQUE$ROWNUM <= ")
222                     .append(limit);
223         }
224 
225         query.setPreLimit(preLimit.toString());
226         query.setPostLimit(postLimit.toString());
227         query.setLimit(null);
228 
229         // the query must not contain same column names or aliases.
230         // Find double column names and aliases and create unique aliases
231         // TODO: does not work for functions yet
232         UniqueList<String> selectColumns = query.getSelectClause();
233         int replacementSuffix = 0;
234         Set<String> columnNames = new HashSet<String>();
235         // first pass: only remember aliased columns
236         // No replacements need to take place because double aliases
237         // are not allowed anyway
238         // So alias names will be retained
239         for (ListIterator<String> columnIt = selectColumns.listIterator();
240                 columnIt.hasNext();)
241         {
242             String selectColumn = columnIt.next();
243 
244             // check for sql function
245             if ((selectColumn.indexOf('(') != -1)
246                 || (selectColumn.indexOf(')') != -1))
247             {
248                 // Sql function. Disregard.
249                 continue;
250             }
251 
252             // check if alias name exists
253             int spacePos = selectColumn.lastIndexOf(' ');
254             if (spacePos == -1)
255             {
256                 // no alias, disregard for now
257                 continue;
258             }
259 
260             String aliasName = selectColumn.substring(spacePos + 1);
261             columnNames.add(aliasName);
262         }
263 
264         // second pass. Regard ordinary columns only
265         for (ListIterator<String> columnIt = selectColumns.listIterator();
266                 columnIt.hasNext();)
267         {
268             String selectColumn = columnIt.next();
269 
270             // check for sql function
271             if ((selectColumn.indexOf('(') != -1)
272                 || (selectColumn.indexOf(')') != -1))
273             {
274                 // Sql function. Disregard.
275                 continue;
276             }
277 
278             {
279                 int spacePos = selectColumn.lastIndexOf(' ');
280                 if (spacePos != -1)
281                 {
282                     // alias, already processed in first pass
283                     continue;
284                 }
285             }
286             // split into column name and tableName
287             String column;
288             {
289                 int dotPos = selectColumn.lastIndexOf('.');
290                 if (dotPos != -1)
291                 {
292                     column = selectColumn.substring(dotPos + 1);
293                 }
294                 else
295                 {
296                     column = selectColumn;
297                 }
298             }
299             if (columnNames.contains(column))
300             {
301                 // column needs to be aliased
302                 // get replacement name
303                 String aliasName;
304                 do
305                 {
306                     aliasName = "a" + replacementSuffix;
307                     ++replacementSuffix;
308                 }
309                 while (columnNames.contains(aliasName));
310 
311                 selectColumn = selectColumn + " " + aliasName;
312                 columnIt.set(selectColumn);
313                 columnNames.add(aliasName);
314             }
315             else
316             {
317                 columnNames.add(column);
318             }
319         }
320     }
321 
322     /**
323      * This method is for the SqlExpression.quoteAndEscape rules.  The rule is,
324      * any string in a SqlExpression with a BACKSLASH will either be changed to
325      * "\\" or left as "\".  SapDB does not need the escape character.
326      *
327      * @return false.
328      */
329     @Override
330     public boolean escapeText()
331     {
332         return false;
333     }
334 
335     /**
336      * Whether an escape clause in like should be used.
337      * Example : select * from AUTHOR where AUTHOR.NAME like '\_%' ESCAPE '\';
338      *
339      * Oracle needs this, so this implementation always returns
340      * <code>true</code>.
341      *
342      * @return whether the escape clause should be appended or not.
343      */
344     @Override
345     public boolean useEscapeClauseForLike()
346     {
347         return true;
348     }
349 }