1 /*
2  * Hibernate, Relational Persistence for Idiomatic Java
3  *
4  * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5  * indicated by the @author tags or express copyright attribution
6  * statements applied by the authors.  All third-party contributions are
7  * distributed under license by Red Hat Middleware LLC.
8  *
9  * This copyrighted material is made available to anyone wishing to use, modify,
10  * copy, or redistribute it subject to the terms and conditions of the GNU
11  * Lesser General Public License, as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
16  * for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this distribution; if not, write to:
20  * Free Software Foundation, Inc.
21  * 51 Franklin Street, Fifth Floor
22  * Boston, MA  02110-1301  USA
23  *
24  */
25 package org.hibernate.dialect;
26 
27 import java.sql.SQLException;
28 import java.sql.Types;
29 import java.io.Serializable;
30 
31 import org.hibernate.Hibernate;
32 import org.hibernate.LockMode;
33 import org.hibernate.StaleObjectStateException;
34 import org.hibernate.JDBCException;
35 import org.hibernate.engine.SessionImplementor;
36 import org.hibernate.persister.entity.Lockable;
37 import org.hibernate.cfg.Environment;
38 import org.hibernate.dialect.function.NoArgSQLFunction;
39 import org.hibernate.dialect.function.StandardSQLFunction;
40 import org.hibernate.dialect.function.VarArgsSQLFunction;
41 import org.hibernate.dialect.lock.LockingStrategy;
42 import org.hibernate.dialect.lock.SelectLockingStrategy;
43 import org.hibernate.exception.JDBCExceptionHelper;
44 import org.hibernate.exception.TemplatedViolatedConstraintNameExtracter;
45 import org.hibernate.exception.ViolatedConstraintNameExtracter;
46 import org.hibernate.util.ReflectHelper;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49 
50 /**
51  * An SQL dialect compatible with HSQLDB (HyperSQL).
52  * <p/>
53  * Note this version supports HSQLDB version 1.8 and higher, only.
54  * <p/>
55  * Enhancements to version 3.3.1 GA dialect to provide basic support for both HSQLDB 1.8.x and 2.0.
56  * For version 3.6.0 GA and later use the dialect supplied with Hibernate.
57  *
58  * @author Christoph Sturm
59  * @author Phillip Baird
60  * @author Fred Toussi
61  */
62 public class HSQLDialect extends Dialect {
63 
64 	private static final Logger log = LoggerFactory.getLogger( HSQLDialect.class );
65 
66         /**
67          * version is 18 for 1.8 or 20 for 2.0
68          */
69         private int hsqldbVersion = 18;
70 
71 
HSQLDialect()72 	public HSQLDialect() {
73 		super();
74 
75                 try {
76                         Class props = ReflectHelper.classForName( "org.hsqldb.persist.HsqlDatabaseProperties" );
77                         String versionString = (String) props.getDeclaredField( "THIS_VERSION" ).get( null );
78 
79                         hsqldbVersion = Integer.parseInt( versionString.substring( 0, 1 ) ) * 10;
80                         hsqldbVersion += Integer.parseInt( versionString.substring( 2, 3 ) );
81                 }
82                 catch ( Throwable e ) {
83                         // must be a very old version
84                 }
85 
86 		registerColumnType( Types.BIGINT, "bigint" );
87                 registerColumnType( Types.BINARY, "binary($l)" );
88 		registerColumnType( Types.BIT, "bit" );
89         registerColumnType( Types.BOOLEAN, "boolean" );
90                 registerColumnType( Types.CHAR, "char($l)" );
91 		registerColumnType( Types.DATE, "date" );
92 
93                 registerColumnType( Types.DECIMAL, "decimal($p,$s)" );
94 		registerColumnType( Types.DOUBLE, "double" );
95 		registerColumnType( Types.FLOAT, "float" );
96 		registerColumnType( Types.INTEGER, "integer" );
97 		registerColumnType( Types.LONGVARBINARY, "longvarbinary" );
98 		registerColumnType( Types.LONGVARCHAR, "longvarchar" );
99 		registerColumnType( Types.SMALLINT, "smallint" );
100 		registerColumnType( Types.TINYINT, "tinyint" );
101 		registerColumnType( Types.TIME, "time" );
102 		registerColumnType( Types.TIMESTAMP, "timestamp" );
103 		registerColumnType( Types.VARCHAR, "varchar($l)" );
104 		registerColumnType( Types.VARBINARY, "varbinary($l)" );
105 
106                 if ( hsqldbVersion < 20 ) {
107 		registerColumnType( Types.NUMERIC, "numeric" );
108                 }
109                 else {
110                         registerColumnType( Types.NUMERIC, "numeric($p,$s)" );
111                 }
112 
113 		//HSQL has no Blob/Clob support .... but just put these here for now!
114                 if ( hsqldbVersion < 20 ) {
115 		registerColumnType( Types.BLOB, "longvarbinary" );
116 		registerColumnType( Types.CLOB, "longvarchar" );
117                 }
118                 else {
119                         registerColumnType( Types.BLOB, "blob" );
120                         registerColumnType( Types.CLOB, "clob" );
121                 }
122 
123 		registerFunction( "ascii", new StandardSQLFunction( "ascii", Hibernate.INTEGER ) );
124 		registerFunction( "char", new StandardSQLFunction( "char", Hibernate.CHARACTER ) );
125 		registerFunction( "length", new StandardSQLFunction( "length", Hibernate.LONG ) );
126 		registerFunction( "lower", new StandardSQLFunction( "lower" ) );
127 		registerFunction( "upper", new StandardSQLFunction( "upper" ) );
128 		registerFunction( "lcase", new StandardSQLFunction( "lcase" ) );
129 		registerFunction( "ucase", new StandardSQLFunction( "ucase" ) );
130 		registerFunction( "soundex", new StandardSQLFunction( "soundex", Hibernate.STRING ) );
131 		registerFunction( "ltrim", new StandardSQLFunction( "ltrim" ) );
132 		registerFunction( "rtrim", new StandardSQLFunction( "rtrim" ) );
133 		registerFunction( "reverse", new StandardSQLFunction( "reverse" ) );
134 		registerFunction( "space", new StandardSQLFunction( "space", Hibernate.STRING ) );
135 		registerFunction( "rawtohex", new StandardSQLFunction( "rawtohex" ) );
136 		registerFunction( "hextoraw", new StandardSQLFunction( "hextoraw" ) );
137 
138 		registerFunction( "user", new NoArgSQLFunction( "user", Hibernate.STRING ) );
139 		registerFunction( "database", new NoArgSQLFunction( "database", Hibernate.STRING ) );
140 
141 		registerFunction( "current_date", new NoArgSQLFunction( "current_date", Hibernate.DATE, false ) );
142 		registerFunction( "curdate", new NoArgSQLFunction( "curdate", Hibernate.DATE ) );
143 		registerFunction( "current_timestamp", new NoArgSQLFunction( "current_timestamp", Hibernate.TIMESTAMP, false ) );
144 		registerFunction( "now", new NoArgSQLFunction( "now", Hibernate.TIMESTAMP ) );
145 		registerFunction( "current_time", new NoArgSQLFunction( "current_time", Hibernate.TIME, false ) );
146 		registerFunction( "curtime", new NoArgSQLFunction( "curtime", Hibernate.TIME ) );
147 		registerFunction( "day", new StandardSQLFunction( "day", Hibernate.INTEGER ) );
148 		registerFunction( "dayofweek", new StandardSQLFunction( "dayofweek", Hibernate.INTEGER ) );
149 		registerFunction( "dayofyear", new StandardSQLFunction( "dayofyear", Hibernate.INTEGER ) );
150 		registerFunction( "dayofmonth", new StandardSQLFunction( "dayofmonth", Hibernate.INTEGER ) );
151 		registerFunction( "month", new StandardSQLFunction( "month", Hibernate.INTEGER ) );
152 		registerFunction( "year", new StandardSQLFunction( "year", Hibernate.INTEGER ) );
153 		registerFunction( "week", new StandardSQLFunction( "week", Hibernate.INTEGER ) );
154 		registerFunction( "quater", new StandardSQLFunction( "quater", Hibernate.INTEGER ) );
155 		registerFunction( "hour", new StandardSQLFunction( "hour", Hibernate.INTEGER ) );
156 		registerFunction( "minute", new StandardSQLFunction( "minute", Hibernate.INTEGER ) );
157 		registerFunction( "second", new StandardSQLFunction( "second", Hibernate.INTEGER ) );
158 		registerFunction( "dayname", new StandardSQLFunction( "dayname", Hibernate.STRING ) );
159 		registerFunction( "monthname", new StandardSQLFunction( "monthname", Hibernate.STRING ) );
160 
161 		registerFunction( "abs", new StandardSQLFunction( "abs" ) );
162 		registerFunction( "sign", new StandardSQLFunction( "sign", Hibernate.INTEGER ) );
163 
164 		registerFunction( "acos", new StandardSQLFunction( "acos", Hibernate.DOUBLE ) );
165 		registerFunction( "asin", new StandardSQLFunction( "asin", Hibernate.DOUBLE ) );
166 		registerFunction( "atan", new StandardSQLFunction( "atan", Hibernate.DOUBLE ) );
167 		registerFunction( "cos", new StandardSQLFunction( "cos", Hibernate.DOUBLE ) );
168 		registerFunction( "cot", new StandardSQLFunction( "cot", Hibernate.DOUBLE ) );
169 		registerFunction( "exp", new StandardSQLFunction( "exp", Hibernate.DOUBLE ) );
170 		registerFunction( "log", new StandardSQLFunction( "log", Hibernate.DOUBLE ) );
171 		registerFunction( "log10", new StandardSQLFunction( "log10", Hibernate.DOUBLE ) );
172 		registerFunction( "sin", new StandardSQLFunction( "sin", Hibernate.DOUBLE ) );
173 		registerFunction( "sqrt", new StandardSQLFunction( "sqrt", Hibernate.DOUBLE ) );
174 		registerFunction( "tan", new StandardSQLFunction( "tan", Hibernate.DOUBLE ) );
175 		registerFunction( "pi", new NoArgSQLFunction( "pi", Hibernate.DOUBLE ) );
176 		registerFunction( "rand", new StandardSQLFunction( "rand", Hibernate.FLOAT ) );
177 
178 		registerFunction( "radians", new StandardSQLFunction( "radians", Hibernate.DOUBLE ) );
179 		registerFunction( "degrees", new StandardSQLFunction( "degrees", Hibernate.DOUBLE ) );
180 		registerFunction( "roundmagic", new StandardSQLFunction( "roundmagic" ) );
181 
182 		registerFunction( "ceiling", new StandardSQLFunction( "ceiling" ) );
183 		registerFunction( "floor", new StandardSQLFunction( "floor" ) );
184 
185 		// Multi-param dialect functions...
186 		registerFunction( "mod", new StandardSQLFunction( "mod", Hibernate.INTEGER ) );
187 
188 		// function templates
189 		registerFunction( "concat", new VarArgsSQLFunction( Hibernate.STRING, "(", "||", ")" ) );
190 
191 		getDefaultProperties().setProperty( Environment.STATEMENT_BATCH_SIZE, DEFAULT_BATCH_SIZE );
192 	}
193 
getAddColumnString()194 	public String getAddColumnString() {
195 		return "add column";
196 	}
197 
supportsIdentityColumns()198 	public boolean supportsIdentityColumns() {
199 		return true;
200 	}
201 
getIdentityColumnString()202 	public String getIdentityColumnString() {
203 		return "generated by default as identity (start with 1)"; //not null is implicit
204 	}
205 
getIdentitySelectString()206 	public String getIdentitySelectString() {
207 		return "call identity()";
208 	}
209 
getIdentityInsertString()210 	public String getIdentityInsertString() {
211                 return hsqldbVersion < 20 ? "null" : "default";
212         }
213 
supportsLockTimeouts()214         public boolean supportsLockTimeouts() {
215                 return false;
216 	}
217 
getForUpdateString()218 	public String getForUpdateString() {
219 		return "";
220 	}
221 
supportsUnique()222 	public boolean supportsUnique() {
223 		return false;
224 	}
225 
supportsLimit()226 	public boolean supportsLimit() {
227 		return true;
228 	}
229 
getLimitString(String sql, boolean hasOffset)230 	public String getLimitString(String sql, boolean hasOffset) {
231                 if ( hsqldbVersion < 20 ) {
232 		return new StringBuffer( sql.length() + 10 )
233 				.append( sql )
234                                         .insert(
235                                                         sql.toLowerCase().indexOf( "select" ) + 6,
236                                                         hasOffset ? " limit ? ?" : " top ?"
237                                         )
238                                         .toString();
239                 }
240                 else {
241                         return new StringBuffer( sql.length() + 20 )
242                                         .append( sql )
243                                         .append( hasOffset ? " offset ? limit ?" : " limit ?" )
244 				.toString();
245 	}
246         }
247 
bindLimitParametersFirst()248 	public boolean bindLimitParametersFirst() {
249                 return hsqldbVersion < 20;
250 	}
251 
supportsIfExistsAfterTableName()252 	public boolean supportsIfExistsAfterTableName() {
253 		return true;
254 	}
255 
supportsColumnCheck()256 	public boolean supportsColumnCheck() {
257                 return hsqldbVersion >= 20;
258 	}
259 
supportsSequences()260 	public boolean supportsSequences() {
261 		return true;
262 	}
263 
supportsPooledSequences()264 	public boolean supportsPooledSequences() {
265 		return true;
266 	}
267 
getCreateSequenceString(String sequenceName)268 	protected String getCreateSequenceString(String sequenceName) {
269 		return "create sequence " + sequenceName;
270 	}
271 
getDropSequenceString(String sequenceName)272 	protected String getDropSequenceString(String sequenceName) {
273 		return "drop sequence " + sequenceName;
274 	}
275 
getSelectSequenceNextValString(String sequenceName)276 	public String getSelectSequenceNextValString(String sequenceName) {
277 		return "next value for " + sequenceName;
278 	}
279 
getSequenceNextValString(String sequenceName)280 	public String getSequenceNextValString(String sequenceName) {
281 		return "call next value for " + sequenceName;
282 	}
283 
getQuerySequencesString()284 	public String getQuerySequencesString() {
285 		// this assumes schema support, which is present in 1.8.0 and later...
286 		return "select sequence_name from information_schema.system_sequences";
287 	}
288 
getViolatedConstraintNameExtracter()289 	public ViolatedConstraintNameExtracter getViolatedConstraintNameExtracter() {
290                 return hsqldbVersion < 20 ? EXTRACTER_18 : EXTRACTER_20;
291 	}
292 
293         private static ViolatedConstraintNameExtracter EXTRACTER_18 = new TemplatedViolatedConstraintNameExtracter() {
294 
295 		/**
296 		 * Extract the name of the violated constraint from the given SQLException.
297 		 *
298 		 * @param sqle The exception that was the result of the constraint violation.
299 		 * @return The extracted constraint name.
300 		 */
301 		public String extractConstraintName(SQLException sqle) {
302 			String constraintName = null;
303 
304 			int errorCode = JDBCExceptionHelper.extractErrorCode( sqle );
305 
306 			if ( errorCode == -8 ) {
307 				constraintName = extractUsingTemplate(
308 						"Integrity constraint violation ", " table:", sqle.getMessage()
309 				);
310 			}
311 			else if ( errorCode == -9 ) {
312 				constraintName = extractUsingTemplate(
313 						"Violation of unique index: ", " in statement [", sqle.getMessage()
314 				);
315 			}
316 			else if ( errorCode == -104 ) {
317 				constraintName = extractUsingTemplate(
318 						"Unique constraint violation: ", " in statement [", sqle.getMessage()
319 				);
320 			}
321 			else if ( errorCode == -177 ) {
322 				constraintName = extractUsingTemplate(
323                                                 "Integrity constraint violation - no parent ", " table:",
324                                                 sqle.getMessage()
325 				);
326 			}
327 			return constraintName;
328 		}
329 
330 	};
331 
332 	/**
333          * HSQLDB 2.0 messages have changed
334          * messages may be localized - therefore use the common, non-locale element " table: "
335          */
336         private static ViolatedConstraintNameExtracter EXTRACTER_20 = new TemplatedViolatedConstraintNameExtracter() {
337 
338                 public String extractConstraintName(SQLException sqle) {
339                         String constraintName = null;
340 
341                         int errorCode = JDBCExceptionHelper.extractErrorCode( sqle );
342 
343                         if ( errorCode == -8 ) {
344                                 constraintName = extractUsingTemplate(
345                                                 "; ", " table: ", sqle.getMessage()
346                                 );
347                         }
348                         else if ( errorCode == -9 ) {
349                                 constraintName = extractUsingTemplate(
350                                                 "; ", " table: ", sqle.getMessage()
351                                 );
352                         }
353                         else if ( errorCode == -104 ) {
354                                 constraintName = extractUsingTemplate(
355                                                 "; ", " table: ", sqle.getMessage()
356                                 );
357                         }
358                         else if ( errorCode == -177 ) {
359                                 constraintName = extractUsingTemplate(
360                                                 "; ", " table: ", sqle.getMessage()
361                                 );
362                         }
363                         return constraintName;
364                 }
365         };
366 
getSelectClauseNullString(int sqlType)367         public String getSelectClauseNullString(int sqlType) {
368                 String literal;
369                 switch ( sqlType ) {
370                         case Types.VARCHAR:
371                         case Types.CHAR:
372                                 literal = "cast(null as varchar(100))";
373                                 break;
374                         case Types.DATE:
375                                 literal = "cast(null as date)";
376                                 break;
377                         case Types.TIMESTAMP:
378                                 literal = "cast(null as timestamp)";
379                                 break;
380                         case Types.TIME:
381                                 literal = "cast(null as time)";
382                                 break;
383                         default:
384                                 literal = "cast(null as int)";
385                 }
386                 return literal;
387         }
388 
supportsUnionAll()389     public boolean supportsUnionAll() {
390         return true;
391     }
392 
393         // temporary table support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
394         // Hibernate uses this information for temporary tables that it uses for its own operations
395         // therefore the appropriate strategy is taken with different versions of HSQLDB
396 
397         // All versions of HSQLDB support GLOBAL TEMPORARY tables where the table
398         // definition is shared by all users but data is private to the session
399         // HSQLDB 2.0 also supports session-based LOCAL TEMPORARY tables where
400         // the definition and data is private to the session and table declaration
401         // can happen in the middle of a transaction
402 
403         /**
404          * Does this dialect support temporary tables?
405          *
406          * @return True if temp tables are supported; false otherwise.
407 	 */
supportsTemporaryTables()408 	public boolean supportsTemporaryTables() {
409 		return true;
410 	}
411 
412         /**
413          * With HSQLDB 2.0, the table name is qualified with MODULE to assist the drop
414          * statement (in-case there is a global name beginning with HT_)
415          *
416          * @param baseTableName The table name from which to base the temp table name.
417          *
418          * @return The generated temp table name.
419          */
generateTemporaryTableName(String baseTableName)420         public String generateTemporaryTableName(String baseTableName) {
421                 if ( hsqldbVersion < 20 ) {
422                         return "HT_" + baseTableName;
423                 }
424                 else {
425                         return "MODULE.HT_" + baseTableName;
426                 }
427         }
428 
429         /**
430          * Command used to create a temporary table.
431          *
432          * @return The command used to create a temporary table.
433          */
getCreateTemporaryTableString()434         public String getCreateTemporaryTableString() {
435                 if ( hsqldbVersion < 20 ) {
436                         return "create global temporary table";
437                 }
438                 else {
439                         return "declare local temporary table";
440                 }
441         }
442 
443         /**
444          * No fragment is needed if data is not needed beyond commit, otherwise
445          * should add "on commit preserve rows"
446          *
447          * @return Any required postfix.
448          */
getCreateTemporaryTablePostfix()449         public String getCreateTemporaryTablePostfix() {
450                 return "";
451         }
452 
453         /**
454          * Command used to drop a temporary table.
455          *
456          * @return The command used to drop a temporary table.
457          */
getDropTemporaryTableString()458         public String getDropTemporaryTableString() {
459                 return "drop table";
460         }
461 
462         /**
463          * Different behavior for GLOBAL TEMPORARY (1.8) and LOCAL TEMPORARY (2.0)
464          * <p/>
465          * Possible return values and their meanings:<ul>
466          * <li>{@link Boolean#TRUE} - Unequivocally, perform the temporary table DDL
467          * in isolation.</li>
468          * <li>{@link Boolean#FALSE} - Unequivocally, do <b>not</b> perform the
469          * temporary table DDL in isolation.</li>
470          * <li><i>null</i> - defer to the JDBC driver response in regards to
471          * {@link java.sql.DatabaseMetaData#dataDefinitionCausesTransactionCommit()}</li>
472          * </ul>
473          *
474          * @return see the result matrix above.
475          */
performTemporaryTableDDLInIsolation()476         public Boolean performTemporaryTableDDLInIsolation() {
477                 if ( hsqldbVersion < 20 ) {
478                         return Boolean.TRUE;
479                 }
480                 else {
481                         return Boolean.FALSE;
482                 }
483         }
484 
485         /**
486          * Do we need to drop the temporary table after use?
487          *
488          * todo - clarify usage by Hibernate
489          *
490          * Version 1.8 GLOBAL TEMPORARY table definitions persist beyond the end
491          * of the session (by default, data is cleared at commit). If there are
492          * not too many such tables, perhaps we can avoid dropping them and reuse
493          * the table next time?<p>
494          *
495          * Version 2.x LOCAL TEMPORARY table definitions do not persist beyond
496          * the end of the session (by default, data is cleared at commit).
497          *
498          * @return True if the table should be dropped.
499          */
dropTemporaryTableAfterUse()500         public boolean dropTemporaryTableAfterUse() {
501             if ( hsqldbVersion < 20 ) {
502                     return Boolean.TRUE;
503             }
504             else {
505                     return Boolean.FALSE;
506             }
507         }
508 
509         // current timestamp support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
510 
511         /**
512          * HSQLDB 1.8.x requires CALL CURRENT_TIMESTAMP but this should not
513          * be treated as a callable statement. It is equivalent to
514          * "select current_timestamp from dual" in some databases.
515          * HSQLDB 2.0 also supports VALUES CURRENT_TIMESTAMP
516          *
517          * @return True if the current timestamp can be retrieved; false otherwise.
518          */
supportsCurrentTimestampSelection()519 	public boolean supportsCurrentTimestampSelection() {
520                 return true;
521         }
522 
523         /**
524          * Should the value returned by {@link #getCurrentTimestampSelectString}
525          * be treated as callable.  Typically this indicates that JDBC escape
526          * syntax is being used...<p>
527          *
528          * CALL CURRENT_TIMESTAMP is used but this should not
529          * be treated as a callable statement.
530          *
531          * @return True if the {@link #getCurrentTimestampSelectString} return
532          *         is callable; false otherwise.
533          */
isCurrentTimestampSelectStringCallable()534         public boolean isCurrentTimestampSelectStringCallable() {
535 		return false;
536 	}
537 
538         /**
539          * Retrieve the command used to retrieve the current timestamp from the
540          * database.
541          *
542          * @return The command.
543          */
getCurrentTimestampSelectString()544         public String getCurrentTimestampSelectString() {
545                 return "call current_timestamp";
546         }
547 
548         /**
549          * The name of the database-specific SQL function for retrieving the
550          * current timestamp.
551          *
552          * @return The function name.
553          */
getCurrentTimestampSQLFunctionName()554         public String getCurrentTimestampSQLFunctionName() {
555                 // the standard SQL function name is current_timestamp...
556                 return "current_timestamp";
557         }
558 
559         /**
560          * For HSQLDB 2.0, this is a copy of the base class implementation.
561          * For HSQLDB 1.8, only READ_UNCOMMITTED is supported.
562          *
563          * @param lockable The persister for the entity to be locked.
564          * @param lockMode The type of lock to be acquired.
565          *
566          * @return The appropriate locking strategy.
567          *
568          * @since 3.2
569          */
getLockingStrategy(Lockable lockable, LockMode lockMode)570 	public LockingStrategy getLockingStrategy(Lockable lockable, LockMode lockMode) {
571                 if ( hsqldbVersion < 20 ) {
572 		return new ReadUncommittedLockingStrategy( lockable, lockMode );
573 	}
574                 else {
575                         return new SelectLockingStrategy( lockable, lockMode );
576                 }
577         }
578 
579 	public static class ReadUncommittedLockingStrategy extends SelectLockingStrategy {
ReadUncommittedLockingStrategy(Lockable lockable, LockMode lockMode)580 		public ReadUncommittedLockingStrategy(Lockable lockable, LockMode lockMode) {
581 			super( lockable, lockMode );
582 		}
583 
lock(Serializable id, Object version, Object object, SessionImplementor session)584 		public void lock(Serializable id, Object version, Object object, SessionImplementor session)
585 				throws StaleObjectStateException, JDBCException {
586 			if ( getLockMode().greaterThan( LockMode.READ ) ) {
587 				log.warn( "HSQLDB supports only READ_UNCOMMITTED isolation" );
588 			}
589 			super.lock( id, version, object, session );
590 		}
591 	}
592 
593 
594 	// Overridden informational metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
595 
supportsEmptyInList()596 	public boolean supportsEmptyInList() {
597 		return false;
598 	}
599 
600         /**
601          * todo - needs usage clarification
602          *
603          * If the SELECT statement is always part of a UNION, then the type of
604          * parameter is resolved by v. 2.0, but not v. 1.8 (assuming the other
605          * SELECT in the UNION has a column reference in the same position and
606          * can be type-resolved).
607          *
608          * On the other hand if the SELECT statement is isolated, all versions of
609          * HSQLDB require casting for "select ? from .." to work.
610          *
611          * @return True if select clause parameter must be cast()ed
612          *
613          * @since 3.2
614          */
requiresCastingOfParametersInSelectClause()615         public boolean requiresCastingOfParametersInSelectClause() {
616                 return true;
617         }
618 
619         /**
620          * For the underlying database, is READ_COMMITTED isolation implemented by
621          * forcing readers to wait for write locks to be released?
622          *
623          * @return True if writers block readers to achieve READ_COMMITTED; false otherwise.
624          */
doesReadCommittedCauseWritersToBlockReaders()625         public boolean doesReadCommittedCauseWritersToBlockReaders() {
626                 return hsqldbVersion >= 20;
627         }
628 
629         /**
630          * For the underlying database, is REPEATABLE_READ isolation implemented by
631          * forcing writers to wait for read locks to be released?
632          *
633          * @return True if readers block writers to achieve REPEATABLE_READ; false otherwise.
634          */
doesRepeatableReadCauseReadersToBlockWriters()635         public boolean doesRepeatableReadCauseReadersToBlockWriters() {
636                 return hsqldbVersion >= 20;
637         }
638 
639 
supportsLobValueChangePropogation()640 	public boolean supportsLobValueChangePropogation() {
641 		return false;
642 	}
643 
toBooleanValueString(boolean bool)644     public String toBooleanValueString(boolean bool) {
645         return String.valueOf( bool );
646     }
647 
supportsTupleDistinctCounts()648         public boolean supportsTupleDistinctCounts() {
649                 return false;
650         }
651 }
652