1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.jdbc.sql;
20
21 import java.io.BufferedReader;
22 import java.io.ByteArrayInputStream;
23 import java.io.CharArrayReader;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.io.OutputStream;
28 import java.io.Reader;
29 import java.io.StringReader;
30 import java.io.Writer;
31 import java.lang.reflect.InvocationTargetException;
32 import java.lang.reflect.Method;
33 import java.math.BigDecimal;
34 import java.math.BigInteger;
35 import java.sql.Array;
36 import java.sql.Blob;
37 import java.sql.Clob;
38 import java.sql.Connection;
39 import java.sql.DatabaseMetaData;
40 import java.sql.PreparedStatement;
41 import java.sql.Ref;
42 import java.sql.ResultSet;
43 import java.sql.SQLException;
44 import java.sql.SQLWarning;
45 import java.sql.Statement;
46 import java.sql.Time;
47 import java.sql.Timestamp;
48 import java.sql.Types;
49 import java.text.MessageFormat;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Calendar;
53 import java.util.Collection;
54 import java.util.Collections;
55 import java.util.Date;
56 import java.util.HashSet;
57 import java.util.Iterator;
58 import java.util.LinkedHashSet;
59 import java.util.List;
60 import java.util.Locale;
61 import java.util.Map;
62 import java.util.Set;
63
64 import javax.sql.DataSource;
65
66 import org.apache.commons.lang.StringUtils;
67 import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
68 import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
69 import org.apache.openjpa.jdbc.kernel.JDBCStore;
70 import org.apache.openjpa.jdbc.kernel.exps.ExpContext;
71 import org.apache.openjpa.jdbc.kernel.exps.ExpState;
72 import org.apache.openjpa.jdbc.kernel.exps.FilterValue;
73 import org.apache.openjpa.jdbc.kernel.exps.Val;
74 import org.apache.openjpa.jdbc.meta.ClassMapping;
75 import org.apache.openjpa.jdbc.meta.FieldMapping;
76 import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
77 import org.apache.openjpa.jdbc.schema.Column;
78 import org.apache.openjpa.jdbc.schema.DataSourceFactory;
79 import org.apache.openjpa.jdbc.schema.ForeignKey;
80 import org.apache.openjpa.jdbc.schema.Index;
81 import org.apache.openjpa.jdbc.schema.NameSet;
82 import org.apache.openjpa.jdbc.schema.PrimaryKey;
83 import org.apache.openjpa.jdbc.schema.Schema;
84 import org.apache.openjpa.jdbc.schema.SchemaGroup;
85 import org.apache.openjpa.jdbc.schema.Sequence;
86 import org.apache.openjpa.jdbc.schema.Table;
87 import org.apache.openjpa.jdbc.schema.Unique;
88 import org.apache.openjpa.kernel.Filters;
89 import org.apache.openjpa.kernel.OpenJPAStateManager;
90 import org.apache.openjpa.kernel.exps.Path;
91 import org.apache.openjpa.lib.conf.Configurable;
92 import org.apache.openjpa.lib.conf.Configuration;
93 import org.apache.openjpa.lib.jdbc.ConnectionDecorator;
94 import org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator;
95 import org.apache.openjpa.lib.log.Log;
96 import org.apache.openjpa.lib.util.Localizer;
97 import org.apache.openjpa.lib.util.Localizer.Message;
98 import org.apache.openjpa.meta.FieldMetaData;
99 import org.apache.openjpa.meta.JavaTypes;
100 import org.apache.openjpa.meta.ValueStrategies;
101 import org.apache.openjpa.util.GeneralException;
102 import org.apache.openjpa.util.InternalException;
103 import org.apache.openjpa.util.InvalidStateException;
104 import org.apache.openjpa.util.OpenJPAException;
105 import org.apache.openjpa.util.ReferentialIntegrityException;
106 import org.apache.openjpa.util.Serialization;
107 import org.apache.openjpa.util.StoreException;
108 import org.apache.openjpa.util.UnsupportedException;
109 import org.apache.openjpa.util.UserException;
110
111 import serp.util.Numbers;
112 import serp.util.Strings;
113
114 /**
115 * Class which allows the creation of SQL dynamically, in a
116 * database agnostic fashion. Subclass for the nuances of different data stores.
117 */
118 public class DBDictionary
119 implements Configurable, ConnectionDecorator, JoinSyntaxes,
120 LoggingConnectionDecorator.SQLWarningHandler {
121
122 public static final String VENDOR_OTHER = "other";
123 public static final String VENDOR_DATADIRECT = "datadirect";
124
125 public static final String SCHEMA_CASE_UPPER = "upper";
126 public static final String SCHEMA_CASE_LOWER = "lower";
127 public static final String SCHEMA_CASE_PRESERVE = "preserve";
128
129 public static final String CONS_NAME_BEFORE = "before";
130 public static final String CONS_NAME_MID = "mid";
131 public static final String CONS_NAME_AFTER = "after";
132
133 public int blobBufferSize = 50000;
134 public int clobBufferSize = 50000;
135
136 protected static final int RANGE_POST_SELECT = 0;
137 protected static final int RANGE_PRE_DISTINCT = 1;
138 protected static final int RANGE_POST_DISTINCT = 2;
139 protected static final int RANGE_POST_LOCK = 3;
140
141 protected static final int NANO = 1;
142 protected static final int MICRO = NANO * 1000;
143 protected static final int MILLI = MICRO * 1000;
144 protected static final int CENTI = MILLI * 10;
145 protected static final int DECI = MILLI * 100;
146 protected static final int SEC = MILLI * 1000;
147
148 protected static final int NAME_ANY = 0;
149 protected static final int NAME_TABLE = 1;
150 protected static final int NAME_SEQUENCE = 2;
151
152 protected static final int UNLIMITED = -1;
153 protected static final int NO_BATCH = 0;
154
155 private static final String ZERO_DATE_STR =
156 "'" + new java.sql.Date(0) + "'";
157 private static final String ZERO_TIME_STR = "'" + new Time(0) + "'";
158 private static final String ZERO_TIMESTAMP_STR =
159 "'" + new Timestamp(0) + "'";
160
161 public static final List EMPTY_STRING_LIST = Arrays.asList(new String[]{});
162 public static final List[] SQL_STATE_CODES =
163 {EMPTY_STRING_LIST, // 0: Default
164 Arrays.asList(new String[]{"41000"}), // 1: LOCK
165 EMPTY_STRING_LIST, // 2: OBJECT_NOT_FOUND
166 EMPTY_STRING_LIST, // 3: OPTIMISTIC
167 Arrays.asList(new String[]{"23000"}), // 4: REFERENTIAL_INTEGRITY
168 EMPTY_STRING_LIST // 5: OBJECT_EXISTS
169 };
170
171 private static final Localizer _loc = Localizer.forPackage
172 (DBDictionary.class);
173
174 // schema data
175 public String platform = "Generic";
176 public String driverVendor = null;
177 public String catalogSeparator = ".";
178 public boolean createPrimaryKeys = true;
179 public String constraintNameMode = CONS_NAME_BEFORE;
180 public int maxTableNameLength = 128;
181 public int maxColumnNameLength = 128;
182 public int maxConstraintNameLength = 128;
183 public int maxIndexNameLength = 128;
184 public int maxIndexesPerTable = Integer.MAX_VALUE;
185 public boolean supportsForeignKeys = true;
186 public boolean supportsTimestampNanos = true;
187 public boolean supportsUniqueConstraints = true;
188 public boolean supportsDeferredConstraints = true;
189 public boolean supportsRestrictDeleteAction = true;
190 public boolean supportsCascadeDeleteAction = true;
191 public boolean supportsNullDeleteAction = true;
192 public boolean supportsDefaultDeleteAction = true;
193 public boolean supportsRestrictUpdateAction = true;
194 public boolean supportsCascadeUpdateAction = true;
195 public boolean supportsNullUpdateAction = true;
196 public boolean supportsDefaultUpdateAction = true;
197 public boolean supportsAlterTableWithAddColumn = true;
198 public boolean supportsAlterTableWithDropColumn = true;
199 public boolean supportsComments = false;
200 public String reservedWords = null;
201 public String systemSchemas = null;
202 public String systemTables = null;
203 public String selectWords = null;
204 public String fixedSizeTypeNames = null;
205 public String schemaCase = SCHEMA_CASE_UPPER;
206
207 // sql
208 public String validationSQL = null;
209 public String closePoolSQL = null;
210 public String initializationSQL = null;
211 public int joinSyntax = SYNTAX_SQL92;
212 public String outerJoinClause = "LEFT OUTER JOIN";
213 public String innerJoinClause = "INNER JOIN";
214 public String crossJoinClause = "CROSS JOIN";
215 public boolean requiresConditionForCrossJoin = false;
216 public String forUpdateClause = "FOR UPDATE";
217 public String tableForUpdateClause = null;
218 public String distinctCountColumnSeparator = null;
219 public boolean supportsSelectForUpdate = true;
220 public boolean supportsLockingWithDistinctClause = true;
221 public boolean supportsLockingWithMultipleTables = true;
222 public boolean supportsLockingWithOrderClause = true;
223 public boolean supportsLockingWithOuterJoin = true;
224 public boolean supportsLockingWithInnerJoin = true;
225 public boolean supportsLockingWithSelectRange = true;
226 public boolean supportsQueryTimeout = true;
227 public boolean simulateLocking = false;
228 public boolean supportsSubselect = true;
229 public boolean supportsCorrelatedSubselect = true;
230 public boolean supportsHaving = true;
231 public boolean supportsSelectStartIndex = false;
232 public boolean supportsSelectEndIndex = false;
233 public int rangePosition = RANGE_POST_SELECT;
234 public boolean requiresAliasForSubselect = false;
235 public boolean requiresTargetForDelete = false;
236 public boolean allowsAliasInBulkClause = true;
237 public boolean supportsMultipleNontransactionalResultSets = true;
238 public String searchStringEscape = "\\";
239 public boolean requiresCastForMathFunctions = false;
240 public boolean requiresCastForComparisons = false;
241 public boolean supportsModOperator = false;
242 public boolean supportsXMLColumn = false;
243
244 // functions
245 public String castFunction = "CAST({0} AS {1})";
246 public String toLowerCaseFunction = "LOWER({0})";
247 public String toUpperCaseFunction = "UPPER({0})";
248 public String stringLengthFunction = "CHAR_LENGTH({0})";
249 public String bitLengthFunction = "(OCTET_LENGTH({0}) * 8)";
250 public String trimLeadingFunction = "TRIM(LEADING {1} FROM {0})";
251 public String trimTrailingFunction = "TRIM(TRAILING {1} FROM {0})";
252 public String trimBothFunction = "TRIM(BOTH {1} FROM {0})";
253 public String concatenateFunction = "({0}||{1})";
254 public String concatenateDelimiter = "'OPENJPATOKEN'";
255 public String substringFunctionName = "SUBSTRING";
256 public String currentDateFunction = "CURRENT_DATE";
257 public String currentTimeFunction = "CURRENT_TIME";
258 public String currentTimestampFunction = "CURRENT_TIMESTAMP";
259 public String dropTableSQL = "DROP TABLE {0}";
260
261 // types
262 public boolean storageLimitationsFatal = false;
263 public boolean storeLargeNumbersAsStrings = false;
264 public boolean storeCharsAsNumbers = true;
265 public boolean useGetBytesForBlobs = false;
266 public boolean useSetBytesForBlobs = false;
267 public boolean useGetObjectForBlobs = false;
268 public boolean useGetStringForClobs = false;
269 public boolean useSetStringForClobs = false;
270 public int maxEmbeddedBlobSize = -1;
271 public int maxEmbeddedClobSize = -1;
272 public int inClauseLimit = -1;
273 public int datePrecision = MILLI;
274 public int characterColumnSize = 255;
275 public String arrayTypeName = "ARRAY";
276 public String bigintTypeName = "BIGINT";
277 public String binaryTypeName = "BINARY";
278 public String bitTypeName = "BIT";
279 public String blobTypeName = "BLOB";
280 public String booleanTypeName = "BOOLEAN";
281 public String charTypeName = "CHAR";
282 public String clobTypeName = "CLOB";
283 public String dateTypeName = "DATE";
284 public String decimalTypeName = "DECIMAL";
285 public String distinctTypeName = "DISTINCT";
286 public String doubleTypeName = "DOUBLE";
287 public String floatTypeName = "FLOAT";
288 public String integerTypeName = "INTEGER";
289 public String javaObjectTypeName = "JAVA_OBJECT";
290 public String longVarbinaryTypeName = "LONGVARBINARY";
291 public String longVarcharTypeName = "LONGVARCHAR";
292 public String nullTypeName = "NULL";
293 public String numericTypeName = "NUMERIC";
294 public String otherTypeName = "OTHER";
295 public String realTypeName = "REAL";
296 public String refTypeName = "REF";
297 public String smallintTypeName = "SMALLINT";
298 public String structTypeName = "STRUCT";
299 public String timeTypeName = "TIME";
300 public String timestampTypeName = "TIMESTAMP";
301 public String tinyintTypeName = "TINYINT";
302 public String varbinaryTypeName = "VARBINARY";
303 public String varcharTypeName = "VARCHAR";
304 public String xmlTypeName = "XML";
305 public String getStringVal = "";
306
307 // schema metadata
308 public boolean useSchemaName = true;
309 public String tableTypes = "TABLE";
310 public boolean supportsSchemaForGetTables = true;
311 public boolean supportsSchemaForGetColumns = true;
312 public boolean supportsNullTableForGetColumns = true;
313 public boolean supportsNullTableForGetPrimaryKeys = false;
314 public boolean supportsNullTableForGetIndexInfo = false;
315 public boolean supportsNullTableForGetImportedKeys = false;
316 public boolean useGetBestRowIdentifierForPrimaryKeys = false;
317 public boolean requiresAutoCommitForMetaData = false;
318
319 // auto-increment
320 public int maxAutoAssignNameLength = 31;
321 public String autoAssignClause = null;
322 public String autoAssignTypeName = null;
323 public boolean supportsAutoAssign = false;
324 public String lastGeneratedKeyQuery = null;
325 public String nextSequenceQuery = null;
326 public String sequenceSQL = null;
327 public String sequenceSchemaSQL = null;
328 public String sequenceNameSQL = null;
329
330 protected JDBCConfiguration conf = null;
331 protected Log log = null;
332 protected boolean connected = false;
333 protected final Set reservedWordSet = new HashSet();
334 protected final Set systemSchemaSet = new HashSet();
335 protected final Set systemTableSet = new HashSet();
336 protected final Set fixedSizeTypeNameSet = new HashSet();
337 protected final Set typeModifierSet = new HashSet();
338
339 /**
340 * If a native query begins with any of the values found here then it will
341 * be treated as a select statement.
342 */
343 protected final Set selectWordSet = new HashSet();
344
345 // when we store values that lose precion, track the types so that the
346 // first time it happens we can warn the user
347 private Set _precisionWarnedTypes = null;
348
349 // cache lob methods
350 private Method _setBytes = null;
351 private Method _setString = null;
352 private Method _setCharStream = null;
353
354 // batchLimit value:
355 // -1 = unlimited
356 // 0 = no batch
357 // any positive number = batch limit
358 public int batchLimit = NO_BATCH;
359
360 public DBDictionary() {
361 fixedSizeTypeNameSet.addAll(Arrays.asList(new String[]{
362 "BIGINT", "BIT", "BLOB", "CLOB", "DATE", "DECIMAL", "DISTINCT",
363 "DOUBLE", "FLOAT", "INTEGER", "JAVA_OBJECT", "NULL", "NUMERIC",
364 "OTHER", "REAL", "REF", "SMALLINT", "STRUCT", "TIME", "TIMESTAMP",
365 "TINYINT",
366 }));
367
368 selectWordSet.add("SELECT");
369 }
370
371 /**
372 * This method is called when the dictionary first sees any connection.
373 * It is used to initialize dictionary metadata if needed. If you
374 * override this method, be sure to call
375 * <code>super.connectedConfiguration</code>.
376 */
377 public void connectedConfiguration(Connection conn)
378 throws SQLException {
379 if (!connected) {
380 try {
381 if (log.isTraceEnabled())
382 log.trace(DBDictionaryFactory.toString
383 (conn.getMetaData()));
384 } catch (Exception e) {
385 log.trace(e.toString(), e);
386 }
387 }
388 connected = true;
389 }
390
391 //////////////////////
392 // ResultSet wrappers
393 //////////////////////
394
395 /**
396 * Convert the specified column of the SQL ResultSet to the proper
397 * java type.
398 */
399 public Array getArray(ResultSet rs, int column)
400 throws SQLException {
401 return rs.getArray(column);
402 }
403
404 /**
405 * Convert the specified column of the SQL ResultSet to the proper
406 * java type.
407 */
408 public InputStream getAsciiStream(ResultSet rs, int column)
409 throws SQLException {
410 return rs.getAsciiStream(column);
411 }
412
413 /**
414 * Convert the specified column of the SQL ResultSet to the proper
415 * java type.
416 */
417 public BigDecimal getBigDecimal(ResultSet rs, int column)
418 throws SQLException {
419 if (storeLargeNumbersAsStrings) {
420 String str = getString(rs, column);
421 return (str == null) ? null : new BigDecimal(str);
422 }
423 return rs.getBigDecimal(column);
424 }
425
426 /**
427 * Returns the specified column value as an unknown numeric type;
428 * we try from the most generic to the least generic.
429 */
430 public Number getNumber(ResultSet rs, int column)
431 throws SQLException {
432 // try from the most generic, and if errors occur, try
433 // less generic types; this enables us to handle values
434 // like Double.NaN without having to introspect on the
435 // ResultSetMetaData (bug #1053). note that we handle
436 // generic exceptions, since some drivers may throw
437 // NumberFormatExceptions, whereas others may throw SQLExceptions
438 try {
439 return getBigDecimal(rs, column);
440 } catch (Exception e1) {
441 try {
442 return new Double(getDouble(rs, column));
443 } catch (Exception e2) {
444 try {
445 return new Float(getFloat(rs, column));
446 } catch (Exception e3) {
447 try {
448 return Numbers.valueOf(getLong(rs, column));
449 } catch (Exception e4) {
450 try {
451 return Numbers.valueOf(getInt(rs, column));
452 } catch (Exception e5) {
453 }
454 }
455 }
456 }
457
458 if (e1 instanceof RuntimeException)
459 throw(RuntimeException) e1;
460 if (e1 instanceof SQLException)
461 throw(SQLException) e1;
462 }
463
464 return null;
465 }
466
467 /**
468 * Convert the specified column of the SQL ResultSet to the proper
469 * java type.
470 */
471 public BigInteger getBigInteger(ResultSet rs, int column)
472 throws SQLException {
473 if (storeLargeNumbersAsStrings) {
474 String str = getString(rs, column);
475 return (str == null) ? null : new BigDecimal(str).toBigInteger();
476 }
477 BigDecimal bd = getBigDecimal(rs, column);
478 return (bd == null) ? null : bd.toBigInteger();
479 }
480
481 /**
482 * Convert the specified column of the SQL ResultSet to the proper
483 * java type.
484 */
485 public InputStream getBinaryStream(ResultSet rs, int column)
486 throws SQLException {
487 return rs.getBinaryStream(column);
488 }
489
490 public InputStream getLOBStream(JDBCStore store, ResultSet rs,
491 int column) throws SQLException {
492 return rs.getBinaryStream(column);
493 }
494
495 /**
496 * Convert the specified column of the SQL ResultSet to the proper
497 * java type.
498 */
499 public Blob getBlob(ResultSet rs, int column)
500 throws SQLException {
501 return rs.getBlob(column);
502 }
503
504 /**
505 * Convert the specified column of the SQL ResultSet to the proper
506 * java type.
507 */
508 public Object getBlobObject(ResultSet rs, int column, JDBCStore store)
509 throws SQLException {
510 InputStream in = null;
511 if (useGetBytesForBlobs || useGetObjectForBlobs) {
512 byte[] bytes = getBytes(rs, column);
513 if (bytes != null && bytes.length > 0)
514 in = new ByteArrayInputStream(bytes);
515 } else {
516 Blob blob = getBlob(rs, column);
517 if (blob != null && blob.length() > 0)
518 in = blob.getBinaryStream();
519 }
520 if (in == null)
521 return null;
522
523 try {
524 if (store == null)
525 return Serialization.deserialize(in, null);
526 return Serialization.deserialize(in, store.getContext());
527 } finally {
528 try {
529 in.close();
530 } catch (IOException ioe) {
531 }
532 }
533 }
534
535 /**
536 * Convert the specified column of the SQL ResultSet to the proper
537 * java type.
538 */
539 public boolean getBoolean(ResultSet rs, int column)
540 throws SQLException {
541 return rs.getBoolean(column);
542 }
543
544 /**
545 * Convert the specified column of the SQL ResultSet to the proper
546 * java type.
547 */
548 public byte getByte(ResultSet rs, int column)
549 throws SQLException {
550 return rs.getByte(column);
551 }
552
553 /**
554 * Convert the specified column of the SQL ResultSet to the proper
555 * java type.
556 */
557 public byte[] getBytes(ResultSet rs, int column)
558 throws SQLException {
559 if (useGetBytesForBlobs)
560 return rs.getBytes(column);
561 if (useGetObjectForBlobs)
562 return (byte[]) rs.getObject(column);
563
564 Blob blob = getBlob(rs, column);
565 if (blob == null)
566 return null;
567 int length = (int) blob.length();
568 if (length == 0)
569 return null;
570 return blob.getBytes(1, length);
571 }
572
573 /**
574 * Convert the specified column of the SQL ResultSet to the proper
575 * java type. Converts the date from a {@link Timestamp} by default.
576 */
577 public Calendar getCalendar(ResultSet rs, int column)
578 throws SQLException {
579 Date d = getDate(rs, column);
580 if (d == null)
581 return null;
582
583 Calendar cal = Calendar.getInstance();
584 cal.setTime(d);
585 return cal;
586 }
587
588 /**
589 * Convert the specified column of the SQL ResultSet to the proper
590 * java type.
591 */
592 public char getChar(ResultSet rs, int column)
593 throws SQLException {
594 if (storeCharsAsNumbers)
595 return (char) getInt(rs, column);
596
597 String str = getString(rs, column);
598 return (StringUtils.isEmpty(str)) ? 0 : str.charAt(0);
599 }
600
601 /**
602 * Convert the specified column of the SQL ResultSet to the proper
603 * java type.
604 */
605 public Reader getCharacterStream(ResultSet rs, int column)
606 throws SQLException {
607 return rs.getCharacterStream(column);
608 }
609
610 /**
611 * Convert the specified column of the SQL ResultSet to the proper
612 * java type.
613 */
614 public Clob getClob(ResultSet rs, int column)
615 throws SQLException {
616 return rs.getClob(column);
617 }
618
619 /**
620 * Convert the specified column of the SQL ResultSet to the proper
621 * java type.
622 */
623 public String getClobString(ResultSet rs, int column)
624 throws SQLException {
625 if (useGetStringForClobs)
626 return rs.getString(column);
627
628 Clob clob = getClob(rs, column);
629 if (clob == null)
630 return null;
631 if (clob.length() == 0)
632 return "";
633
634 // unlikely that we'll have strings over Integer.MAX_VALUE chars
635 return clob.getSubString(1, (int) clob.length());
636 }
637
638 /**
639 * Convert the specified column of the SQL ResultSet to the proper
640 * java type. Converts the date from a {@link Timestamp} by default.
641 */
642 public Date getDate(ResultSet rs, int column)
643 throws SQLException {
644 Timestamp tstamp = getTimestamp(rs, column, null);
645 if (tstamp == null)
646 return null;
647
648 // get the fractional seconds component, rounding away anything beyond
649 // milliseconds
650 int fractional = (int) Math.round(tstamp.getNanos() / (double) MILLI);
651
652 // get the millis component; some JDBC drivers round this to the
653 // nearest second, while others do not
654 long millis = (tstamp.getTime() / 1000L) * 1000L;
655 return new Date(millis + fractional);
656 }
657
658 /**
659 * Convert the specified column of the SQL ResultSet to the proper
660 * java type.
661 */
662 public java.sql.Date getDate(ResultSet rs, int column, Calendar cal)
663 throws SQLException {
664 if (cal == null)
665 return rs.getDate(column);
666 return rs.getDate(column, cal);
667 }
668
669 /**
670 * Convert the specified column of the SQL ResultSet to the proper
671 * java type.
672 */
673 public double getDouble(ResultSet rs, int column)
674 throws SQLException {
675 return rs.getDouble(column);
676 }
677
678 /**
679 * Convert the specified column of the SQL ResultSet to the proper
680 * java type.
681 */
682 public float getFloat(ResultSet rs, int column)
683 throws SQLException {
684 return rs.getFloat(column);
685 }
686
687 /**
688 * Convert the specified column of the SQL ResultSet to the proper
689 * java type.
690 */
691 public int getInt(ResultSet rs, int column)
692 throws SQLException {
693 return rs.getInt(column);
694 }
695
696 /**
697 * Convert the specified column of the SQL ResultSet to the proper
698 * java type.
699 */
700 public Locale getLocale(ResultSet rs, int column)
701 throws SQLException {
702 String str = getString(rs, column);
703 if (StringUtils.isEmpty(str))
704 return null;
705
706 String[] params = Strings.split(str, "_", 3);
707 if (params.length < 3)
708 return null;
709 return new Locale(params[0], params[1], params[2]);
710 }
711
712 /**
713 * Convert the specified column of the SQL ResultSet to the proper
714 * java type.
715 */
716 public long getLong(ResultSet rs, int column)
717 throws SQLException {
718 return rs.getLong(column);
719 }
720
721 /**
722 * Convert the specified column of the SQL ResultSet to the proper
723 * java type.
724 */
725 public Object getObject(ResultSet rs, int column, Map map)
726 throws SQLException {
727 if (map == null)
728 return rs.getObject(column);
729 return rs.getObject(column, map);
730 }
731
732 /**
733 * Convert the specified column of the SQL ResultSet to the proper
734 * java type.
735 */
736 public Ref getRef(ResultSet rs, int column, Map map)
737 throws SQLException {
738 return rs.getRef(column);
739 }
740
741 /**
742 * Convert the specified column of the SQL ResultSet to the proper
743 * java type.
744 */
745 public short getShort(ResultSet rs, int column)
746 throws SQLException {
747 return rs.getShort(column);
748 }
749
750 /**
751 * Convert the specified column of the SQL ResultSet to the proper
752 * java type.
753 */
754 public String getString(ResultSet rs, int column)
755 throws SQLException {
756 return rs.getString(column);
757 }
758
759 /**
760 * Convert the specified column of the SQL ResultSet to the proper
761 * java type.
762 */
763 public Time getTime(ResultSet rs, int column, Calendar cal)
764 throws SQLException {
765 if (cal == null)
766 return rs.getTime(column);
767 return rs.getTime(column, cal);
768 }
769
770 /**
771 * Convert the specified column of the SQL ResultSet to the proper
772 * java type.
773 */
774 public Timestamp getTimestamp(ResultSet rs, int column, Calendar cal)
775 throws SQLException {
776 if (cal == null)
777 return rs.getTimestamp(column);
778 return rs.getTimestamp(column, cal);
779 }
780
781 //////////////////////////////
782 // PreparedStatement wrappers
783 //////////////////////////////
784
785 /**
786 * Set the given value as a parameter to the statement.
787 */
788 public void setArray(PreparedStatement stmnt, int idx, Array val,
789 Column col)
790 throws SQLException {
791 stmnt.setArray(idx, val);
792 }
793
794 /**
795 * Set the given value as a parameter to the statement.
796 */
797 public void setAsciiStream(PreparedStatement stmnt, int idx,
798 InputStream val, int length, Column col)
799 throws SQLException {
800 stmnt.setAsciiStream(idx, val, length);
801 }
802
803 /**
804 * Set the given value as a parameter to the statement.
805 */
806 public void setBigDecimal(PreparedStatement stmnt, int idx, BigDecimal val,
807 Column col)
808 throws SQLException {
809 if ((col != null && col.isCompatible(Types.VARCHAR, null, 0, 0))
810 || (col == null && storeLargeNumbersAsStrings))
811 setString(stmnt, idx, val.toString(), col);
812 else
813 stmnt.setBigDecimal(idx, val);
814 }
815
816 /**
817 * Set the given value as a parameter to the statement.
818 */
819 public void setBigInteger(PreparedStatement stmnt, int idx, BigInteger val,
820 Column col)
821 throws SQLException {
822 if ((col != null && col.isCompatible(Types.VARCHAR, null, 0, 0))
823 || (col == null && storeLargeNumbersAsStrings))
824 setString(stmnt, idx, val.toString(), col);
825 else
826 setBigDecimal(stmnt, idx, new BigDecimal(val), col);
827 }
828
829 /**
830 * Set the given value as a parameter to the statement.
831 */
832 public void setBinaryStream(PreparedStatement stmnt, int idx,
833 InputStream val, int length, Column col)
834 throws SQLException {
835 stmnt.setBinaryStream(idx, val, length);
836 }
837
838 /**
839 * Set the given value as a parameter to the statement.
840 */
841 public void setBlob(PreparedStatement stmnt, int idx, Blob val, Column col)
842 throws SQLException {
843 stmnt.setBlob(idx, val);
844 }
845
846 /**
847 * Set the given value as a parameter to the statement. Uses the
848 * {@link #serialize} method to serialize the value.
849 */
850 public void setBlobObject(PreparedStatement stmnt, int idx, Object val,
851 Column col, JDBCStore store)
852 throws SQLException {
853 setBytes(stmnt, idx, serialize(val, store), col);
854 }
855
856 /**
857 * Set the given value as a parameter to the statement.
858 */
859 public void setBoolean(PreparedStatement stmnt, int idx, boolean val,
860 Column col)
861 throws SQLException {
862 stmnt.setInt(idx, (val) ? 1 : 0);
863 }
864
865 /**
866 * Set the given value as a parameter to the statement.
867 */
868 public void setByte(PreparedStatement stmnt, int idx, byte val, Column col)
869 throws SQLException {
870 stmnt.setByte(idx, val);
871 }
872
873 /**
874 * Set the given value as a parameter to the statement.
875 */
876 public void setBytes(PreparedStatement stmnt, int idx, byte[] val,
877 Column col)
878 throws SQLException {
879 if (useSetBytesForBlobs)
880 stmnt.setBytes(idx, val);
881 else
882 setBinaryStream(stmnt, idx, new ByteArrayInputStream(val),
883 val.length, col);
884 }
885
886 /**
887 * Set the given value as a parameter to the statement.
888 */
889 public void setChar(PreparedStatement stmnt, int idx, char val, Column col)
890 throws SQLException {
891 if ((col != null && col.isCompatible(Types.INTEGER, null, 0, 0))
892 || (col == null && storeCharsAsNumbers))
893 setInt(stmnt, idx, (int) val, col);
894 else
895 setString(stmnt, idx, String.valueOf(val), col);
896 }
897
898 /**
899 * Set the given value as a parameter to the statement.
900 */
901 public void setCharacterStream(PreparedStatement stmnt, int idx,
902 Reader val, int length, Column col)
903 throws SQLException {
904 stmnt.setCharacterStream(idx, val, length);
905 }
906
907 /**
908 * Set the given value as a parameter to the statement.
909 */
910 public void setClob(PreparedStatement stmnt, int idx, Clob val, Column col)
911 throws SQLException {
912 stmnt.setClob(idx, val);
913 }
914
915 /**
916 * Set the given value as a parameter to the statement.
917 */
918 public void setClobString(PreparedStatement stmnt, int idx, String val,
919 Column col)
920 throws SQLException {
921 if (useSetStringForClobs)
922 stmnt.setString(idx, val);
923 else {
924 // set reader from string
925 StringReader in = new StringReader(val);
926 setCharacterStream(stmnt, idx, in, val.length(), col);
927 }
928 }
929
930 /**
931 * Set the given value as a parameter to the statement.
932 */
933 public void setDate(PreparedStatement stmnt, int idx, Date val, Column col)
934 throws SQLException {
935 if (col != null && col.getType() == Types.DATE)
936 setDate(stmnt, idx, new java.sql.Date(val.getTime()), null, col);
937 else if (col != null && col.getType() == Types.TIME)
938 setTime(stmnt, idx, new Time(val.getTime()), null, col);
939 else if (val instanceof Timestamp)
940 setTimestamp(stmnt, idx,(Timestamp) val, null, col);
941 else
942 setTimestamp(stmnt, idx, new Timestamp(val.getTime()), null, col);
943 }
944
945 /**
946 * Set the given value as a parameter to the statement.
947 */
948 public void setDate(PreparedStatement stmnt, int idx, java.sql.Date val,
949 Calendar cal, Column col)
950 throws SQLException {
951 if (cal == null)
952 stmnt.setDate(idx, val);
953 else
954 stmnt.setDate(idx, val, cal);
955 }
956
957 /**
958 * Set the given value as a parameter to the statement.
959 */
960 public void setCalendar(PreparedStatement stmnt, int idx, Calendar val,
961 Column col)
962 throws SQLException {
963 // by default we merely delegate the the Date parameter
964 setDate(stmnt, idx, val.getTime(), col);
965 }
966
967 /**
968 * Set the given value as a parameter to the statement.
969 */
970 public void setDouble(PreparedStatement stmnt, int idx, double val,
971 Column col)
972 throws SQLException {
973 stmnt.setDouble(idx, val);
974 }
975
976 /**
977 * Set the given value as a parameter to the statement.
978 */
979 public void setFloat(PreparedStatement stmnt, int idx, float val,
980 Column col)
981 throws SQLException {
982 stmnt.setFloat(idx, val);
983 }
984
985 /**
986 * Set the given value as a parameter to the statement.
987 */
988 public void setInt(PreparedStatement stmnt, int idx, int val, Column col)
989 throws SQLException {
990 stmnt.setInt(idx, val);
991 }
992
993 /**
994 * Set the given value as a parameter to the statement.
995 */
996 public void setLong(PreparedStatement stmnt, int idx, long val, Column col)
997 throws SQLException {
998 stmnt.setLong(idx, val);
999 }
1000
1001 /**
1002 * Set the given value as a parameter to the statement.
1003 */
1004 public void setLocale(PreparedStatement stmnt, int idx, Locale val,
1005 Column col)
1006 throws SQLException {
1007 setString(stmnt, idx, val.getLanguage() + "_" + val.getCountry()
1008 + "_" + val.getVariant(), col);
1009 }
1010
1011 /**
1012 * Set the given value as a parameters to the statement. The column
1013 * type will come from {@link Types}.
1014 */
1015 public void setNull(PreparedStatement stmnt, int idx, int colType,
1016 Column col)
1017 throws SQLException {
1018 stmnt.setNull(idx, colType);
1019 }
1020
1021 /**
1022 * Set the given value as a parameter to the statement.
1023 */
1024 public void setNumber(PreparedStatement stmnt, int idx, Number num,
1025 Column col)
1026 throws SQLException {
1027 // check for known floating point types to give driver a chance to
1028 // handle special numbers like NaN and infinity; bug #1053
1029 if (num instanceof Double)
1030 setDouble(stmnt, idx, ((Double) num).doubleValue(), col);
1031 else if (num instanceof Float)
1032 setFloat(stmnt, idx, ((Float) num).floatValue(), col);
1033 else
1034 setBigDecimal(stmnt, idx, new BigDecimal(num.toString()), col);
1035 }
1036
1037 /**
1038 * Set the given value as a parameters to the statement. The column
1039 * type will come from {@link Types}.
1040 */
1041 public void setObject(PreparedStatement stmnt, int idx, Object val,
1042 int colType, Column col)
1043 throws SQLException {
1044 if (colType == -1 || colType == Types.OTHER)
1045 stmnt.setObject(idx, val);
1046 else
1047 stmnt.setObject(idx, val, colType);
1048 }
1049
1050 /**
1051 * Set the given value as a parameter to the statement.
1052 */
1053 public void setRef(PreparedStatement stmnt, int idx, Ref val, Column col)
1054 throws SQLException {
1055 stmnt.setRef(idx, val);
1056 }
1057
1058 /**
1059 * Set the given value as a parameter to the statement.
1060 */
1061 public void setShort(PreparedStatement stmnt, int idx, short val,
1062 Column col)
1063 throws SQLException {
1064 stmnt.setShort(idx, val);
1065 }
1066
1067 /**
1068 * Set the given value as a parameter to the statement.
1069 */
1070 public void setString(PreparedStatement stmnt, int idx, String val,
1071 Column col)
1072 throws SQLException {
1073 stmnt.setString(idx, val);
1074 }
1075
1076 /**
1077 * Set the given value as a parameter to the statement.
1078 */
1079 public void setTime(PreparedStatement stmnt, int idx, Time val,
1080 Calendar cal, Column col)
1081 throws SQLException {
1082 if (cal == null)
1083 stmnt.setTime(idx, val);
1084 else
1085 stmnt.setTime(idx, val, cal);
1086 }
1087
1088 /**
1089 * Set the given value as a parameter to the statement.
1090 */
1091 public void setTimestamp(PreparedStatement stmnt, int idx,
1092 Timestamp val, Calendar cal, Column col)
1093 throws SQLException {
1094 // ensure that we do not insert dates at a greater precision than
1095 // that at which they will be returned by a SELECT
1096 int rounded = (int) Math.round(val.getNanos() /
1097 (double) datePrecision);
1098 int nanos = rounded * datePrecision;
1099 if (nanos > 999999999) {
1100 // rollover to next second
1101 val.setTime(val.getTime() + 1000);
1102 nanos = 0;
1103 }
1104
1105 if (supportsTimestampNanos)
1106 val.setNanos(nanos);
1107 else
1108 val.setNanos(0);
1109
1110 if (cal == null)
1111 stmnt.setTimestamp(idx, val);
1112 else
1113 stmnt.setTimestamp(idx, val, cal);
1114 }
1115
1116 /**
1117 * Set a column value into a prepared statement.
1118 *
1119 * @param stmnt the prepared statement to parameterize
1120 * @param idx the index of the parameter in the prepared statement
1121 * @param val the value of the column
1122 * @param col the column being set
1123 * @param type the field mapping type code for the value
1124 * @param store the store manager for the current context
1125 */
1126 public void setTyped(PreparedStatement stmnt, int idx, Object val,
1127 Column col, int type, JDBCStore store)
1128 throws SQLException {
1129 if (val == null) {
1130 setNull(stmnt, idx, (col == null) ? Types.OTHER : col.getType(),
1131 col);
1132 return;
1133 }
1134
1135 Sized s;
1136 Calendard c;
1137 switch (type) {
1138 case JavaTypes.BOOLEAN:
1139 case JavaTypes.BOOLEAN_OBJ:
1140 setBoolean(stmnt, idx, ((Boolean) val).booleanValue(), col);
1141 break;
1142 case JavaTypes.BYTE:
1143 case JavaTypes.BYTE_OBJ:
1144 setByte(stmnt, idx, ((Number) val).byteValue(), col);
1145 break;
1146 case JavaTypes.CHAR:
1147 case JavaTypes.CHAR_OBJ:
1148 setChar(stmnt, idx, ((Character) val).charValue(), col);
1149 break;
1150 case JavaTypes.DOUBLE:
1151 case JavaTypes.DOUBLE_OBJ:
1152 setDouble(stmnt, idx, ((Number) val).doubleValue(), col);
1153 break;
1154 case JavaTypes.FLOAT:
1155 case JavaTypes.FLOAT_OBJ:
1156 setFloat(stmnt, idx, ((Number) val).floatValue(), col);
1157 break;
1158 case JavaTypes.INT:
1159 case JavaTypes.INT_OBJ:
1160 setInt(stmnt, idx, ((Number) val).intValue(), col);
1161 break;
1162 case JavaTypes.LONG:
1163 case JavaTypes.LONG_OBJ:
1164 setLong(stmnt, idx, ((Number) val).longValue(), col);
1165 break;
1166 case JavaTypes.SHORT:
1167 case JavaTypes.SHORT_OBJ:
1168 setShort(stmnt, idx, ((Number) val).shortValue(), col);
1169 break;
1170 case JavaTypes.STRING:
1171 if (col != null && (col.getType() == Types.CLOB
1172 || col.getType() == Types.LONGVARCHAR))
1173 setClobString(stmnt, idx, (String) val, col);
1174 else
1175 setString(stmnt, idx, (String) val, col);
1176 break;
1177 case JavaTypes.OBJECT:
1178 setBlobObject(stmnt, idx, val, col, store);
1179 break;
1180 case JavaTypes.DATE:
1181 setDate(stmnt, idx, (Date) val, col);
1182 break;
1183 case JavaTypes.CALENDAR:
1184 setCalendar(stmnt, idx, (Calendar) val, col);
1185 break;
1186 case JavaTypes.BIGDECIMAL:
1187 setBigDecimal(stmnt, idx, (BigDecimal) val, col);
1188 break;
1189 case JavaTypes.BIGINTEGER:
1190 setBigInteger(stmnt, idx, (BigInteger) val, col);
1191 break;
1192 case JavaTypes.NUMBER:
1193 setNumber(stmnt, idx, (Number) val, col);
1194 break;
1195 case JavaTypes.LOCALE:
1196 setLocale(stmnt, idx, (Locale) val, col);
1197 break;
1198 case JavaSQLTypes.SQL_ARRAY:
1199 setArray(stmnt, idx, (Array) val, col);
1200 break;
1201 case JavaSQLTypes.ASCII_STREAM:
1202 s = (Sized) val;
1203 setAsciiStream(stmnt, idx, (InputStream) s.value, s.size, col);
1204 break;
1205 case JavaSQLTypes.BINARY_STREAM:
1206 s = (Sized) val;
1207 setBinaryStream(stmnt, idx, (InputStream) s.value, s.size, col);
1208 break;
1209 case JavaSQLTypes.BLOB:
1210 setBlob(stmnt, idx, (Blob) val, col);
1211 break;
1212 case JavaSQLTypes.BYTES:
1213 setBytes(stmnt, idx, (byte[]) val, col);
1214 break;
1215 case JavaSQLTypes.CHAR_STREAM:
1216 s = (Sized) val;
1217 setCharacterStream(stmnt, idx, (Reader) s.value, s.size, col);
1218 break;
1219 case JavaSQLTypes.CLOB:
1220 setClob(stmnt, idx, (Clob) val, col);
1221 break;
1222 case JavaSQLTypes.SQL_DATE:
1223 if (val instanceof Calendard) {
1224 c = (Calendard) val;
1225 setDate(stmnt, idx, (java.sql.Date) c.value, c.calendar,
1226 col);
1227 } else
1228 setDate(stmnt, idx, (java.sql.Date) val, null, col);
1229 break;
1230 case JavaSQLTypes.REF:
1231 setRef(stmnt, idx, (Ref) val, col);
1232 break;
1233 case JavaSQLTypes.TIME:
1234 if (val instanceof Calendard) {
1235 c = (Calendard) val;
1236 setTime(stmnt, idx, (Time) c.value, c.calendar, col);
1237 } else
1238 setTime(stmnt, idx, (Time) val, null, col);
1239 break;
1240 case JavaSQLTypes.TIMESTAMP:
1241 if (val instanceof Calendard) {
1242 c = (Calendard) val;
1243 setTimestamp(stmnt, idx, (Timestamp) c.value, c.calendar,
1244 col);
1245 } else
1246 setTimestamp(stmnt, idx, (Timestamp) val, null, col);
1247 break;
1248 default:
1249 if (col != null && (col.getType() == Types.BLOB
1250 || col.getType() == Types.VARBINARY))
1251 setBlobObject(stmnt, idx, val, col, store);
1252 else
1253 setObject(stmnt, idx, val, col.getType(), col);
1254 }
1255 }
1256
1257 /**
1258 * Set a completely unknown parameter into a prepared statement.
1259 */
1260 public void setUnknown(PreparedStatement stmnt, int idx, Object val,
1261 Column col)
1262 throws SQLException {
1263 Sized sized = null;
1264 Calendard cald = null;
1265 if (val instanceof Sized) {
1266 sized = (Sized) val;
1267 val = sized.value;
1268 } else if (val instanceof Calendard) {
1269 cald = (Calendard) val;
1270 val = cald.value;
1271 }
1272
1273 if (val == null)
1274 setNull(stmnt, idx, (col == null) ? Types.OTHER : col.getType(),
1275 col);
1276 else if (val instanceof String)
1277 setString(stmnt, idx, val.toString(), col);
1278 else if (val instanceof Integer)
1279 setInt(stmnt, idx, ((Integer) val).intValue(), col);
1280 else if (val instanceof Boolean)
1281 setBoolean(stmnt, idx, ((Boolean) val).booleanValue(), col);
1282 else if (val instanceof Long)
1283 setLong(stmnt, idx, ((Long) val).longValue(), col);
1284 else if (val instanceof Float)
1285 setFloat(stmnt, idx, ((Float) val).floatValue(), col);
1286 else if (val instanceof Double)
1287 setDouble(stmnt, idx, ((Double) val).doubleValue(), col);
1288 else if (val instanceof Byte)
1289 setByte(stmnt, idx, ((Byte) val).byteValue(), col);
1290 else if (val instanceof Character)
1291 setChar(stmnt, idx, ((Character) val).charValue(), col);
1292 else if (val instanceof Short)
1293 setShort(stmnt, idx, ((Short) val).shortValue(), col);
1294 else if (val instanceof Locale)
1295 setLocale(stmnt, idx, (Locale) val, col);
1296 else if (val instanceof BigDecimal)
1297 setBigDecimal(stmnt, idx, (BigDecimal) val, col);
1298 else if (val instanceof BigInteger)
1299 setBigInteger(stmnt, idx, (BigInteger) val, col);
1300 else if (val instanceof Array)
1301 setArray(stmnt, idx, (Array) val, col);
1302 else if (val instanceof Blob)
1303 setBlob(stmnt, idx, (Blob) val, col);
1304 else if (val instanceof byte[])
1305 setBytes(stmnt, idx, (byte[]) val, col);
1306 else if (val instanceof Clob)
1307 setClob(stmnt, idx, (Clob) val, col);
1308 else if (val instanceof Ref)
1309 setRef(stmnt, idx, (Ref) val, col);
1310 else if (val instanceof java.sql.Date)
1311 setDate(stmnt, idx, (java.sql.Date) val,
1312 (cald == null) ? null : cald.calendar, col);
1313 else if (val instanceof Timestamp)
1314 setTimestamp(stmnt, idx, (Timestamp) val,
1315 (cald == null) ? null : cald.calendar, col);
1316 else if (val instanceof Time)
1317 setTime(stmnt, idx, (Time) val,
1318 (cald == null) ? null : cald.calendar, col);
1319 else if (val instanceof Date)
1320 setDate(stmnt, idx, (Date) val, col);
1321 else if (val instanceof Calendar)
1322 setDate(stmnt, idx, ((Calendar) val).getTime(), col);
1323 else if (val instanceof Reader)
1324 setCharacterStream(stmnt, idx, (Reader) val,
1325 (sized == null) ? 0 : sized.size, col);
1326 else
1327 throw new UserException(_loc.get("bad-param", val.getClass()));
1328 }
1329
1330 /**
1331 * Return the serialized bytes for the given object.
1332 */
1333 public byte[] serialize(Object val, JDBCStore store)
1334 throws SQLException {
1335 if (val == null)
1336 return null;
1337 if (val instanceof SerializedData)
1338 return ((SerializedData) val).bytes;
1339 return Serialization.serialize(val, store.getContext());
1340 }
1341
1342 /**
1343 * Invoke the JDK 1.4 <code>setBytes</code> method on the given BLOB object.
1344 */
1345 public void putBytes(Object blob, byte[] data)
1346 throws SQLException {
1347 if (_setBytes == null) {
1348 try {
1349 _setBytes = blob.getClass().getMethod("setBytes",
1350 new Class[]{ long.class, byte[].class });
1351 } catch (Exception e) {
1352 throw new StoreException(e);
1353 }
1354 }
1355 invokePutLobMethod(_setBytes, blob,
1356 new Object[]{ Numbers.valueOf(1L), data });
1357 }
1358
1359 /**
1360 * Invoke the JDK 1.4 <code>setString</code> method on the given CLOB
1361 * object.
1362 */
1363 public void putString(Object clob, String data)
1364 throws SQLException {
1365 if (_setString == null) {
1366 try {
1367 _setString = clob.getClass().getMethod("setString",
1368 new Class[]{ long.class, String.class });
1369 } catch (Exception e) {
1370 throw new StoreException(e);
1371 }
1372 }
1373 invokePutLobMethod(_setString, clob,
1374 new Object[]{ Numbers.valueOf(1L), data });
1375 }
1376
1377 /**
1378 * Invoke the JDK 1.4 <code>setCharacterStream</code> method on the given
1379 * CLOB object.
1380 */
1381 public void putChars(Object clob, char[] data)
1382 throws SQLException {
1383 if (_setCharStream == null) {
1384 try {
1385 _setCharStream = clob.getClass().getMethod
1386 ("setCharacterStream", new Class[]{ long.class });
1387 } catch (Exception e) {
1388 throw new StoreException(e);
1389 }
1390 }
1391
1392 Writer writer = (Writer) invokePutLobMethod(_setCharStream, clob,
1393 new Object[]{ Numbers.valueOf(1L) });
1394 try {
1395 writer.write(data);
1396 writer.flush();
1397 } catch (IOException ioe) {
1398 throw new SQLException(ioe.toString());
1399 }
1400 }
1401
1402 /**
1403 * Invoke the given LOB method on the given target with the given data.
1404 */
1405 private static Object invokePutLobMethod(Method method, Object target,
1406 Object[] args)
1407 throws SQLException {
1408 try {
1409 return method.invoke(target, args);
1410 } catch (InvocationTargetException ite) {
1411 Throwable t = ite.getTargetException();
1412 if (t instanceof SQLException)
1413 throw(SQLException) t;
1414 throw new StoreException(t);
1415 } catch (Exception e) {
1416 throw new StoreException(e);
1417 }
1418 }
1419
1420 /**
1421 * Warn that a particular value could not be stored precisely.
1422 * After the first warning for a particular type, messages
1423 * will be turned into trace messages.
1424 */
1425 protected void storageWarning(Object orig, Object converted) {
1426 boolean warn;
1427 synchronized (this) {
1428 if (_precisionWarnedTypes == null)
1429 _precisionWarnedTypes = new HashSet();
1430 warn = _precisionWarnedTypes.add(orig.getClass());
1431 }
1432
1433 if (storageLimitationsFatal || (warn && log.isWarnEnabled())
1434 || (!warn && log.isTraceEnabled())) {
1435 Message msg = _loc.get("storage-restriction", new Object[]{
1436 platform,
1437 orig,
1438 orig.getClass().getName(),
1439 converted,
1440 });
1441
1442 if (storageLimitationsFatal)
1443 throw new StoreException(msg);
1444
1445 if (warn)
1446 log.warn(msg);
1447 else
1448 log.trace(msg);
1449 }
1450 }
1451
1452 /////////
1453 // Types
1454 /////////
1455
1456 /**
1457 * Return the preferred {@link Types} constant for the given
1458 * {@link JavaTypes} or {@link JavaSQLTypes} constant.
1459 */
1460 public int getJDBCType(int metaTypeCode, boolean lob) {
1461 if (lob) {
1462 switch (metaTypeCode) {
1463 case JavaTypes.STRING:
1464 case JavaSQLTypes.ASCII_STREAM:
1465 case JavaSQLTypes.CHAR_STREAM:
1466 return getPreferredType(Types.CLOB);
1467 default:
1468 return getPreferredType(Types.BLOB);
1469 }
1470 }
1471
1472 switch (metaTypeCode) {
1473 case JavaTypes.BOOLEAN:
1474 case JavaTypes.BOOLEAN_OBJ:
1475 return getPreferredType(Types.BIT);
1476 case JavaTypes.BYTE:
1477 case JavaTypes.BYTE_OBJ:
1478 return getPreferredType(Types.TINYINT);
1479 case JavaTypes.CHAR:
1480 case JavaTypes.CHAR_OBJ:
1481 if (storeCharsAsNumbers)
1482 return getPreferredType(Types.INTEGER);
1483 return getPreferredType(Types.CHAR);
1484 case JavaTypes.DOUBLE:
1485 case JavaTypes.DOUBLE_OBJ:
1486 return getPreferredType(Types.DOUBLE);
1487 case JavaTypes.FLOAT:
1488 case JavaTypes.FLOAT_OBJ:
1489 return getPreferredType(Types.REAL);
1490 case JavaTypes.INT:
1491 case JavaTypes.INT_OBJ:
1492 return getPreferredType(Types.INTEGER);
1493 case JavaTypes.LONG:
1494 case JavaTypes.LONG_OBJ:
1495 return getPreferredType(Types.BIGINT);
1496 case JavaTypes.SHORT:
1497 case JavaTypes.SHORT_OBJ:
1498 return getPreferredType(Types.SMALLINT);
1499 case JavaTypes.STRING:
1500 case JavaTypes.LOCALE:
1501 case JavaSQLTypes.ASCII_STREAM:
1502 case JavaSQLTypes.CHAR_STREAM:
1503 return getPreferredType(Types.VARCHAR);
1504 case JavaTypes.BIGINTEGER:
1505 if (storeLargeNumbersAsStrings)
1506 return getPreferredType(Types.VARCHAR);
1507 return getPreferredType(Types.BIGINT);
1508 case JavaTypes.BIGDECIMAL:
1509 if (storeLargeNumbersAsStrings)
1510 return getPreferredType(Types.VARCHAR);
1511 return getPreferredType(Types.DOUBLE);
1512 case JavaTypes.NUMBER:
1513 if (storeLargeNumbersAsStrings)
1514 return getPreferredType(Types.VARCHAR);
1515 return getPreferredType(Types.NUMERIC);
1516 case JavaTypes.CALENDAR:
1517 case JavaTypes.DATE:
1518 return getPreferredType(Types.TIMESTAMP);
1519 case JavaSQLTypes.SQL_ARRAY:
1520 return getPreferredType(Types.ARRAY);
1521 case JavaSQLTypes.BINARY_STREAM:
1522 case JavaSQLTypes.BLOB:
1523 case JavaSQLTypes.BYTES:
1524 return getPreferredType(Types.BLOB);
1525 case JavaSQLTypes.CLOB:
1526 return getPreferredType(Types.CLOB);
1527 case JavaSQLTypes.SQL_DATE:
1528 return getPreferredType(Types.DATE);
1529 case JavaSQLTypes.TIME:
1530 return getPreferredType(Types.TIME);
1531 case JavaSQLTypes.TIMESTAMP:
1532 return getPreferredType(Types.TIMESTAMP);
1533 default:
1534 return getPreferredType(Types.BLOB);
1535 }
1536 }
1537
1538 /**
1539 * Return the preferred {@link Types} type for the given one. Returns
1540 * the given type by default.
1541 */
1542 public int getPreferredType(int type) {
1543 return type;
1544 }
1545
1546 /**
1547 * Return the preferred database type name for the given column's type
1548 * from {@link Types}.
1549 */
1550 public String getTypeName(Column col) {
1551 if (!StringUtils.isEmpty(col.getTypeName()))
1552 return appendSize(col, col.getTypeName());
1553
1554 if (col.isAutoAssigned() && autoAssignTypeName != null)
1555 return appendSize(col, autoAssignTypeName);
1556
1557 return appendSize(col, getTypeName(col.getType()));
1558 }
1559
1560 /**
1561 * Returns the type name for the specific constant as defined
1562 * by {@link java.sql.Types}.
1563 *
1564 * @param type the type
1565 * @return the name for the type
1566 */
1567 public String getTypeName(int type) {
1568 switch (type) {
1569 case Types.ARRAY:
1570 return arrayTypeName;
1571 case Types.BIGINT:
1572 return bigintTypeName;
1573 case Types.BINARY:
1574 return binaryTypeName;
1575 case Types.BIT:
1576 return bitTypeName;
1577 case Types.BLOB:
1578 return blobTypeName;
1579 case Types.BOOLEAN:
1580 return booleanTypeName;
1581 case Types.CHAR:
1582 return charTypeName;
1583 case Types.CLOB:
1584 return clobTypeName;
1585 case Types.DATE:
1586 return dateTypeName;
1587 case Types.DECIMAL:
1588 return decimalTypeName;
1589 case Types.DISTINCT:
1590 return distinctTypeName;
1591 case Types.DOUBLE:
1592 return doubleTypeName;
1593 case Types.FLOAT:
1594 return floatTypeName;
1595 case Types.INTEGER:
1596 return integerTypeName;
1597 case Types.JAVA_OBJECT:
1598 return javaObjectTypeName;
1599 case Types.LONGVARBINARY:
1600 return longVarbinaryTypeName;
1601 case Types.LONGVARCHAR:
1602 return longVarcharTypeName;
1603 case Types.NULL:
1604 return nullTypeName;
1605 case Types.NUMERIC:
1606 return numericTypeName;
1607 case Types.OTHER:
1608 return otherTypeName;
1609 case Types.REAL:
1610 return realTypeName;
1611 case Types.REF:
1612 return refTypeName;
1613 case Types.SMALLINT:
1614 return smallintTypeName;
1615 case Types.STRUCT:
1616 return structTypeName;
1617 case Types.TIME:
1618 return timeTypeName;
1619 case Types.TIMESTAMP:
1620 return timestampTypeName;
1621 case Types.TINYINT:
1622 return tinyintTypeName;
1623 case Types.VARBINARY:
1624 return varbinaryTypeName;
1625 case Types.VARCHAR:
1626 return varcharTypeName;
1627 default:
1628 return otherTypeName;
1629 }
1630 }
1631
1632 /**
1633 * Helper method to add size properties to the specified type.
1634 * If present, the string "{0}" will be replaced with the size definition;
1635 * otherwise the size definition will be appended to the type name.
1636 * If your database has column types that don't allow size definitions,
1637 * override this method to return the unaltered type name for columns of
1638 * those types (or add the type names to the
1639 * <code>fixedSizeTypeNameSet</code>).
1640 *
1641 * <P>Some databases support "type modifiers" for example the unsigned
1642 * "modifier" in MySQL. In these cases the size should go between the type
1643 * and the "modifier", instead of after the modifier. For example
1644 * CREATE table FOO ( myint INT (10) UNSIGNED . . .) instead of
1645 * CREATE table FOO ( myint INT UNSIGNED (10) . . .).
1646 * Type modifiers should be added to <code>typeModifierSet</code> in
1647 * subclasses.
1648 */
1649 protected String appendSize(Column col, String typeName) {
1650 if (fixedSizeTypeNameSet.contains(typeName.toUpperCase()))
1651 return typeName;
1652 if (typeName.indexOf('(') != -1)
1653 return typeName;
1654
1655 String size = null;
1656 if (col.getSize() > 0) {
1657 StringBuffer buf = new StringBuffer(10);
1658 buf.append("(").append(col.getSize());
1659 if (col.getDecimalDigits() > 0)
1660 buf.append(", ").append(col.getDecimalDigits());
1661 buf.append(")");
1662 size = buf.toString();
1663 }
1664
1665 return insertSize(typeName, size);
1666 }
1667
1668 /**
1669 * Helper method that inserts a size clause for a given SQL type.
1670 *
1671 * @see appendSize
1672 *
1673 * @param typeName The SQL type ie INT
1674 * @param size The size clause ie (10)
1675 * @return The typeName + size clause. Usually the size clause will
1676 * be appended to typeName. If the typeName contains a
1677 * marker : {0} or if typeName contains a modifier the
1678 * size clause will be inserted appropriately.
1679 */
1680 protected String insertSize(String typeName, String size) {
1681 if (StringUtils.isEmpty(size)) {
1682 int idx = typeName.indexOf("{0}");
1683 if (idx != -1) {
1684 return typeName.substring(0, idx);
1685 }
1686 return typeName;
1687 }
1688
1689 int idx = typeName.indexOf("{0}");
1690 if (idx != -1) {
1691 // replace '{0}' with size
1692 String ret = typeName.substring(0, idx);
1693 if (size != null)
1694 ret = ret + size;
1695 if (typeName.length() > idx + 3)
1696 ret = ret + typeName.substring(idx + 3);
1697 return ret;
1698 }
1699 if (!typeModifierSet.isEmpty()) {
1700 String s;
1701 idx = typeName.length();
1702 int curIdx = -1;
1703 for (Iterator i = typeModifierSet.iterator(); i.hasNext();) {
1704 s = (String) i.next();
1705 if (typeName.toUpperCase().indexOf(s) != -1) {
1706 curIdx = typeName.toUpperCase().indexOf(s);
1707 if (curIdx != -1 && curIdx < idx) {
1708 idx = curIdx;
1709 }
1710 }
1711 }
1712 if(idx != typeName.length()) {
1713 String ret = typeName.substring(0, idx);
1714 ret = ret + size;
1715 ret = ret + ' ' + typeName.substring(idx);
1716 return ret;
1717 }
1718 }
1719 return typeName + size;
1720 }
1721
1722 ///////////
1723 // Selects
1724 ///////////
1725
1726 /**
1727 * Set the name of the join syntax to use: sql92, traditional, database
1728 */
1729 public void setJoinSyntax(String syntax) {
1730 if ("sql92".equals(syntax))
1731 joinSyntax = SYNTAX_SQL92;
1732 else if ("traditional".equals(syntax))
1733 joinSyntax = SYNTAX_TRADITIONAL;
1734 else if ("database".equals(syntax))
1735 joinSyntax = SYNTAX_DATABASE;
1736 else if (!StringUtils.isEmpty(syntax))
1737 throw new IllegalArgumentException(syntax);
1738 }
1739
1740 /**
1741 * Return a SQL string to act as a placeholder for the given column.
1742 */
1743 public String getPlaceholderValueString(Column col) {
1744 switch (col.getType()) {
1745 case Types.BIGINT:
1746 case Types.BIT:
1747 case Types.INTEGER:
1748 case Types.NUMERIC:
1749 case Types.SMALLINT:
1750 case Types.TINYINT:
1751 return "0";
1752 case Types.CHAR:
1753 return (storeCharsAsNumbers) ? "0" : "' '";
1754 case Types.CLOB:
1755 case Types.LONGVARCHAR:
1756 case Types.VARCHAR:
1757 return "''";
1758 case Types.DATE:
1759 return ZERO_DATE_STR;
1760 case Types.DECIMAL:
1761 case Types.DOUBLE:
1762 case Types.FLOAT:
1763 case Types.REAL:
1764 return "0.0";
1765 case Types.TIME:
1766 return ZERO_TIME_STR;
1767 case Types.TIMESTAMP:
1768 return ZERO_TIMESTAMP_STR;
1769 default:
1770 return "NULL";
1771 }
1772 }
1773
1774 /**
1775 * Create a SELECT COUNT statement in the proper join syntax for the
1776 * given instance.
1777 */
1778 public SQLBuffer toSelectCount(Select sel) {
1779 SQLBuffer selectSQL = new SQLBuffer(this);
1780 SQLBuffer from;
1781 sel.addJoinClassConditions();
1782 if (sel.getFromSelect() != null)
1783 from = getFromSelect(sel, false);
1784 else
1785 from = getFrom(sel, false);
1786 SQLBuffer where = getWhere(sel, false);
1787
1788 // if no grouping and no range, we might be able to get by without
1789 // a subselect
1790 if (sel.getGrouping() == null && sel.getStartIndex() == 0
1791 && sel.getEndIndex() == Long.MAX_VALUE) {
1792 // if the select has no identifier cols, use COUNT(*)
1793 List aliases = (!sel.isDistinct()) ? Collections.EMPTY_LIST
1794 : sel.getIdentifierAliases();
1795 if (aliases.isEmpty()) {
1796 selectSQL.append("COUNT(*)");
1797 return toSelect(selectSQL, null, from, where, null, null, null,
1798 false, false, 0, Long.MAX_VALUE);
1799 }
1800
1801 // if there is a single distinct col, use COUNT(DISTINCT col)
1802 if (aliases.size() == 1) {
1803 selectSQL.append("COUNT(DISTINCT ").
1804 append(aliases.get(0).toString()).append(")");
1805 return toSelect(selectSQL, null, from, where, null, null, null,
1806 false, false, 0, Long.MAX_VALUE);
1807 }
1808
1809 // can we combine distinct cols?
1810 if (distinctCountColumnSeparator != null) {
1811 selectSQL.append("COUNT(DISTINCT ");
1812 for (int i = 0; i < aliases.size(); i++) {
1813 if (i > 0) {
1814 selectSQL.append(" ");
1815 selectSQL.append(distinctCountColumnSeparator);
1816 selectSQL.append(" ");
1817 }
1818 selectSQL.append(aliases.get(i).toString());
1819 }
1820 selectSQL.append(")");
1821 return toSelect(selectSQL, null, from, where, null, null, null,
1822 false, false, 0, Long.MAX_VALUE);
1823 }
1824 }
1825
1826 // since we can't combine distinct cols, we have to perform an outer
1827 // COUNT(*) select using the original select as a subselect in the
1828 // FROM clause
1829 assertSupport(supportsSubselect, "SupportsSubselect");
1830
1831 SQLBuffer subSelect = getSelects(sel, true, false);
1832 SQLBuffer subFrom = from;
1833 from = new SQLBuffer(this);
1834 from.append("(");
1835 from.append(toSelect(subSelect, null, subFrom, where,
1836 sel.getGrouping(), sel.getHaving(), null, sel.isDistinct(),
1837 false, sel.getStartIndex(), sel.getEndIndex(), true));
1838 from.append(")");
1839 if (requiresAliasForSubselect)
1840 from.append(" ").append(Select.FROM_SELECT_ALIAS);
1841
1842 selectSQL.append("COUNT(*)");
1843 return toSelect(selectSQL, null, from, null, null, null, null,
1844 false, false, 0, Long.MAX_VALUE);
1845 }
1846
1847 /**
1848 * Create a DELETE statement for the specified Select. If the
1849 * database does not support the bulk delete statement (such as
1850 * cases where a subselect is required and the database doesn't support
1851 * subselects), this method should return null.
1852 */
1853 public SQLBuffer toDelete(ClassMapping mapping, Select sel,
1854 Object[] params) {
1855 return toBulkOperation(mapping, sel, null, params, null);
1856 }
1857
1858 public SQLBuffer toUpdate(ClassMapping mapping, Select sel,
1859 JDBCStore store, Object[] params, Map updates) {
1860 return toBulkOperation(mapping, sel, store, params, updates);
1861 }
1862
1863 /**
1864 * Returns the SQL for a bulk operation, either a DELETE or an UPDATE.
1865 *
1866 * @param mapping the mappng against which we are operating
1867 * @param sel the Select that will constitute the WHERE clause
1868 * @param store the current store
1869 * @param updateParams the Map that holds the update parameters; a null
1870 * value indicates that this is a delete operation
1871 * @return the SQLBuffer for the update, or <em>null</em> if it is not
1872 * possible to perform the bulk update
1873 */
1874 protected SQLBuffer toBulkOperation(ClassMapping mapping, Select sel,
1875 JDBCStore store, Object[] params, Map updateParams) {
1876 SQLBuffer sql = new SQLBuffer(this);
1877 if (updateParams == null) {
1878 if (requiresTargetForDelete) {
1879 sql.append("DELETE ");
1880 SQLBuffer deleteTargets = getDeleteTargets(sel);
1881 sql.append(deleteTargets);
1882 sql.append(" FROM ");
1883 } else {
1884 sql.append("DELETE FROM ");
1885 }
1886 }
1887 else
1888 sql.append("UPDATE ");
1889 sel.addJoinClassConditions();
1890
1891 // if there is only a single table in the select, then we can
1892 // just issue a single DELETE FROM TABLE WHERE <conditions>
1893 // statement; otherwise, since SQL doesn't allow deleting
1894 // from one of a multi-table select, we need to issue a subselect
1895 // like DELETE FROM TABLE WHERE EXISTS
1896 // (SELECT 1 FROM TABLE t0 WHERE t0.ID = TABLE.ID); also, some
1897 // databases do not allow aliases in delete statements, which
1898 // also causes us to use a subselect
1899 if (sel.getTableAliases().size() == 1 && supportsSubselect
1900 && allowsAliasInBulkClause) {
1901 SQLBuffer from;
1902 if (sel.getFromSelect() != null)
1903 from = getFromSelect(sel, false);
1904 else
1905 from = getFrom(sel, false);
1906
1907 sql.append(from);
1908 appendUpdates(sel, store, sql, params, updateParams,
1909 allowsAliasInBulkClause);
1910
1911 SQLBuffer where = sel.getWhere();
1912 if (where != null && !where.isEmpty()) {
1913 sql.append(" WHERE ");
1914 sql.append(where);
1915 }
1916 return sql;
1917 }
1918
1919 Table table = mapping.getTable();
1920 String tableName = getFullName(table, false);
1921
1922 // only use a subselect if the where is not empty; otherwise
1923 // an unqualified delete or update will work
1924 if (sel.getWhere() == null || sel.getWhere().isEmpty()) {
1925 sql.append(tableName);
1926 appendUpdates(sel, store, sql, params, updateParams, false);
1927 return sql;
1928 }
1929
1930 // we need to use a subselect if we are to bulk delete where
1931 // the select includes multiple tables; if the database
1932 // doesn't support it, then we need to sigal this by returning null
1933 if (!supportsSubselect || !supportsCorrelatedSubselect)
1934 return null;
1935
1936 Column[] pks = mapping.getPrimaryKeyColumns();
1937 sel.clearSelects();
1938 sel.setDistinct(true);
1939
1940 // if we have only a single PK, we can use a non-correlated
1941 // subquery (using an IN statement), which is much faster than
1942 // a correlated subquery (since a correlated subquery needs
1943 // to be executed once for each row in the table)
1944 if (pks.length == 1) {
1945 sel.select(pks[0]);
1946 sql.append(tableName);
1947 appendUpdates(sel, store, sql, params, updateParams, false);
1948 sql.append(" WHERE ").
1949 append(pks[0]).append(" IN (").
1950 append(sel.toSelect(false, null)).append(")");
1951 } else {
1952 sel.clearSelects();
1953 sel.setDistinct(false);
1954
1955 // since the select is using a correlated subquery, we
1956 // only need to select a bogus virtual column
1957 sel.select("1", null);
1958
1959 // add in the joins to the table
1960 Column[] cols = table.getPrimaryKey().getColumns();
1961 SQLBuffer buf = new SQLBuffer(this);
1962 buf.append("(");
1963 for (int i = 0; i < cols.length; i++) {
1964 if (i > 0)
1965 buf.append(" AND ");
1966
1967 // add in "t0.PK = MYTABLE.PK"
1968 buf.append(sel.getColumnAlias(cols[i], null)).append(" = ").
1969 append(table).append(catalogSeparator).append(cols[i]);
1970 }
1971 buf.append(")");
1972 sel.where(buf, null);
1973
1974 sql.append(tableName);
1975 appendUpdates(sel, store, sql, params, updateParams, false);
1976 sql.append(" WHERE EXISTS (").
1977 append(sel.toSelect(false, null)).append(")");
1978 }
1979 return sql;
1980 }
1981
1982 protected SQLBuffer getDeleteTargets(Select sel) {
1983 SQLBuffer deleteTargets = new SQLBuffer(this);
1984 Collection aliases = sel.getTableAliases();
1985 // Assumes aliases are of the form "TABLENAME t0"
1986 for (Iterator itr = aliases.iterator(); itr.hasNext();) {
1987 String tableAlias = itr.next().toString();
1988 int spaceIndex = tableAlias.indexOf(' ');
1989 if (spaceIndex > 0 && spaceIndex < tableAlias.length() - 1) {
1990 if (allowsAliasInBulkClause) {
1991 deleteTargets.append(tableAlias.substring(spaceIndex + 1));
1992 } else {
1993 deleteTargets.append(tableAlias.substring(0, spaceIndex));
1994 }
1995 } else {
1996 deleteTargets.append(tableAlias);
1997 }
1998 if (itr.hasNext())
1999 deleteTargets.append(", ");
2000 }
2001 return deleteTargets;
2002 }
2003
2004 protected void appendUpdates(Select sel, JDBCStore store, SQLBuffer sql,
2005 Object[] params, Map updateParams, boolean allowAlias) {
2006 if (updateParams == null || updateParams.size() == 0)
2007 return;
2008
2009 // manually build up the SET clause for the UPDATE statement
2010 sql.append(" SET ");
2011 ExpContext ctx = new ExpContext(store, params,
2012 store.getFetchConfiguration());
2013
2014 // If the updates map contains any version fields, assume that the
2015 // optimistic lock version data is being handled properly by the
2016 // caller. Otherwise, give the version indicator an opportunity to
2017 // add more update clauses as needed.
2018 boolean augmentUpdates = true;
2019
2020 for (Iterator i = updateParams.entrySet().iterator(); i.hasNext();) {
2021 Map.Entry next = (Map.Entry) i.next();
2022 Path path = (Path) next.getKey();
2023 FieldMapping fmd = (FieldMapping) path.last();
2024
2025 if (fmd.isVersion())
2026 augmentUpdates = false;
2027
2028 Val val = (Val) next.getValue();
2029
2030 Column col = fmd.getColumns()[0];
2031 if (allowAlias) {
2032 sql.append(sel.getColumnAlias(col));
2033 } else {
2034 sql.append(col.getName());
2035 }
2036 sql.append(" = ");
2037
2038 ExpState state = val.initialize(sel, ctx, 0);
2039 // JDBC Paths are always PCPaths; PCPath implements Val
2040 ExpState pathState = ((Val) path).initialize(sel, ctx, 0);
2041 calculateValue(val, sel, ctx, state, path, pathState);
2042
2043 // append the value with a null for the Select; i
2044 // indicates that the
2045 int length = val.length(sel, ctx, state);
2046 for (int j = 0; j < length; j++)
2047 val.appendTo((allowAlias) ? sel : null, ctx, state, sql, j);
2048
2049 if (i.hasNext())
2050 sql.append(", ");
2051 }
2052
2053 if (augmentUpdates) {
2054 Path path = (Path) updateParams.keySet().iterator().next();
2055 FieldMapping fm = (FieldMapping) path.last();
2056 ClassMapping meta = fm.getDeclaringMapping();
2057 Map updates = meta.getVersion().getBulkUpdateValues();
2058 for (Iterator iter = updates.entrySet().iterator();
2059 iter.hasNext(); ) {
2060 Map.Entry e = (Map.Entry) iter.next();
2061 Column col = (Column) e.getKey();
2062 String val = (String) e.getValue();
2063 sql.append(", ").append(col.getName())
2064 .append(" = ").append(val);
2065 }
2066 }
2067 }
2068
2069 /**
2070 * Create SQL to delete the contents of the specified tables.
2071 * The default implementation drops all non-deferred RESTRICT foreign key
2072 * constraints involving the specified tables, issues DELETE statements
2073 * against the tables, and then adds the dropped constraints back in.
2074 * Databases with more optimal ways of deleting the contents of several
2075 * tables should override this method.
2076 */
2077 public String[] getDeleteTableContentsSQL(Table[] tables) {
2078 Collection sql = new ArrayList();
2079
2080 // collect and drop non-deferred physical restrict constraints, and
2081 // collect the DELETE FROM statements
2082 Collection deleteSQL = new ArrayList(tables.length);
2083 Collection restrictConstraints = new LinkedHashSet();
2084 for (int i = 0; i < tables.length; i++) {
2085 ForeignKey[] fks = tables[i].getForeignKeys();
2086 for (int j = 0; j < fks.length; j++) {
2087 if (!fks[j].isLogical() && !fks[j].isDeferred()
2088 && fks[j].getDeleteAction() == ForeignKey.ACTION_RESTRICT)
2089 restrictConstraints.add(fks[j]);
2090 String[] constraintSQL = getDropForeignKeySQL(fks[j]);
2091 sql.addAll(Arrays.asList(constraintSQL));
2092 }
2093
2094 deleteSQL.add("DELETE FROM " + tables[i].getFullName());
2095 }
2096
2097 // add the delete statements after all the constraint mutations
2098 sql.addAll(deleteSQL);
2099
2100 // add the deleted constraints back to the schema
2101 for (Iterator iter = restrictConstraints.iterator(); iter.hasNext(); ) {
2102 String[] constraintSQL =
2103 getAddForeignKeySQL((ForeignKey) iter.next());
2104 sql.addAll(Arrays.asList(constraintSQL));
2105 }
2106
2107 return (String[]) sql.toArray(new String[sql.size()]);
2108 }
2109
2110 /**
2111 * Create a SELECT statement in the proper join syntax for the given
2112 * instance.
2113 */
2114 public SQLBuffer toSelect(Select sel, boolean forUpdate,
2115 JDBCFetchConfiguration fetch) {
2116 sel.addJoinClassConditions();
2117 boolean update = forUpdate && sel.getFromSelect() == null;
2118 SQLBuffer select = getSelects(sel, false, update);
2119 SQLBuffer ordering = null;
2120 if (!sel.isAggregate() || sel.getGrouping() != null)
2121 ordering = sel.getOrdering();
2122 SQLBuffer from;
2123 if (sel.getFromSelect() != null)
2124 from = getFromSelect(sel, forUpdate);
2125 else
2126 from = getFrom(sel, update);
2127 SQLBuffer where = getWhere(sel, update);
2128 return toSelect(select, fetch, from, where, sel.getGrouping(),
2129 sel.getHaving(), ordering, sel.isDistinct(), forUpdate,
2130 sel.getStartIndex(), sel.getEndIndex(), sel);
2131 }
2132
2133 /**
2134 * Return the portion of the select statement between the FROM keyword
2135 * and the WHERE keyword.
2136 */
2137 protected SQLBuffer getFrom(Select sel, boolean forUpdate) {
2138 SQLBuffer fromSQL = new SQLBuffer(this);
2139 Collection aliases = sel.getTableAliases();
2140 if (aliases.size() < 2 || sel.getJoinSyntax() != SYNTAX_SQL92) {
2141 for (Iterator itr = aliases.iterator(); itr.hasNext();) {
2142 fromSQL.append(itr.next().toString());
2143 if (forUpdate && tableForUpdateClause != null)
2144 fromSQL.append(" ").append(tableForUpdateClause);
2145 if (itr.hasNext())
2146 fromSQL.append(", ");
2147 }
2148 } else {
2149 Iterator itr = sel.getJoinIterator();
2150 boolean first = true;
2151 while (itr.hasNext()) {
2152 fromSQL.append(toSQL92Join((Join) itr.next(), forUpdate,
2153 first));
2154 first = false;
2155 }
2156 }
2157 return fromSQL;
2158 }
2159
2160 /**
2161 * Return the FROM clause for a select that selects from a tmp table
2162 * created by an inner select.
2163 */
2164 protected SQLBuffer getFromSelect(Select sel, boolean forUpdate) {
2165 SQLBuffer fromSQL = new SQLBuffer(this);
2166 fromSQL.append("(");
2167 fromSQL.append(toSelect(sel.getFromSelect(), forUpdate, null));
2168 fromSQL.append(")");
2169 if (requiresAliasForSubselect)
2170 fromSQL.append(" ").append(Select.FROM_SELECT_ALIAS);
2171 return fromSQL;
2172 }
2173
2174 /**
2175 * Return the WHERE portion of the select statement, or null if no where
2176 * conditions.
2177 */
2178 protected SQLBuffer getWhere(Select sel, boolean forUpdate) {
2179 Joins joins = sel.getJoins();
2180 if (sel.getJoinSyntax() == SYNTAX_SQL92
2181 || joins == null || joins.isEmpty())
2182 return sel.getWhere();
2183
2184 SQLBuffer where = new SQLBuffer(this);
2185 if (sel.getWhere() != null)
2186 where.append(sel.getWhere());
2187 if (joins != null)
2188 sel.append(where, joins);
2189 return where;
2190 }
2191
2192 /**
2193 * Use the given join instance to create SQL joining its tables in
2194 * the traditional style.
2195 */
2196 public SQLBuffer toTraditionalJoin(Join join) {
2197 ForeignKey fk = join.getForeignKey();
2198 if (fk == null)
2199 return null;
2200
2201 boolean inverse = join.isForeignKeyInversed();
2202 Column[] from = (inverse) ? fk.getPrimaryKeyColumns()
2203 : fk.getColumns();
2204 Column[] to = (inverse) ? fk.getColumns()
2205 : fk.getPrimaryKeyColumns();
2206
2207 // do column joins
2208 SQLBuffer buf = new SQLBuffer(this);
2209 int count = 0;
2210 for (int i = 0; i < from.length; i++, count++) {
2211 if (count > 0)
2212 buf.append(" AND ");
2213 buf.append(join.getAlias1()).append(".").append(from[i]);
2214 buf.append(" = ");
2215 buf.append(join.getAlias2()).append(".").append(to[i]);
2216 }
2217
2218 // do constant joins
2219 Column[] constCols = fk.getConstantColumns();
2220 for (int i = 0; i < constCols.length; i++, count++) {
2221 if (count > 0)
2222 buf.append(" AND ");
2223 if (inverse)
2224 buf.appendValue(fk.getConstant(constCols[i]), constCols[i]);
2225 else
2226 buf.append(join.getAlias1()).append(".").
2227 append(constCols[i]);
2228 buf.append(" = ");
2229
2230 if (inverse)
2231 buf.append(join.getAlias2()).append(".").
2232 append(constCols[i]);
2233 else
2234 buf.appendValue(fk.getConstant(constCols[i]), constCols[i]);
2235 }
2236
2237 Column[] constColsPK = fk.getConstantPrimaryKeyColumns();
2238 for (int i = 0; i < constColsPK.length; i++, count++) {
2239 if (count > 0)
2240 buf.append(" AND ");
2241 if (inverse)
2242 buf.append(join.getAlias1()).append(".").
2243 append(constColsPK[i]);
2244 else
2245 buf.appendValue(fk.getPrimaryKeyConstant(constColsPK[i]),
2246 constColsPK[i]);
2247 buf.append(" = ");
2248
2249 if (inverse)
2250 buf.appendValue(fk.getPrimaryKeyConstant(constColsPK[i]),
2251 constColsPK[i]);
2252 else
2253 buf.append(join.getAlias2()).append(".").
2254 append(constColsPK[i]);
2255 }
2256 return buf;
2257 }
2258
2259 /**
2260 * Use the given join instance to create SQL joining its tables in
2261 * the SQL92 style.
2262 */
2263 public SQLBuffer toSQL92Join(Join join, boolean forUpdate, boolean first) {
2264 SQLBuffer buf = new SQLBuffer(this);
2265 if (first) {
2266 buf.append(join.getTable1()).append(" ").
2267 append(join.getAlias1());
2268 if (forUpdate && tableForUpdateClause != null)
2269 buf.append(" ").append(tableForUpdateClause);
2270 }
2271
2272 buf.append(" ");
2273 if (join.getType() == Join.TYPE_OUTER)
2274 buf.append(outerJoinClause);
2275 else if (join.getType() == Join.TYPE_INNER)
2276 buf.append(innerJoinClause);
2277 else // cross
2278 buf.append(crossJoinClause);
2279 buf.append(" ");
2280
2281 buf.append(join.getTable2()).append(" ").append(join.getAlias2());
2282 if (forUpdate && tableForUpdateClause != null)
2283 buf.append(" ").append(tableForUpdateClause);
2284
2285 if (join.getForeignKey() != null)
2286 buf.append(" ON ").append(toTraditionalJoin(join));
2287 else if (requiresConditionForCrossJoin &&
2288 join.getType() == Join.TYPE_CROSS)
2289 buf.append(" ON (1 = 1)");
2290
2291 return buf;
2292 }
2293
2294 /**
2295 * Use the given join instance to create SQL joining its tables in
2296 * the database's native syntax. Throws an exception by default.
2297 */
2298 public SQLBuffer toNativeJoin(Join join) {
2299 throw new UnsupportedException();
2300 }
2301
2302 /**
2303 * Returns if the given foreign key can be eagerly loaded using other joins.
2304 */
2305 public boolean canOuterJoin(int syntax, ForeignKey fk) {
2306 return syntax != SYNTAX_TRADITIONAL;
2307 }
2308
2309 /**
2310 * Combine the given components into a SELECT statement.
2311 */
2312 public SQLBuffer toSelect(SQLBuffer selects, JDBCFetchConfiguration fetch,
2313 SQLBuffer from, SQLBuffer where, SQLBuffer group,
2314 SQLBuffer having, SQLBuffer order,
2315 boolean distinct, boolean forUpdate, long start, long end) {
2316 return toOperation(getSelectOperation(fetch), selects, from, where,
2317 group, having, order, distinct, start, end,
2318 getForUpdateClause(fetch, forUpdate, null));
2319 }
2320
2321 /**
2322 * Combine the given components into a SELECT statement.
2323 */
2324 public SQLBuffer toSelect(SQLBuffer selects, JDBCFetchConfiguration fetch,
2325 SQLBuffer from, SQLBuffer where, SQLBuffer group,
2326 SQLBuffer having, SQLBuffer order,
2327 boolean distinct, boolean forUpdate, long start, long end,
2328 boolean subselect) {
2329 return toOperation(getSelectOperation(fetch), selects, from, where,
2330 group, having, order, distinct, start, end,
2331 getForUpdateClause(fetch, forUpdate, null), subselect);
2332 }
2333
2334 public SQLBuffer toSelect(SQLBuffer selects, JDBCFetchConfiguration fetch,
2335 SQLBuffer from, SQLBuffer where, SQLBuffer group,
2336 SQLBuffer having, SQLBuffer order,
2337 boolean distinct, boolean forUpdate, long start, long end,
2338 boolean subselect, boolean checkTableForUpdate) {
2339 return toOperation(getSelectOperation(fetch), selects, from, where,
2340 group, having, order, distinct, start, end,
2341 getForUpdateClause(fetch, forUpdate, null), subselect, checkTableForUpdate);
2342 }
2343
2344 /**
2345 * Combine the given components into a SELECT statement.
2346 */
2347 public SQLBuffer toSelect(SQLBuffer selects, JDBCFetchConfiguration fetch,
2348 SQLBuffer from, SQLBuffer where, SQLBuffer group,
2349 SQLBuffer having, SQLBuffer order,
2350 boolean distinct, boolean forUpdate, long start, long end,
2351 Select sel) {
2352 return toOperation(getSelectOperation(fetch), selects, from, where,
2353 group, having, order, distinct, start, end,
2354 getForUpdateClause(fetch, forUpdate, sel));
2355 }
2356
2357 /**
2358 * Get the update clause for the query based on the
2359 * updateClause and isolationLevel hints
2360 */
2361 protected String getForUpdateClause(JDBCFetchConfiguration fetch,
2362 boolean isForUpdate, Select sel) {
2363 if (fetch != null && fetch.getIsolation() != -1) {
2364 throw new InvalidStateException(_loc.get(
2365 "isolation-level-config-not-supported", getClass().getName()));
2366 } else if (isForUpdate && !simulateLocking) {
2367 assertSupport(supportsSelectForUpdate, "SupportsSelectForUpdate");
2368 return forUpdateClause;
2369 } else {
2370 return null;
2371 }
2372 }
2373
2374 /**
2375 * Return the "SELECT" operation clause, adding any available hints, etc.
2376 */
2377 public String getSelectOperation(JDBCFetchConfiguration fetch) {
2378 return "SELECT";
2379 }
2380
2381 /**
2382 * Return the SQL for the given selecting operation.
2383 */
2384 public SQLBuffer toOperation(String op, SQLBuffer selects,
2385 SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having,
2386 SQLBuffer order, boolean distinct, long start, long end,
2387 String forUpdateClause) {
2388 return toOperation(op, selects, from, where, group, having, order,
2389 distinct, start, end, forUpdateClause, false);
2390 }
2391
2392 /**
2393 * Return the SQL for the given selecting operation.
2394 */
2395 public SQLBuffer toOperation(String op, SQLBuffer selects,
2396 SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having,
2397 SQLBuffer order, boolean distinct, long start, long end,
2398 String forUpdateClause, boolean subselect) {
2399 return toOperation(op, selects, from, where, group, having, order,
2400 distinct, start, end, forUpdateClause, subselect, false);
2401 }
2402
2403 /**
2404 * Return the SQL for the given selecting operation.
2405 */
2406 private SQLBuffer toOperation(String op, SQLBuffer selects,
2407 SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having,
2408 SQLBuffer order, boolean distinct, long start, long end,
2409 String forUpdateClause, boolean subselect, boolean checkTableForUpdate) {
2410 SQLBuffer buf = new SQLBuffer(this);
2411 buf.append(op);
2412
2413 boolean range = start != 0 || end != Long.MAX_VALUE;
2414 if (range && rangePosition == RANGE_PRE_DISTINCT)
2415 appendSelectRange(buf, start, end, subselect);
2416 if (distinct)
2417 buf.append(" DISTINCT");
2418 if (range && rangePosition == RANGE_POST_DISTINCT)
2419 appendSelectRange(buf, start, end, subselect);
2420
2421 buf.append(" ").append(selects).append(" FROM ").append(from);
2422
2423 if (checkTableForUpdate
2424 && (StringUtils.isEmpty(forUpdateClause) && !StringUtils
2425 .isEmpty(tableForUpdateClause))) {
2426 buf.append(" ").append(tableForUpdateClause);
2427 }
2428
2429 if (where != null && !where.isEmpty())
2430 buf.append(" WHERE ").append(where);
2431 if (group != null && !group.isEmpty())
2432 buf.append(" GROUP BY ").append(group);
2433 if (having != null && !having.isEmpty()) {
2434 assertSupport(supportsHaving, "SupportsHaving");
2435 buf.append(" HAVING ").append(having);
2436 }
2437 if (order != null && !order.isEmpty())
2438 buf.append(" ORDER BY ").append(order);
2439 if (range && rangePosition == RANGE_POST_SELECT)
2440 appendSelectRange(buf, start, end, subselect);
2441 if (forUpdateClause != null)
2442 buf.append(" ").append(forUpdateClause);
2443 if (range && rangePosition == RANGE_POST_LOCK)
2444 appendSelectRange(buf, start, end, subselect);
2445 return buf;
2446 }
2447
2448 /**
2449 * If this dictionary can select ranges,
2450 * use this method to append the range SQL.
2451 */
2452 protected void appendSelectRange(SQLBuffer buf, long start, long end,
2453 boolean subselect) {
2454 }
2455
2456 /**
2457 * Return the portion of the select statement between the SELECT keyword
2458 * and the FROM keyword.
2459 */
2460 protected SQLBuffer getSelects(Select sel, boolean distinctIdentifiers,
2461 boolean forUpdate) {
2462 // append the aliases for all the columns
2463 SQLBuffer selectSQL = new SQLBuffer(this);
2464 List aliases;
2465 if (distinctIdentifiers)
2466 aliases = sel.getIdentifierAliases();
2467 else
2468 aliases = sel.getSelectAliases();
2469
2470 Object alias;
2471 for (int i = 0; i < aliases.size(); i++) {
2472 alias = aliases.get(i);
2473 appendSelect(selectSQL, alias, sel, i);
2474 if (i < aliases.size() - 1)
2475 selectSQL.append(", ");
2476 }
2477 return selectSQL;
2478 }
2479
2480 /**
2481 * Append <code>elem</code> to <code>selectSQL</code>.
2482 * @param selectSQL The SQLBuffer to append to.
2483 * @param alias A {@link SQLBuffer} or a {@link String} to append.
2484 *
2485 * @since 1.1.0
2486 */
2487 protected void appendSelect(SQLBuffer selectSQL, Object elem, Select sel,
2488 int idx) {
2489 if (elem instanceof SQLBuffer)
2490 selectSQL.append((SQLBuffer) elem);
2491 else
2492 selectSQL.append(elem.toString());
2493 }
2494
2495 /**
2496 * Returns true if a "FOR UPDATE" clause can be used for the specified
2497 * Select object.
2498 */
2499 public boolean supportsLocking(Select sel) {
2500 if (sel.isAggregate())
2501 return false;
2502 if (!supportsSelectForUpdate)
2503 return false;
2504 if (!supportsLockingWithSelectRange && (sel.getStartIndex() != 0
2505 || sel.getEndIndex() != Long.MAX_VALUE))
2506 return false;
2507
2508 // only inner select is locked
2509 if (sel.getFromSelect() != null)
2510 sel = sel.getFromSelect();
2511
2512 if (!supportsLockingWithDistinctClause && sel.isDistinct())
2513 return false;
2514 if (!supportsLockingWithMultipleTables
2515 && sel.getTableAliases().size() > 1)
2516 return false;
2517 if (!supportsLockingWithOrderClause && sel.getOrdering() != null)
2518 return false;
2519 if (!supportsLockingWithOuterJoin || !supportsLockingWithInnerJoin) {
2520 for (Iterator itr = sel.getJoinIterator(); itr.hasNext();) {
2521 Join join = (Join) itr.next();
2522 if (!supportsLockingWithOuterJoin
2523 && join.getType() == Join.TYPE_OUTER)
2524 return false;
2525 if (!supportsLockingWithInnerJoin
2526 && join.getType() == Join.TYPE_INNER)
2527 return false;
2528 }
2529 }
2530 return true;
2531 }
2532
2533 /**
2534 * Return false if the given select requires a forward-only result set.
2535 */
2536 public boolean supportsRandomAccessResultSet(Select sel,
2537 boolean forUpdate) {
2538 return !sel.isAggregate();
2539 }
2540
2541 /**
2542 * Assert that the given dictionary flag is true. If it is not true,
2543 * throw an error saying that the given setting needs to return true for
2544 * the current operation to work.
2545 */
2546 public void assertSupport(boolean feature, String property) {
2547 if (!feature)
2548 throw new UnsupportedException(_loc.get("feature-not-supported",
2549 getClass(), property));
2550 }
2551
2552 ////////////////////
2553 // Query functions
2554 ////////////////////
2555
2556 /**
2557 * Invoke this database's substring function.
2558 *
2559 * @param buf the SQL buffer to write the substring invocation to
2560 * @param str a query value representing the target string
2561 * @param start a query value representing the start index
2562 * @param end a query value representing the end index, or null for none
2563 */
2564 public void substring(SQLBuffer buf, FilterValue str, FilterValue start,
2565 FilterValue end) {
2566 buf.append(substringFunctionName).append("(");
2567 str.appendTo(buf);
2568 buf.append(", ");
2569 if (start.getValue() instanceof Number) {
2570 long startLong = toLong(start);
2571 buf.append(Long.toString(startLong + 1));
2572 } else {
2573 buf.append("(");
2574 start.appendTo(buf);
2575 buf.append(" + 1)");
2576 }
2577 if (end != null) {
2578 buf.append(", ");
2579 if (start.getValue() instanceof Number
2580 && end.getValue() instanceof Number) {
2581 long startLong = toLong(start);
2582 long endLong = toLong(end);
2583 buf.append(Long.toString(endLong - startLong));
2584 } else {
2585 end.appendTo(buf);
2586 buf.append(" - (");
2587 start.appendTo(buf);
2588 buf.append(")");
2589 }
2590 }
2591 buf.append(")");
2592 }
2593
2594 long toLong(FilterValue litValue) {
2595 return ((Number) litValue.getValue()).longValue();
2596 }
2597
2598 /**
2599 * Invoke this database's indexOf function.
2600 *
2601 * @param buf the SQL buffer to write the indexOf invocation to
2602 * @param str a query value representing the target string
2603 * @param find a query value representing the search string
2604 * @param start a query value representing the start index, or null
2605 * to start at the beginning
2606 */
2607 public void indexOf(SQLBuffer buf, FilterValue str, FilterValue find,
2608 FilterValue start) {
2609 buf.append("(INSTR((");
2610 if (start != null)
2611 substring(buf, str, start, null);
2612 else
2613 str.appendTo(buf);
2614 buf.append("), (");
2615 find.appendTo(buf);
2616 buf.append(")) - 1");
2617 if (start != null) {
2618 buf.append(" + ");
2619 start.appendTo(buf);
2620 }
2621 buf.append(")");
2622 }
2623
2624 /**
2625 * Append the numeric parts of a mathematical function.
2626 *
2627 * @param buf the SQL buffer to write the math function
2628 * @param op the mathematical operation to perform
2629 * @param lhs the left hand side of the math function
2630 * @param rhs the right hand side of the math function
2631 */
2632 public void mathFunction(SQLBuffer buf, String op, FilterValue lhs,
2633 FilterValue rhs) {
2634 boolean castlhs = false;
2635 boolean castrhs = false;
2636 Class lc = Filters.wrap(lhs.getType());
2637 Class rc = Filters.wrap(rhs.getType());
2638 int type = 0;
2639 if (requiresCastForMathFunctions && (lc != rc
2640 || (lhs.isConstant() && rhs.isConstant()))) {
2641 Class c = Filters.promote(lc, rc);
2642 type = getJDBCType(JavaTypes.getTypeCode(c), false);
2643 if (type != Types.VARBINARY && type != Types.BLOB) {
2644 castlhs = (lhs.isConstant() && rhs.isConstant()) || lc != c;
2645 castrhs = (lhs.isConstant() && rhs.isConstant()) || rc != c;
2646 }
2647 }
2648
2649 boolean mod = "MOD".equals(op);
2650 if (mod) {
2651 if (supportsModOperator)
2652 op = "%";
2653 else
2654 buf.append(op);
2655 }
2656 buf.append("(");
2657
2658 if (castlhs)
2659 appendCast(buf, lhs, type);
2660 else
2661 lhs.appendTo(buf);
2662
2663 if (mod && !supportsModOperator)
2664 buf.append(", ");
2665 else
2666 buf.append(" ").append(op).append(" ");
2667
2668 if (castrhs)
2669 appendCast(buf, rhs, type);
2670 else
2671 rhs.appendTo(buf);
2672
2673 buf.append(")");
2674 }
2675
2676 /**
2677 * Append a comparison.
2678 *
2679 * @param buf the SQL buffer to write the comparison
2680 * @param op the comparison operation to perform
2681 * @param lhs the left hand side of the comparison
2682 * @param rhs the right hand side of the comparison
2683 */
2684 public void comparison(SQLBuffer buf, String op, FilterValue lhs,
2685 FilterValue rhs) {
2686 boolean lhsxml = lhs.getXPath() != null;
2687 boolean rhsxml = rhs.getXPath() != null;
2688 if (lhsxml || rhsxml) {
2689 appendXmlComparison(buf, op, lhs, rhs, lhsxml, rhsxml);
2690 return;
2691 }
2692 boolean castlhs = false;
2693 boolean castrhs = false;
2694 Class lc = Filters.wrap(lhs.getType());
2695 Class rc = Filters.wrap(rhs.getType());
2696 int type = 0;
2697 if (requiresCastForComparisons && (lc != rc
2698 || (lhs.isConstant() && rhs.isConstant()))) {
2699 Class c = Filters.promote(lc, rc);
2700 type = getJDBCType(JavaTypes.getTypeCode(c), false);
2701 if (type != Types.VARBINARY && type != Types.BLOB) {
2702 castlhs = (lhs.isConstant() && rhs.isConstant()) || lc != c;
2703 castrhs = (lhs.isConstant() && rhs.isConstant()) || rc != c;
2704 }
2705 }
2706
2707 if (castlhs)
2708 appendCast(buf, lhs, type);
2709 else
2710 lhs.appendTo(buf);
2711
2712 buf.append(" ").append(op).append(" ");
2713
2714 if (castrhs)
2715 appendCast(buf, rhs, type);
2716 else
2717 rhs.appendTo(buf);
2718 }
2719
2720 /**
2721 * If this dictionary supports XML type,
2722 * use this method to append xml predicate.
2723 */
2724 public void appendXmlComparison(SQLBuffer buf, String op, FilterValue lhs,
2725 FilterValue rhs, boolean lhsxml, boolean rhsxml) {
2726 assertSupport(supportsXMLColumn, "SupportsXMLColumn");
2727 }
2728
2729 /**
2730 * Append SQL for the given numeric value to the buffer, casting as needed.
2731 */
2732 protected void appendNumericCast(SQLBuffer buf, FilterValue val) {
2733 if (val.isConstant())
2734 appendCast(buf, val, Types.NUMERIC);
2735 else
2736 val.appendTo(buf);
2737 }
2738
2739 /**
2740 * Cast the specified value to the specified type.
2741 *
2742 * @param buf the buffer to append the cast to
2743 * @param val the value to cast
2744 * @param type the type of the case, e.g. {@link Types#NUMERIC}
2745 */
2746 public void appendCast(SQLBuffer buf, Object val, int type) {
2747 // Convert the cast function: "CAST({0} AS {1})"
2748 int firstParam = castFunction.indexOf("{0}");
2749 String pre = castFunction.substring(0, firstParam); // "CAST("
2750 String mid = castFunction.substring(firstParam + 3);
2751 int secondParam = mid.indexOf("{1}");
2752 String post;
2753 if (secondParam > -1) {
2754 post = mid.substring(secondParam + 3); // ")"
2755 mid = mid.substring(0, secondParam); // " AS "
2756 } else
2757 post = "";
2758
2759 buf.append(pre);
2760 if (val instanceof FilterValue)
2761 ((FilterValue) val).appendTo(buf);
2762 else if (val instanceof SQLBuffer)
2763 buf.append(((SQLBuffer) val));
2764 else
2765 buf.append(val.toString());
2766 buf.append(mid);
2767 buf.append(getTypeName(type));
2768 appendLength(buf, type);
2769 buf.append(post);
2770 }
2771
2772 protected void appendLength(SQLBuffer buf, int type) {
2773 }
2774
2775
2776 /**
2777 * add CAST for a function operator where operand is a param
2778 * @param func function name
2779 * @param val
2780 * @return updated func
2781 */
2782 public String addCastAsType(String func, Val val) {
2783 return null;
2784 }
2785
2786
2787 ///////////
2788 // DDL SQL
2789 ///////////
2790
2791 /**
2792 * Increment the reference count of any table components that this
2793 * dictionary adds that are not used by mappings. Does nothing by default.
2794 */
2795 public void refSchemaComponents(Table table) {
2796 }
2797
2798 /**
2799 * Returns the full name of the table, including the schema (delimited
2800 * by {@link #catalogSeparator}).
2801 */
2802 public String getFullName(Table table, boolean logical) {
2803 if (!useSchemaName || table.getSchemaName() == null)
2804 return table.getName();
2805 if (logical || ".".equals(catalogSeparator))
2806 return table.getFullName();
2807 return table.getSchemaName() + catalogSeparator + table.getName();
2808 }
2809
2810 /**
2811 * Returns the full name of the index, including the schema (delimited
2812 * by the result of {@link #catalogSeparator}).
2813 */
2814 public String getFullName(Index index) {
2815 if (!useSchemaName || index.getSchemaName() == null)
2816 return index.getName();
2817 if (".".equals(catalogSeparator))
2818 return index.getFullName();
2819 return index.getSchemaName() + catalogSeparator + index.getName();
2820 }
2821
2822 /**
2823 * Returns the full name of the sequence, including the schema (delimited
2824 * by the result of {@link #catalogSeparator}).
2825 */
2826 public String getFullName(Sequence seq) {
2827 if (!useSchemaName || seq.getSchemaName() == null)
2828 return seq.getName();
2829 if (".".equals(catalogSeparator))
2830 return seq.getFullName();
2831 return seq.getSchemaName() + catalogSeparator + seq.getName();
2832 }
2833
2834 /**
2835 * Make any necessary changes to the given table name to make it valid
2836 * for the current DB.
2837 */
2838 public String getValidTableName(String name, Schema schema) {
2839 while (name.startsWith("_"))
2840 name = name.substring(1);
2841 return makeNameValid(name, schema.getSchemaGroup(),
2842 maxTableNameLength, NAME_TABLE);
2843 }
2844
2845 /**
2846 * Make any necessary changes to the given sequence name to make it valid
2847 * for the current DB.
2848 */
2849 public String getValidSequenceName(String name, Schema schema) {
2850 while (name.startsWith("_"))
2851 name = name.substring(1);
2852 return makeNameValid("S_" + name, schema.getSchemaGroup(),
2853 maxTableNameLength, NAME_SEQUENCE);
2854 }
2855
2856 /**
2857 * Make any necessary changes to the given column name to make it valid
2858 * for the current DB. The column name will be made unique for the
2859 * specified table.
2860 */
2861 public String getValidColumnName(String name, Table table) {
2862 return getValidColumnName(name, table, true);
2863 }
2864
2865 /**
2866 * Make any necessary changes to the given column name to make it valid
2867 * for the current DB. If checkForUniqueness is true, the column name will
2868 * be made unique for the specified table.
2869 */
2870 public String getValidColumnName(String name, Table table,
2871 boolean checkForUniqueness) {
2872 while (name.startsWith("_"))
2873 name = name.substring(1);
2874 return makeNameValid(name, table, maxColumnNameLength, NAME_ANY,
2875 checkForUniqueness);
2876 }
2877
2878 /**
2879 * Make any necessary changes to the given primary key name to make it
2880 * valid for the current DB.
2881 */
2882 public String getValidPrimaryKeyName(String name, Table table) {
2883 while (name.startsWith("_"))
2884 name = name.substring(1);
2885 return makeNameValid("P_" + name, table.getSchema().getSchemaGroup(),
2886 maxConstraintNameLength, NAME_ANY);
2887 }
2888
2889 /**
2890 * Make any necessary changes to the given foreign key name to make it
2891 * valid for the current DB.
2892 */
2893 public String getValidForeignKeyName(String name, Table table,
2894 Table toTable) {
2895 while (name.startsWith("_"))
2896 name = name.substring(1);
2897 String tableName = table.getName();
2898 int len = Math.min(tableName.length(), 7);
2899 name = "F_" + shorten(tableName, len) + "_" + name;
2900 return makeNameValid(name, table.getSchema().getSchemaGroup(),
2901 maxConstraintNameLength, NAME_ANY);
2902 }
2903
2904 /**
2905 * Make any necessary changes to the given index name to make it valid
2906 * for the current DB.
2907 */
2908 public String getValidIndexName(String name, Table table) {
2909 while (name.startsWith("_"))
2910 name = name.substring(1);
2911 String tableName = table.getName();
2912 int len = Math.min(tableName.length(), 7);
2913 name = "I_" + shorten(tableName, len) + "_" + name;
2914 return makeNameValid(name, table.getSchema().getSchemaGroup(),
2915 maxIndexNameLength, NAME_ANY);
2916 }
2917
2918 /**
2919 * Make any necessary changes to the given unique constraint name to make
2920 * it valid for the current DB.
2921 */
2922 public String getValidUniqueName(String name, Table table) {
2923 while (name.startsWith("_"))
2924 name = name.substring(1);
2925 String tableName = table.getName();
2926 int len = Math.min(tableName.length(), 7);
2927 name = "U_" + shorten(tableName, len) + "_" + name;
2928 return makeNameValid(name, table.getSchema().getSchemaGroup(),
2929 maxConstraintNameLength, NAME_ANY);
2930 }
2931
2932 /**
2933 * Shorten the specified name to the specified target name. This will
2934 * be done by first stripping out the vowels, and then removing
2935 * characters from the middle of the word until it reaches the target
2936 * length.
2937 */
2938 protected static String shorten(String name, int targetLength) {
2939 if (name == null || name.length() <= targetLength)
2940 return name;
2941
2942 StringBuffer nm = new StringBuffer(name);
2943 while (nm.length() > targetLength) {
2944 if (!stripVowel(nm)) {
2945 // cut out the middle char
2946 nm.replace(nm.length() / 2, (nm.length() / 2) + 1, "");
2947 }
2948 }
2949 return nm.toString();
2950 }
2951
2952 /**
2953 * Remove vowels from the specified StringBuffer.
2954 *
2955 * @return true if any vowels have been removed
2956 */
2957 private static boolean stripVowel(StringBuffer name) {
2958 if (name == null || name.length() == 0)
2959 return false;
2960
2961 char[] vowels = { 'A', 'E', 'I', 'O', 'U', };
2962 for (int i = 0; i < vowels.length; i++) {
2963 int index = name.toString().toUpperCase().indexOf(vowels[i]);
2964 if (index != -1) {
2965 name.replace(index, index + 1, "");
2966 return true;
2967 }
2968 }
2969 return false;
2970 }
2971
2972 /**
2973 * Shortens the given name to the given maximum length, then checks that
2974 * it is not a reserved word. If it is reserved, appends a "0". If
2975 * the name conflicts with an existing schema component, the last
2976 * character is replace with '0', then '1', etc.
2977 * Note that the given max len may be 0 if the database metadata is
2978 * incomplete.
2979 */
2980 protected String makeNameValid(String name, NameSet set, int maxLen,
2981 int nameType) {
2982 return makeNameValid(name, set, maxLen, nameType, true);
2983 }
2984
2985 /**
2986 * Shortens the given name to the given maximum length, then checks that
2987 * it is not a reserved word. If it is reserved, appends a "0". If
2988 * the name conflicts with an existing schema component and uniqueness
2989 * checking is enabled, the last character is replace with '0', then
2990 * '1', etc.
2991 * Note that the given max len may be 0 if the database metadata is
2992 * incomplete.
2993 */
2994 protected String makeNameValid(String name, NameSet set, int maxLen,
2995 int nameType, boolean checkForUniqueness) {
2996 if (maxLen < 1)
2997 maxLen = 255;
2998 if (name.length() > maxLen)
2999 name = name.substring(0, maxLen);
3000 if (reservedWordSet.contains(name.toUpperCase())) {
3001 if (name.length() == maxLen)
3002 name = name.substring(0, name.length() - 1);
3003 name += "0";
3004 }
3005
3006 // now make sure the name is unique
3007 if (set != null && checkForUniqueness) {
3008 outer:
3009 for (int version = 1, chars = 1; true; version++) {
3010 // for table names, we check for the table itself in case the
3011 // name set is lazy about schema reflection
3012 switch (nameType) {
3013 case NAME_TABLE:
3014 if (!((SchemaGroup) set).isKnownTable(name))
3015 break outer;
3016 break;
3017 case NAME_SEQUENCE:
3018 if (!((SchemaGroup) set).isKnownSequence(name))
3019 break outer;
3020 break;
3021 default:
3022 if (!set.isNameTaken(name))
3023 break outer;
3024 }
3025
3026 // a single char for the version is probably enough, but might
3027 // as well be general about it...
3028 if (version > 1)
3029 name = name.substring(0, name.length() - chars);
3030 if (version >= Math.pow(10, chars))
3031 chars++;
3032 if (name.length() + chars > maxLen)
3033 name = name.substring(0, maxLen - chars);
3034 name = name + version;
3035 }
3036 }
3037 return name.toUpperCase();
3038 }
3039
3040 /**
3041 * Return a series of SQL statements to create the given table, complete
3042 * with columns. Indexes and constraints will be created separately.
3043 */
3044 public String[] getCreateTableSQL(Table table) {
3045 StringBuffer buf = new StringBuffer();
3046 buf.append("CREATE TABLE ").append(getFullName(table, false));
3047 if (supportsComments && table.hasComment()) {
3048 buf.append(" ");
3049 comment(buf, table.getComment());
3050 buf.append("\n (");
3051 } else {
3052 buf.append(" (");
3053 }
3054
3055 // do this before getting the columns so we know how to handle
3056 // the last comma
3057 StringBuffer endBuf = new StringBuffer();
3058 PrimaryKey pk = table.getPrimaryKey();
3059 String pkStr;
3060 if (pk != null) {
3061 pkStr = getPrimaryKeyConstraintSQL(pk);
3062 if (pkStr != null)
3063 endBuf.append(pkStr);
3064 }
3065
3066 Unique[] unqs = table.getUniques();
3067 String unqStr;
3068 for (int i = 0; i < unqs.length; i++) {
3069 unqStr = getUniqueConstraintSQL(unqs[i]);
3070 if (unqStr != null) {
3071 if (endBuf.length() > 0)
3072 endBuf.append(", ");
3073 endBuf.append(unqStr);
3074 }
3075 }
3076
3077 Column[] cols = table.getColumns();
3078 for (int i = 0; i < cols.length; i++) {
3079 buf.append(getDeclareColumnSQL(cols[i], false));
3080 if (i < cols.length - 1 || endBuf.length() > 0)
3081 buf.append(", ");
3082 if (supportsComments && cols[i].hasComment()) {
3083 comment(buf, cols[i].getComment());
3084 buf.append("\n ");
3085 }
3086 }
3087
3088 buf.append(endBuf.toString());
3089 buf.append(")");
3090 return new String[]{ buf.toString() };
3091 }
3092
3093 protected StringBuffer comment(StringBuffer buf, String comment) {
3094 return buf.append("-- ").append(comment);
3095 }
3096
3097 /**
3098 * Return a series of SQL statements to drop the given table. Indexes
3099 * will be dropped separately. Returns
3100 * <code>DROP TABLE <table name></code> by default.
3101 */
3102 public String[] getDropTableSQL(Table table) {
3103 String drop = MessageFormat.format(dropTableSQL, new Object[]{
3104 getFullName(table, false) });
3105 return new String[]{ drop };
3106 }
3107
3108 /**
3109 * Return a series of SQL statements to create the given sequence. Returns
3110 * <code>CREATE SEQUENCE <sequence name>[ START WITH <start>]
3111 * [ INCREMENT BY <increment>]</code> by default.
3112 */
3113 public String[] getCreateSequenceSQL(Sequence seq) {
3114 if (nextSequenceQuery == null)
3115 return null;
3116
3117 StringBuffer buf = new StringBuffer();
3118 buf.append("CREATE SEQUENCE ");
3119 buf.append(getFullName(seq));
3120 if (seq.getInitialValue() != 0)
3121 buf.append(" START WITH ").append(seq.getInitialValue());
3122 if (seq.getIncrement() > 1)
3123 buf.append(" INCREMENT BY ").append(seq.getIncrement());
3124 return new String[]{ buf.toString() };
3125 }
3126
3127 /**
3128 * Return a series of SQL statements to drop the given sequence. Returns
3129 * <code>DROP SEQUENCE <sequence name></code> by default.
3130 */
3131 public String[] getDropSequenceSQL(Sequence seq) {
3132 return new String[]{ "DROP SEQUENCE " + getFullName(seq) };
3133 }
3134
3135 /**
3136 * Return a series of SQL statements to create the given index. Returns
3137 * <code>CREATE [UNIQUE] INDEX <index name> ON <table name>
3138 * (<col list>)</code> by default.
3139 */
3140 public String[] getCreateIndexSQL(Index index) {
3141 StringBuffer buf = new StringBuffer();
3142 buf.append("CREATE ");
3143 if (index.isUnique())
3144 buf.append("UNIQUE ");
3145 buf.append("INDEX ").append(index.getName());
3146
3147 buf.append(" ON ").append(getFullName(index.getTable(), false));
3148 buf.append(" (").append(Strings.join(index.getColumns(), ", ")).
3149 append(")");
3150
3151 return new String[]{ buf.toString() };
3152 }
3153
3154 /**
3155 * Return a series of SQL statements to drop the given index. Returns
3156 * <code>DROP INDEX <index name></code> by default.
3157 */
3158 public String[] getDropIndexSQL(Index index) {
3159 return new String[]{ "DROP INDEX " + getFullName(index) };
3160 }
3161
3162 /**
3163 * Return a series of SQL statements to add the given column to
3164 * its table. Return an empty array if operation not supported. Returns
3165 * <code>ALTER TABLE <table name> ADD (<col dec>)</code>
3166 * by default.
3167 */
3168 public String[] getAddColumnSQL(Column column) {
3169 if (!supportsAlterTableWithAddColumn)
3170 return new String[0];
3171
3172 String dec = getDeclareColumnSQL(column, true);
3173 if (dec == null)
3174 return new String[0];
3175 return new String[]{ "ALTER TABLE "
3176 + getFullName(column.getTable(), false) + " ADD " + dec };
3177 }
3178
3179 /**
3180 * Return a series of SQL statements to drop the given column from
3181 * its table. Return an empty array if operation not supported. Returns
3182 * <code>ALTER TABLE <table name> DROP COLUMN <col name></code>
3183 * by default.
3184 */
3185 public String[] getDropColumnSQL(Column column) {
3186 if (!supportsAlterTableWithDropColumn)
3187 return new String[0];
3188 return new String[]{ "ALTER TABLE "
3189 + getFullName(column.getTable(), false)
3190 + " DROP COLUMN " + column };
3191 }
3192
3193 /**
3194 * Return a series of SQL statements to add the given primary key to
3195 * its table. Return an empty array if operation not supported.
3196 * Returns <code>ALTER TABLE <table name> ADD
3197 * <pk cons sql ></code> by default.
3198 */
3199 public String[] getAddPrimaryKeySQL(PrimaryKey pk) {
3200 String pksql = getPrimaryKeyConstraintSQL(pk);
3201 if (pksql == null)
3202 return new String[0];
3203 return new String[]{ "ALTER TABLE "
3204 + getFullName(pk.getTable(), false) + " ADD " + pksql };
3205 }
3206
3207 /**
3208 * Return a series of SQL statements to drop the given primary key from
3209 * its table. Return an empty array if operation not supported.
3210 * Returns <code>ALTER TABLE <table name> DROP CONSTRAINT
3211 * <pk name></code> by default.
3212 */
3213 public String[] getDropPrimaryKeySQL(PrimaryKey pk) {
3214 if (pk.getName() == null)
3215 return new String[0];
3216 return new String[]{ "ALTER TABLE "
3217 + getFullName(pk.getTable(), false)
3218 + " DROP CONSTRAINT " + pk.getName() };
3219 }
3220
3221 /**
3222 * Return a series of SQL statements to add the given foreign key to
3223 * its table. Return an empty array if operation not supported.
3224 * Returns <code>ALTER TABLE <table name> ADD
3225 * <fk cons sql ></code> by default.
3226 */
3227 public String[] getAddForeignKeySQL(ForeignKey fk) {
3228 String fkSQL = getForeignKeyConstraintSQL(fk);
3229 if (fkSQL == null)
3230 return new String[0];
3231 return new String[]{ "ALTER TABLE "
3232 + getFullName(fk.getTable(), false) + " ADD " + fkSQL };
3233 }
3234
3235 /**
3236 * Return a series of SQL statements to drop the given foreign key from
3237 * its table. Return an empty array if operation not supported.
3238 * Returns <code>ALTER TABLE <table name> DROP CONSTRAINT
3239 * <fk name></code> by default.
3240 */
3241 public String[] getDropForeignKeySQL(ForeignKey fk) {
3242 if (fk.getName() == null)
3243 return new String[0];
3244 return new String[]{ "ALTER TABLE "
3245 + getFullName(fk.getTable(), false)
3246 + " DROP CONSTRAINT " + fk.getName() };
3247 }
3248
3249 /**
3250 * Return the declaration SQL for the given column. This method is used
3251 * for each column from within {@link #getCreateTableSQL} and
3252 * {@link #getAddColumnSQL}.
3253 */
3254 protected String getDeclareColumnSQL(Column col, boolean alter) {
3255 StringBuffer buf = new StringBuffer();
3256 buf.append(col).append(" ");
3257 buf.append(getTypeName(col));
3258
3259 // can't add constraints to a column we're adding after table
3260 // creation, cause some data might already be inserted
3261 if (!alter) {
3262 if (col.getDefaultString() != null && !col.isAutoAssigned())
3263 buf.append(" DEFAULT ").append(col.getDefaultString());
3264 if (col.isNotNull())
3265 buf.append(" NOT NULL");
3266 }
3267 if (col.isAutoAssigned()) {
3268 if (!supportsAutoAssign)
3269 log.warn(_loc.get("invalid-autoassign", platform, col));
3270 else if (autoAssignClause != null)
3271 buf.append(" ").append(autoAssignClause);
3272 }
3273 return buf.toString();
3274 }
3275
3276 /**
3277 * Return the declaration SQL for the given primary key. This method is
3278 * used from within {@link #getCreateTableSQL} and
3279 * {@link #getAddPrimaryKeySQL}. Returns
3280 * <code>CONSTRAINT <pk name> PRIMARY KEY (<col list>)</code>
3281 * by default.
3282 */
3283 protected String getPrimaryKeyConstraintSQL(PrimaryKey pk) {
3284 // if we have disabled the creation of primary keys, abort here
3285 if (!createPrimaryKeys)
3286 return null;
3287
3288 String name = pk.getName();
3289 if (name != null && reservedWordSet.contains(name.toUpperCase()))
3290 name = null;
3291
3292 StringBuffer buf = new StringBuffer();
3293 if (name != null && CONS_NAME_BEFORE.equals(constraintNameMode))
3294 buf.append("CONSTRAINT ").append(name).append(" ");
3295 buf.append("PRIMARY KEY ");
3296 if (name != null && CONS_NAME_MID.equals(constraintNameMode))
3297 buf.append(name).append(" ");
3298 buf.append("(").append(Strings.join(pk.getColumns(), ", ")).
3299 append(")");
3300 if (name != null && CONS_NAME_AFTER.equals(constraintNameMode))
3301 buf.append(" CONSTRAINT ").append(name);
3302 return buf.toString();
3303 }
3304
3305 /**
3306 * Return the declaration SQL for the given foreign key, or null if it is
3307 * not supported. This method is used from within
3308 * {@link #getCreateTableSQL} and {@link #getAddForeignKeySQL}. Returns
3309 * <code>CONSTRAINT <cons name> FOREIGN KEY (<col list>)
3310 * REFERENCES <foreign table> (<col list>)
3311 * [ON DELETE <action>] [ON UPDATE <action>]</code> by default.
3312 */
3313 protected String getForeignKeyConstraintSQL(ForeignKey fk) {
3314 if (!supportsForeignKeys)
3315 return null;
3316 if (fk.getDeleteAction() == ForeignKey.ACTION_NONE)
3317 return null;
3318 if (fk.isDeferred() && !supportsDeferredForeignKeyConstraints())
3319 return null;
3320 if (!supportsDeleteAction(fk.getDeleteAction())
3321 || !supportsUpdateAction(fk.getUpdateAction()))
3322 return null;
3323
3324 Column[] locals = fk.getColumns();
3325 Column[] foreigns = fk.getPrimaryKeyColumns();
3326
3327 int delActionId = fk.getDeleteAction();
3328 if (delActionId == ForeignKey.ACTION_NULL) {
3329 for (int i = 0; i < locals.length; i++) {
3330 if (locals[i].isNotNull())
3331 delActionId = ForeignKey.ACTION_NONE;
3332 }
3333 }
3334
3335 String delAction = getActionName(delActionId);
3336 String upAction = getActionName(fk.getUpdateAction());
3337
3338 StringBuffer buf = new StringBuffer();
3339 if (fk.getName() != null
3340 && CONS_NAME_BEFORE.equals(constraintNameMode))
3341 buf.append("CONSTRAINT ").append(fk.getName()).append(" ");
3342 buf.append("FOREIGN KEY ");
3343 if (fk.getName() != null && CONS_NAME_MID.equals(constraintNameMode))
3344 buf.append(fk.getName()).append(" ");
3345 buf.append("(").append(Strings.join(locals, ", ")).append(")");
3346 buf.append(" REFERENCES ");
3347 buf.append(getFullName(foreigns[0].getTable(), false));
3348 buf.append(" (").append(Strings.join(foreigns, ", ")).append(")");
3349 if (delAction != null)
3350 buf.append(" ON DELETE ").append(delAction);
3351 if (upAction != null)
3352 buf.append(" ON UPDATE ").append(upAction);
3353 if (fk.isDeferred())
3354 buf.append(" INITIALLY DEFERRED");
3355 if (supportsDeferredForeignKeyConstraints())
3356 buf.append(" DEFERRABLE");
3357 if (fk.getName() != null
3358 && CONS_NAME_AFTER.equals(constraintNameMode))
3359 buf.append(" CONSTRAINT ").append(fk.getName());
3360 return buf.toString();
3361 }
3362
3363 /**
3364 * Whether or not this dictionary supports deferred foreign key constraints.
3365 * This implementation returns {@link #supportsUniqueConstraints}.
3366 *
3367 * @since 1.1.0
3368 */
3369 protected boolean supportsDeferredForeignKeyConstraints() {
3370 return supportsDeferredConstraints;
3371 }
3372
3373 /**
3374 * Return the name of the given foreign key action.
3375 */
3376 private String getActionName(int action) {
3377 switch (action) {
3378 case ForeignKey.ACTION_CASCADE:
3379 return "CASCADE";
3380 case ForeignKey.ACTION_NULL:
3381 return "SET NULL";
3382 case ForeignKey.ACTION_DEFAULT:
3383 return "SET DEFAULT";
3384 default:
3385 return null;
3386 }
3387 }
3388
3389 /**
3390 * Whether this database supports the given foreign key delete action.
3391 */
3392 public boolean supportsDeleteAction(int action) {
3393 if (action == ForeignKey.ACTION_NONE)
3394 return true;
3395 if (!supportsForeignKeys)
3396 return false;
3397 switch (action) {
3398 case ForeignKey.ACTION_RESTRICT:
3399 return supportsRestrictDeleteAction;
3400 case ForeignKey.ACTION_CASCADE:
3401 return supportsCascadeDeleteAction;
3402 case ForeignKey.ACTION_NULL:
3403 return supportsNullDeleteAction;
3404 case ForeignKey.ACTION_DEFAULT:
3405 return supportsDefaultDeleteAction;
3406 default:
3407 return false;
3408 }
3409 }
3410
3411 /**
3412 * Whether this database supports the given foreign key update action.
3413 */
3414 public boolean supportsUpdateAction(int action) {
3415 if (action == ForeignKey.ACTION_NONE)
3416 return true;
3417 if (!supportsForeignKeys)
3418 return false;
3419 switch (action) {
3420 case ForeignKey.ACTION_RESTRICT:
3421 return supportsRestrictUpdateAction;
3422 case ForeignKey.ACTION_CASCADE:
3423 return supportsCascadeUpdateAction;
3424 case ForeignKey.ACTION_NULL:
3425 return supportsNullUpdateAction;
3426 case ForeignKey.ACTION_DEFAULT:
3427 return supportsDefaultUpdateAction;
3428 default:
3429 return false;
3430 }
3431 }
3432
3433 /**
3434 * Return the declaration SQL for the given unique constraint. This
3435 * method is used from within {@link #getCreateTableSQL}.
3436 * Returns <code>CONSTRAINT <name> UNIQUE (<col list>)</code>
3437 * by default.
3438 */
3439 protected String getUniqueConstraintSQL(Unique unq) {
3440 if (!supportsUniqueConstraints
3441 || (unq.isDeferred() && !supportsDeferredUniqueConstraints()))
3442 return null;
3443
3444 StringBuffer buf = new StringBuffer();
3445 if (unq.getName() != null
3446 && CONS_NAME_BEFORE.equals(constraintNameMode))
3447 buf.append("CONSTRAINT ").append(unq.getName()).append(" ");
3448 buf.append("UNIQUE ");
3449 if (unq.getName() != null && CONS_NAME_MID.equals(constraintNameMode))
3450 buf.append(unq.getName()).append(" ");
3451 buf.append("(").append(Strings.join(unq.getColumns(), ", ")).
3452 append(")");
3453 if (unq.isDeferred())
3454 buf.append(" INITIALLY DEFERRED");
3455 if (supportsDeferredUniqueConstraints())
3456 buf.append(" DEFERRABLE");
3457 if (unq.getName() != null
3458 && CONS_NAME_AFTER.equals(constraintNameMode))
3459 buf.append(" CONSTRAINT ").append(unq.getName());
3460 return buf.toString();
3461 }
3462
3463 /**
3464 * Whether or not this dictionary supports deferred unique constraints.
3465 * This implementation returns {@link #supportsUniqueConstraints}.
3466 *
3467 * @since 1.1.0
3468 */
3469 protected boolean supportsDeferredUniqueConstraints() {
3470 return supportsDeferredConstraints;
3471 }
3472
3473 /////////////////////
3474 // Database metadata
3475 /////////////////////
3476
3477 /**
3478 * This method is used to filter system tables from database metadata.
3479 * Return true if the given table name represents a system table that
3480 * should not appear in the schema definition. By default, returns
3481 * true only if the given table is in the internal list of system tables,
3482 * or if the given schema is in the list of system schemas and is not
3483 * the target schema.
3484 *
3485 * @param name the table name
3486 * @param schema the table schema; may be null
3487 * @param targetSchema if true, then the given schema was listed by
3488 * the user as one of his schemas
3489 */
3490 public boolean isSystemTable(String name, String schema,
3491 boolean targetSchema) {
3492 if (systemTableSet.contains(name.toUpperCase()))
3493 return true;
3494 return !targetSchema && schema != null
3495 && systemSchemaSet.contains(schema.toUpperCase());
3496 }
3497
3498 /**
3499 * This method is used to filter system indexes from database metadata.
3500 * Return true if the given index name represents a system index that
3501 * should not appear in the schema definition. Returns false by default.
3502 *
3503 * @param name the index name
3504 * @param table the index table
3505 */
3506 public boolean isSystemIndex(String name, Table table) {
3507 return false;
3508 }
3509
3510 /**
3511 * This method is used to filter system sequences from database metadata.
3512 * Return true if the given sequence represents a system sequence that
3513 * should not appear in the schema definition. Returns true if system
3514 * schema by default.
3515 *
3516 * @param name the table name
3517 * @param schema the table schema; may be null
3518 * @param targetSchema if true, then the given schema was listed by
3519 * the user as one of his schemas
3520 */
3521 public boolean isSystemSequence(String name, String schema,
3522 boolean targetSchema) {
3523 return !targetSchema && schema != null
3524 && systemSchemaSet.contains(schema.toUpperCase());
3525 }
3526
3527 /**
3528 * Reflect on the schema to find tables matching the given name pattern.
3529 */
3530 public Table[] getTables(DatabaseMetaData meta, String catalog,
3531 String schemaName, String tableName, Connection conn)
3532 throws SQLException {
3533 if (!supportsSchemaForGetTables)
3534 schemaName = null;
3535 else
3536 schemaName = getSchemaNameForMetadata(schemaName);
3537
3538 String[] types = Strings.split(tableTypes, ",", 0);
3539 for (int i = 0; i < types.length; i++)
3540 types[i] = types[i].trim();
3541
3542 beforeMetadataOperation(conn);
3543 ResultSet tables = null;
3544 try {
3545 tables = meta.getTables(getCatalogNameForMetadata(catalog),
3546 schemaName, getTableNameForMetadata(tableName), types);
3547 List tableList = new ArrayList();
3548 while (tables != null && tables.next())
3549 tableList.add(newTable(tables));
3550 return (Table[]) tableList.toArray(new Table[tableList.size()]);
3551 } finally {
3552 if (tables != null)
3553 try {
3554 tables.close();
3555 } catch (Exception e) {
3556 }
3557 }
3558 }
3559
3560 /**
3561 * Create a new table from the information in the schema metadata.
3562 */
3563 protected Table newTable(ResultSet tableMeta)
3564 throws SQLException {
3565 Table t = new Table();
3566 t.setName(tableMeta.getString("TABLE_NAME"));
3567 return t;
3568 }
3569
3570 /**
3571 * Reflect on the schema to find sequences matching the given name pattern.
3572 * Returns an empty array by default, as there is no standard way to
3573 * retrieve a list of sequences.
3574 */
3575 public Sequence[] getSequences(DatabaseMetaData meta, String catalog,
3576 String schemaName, String sequenceName, Connection conn)
3577 throws SQLException {
3578 String str = getSequencesSQL(schemaName, sequenceName);
3579 if (str == null)
3580 return new Sequence[0];
3581
3582 PreparedStatement stmnt = prepareStatement(conn, str);
3583 ResultSet rs = null;
3584 try {
3585 int idx = 1;
3586 if (schemaName != null)
3587 stmnt.setString(idx++, schemaName.toUpperCase());
3588 if (sequenceName != null)
3589 stmnt.setString(idx++, sequenceName);
3590
3591 rs = executeQuery(conn, stmnt, str);
3592 return getSequence(rs);
3593 } finally {
3594 if (rs != null)
3595 try {
3596 rs.close();
3597 } catch (SQLException se) {
3598 }
3599 if (stmnt != null)
3600 try {
3601 stmnt.close();
3602 } catch (SQLException se) {
3603 }
3604 }
3605 }
3606
3607 /**
3608 * Create a new sequence from the information in the schema metadata.
3609 */
3610 protected Sequence newSequence(ResultSet sequenceMeta)
3611 throws SQLException {
3612 Sequence seq = new Sequence();
3613 seq.setSchemaName(sequenceMeta.getString("SEQUENCE_SCHEMA"));
3614 seq.setName(sequenceMeta.getString("SEQUENCE_NAME"));
3615 return seq;
3616 }
3617
3618 /**
3619 * Return the SQL needed to select the list of sequences.
3620 */
3621 protected String getSequencesSQL(String schemaName, String sequenceName) {
3622 return null;
3623 }
3624
3625 /**
3626 * Reflect on the schema to find columns matching the given table and
3627 * column patterns.
3628 */
3629 public Column[] getColumns(DatabaseMetaData meta, String catalog,
3630 String schemaName, String tableName, String columnName, Connection conn)
3631 throws SQLException {
3632 if (tableName == null && !supportsNullTableForGetColumns)
3633 return null;
3634
3635 if (!supportsSchemaForGetColumns)
3636 schemaName = null;
3637 else
3638 schemaName = getSchemaNameForMetadata(schemaName);
3639
3640 beforeMetadataOperation(conn);
3641 ResultSet cols = null;
3642 try {
3643 cols = meta.getColumns(getCatalogNameForMetadata(catalog),
3644 schemaName, getTableNameForMetadata(tableName),
3645 getColumnNameForMetadata(columnName));
3646
3647 List columnList = new ArrayList();
3648 while (cols != null && cols.next())
3649 columnList.add(newColumn(cols));
3650 return (Column[]) columnList.toArray
3651 (new Column[columnList.size()]);
3652 } finally {
3653 if (cols != null)
3654 try {
3655 cols.close();
3656 } catch (Exception e) {
3657 }
3658 }
3659 }
3660
3661 /**
3662 * Create a new column from the information in the schema metadata.
3663 */
3664 protected Column newColumn(ResultSet colMeta)
3665 throws SQLException {
3666 Column c = new Column();
3667 c.setSchemaName(colMeta.getString("TABLE_SCHEM"));
3668 c.setTableName(colMeta.getString("TABLE_NAME"));
3669 c.setName(colMeta.getString("COLUMN_NAME"));
3670 c.setType(colMeta.getInt("DATA_TYPE"));
3671 c.setTypeName(colMeta.getString("TYPE_NAME"));
3672 c.setSize(colMeta.getInt("COLUMN_SIZE"));
3673 c.setDecimalDigits(colMeta.getInt("DECIMAL_DIGITS"));
3674 c.setNotNull(colMeta.getInt("NULLABLE")
3675 == DatabaseMetaData.columnNoNulls);
3676
3677 String def = colMeta.getString("COLUMN_DEF");
3678 if (!StringUtils.isEmpty(def) && !"null".equalsIgnoreCase(def))
3679 c.setDefaultString(def);
3680 return c;
3681 }
3682
3683 /**
3684 * Reflect on the schema to find primary keys for the given table pattern.
3685 */
3686 public PrimaryKey[] getPrimaryKeys(DatabaseMetaData meta,
3687 String catalog, String schemaName, String tableName, Connection conn)
3688 throws SQLException {
3689 if (useGetBestRowIdentifierForPrimaryKeys)
3690 return getPrimaryKeysFromBestRowIdentifier(meta, catalog,
3691 schemaName, tableName, conn);
3692 return getPrimaryKeysFromGetPrimaryKeys(meta, catalog,
3693 schemaName, tableName, conn);
3694 }
3695
3696 /**
3697 * Reflect on the schema to find primary keys for the given table pattern.
3698 */
3699 protected PrimaryKey[] getPrimaryKeysFromGetPrimaryKeys
3700 (DatabaseMetaData meta, String catalog, String schemaName,
3701 String tableName, Connection conn)
3702 throws SQLException {
3703 if (tableName == null && !supportsNullTableForGetPrimaryKeys)
3704 return null;
3705
3706 beforeMetadataOperation(conn);
3707 ResultSet pks = null;
3708 try {
3709 pks = meta.getPrimaryKeys(getCatalogNameForMetadata(catalog),
3710 getSchemaNameForMetadata(schemaName),
3711 getTableNameForMetadata(tableName));
3712
3713 List pkList = new ArrayList();
3714 while (pks != null && pks.next())
3715 pkList.add(newPrimaryKey(pks));
3716 return (PrimaryKey[]) pkList.toArray
3717 (new PrimaryKey[pkList.size()]);
3718 } finally {
3719 if (pks != null)
3720 try {
3721 pks.close();
3722 } catch (Exception e) {
3723 }
3724 }
3725 }
3726
3727 /**
3728 * Create a new primary key from the information in the schema metadata.
3729 */
3730 protected PrimaryKey newPrimaryKey(ResultSet pkMeta)
3731 throws SQLException {
3732 PrimaryKey pk = new PrimaryKey();
3733 pk.setSchemaName(pkMeta.getString("TABLE_SCHEM"));
3734 pk.setTableName(pkMeta.getString("TABLE_NAME"));
3735 pk.setColumnName(pkMeta.getString("COLUMN_NAME"));
3736 pk.setName(pkMeta.getString("PK_NAME"));
3737 return pk;
3738 }
3739
3740 /**
3741 * Reflect on the schema to find primary keys for the given table pattern.
3742 */
3743 protected PrimaryKey[] getPrimaryKeysFromBestRowIdentifier
3744 (DatabaseMetaData meta, String catalog, String schemaName,
3745 String tableName, Connection conn)
3746 throws SQLException {
3747 if (tableName == null)
3748 return null;
3749
3750 beforeMetadataOperation(conn);
3751 ResultSet pks = null;
3752 try {
3753 pks = meta.getBestRowIdentifier(catalog, schemaName,
3754 tableName, 0, false);
3755
3756 List pkList = new ArrayList();
3757 while (pks != null && pks.next()) {
3758 PrimaryKey pk = new PrimaryKey();
3759 pk.setSchemaName(schemaName);
3760 pk.setTableName(tableName);
3761 pk.setColumnName(pks.getString("COLUMN_NAME"));
3762 pkList.add(pk);
3763 }
3764 return (PrimaryKey[]) pkList.toArray
3765 (new PrimaryKey[pkList.size()]);
3766 } finally {
3767 if (pks != null)
3768 try {
3769 pks.close();
3770 } catch (Exception e) {
3771 }
3772 }
3773 }
3774
3775 /**
3776 * Reflect on the schema to find indexes matching the given table pattern.
3777 */
3778 public Index[] getIndexInfo(DatabaseMetaData meta, String catalog,
3779 String schemaName, String tableName, boolean unique,
3780 boolean approx, Connection conn)
3781 throws SQLException {
3782 if (tableName == null && !supportsNullTableForGetIndexInfo)
3783 return null;
3784
3785 beforeMetadataOperation(conn);
3786 ResultSet indexes = null;
3787 try {
3788 indexes = meta.getIndexInfo(getCatalogNameForMetadata(catalog),
3789 getSchemaNameForMetadata(schemaName),
3790 getTableNameForMetadata(tableName), unique, approx);
3791
3792 List indexList = new ArrayList();
3793 while (indexes != null && indexes.next())
3794 indexList.add(newIndex(indexes));
3795 return (Index[]) indexList.toArray(new Index[indexList.size()]);
3796 } finally {
3797 if (indexes != null)
3798 try {
3799 indexes.close();
3800 } catch (Exception e) {
3801 }
3802 }
3803 }
3804
3805 /**
3806 * Create a new index from the information in the schema metadata.
3807 */
3808 protected Index newIndex(ResultSet idxMeta)
3809 throws SQLException {
3810 Index idx = new Index();
3811 idx.setSchemaName(idxMeta.getString("TABLE_SCHEM"));
3812 idx.setTableName(idxMeta.getString("TABLE_NAME"));
3813 idx.setColumnName(idxMeta.getString("COLUMN_NAME"));
3814 idx.setName(idxMeta.getString("INDEX_NAME"));
3815 idx.setUnique(!idxMeta.getBoolean("NON_UNIQUE"));
3816 return idx;
3817 }
3818
3819 /**
3820 * Reflect on the schema to return foreign keys imported by the given
3821 * table pattern.
3822 */
3823 public ForeignKey[] getImportedKeys(DatabaseMetaData meta, String catalog,
3824 String schemaName, String tableName, Connection conn)
3825 throws SQLException {
3826 if (!supportsForeignKeys)
3827 return null;
3828 if (tableName == null && !supportsNullTableForGetImportedKeys)
3829 return null;
3830
3831 beforeMetadataOperation(conn);
3832 ResultSet keys = null;
3833 try {
3834 keys = meta.getImportedKeys(getCatalogNameForMetadata(catalog),
3835 getSchemaNameForMetadata(schemaName),
3836 getTableNameForMetadata(tableName));
3837
3838 List importedKeyList = new ArrayList();
3839 while (keys != null && keys.next())
3840 importedKeyList.add(newForeignKey(keys));
3841 return (ForeignKey[]) importedKeyList.toArray
3842 (new ForeignKey[importedKeyList.size()]);
3843 } finally {
3844 if (keys != null)
3845 try {
3846 keys.close();
3847 } catch (Exception e) {
3848 }
3849 }
3850 }
3851
3852 /**
3853 * Create a new foreign key from the information in the schema metadata.
3854 */
3855 protected ForeignKey newForeignKey(ResultSet fkMeta)
3856 throws SQLException {
3857 ForeignKey fk = new ForeignKey();
3858 fk.setSchemaName(fkMeta.getString("FKTABLE_SCHEM"));
3859 fk.setTableName(fkMeta.getString("FKTABLE_NAME"));
3860 fk.setColumnName(fkMeta.getString("FKCOLUMN_NAME"));
3861 fk.setName(fkMeta.getString("FK_NAME"));
3862 fk.setPrimaryKeySchemaName(fkMeta.getString("PKTABLE_SCHEM"));
3863 fk.setPrimaryKeyTableName(fkMeta.getString("PKTABLE_NAME"));
3864 fk.setPrimaryKeyColumnName(fkMeta.getString("PKCOLUMN_NAME"));
3865 fk.setKeySequence(fkMeta.getShort("KEY_SEQ"));
3866 fk.setDeferred(fkMeta.getShort("DEFERRABILITY")
3867 == DatabaseMetaData.importedKeyInitiallyDeferred);
3868
3869 int del = fkMeta.getShort("DELETE_RULE");
3870 switch (del) {
3871 case DatabaseMetaData.importedKeySetNull:
3872 fk.setDeleteAction(ForeignKey.ACTION_NULL);
3873 break;
3874 case DatabaseMetaData.importedKeySetDefault:
3875 fk.setDeleteAction(ForeignKey.ACTION_DEFAULT);
3876 break;
3877 case DatabaseMetaData.importedKeyCascade:
3878 fk.setDeleteAction(ForeignKey.ACTION_CASCADE);
3879 break;
3880 default:
3881 fk.setDeleteAction(ForeignKey.ACTION_RESTRICT);
3882 break;
3883 }
3884 return fk;
3885 }
3886
3887 /**
3888 * Returns the table name that will be used for obtaining information
3889 * from {@link DatabaseMetaData}.
3890 */
3891 protected String getTableNameForMetadata(String tableName) {
3892 return convertSchemaCase(tableName);
3893 }
3894
3895 /**
3896 * Returns the schema name that will be used for obtaining information
3897 * from {@link DatabaseMetaData}.
3898 */
3899 protected String getSchemaNameForMetadata(String schemaName) {
3900 if (schemaName == null)
3901 schemaName = conf.getSchema();
3902 return convertSchemaCase(schemaName);
3903 }
3904
3905 /**
3906 * Returns the catalog name that will be used for obtaining information
3907 * from {@link DatabaseMetaData}.
3908 */
3909 protected String getCatalogNameForMetadata(String catalogName) {
3910 return convertSchemaCase(catalogName);
3911 }
3912
3913 /**
3914 * Returns the column name that will be used for obtaining information
3915 * from {@link DatabaseMetaData}.
3916 */
3917 protected String getColumnNameForMetadata(String columnName) {
3918 return convertSchemaCase(columnName);
3919 }
3920
3921 /**
3922 * Convert the specified schema name to a name that the database will
3923 * be able to understand.
3924 */
3925 protected String convertSchemaCase(String objectName) {
3926 if (objectName == null)
3927 return null;
3928
3929 String scase = getSchemaCase();
3930 if (SCHEMA_CASE_LOWER.equals(scase))
3931 return objectName.toLowerCase();
3932 if (SCHEMA_CASE_PRESERVE.equals(scase))
3933 return objectName;
3934 return objectName.toUpperCase();
3935 }
3936
3937 /**
3938 * Return DB specific schemaCase
3939 */
3940 public String getSchemaCase(){
3941 return schemaCase;
3942 }
3943
3944 /**
3945 * Prepared the connection for metadata operations.
3946 */
3947 private void beforeMetadataOperation(Connection c) {
3948 if (requiresAutoCommitForMetaData) {
3949 try {
3950 c.rollback();
3951 } catch (SQLException sqle) {
3952 }
3953 try {
3954 if (!c.getAutoCommit())
3955 c.setAutoCommit(true);
3956 } catch (SQLException sqle) {
3957 }
3958 }
3959 }
3960
3961 /////////////////////////////
3962 // Sequences and Auto-Assign
3963 /////////////////////////////
3964
3965 /**
3966 * Return the last generated value for the given column.
3967 * Throws an exception by default if {@link #lastGeneratedKeyQuery} is null.
3968 */
3969 public Object getGeneratedKey(Column col, Connection conn)
3970 throws SQLException {
3971 if (lastGeneratedKeyQuery == null)
3972 throw new StoreException(_loc.get("no-auto-assign"));
3973
3974 // replace things like "SELECT MAX({0}) FROM {1}"
3975 String query = lastGeneratedKeyQuery;
3976 if (query.indexOf('{') != -1) // only if the token is in the string
3977 {
3978 query = MessageFormat.format(query, new Object[]{
3979 col.getName(), getFullName(col.getTable(), false),
3980 getGeneratedKeySequenceName(col),
3981 });
3982 }
3983
3984 PreparedStatement stmnt = prepareStatement(conn, query);
3985 ResultSet rs = null;
3986 try {
3987 rs = executeQuery(conn, stmnt, query);
3988 return getKey(rs, col);
3989 } finally {
3990 if (rs != null)
3991 try { rs.close(); } catch (SQLException se) {}
3992 if (stmnt != null)
3993 try { stmnt.close(); } catch (SQLException se) {}
3994 }
3995 }
3996
3997 /**
3998 * Return the sequence name used by databases for the given autoassigned
3999 * column. This is only used by databases that require an explicit name
4000 * to be used for auto-assign support.
4001 */
4002 protected String getGeneratedKeySequenceName(Column col) {
4003 String tname = col.getTableName();
4004 String cname = col.getName();
4005 int max = maxAutoAssignNameLength;
4006 int extraChars = -max + tname.length() + 1 // <tname> + '_'
4007 + cname.length() + 4; // <cname> + '_SEQ'
4008 if (extraChars > 0) {
4009 // this assumes that tname is longer than extraChars
4010 tname = tname.substring(0, tname.length() - extraChars);
4011 }
4012 StringBuffer buf = new StringBuffer(max);
4013 buf.append(tname).append("_").append(cname).append("_SEQ");
4014 return buf.toString();
4015 }
4016
4017 ///////////////////////////////
4018 // Configurable implementation
4019 ///////////////////////////////
4020
4021 public void setConfiguration(Configuration conf) {
4022 this.conf = (JDBCConfiguration) conf;
4023 this.log = this.conf.getLog(JDBCConfiguration.LOG_JDBC);
4024
4025 // warn about unsupported dicts
4026 if (log.isWarnEnabled() && !isSupported())
4027 log.warn(_loc.get("dict-not-supported", getClass()));
4028 }
4029
4030 private boolean isSupported() {
4031 // if this is a custom dict, traverse to whatever openjpa dict it extends
4032 Class c = getClass();
4033 while (!c.getName().startsWith("org.apache.openjpa."))
4034 c = c.getSuperclass();
4035
4036 // the generic dbdictionary is not considered a supported dict; all
4037 // other concrete dictionaries are
4038 if (c == DBDictionary.class)
4039 return false;
4040 return true;
4041 }
4042
4043 public void startConfiguration() {
4044 }
4045
4046 public void endConfiguration() {
4047 // initialize the set of reserved SQL92 words from resource
4048 InputStream in = DBDictionary.class.getResourceAsStream
4049 ("sql-keywords.rsrc");
4050 try {
4051 String keywords = new BufferedReader(new InputStreamReader(in)).
4052 readLine();
4053 in.close();
4054 reservedWordSet.addAll(Arrays.asList(Strings.split
4055 (keywords, ",", 0)));
4056 } catch (IOException ioe) {
4057 throw new GeneralException(ioe);
4058 } finally {
4059 try { in.close(); } catch (IOException e) {}
4060 }
4061
4062 // add additional reserved words set by user
4063 if (reservedWords != null)
4064 reservedWordSet.addAll(Arrays.asList(Strings.split
4065 (reservedWords.toUpperCase(), ",", 0)));
4066
4067 // add system schemas set by user
4068 if (systemSchemas != null)
4069 systemSchemaSet.addAll(Arrays.asList(Strings.split
4070 (systemSchemas.toUpperCase(), ",", 0)));
4071
4072 // add system tables set by user
4073 if (systemTables != null)
4074 systemTableSet.addAll(Arrays.asList(Strings.split
4075 (systemTables.toUpperCase(), ",", 0)));
4076
4077 // add fixed size type names set by the user
4078 if (fixedSizeTypeNames != null)
4079 fixedSizeTypeNameSet.addAll(Arrays.asList(Strings.split
4080 (fixedSizeTypeNames.toUpperCase(), ",", 0)));
4081
4082 // if user has unset sequence sql, null it out so we know sequences
4083 // aren't supported
4084 nextSequenceQuery = StringUtils.trimToNull(nextSequenceQuery);
4085
4086 if (selectWords != null)
4087 selectWordSet.addAll(Arrays.asList(Strings.split(selectWords
4088 .toUpperCase(), ",", 0)));
4089 }
4090
4091 //////////////////////////////////////
4092 // ConnectionDecorator implementation
4093 //////////////////////////////////////
4094
4095 /**
4096 * Decorate the given connection if needed. Some databases require special
4097 * handling for JDBC bugs. This implementation issues any
4098 * {@link #initializationSQL} that has been set for the dictionary but
4099 * does not decoreate the connection.
4100 */
4101 public Connection decorate(Connection conn)
4102 throws SQLException {
4103 if (!connected)
4104 connectedConfiguration(conn);
4105 if (!StringUtils.isEmpty(initializationSQL)) {
4106 PreparedStatement stmnt = null;
4107 try {
4108 stmnt = conn.prepareStatement(initializationSQL);
4109 stmnt.execute();
4110 } catch (Exception e) {
4111 if (log.isTraceEnabled())
4112 log.trace(e.toString(), e);
4113 } finally {
4114 if (stmnt != null)
4115 try {
4116 stmnt.close();
4117 } catch (SQLException se) {
4118 }
4119 }
4120 }
4121 return conn;
4122 }
4123
4124 /**
4125 * Implementation of the
4126 * {@link LoggingConnectionDecorator.SQLWarningHandler} interface
4127 * that allows customization of the actions to perform when a
4128 * {@link SQLWarning} occurs at any point on a {@link Connection},
4129 * {@link Statement}, or {@link ResultSet}. This method may
4130 * be used determine those warnings the application wants to
4131 * consider critical failures, and throw the warning in those
4132 * cases. By default, this method does nothing.
4133 *
4134 * @see LoggingConnectionDecorator#setWarningAction
4135 * @see LoggingConnectionDecorator#setWarningHandler
4136 */
4137 public void handleWarning(SQLWarning warning)
4138 throws SQLException {
4139 }
4140
4141 /**
4142 * Return a new exception that wraps <code>causes</code>.
4143 * However, the details of exactly what type of exception is returned can
4144 * be determined by the implementation. This may take into account
4145 * DB-specific exception information in <code>causes</code>.
4146 */
4147 public OpenJPAException newStoreException(String msg, SQLException[] causes,
4148 Object failed) {
4149 if (causes != null && causes.length > 0) {
4150 OpenJPAException ret = SQLExceptions.narrow(msg, causes[0], this);
4151 ret.setFailedObject(failed).setNestedThrowables(causes);
4152 return ret;
4153 }
4154 return new StoreException(msg).setFailedObject(failed).
4155 setNestedThrowables(causes);
4156 }
4157
4158 /**
4159 * Gets the list of String, each represents an error that can help
4160 * to narrow down a SQL exception to specific type of StoreException.<br>
4161 * For example, error code <code>"23000"</code> represents referential
4162 * integrity violation and hence can be narrowed down to
4163 * {@link ReferentialIntegrityException} rather than more general
4164 * {@link StoreException}.<br>
4165 * JDBC Drivers are not uniform in return values of SQLState for the same
4166 * error and hence each database specific Dictionary can specialize.<br>
4167 *
4168 *
4169 * @return an <em>unmodifiable</em> list of Strings representing supposedly
4170 * uniform SQL States for a given type of StoreException.
4171 * Default behavior is to return an empty list.
4172 */
4173 public List/*<String>*/ getSQLStates(int exceptionType) {
4174 if (exceptionType>=0 && exceptionType<SQL_STATE_CODES.length)
4175 return SQL_STATE_CODES[exceptionType];
4176 return EMPTY_STRING_LIST;
4177 }
4178
4179 /**
4180 * Closes the specified {@link DataSource} and releases any
4181 * resources associated with it.
4182 *
4183 * @param dataSource the DataSource to close
4184 */
4185 public void closeDataSource(DataSource dataSource) {
4186 DataSourceFactory.closeDataSource(dataSource);
4187 }
4188
4189 /**
4190 * Used by some mappings to represent data that has already been
4191 * serialized so that we don't have to serialize multiple times.
4192 */
4193 public static class SerializedData {
4194
4195 public final byte[] bytes;
4196
4197 public SerializedData(byte[] bytes) {
4198 this.bytes = bytes;
4199 }
4200 }
4201
4202 /**
4203 * Return version column name
4204 * @param column
4205 * @param tableAlias : this is needed for platform specific version column
4206 * @return
4207 */
4208 public String getVersionColumn(Column column, String tableAlias) {
4209 return column.toString();
4210 }
4211
4212 public void insertBlobForStreamingLoad(Row row, Column col,
4213 JDBCStore store, Object ob, Select sel) throws SQLException {
4214 if (ob != null) {
4215 row.setBinaryStream(col,
4216 new ByteArrayInputStream(new byte[0]), 0);
4217 } else {
4218 row.setNull(col);
4219 }
4220 }
4221
4222 public void insertClobForStreamingLoad(Row row, Column col, Object ob)
4223 throws SQLException {
4224 if (ob != null) {
4225 row.setCharacterStream(col,
4226 new CharArrayReader(new char[0]), 0);
4227 } else {
4228 row.setNull(col);
4229 }
4230 }
4231
4232 public void updateBlob(Select sel, JDBCStore store, InputStream is)
4233 throws SQLException {
4234 SQLBuffer sql = sel.toSelect(true, store.getFetchConfiguration());
4235 ResultSet res = null;
4236 Connection conn = store.getConnection();
4237 PreparedStatement stmnt = null;
4238 try {
4239 stmnt = sql.prepareStatement(conn, store.getFetchConfiguration(),
4240 ResultSet.TYPE_SCROLL_SENSITIVE,
4241 ResultSet.CONCUR_UPDATABLE);
4242 res = stmnt.executeQuery();
4243 if (!res.next()) {
4244 throw new InternalException(_loc.get("stream-exception"));
4245 }
4246 Blob blob = res.getBlob(1);
4247 OutputStream os = blob.setBinaryStream(1);
4248 copy(is, os);
4249 os.close();
4250 res.updateBlob(1, blob);
4251 res.updateRow();
4252
4253 } catch (IOException ioe) {
4254 throw new StoreException(ioe);
4255 } finally {
4256 if (res != null)
4257 try { res.close (); } catch (SQLException e) {}
4258 if (stmnt != null)
4259 try { stmnt.close (); } catch (SQLException e) {}
4260 if (conn != null)
4261 try { conn.close (); } catch (SQLException e) {}
4262 }
4263 }
4264
4265 public void updateClob(Select sel, JDBCStore store, Reader reader)
4266 throws SQLException {
4267 SQLBuffer sql = sel.toSelect(true, store.getFetchConfiguration());
4268 ResultSet res = null;
4269 Connection conn = store.getConnection();
4270 PreparedStatement stmnt = null;
4271 try {
4272 stmnt = sql.prepareStatement(conn, store.getFetchConfiguration(),
4273 ResultSet.TYPE_SCROLL_SENSITIVE,
4274 ResultSet.CONCUR_UPDATABLE);
4275 res = stmnt.executeQuery();
4276 if (!res.next()) {
4277 throw new InternalException(_loc.get("stream-exception"));
4278 }
4279 Clob clob = res.getClob(1);
4280 Writer writer = clob.setCharacterStream(1);
4281 copy(reader, writer);
4282 writer.close();
4283 res.updateClob(1, clob);
4284 res.updateRow();
4285
4286 } catch (IOException ioe) {
4287 throw new StoreException(ioe);
4288 } finally {
4289 if (res != null)
4290 try { res.close (); } catch (SQLException e) {}
4291 if (stmnt != null)
4292 try { stmnt.close (); } catch (SQLException e) {}
4293 if (conn != null)
4294 try { conn.close (); } catch (SQLException e) {}
4295 }
4296 }
4297
4298 protected long copy(InputStream in, OutputStream out) throws IOException {
4299 byte[] copyBuffer = new byte[blobBufferSize];
4300 long bytesCopied = 0;
4301 int read = -1;
4302
4303 while ((read = in.read(copyBuffer, 0, copyBuffer.length)) != -1) {
4304 out.write(copyBuffer, 0, read);
4305 bytesCopied += read;
4306 }
4307 return bytesCopied;
4308 }
4309
4310 protected long copy(Reader reader, Writer writer) throws IOException {
4311 char[] copyBuffer = new char[clobBufferSize];
4312 long bytesCopied = 0;
4313 int read = -1;
4314
4315 while ((read = reader.read(copyBuffer, 0, copyBuffer.length)) != -1) {
4316 writer.write(copyBuffer, 0, read);
4317 bytesCopied += read;
4318 }
4319
4320 return bytesCopied;
4321 }
4322
4323 /**
4324 * Attach CAST to the current function if necessary
4325 *
4326 * @param val operand value
4327 * @parma func the sql function statement
4328 * @return a String with the correct CAST function syntax
4329 */
4330 public String getCastFunction(Val val, String func) {
4331 return func;
4332 }
4333
4334 /**
4335 * Create an index if necessary for some database tables
4336 */
4337 public void createIndexIfNecessary(Schema schema, String table,
4338 Column pkColumn) {
4339 }
4340
4341 /**
4342 * Return the batchLimit
4343 */
4344 public int getBatchLimit(){
4345 return batchLimit;
4346 }
4347
4348 /**
4349 * Set the batchLimit value
4350 */
4351 public void setBatchLimit(int limit){
4352 batchLimit = limit;
4353 }
4354
4355 /**
4356 * Validate the batch process. In some cases, we can't batch the statements
4357 * due to some restrictions. For example, if the GeneratedType=IDENTITY,
4358 * we have to disable the batch process because we need to get the ID value
4359 * right away for the in-memory entity to use.
4360 */
4361 public boolean validateBatchProcess(RowImpl row, Column[] autoAssign,
4362 OpenJPAStateManager sm, ClassMapping cmd ) {
4363 boolean disableBatch = false;
4364 if (getBatchLimit()== 0) return false;
4365 if (autoAssign != null && sm != null) {
4366 FieldMetaData[] fmd = cmd.getPrimaryKeyFields();
4367 int i = 0;
4368 while (!disableBatch && i < fmd.length) {
4369 if (fmd[i].getValueStrategy() == ValueStrategies.AUTOASSIGN)
4370 disableBatch = true;
4371 i++;
4372 }
4373 }
4374 // go to each Dictionary to validate the batch capability
4375 if (!disableBatch)
4376 disableBatch = validateDBSpecificBatchProcess(disableBatch, row,
4377 autoAssign, sm, cmd);
4378 return disableBatch;
4379 }
4380
4381 /**
4382 * Allow each Dictionary to validate its own batch process.
4383 */
4384 public boolean validateDBSpecificBatchProcess (boolean disableBatch,
4385 RowImpl row, Column[] autoAssign,
4386 OpenJPAStateManager sm, ClassMapping cmd ) {
4387 return disableBatch;
4388 }
4389
4390 /**
4391 * This method is to provide override for non-JDBC or JDBC-like
4392 * implementation of executing query.
4393 */
4394 protected ResultSet executeQuery(Connection conn, PreparedStatement stmnt, String sql
4395 ) throws SQLException {
4396 return stmnt.executeQuery();
4397 }
4398
4399 /**
4400 * This method is to provide override for non-JDBC or JDBC-like
4401 * implementation of preparing statement.
4402 */
4403 protected PreparedStatement prepareStatement(Connection conn, String sql)
4404 throws SQLException {
4405 return conn.prepareStatement(sql);
4406 }
4407
4408 /**
4409 * This method is to provide override for non-JDBC or JDBC-like
4410 * implementation of getting sequence from the result set.
4411 */
4412 protected Sequence[] getSequence(ResultSet rs) throws SQLException {
4413 List seqList = new ArrayList();
4414 while (rs != null && rs.next())
4415 seqList.add(newSequence(rs));
4416 return (Sequence[]) seqList.toArray(new Sequence[seqList.size()]);
4417 }
4418
4419 /**
4420 * This method is to provide override for non-JDBC or JDBC-like
4421 * implementation of getting key from the result set.
4422 */
4423 protected Object getKey (ResultSet rs, Column col) throws SQLException {
4424 if (!rs.next())
4425 throw new StoreException(_loc.get("no-genkey"));
4426 Object key = rs.getObject(1);
4427 if (key == null)
4428 log.warn(_loc.get("invalid-genkey", col));
4429 return key;
4430 }
4431
4432 /**
4433 * This method is to provide override for non-JDBC or JDBC-like
4434 * implementation of calculating value.
4435 */
4436 protected void calculateValue(Val val, Select sel, ExpContext ctx,
4437 ExpState state, Path path, ExpState pathState) {
4438 val.calculateValue(sel, ctx, state, (Val) path, pathState);
4439 }
4440
4441 /**
4442 * Determine whether the provided <code>sql</code> may be treated as a
4443 * select statement on this database.
4444 *
4445 * @param sql A sql statement.
4446 *
4447 * @return true if <code>sql</code> represents a select statement.
4448 */
4449 public boolean isSelect(String sql) {
4450 Iterator i = selectWordSet.iterator();
4451 String cur;
4452 while (i.hasNext()) {
4453 cur = (String) i.next();
4454 if (sql.length() >= cur.length()
4455 && sql.substring(0, cur.length()).equalsIgnoreCase(cur)) {
4456 return true;
4457 }
4458 }
4459 return false;
4460 }
4461
4462 public void deleteStream(JDBCStore store, Select sel) throws SQLException {
4463 //Do nothing
4464 }
4465 }