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 }