001    package liquibase.database.core;
002    
003    import liquibase.database.DatabaseConnection;
004    import liquibase.database.AbstractDatabase;
005    import liquibase.exception.DatabaseException;
006    import liquibase.exception.DateParseException;
007    import liquibase.statement.DatabaseFunction;
008    import liquibase.util.ISODateFormat;
009    
010    import java.text.ParseException;
011    import java.text.SimpleDateFormat;
012    import java.text.DateFormat;
013    import java.util.Date;
014    import java.util.Arrays;
015    import java.util.List;
016    
017    public class H2Database extends AbstractDatabase {
018    
019        private static String START_CONCAT = "CONCAT(";
020        private static String END_CONCAT = ")";
021        private static String SEP_CONCAT = ", ";
022    
023        public H2Database() {
024            this.databaseFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP()"));
025        }
026    
027        public String getTypeName() {
028            return "h2";
029        }
030    
031        public String getDefaultDriver(String url) {
032            if (url.startsWith("jdbc:h2")) {
033                return "org.h2.Driver";
034            }
035            return null;
036        }
037    
038        public int getPriority() {
039            return PRIORITY_DATABASE;
040        }
041    
042        public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
043            return "H2".equals(conn.getDatabaseProductName());
044        }
045    
046        // public void dropDatabaseObjects(String schema) throws DatabaseException {
047        // DatabaseConnection conn = getConnection();
048        // Statement dropStatement = null;
049        // try {
050        // dropStatement = conn.createStatement();
051        // dropStatement.executeUpdate("DROP ALL OBJECTS");
052        // changeLogTableExists = false;
053        // changeLogLockTableExists = false;
054        // changeLogCreateAttempted = false;
055        // changeLogLockCreateAttempted = false;
056        // } catch (SQLException e) {
057        // throw new DatabaseException(e);
058        // } finally {
059        // try {
060        // if (dropStatement != null) {
061        // dropStatement.close();
062        // }
063        // conn.commit();
064        // } catch (SQLException e) {
065        // ;
066        // }
067        // }
068        //
069        // }
070    
071        public boolean supportsTablespaces() {
072            return false;
073        }
074    
075        @Override
076        public String getViewDefinition(String schemaName, String name) throws DatabaseException {
077            String definition = super.getViewDefinition(schemaName, name);
078            if (!definition.startsWith("SELECT")) {
079                definition = definition.replaceFirst(".*?\n", ""); // some h2 versions return "create view....as\nselect
080            }
081    
082            definition = definition.replaceFirst("/\\*.*", ""); // sometimes includes comments at the end
083            return definition;
084        }
085    
086        @Override
087        public Date parseDate(String dateAsString) throws DateParseException {
088            try {
089                if (dateAsString.indexOf(' ') > 0) {
090                    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSSSS").parse(dateAsString);
091                } else {
092                    if (dateAsString.indexOf(':') > 0) {
093                        return new SimpleDateFormat("HH:mm:ss").parse(dateAsString);
094                    } else {
095                        return new SimpleDateFormat("yyyy-MM-dd").parse(dateAsString);
096                    }
097                }
098            } catch (ParseException e) {
099                throw new DateParseException(dateAsString);
100            }
101        }
102    
103        @Override
104        public boolean isLocalDatabase() throws DatabaseException {
105            String url = getConnection().getURL();
106            boolean isLocalURL = (super.isLocalDatabase() || url.startsWith("jdbc:h2:file:")
107                    || url.startsWith("jdbc:h2:mem:") || url.startsWith("jdbc:h2:zip:") || url.startsWith("jdbc:h2:~"));
108            return isLocalURL;
109        }
110    
111        // @Override
112        // public String convertRequestedSchemaToSchema(String requestedSchema) throws DatabaseException {
113        // return super.convertRequestedSchemaToSchema(requestedSchema).toLowerCase();
114        // }
115    
116        @Override
117        public boolean supportsSequences() {
118            return true;
119        }
120    
121        @Override
122        protected String getDefaultDatabaseSchemaName() throws DatabaseException {
123            return "PUBLIC";
124        }
125    
126        @Override
127        public String getAutoIncrementClause() {
128            return "GENERATED BY DEFAULT AS IDENTITY IDENTITY";
129        }
130    
131        @Override
132        public String getConcatSql(String... values) {
133            if (values == null) {
134                return null;
135            }
136    
137            return getConcatSql(Arrays.asList(values));
138        }
139    
140        /**
141         * Recursive way of building CONCAT instruction
142         * 
143         * @param values
144         *            a non null List of String
145         * @return a String containing the CONCAT instruction with all elements, or only a value if there is only one
146         *         element in the list
147         */
148        private String getConcatSql(List<String> values) {
149            if (values.size() == 1) {
150                return values.get(0);
151            } else {
152                return START_CONCAT + values.get(0) + SEP_CONCAT + getConcatSql(values.subList(1, values.size()))
153                        + END_CONCAT;
154            }
155        }
156    
157        @Override
158        public String getDateLiteral(String isoDate) {
159            String returnString = isoDate;
160            try {
161                if (isDateTime(isoDate)) {
162                    ISODateFormat isoTimestampFormat = new ISODateFormat();
163                    DateFormat dbTimestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
164                    returnString = dbTimestampFormat.format(isoTimestampFormat.parse(isoDate));
165                }
166            } catch (ParseException e) {
167                throw new RuntimeException("Unexpected date format: " + isoDate, e);
168            }
169            return "'" + returnString + "'";
170        }
171    
172        @Override
173        public String convertRequestedSchemaToSchema(String requestedSchema) throws DatabaseException {
174            return super.convertRequestedSchemaToSchema(requestedSchema).toUpperCase();
175        }
176    
177        @Override
178        public String escapeDatabaseObject(String objectName) {
179            if (objectName != null) {
180                if (isReservedWord(objectName)) {
181                    return "\"" + objectName + "\"";
182                }
183            }
184            return objectName;
185        }
186    
187        @Override
188        public boolean isReservedWord(String objectName) {
189            return keywords.contains(objectName.toUpperCase());
190        }
191    
192        private static List keywords = Arrays.asList(
193        // "ADD",
194        // "ALL",
195        // "ALLOCATE",
196        // "ALTER",
197        // "AND",
198        // "ANY",
199        // "ARE",
200        // "ARRAY",
201        // "AS",
202        // "ASENSITIVE",
203        // "ASYMMETRIC",
204        // "AT",
205        // "ATOMIC",
206        // "AUTHORIZATION",
207        // "BEGIN",
208        // "BETWEEN",
209        // "BIGINT",
210        // "BINARY",
211        // "BLOB",
212        // "BOOLEAN",
213        // "BOTH",
214        // "BY",
215        // "CALL",
216        // "CALLED",
217        // "CASCADED",
218        // "CASE",
219        // "CAST",
220        // "CHAR",
221        // "CHARACTER",
222        // "CHECK",
223        // "CLOB",
224        // "CLOSE",
225        // "COLLATE",
226        // "COLUMN",
227        // "COMMIT",
228        // "CONDITION",
229        // "CONNECT",
230        // "CONSTRAINT",
231        // "CONTINUE",
232        // "CORRESPONDING",
233        // "CREATE",
234        // "CROSS",
235        // "CUBE",
236        // "CURRENT",
237        // "CURRENT_DATE",
238        // "CURRENT_DEFAULT_TRANSFORM_GRO",
239        // "CURRENT_PATH",
240        // "CURRENT_ROLE",
241        // "CURRENT_TIME",
242        // "CURRENT_TIMESTAMP",
243        // "CURRENT_TRANSFORM_GROUP_FOR_T",
244        // "CURRENT_USER",
245        // "CURSOR",
246        // "CYCLE",
247        // "DATE",
248        // "DAY",
249        // "DEALLOCATE",
250        // "DEC",
251        // "DECIMAL",
252        // "DECLARE",
253        // "DEFAULT",
254        // "DELETE",
255        // "DEREF",
256        // "DESCRIBE",
257        // "DETERMINISTIC",
258        // "DISCONNECT",
259        // "DISTINCT",
260        // "DO",
261        // "DOUBLE",
262        // "DROP",
263        // "DYNAMIC",
264        // "EACH",
265        // "ELEMENT",
266        // "ELSE",
267        // "ELSEIF",
268        // "END",
269        // "ESCAPE",
270        // "EXCEPT",
271        // "EXEC",
272        // "EXECUTE",
273        // "EXISTS",
274        // "EXIT",
275        // "EXTERNAL",
276        // "FALSE",
277        // "FETCH",
278        // "FILTER",
279        // "FLOAT",
280        // "FOR",
281        // "FOREIGN",
282        // "FREE",
283        // "FROM",
284        // "FULL",
285        // "FUNCTION",
286        // "GET",
287        // "GLOBAL",
288        // "GRANT",
289        // "GROUP",
290        // "GROUPING",
291        // "HANDLER",
292        // "HAVING",
293        // "HOLD",
294        // "HOUR",
295        // "IDENTITY",
296        // "IF",
297        // "IMMEDIATE",
298        // "IN",
299        // "INDICATOR",
300        // "INNER",
301        // "INOUT",
302        // "INPUT",
303        // "INSENSITIVE",
304        // "INSERT",
305        // "INT",
306        // "INTEGER",
307        // "INTERSECT",
308        // "INTERVAL",
309        // "INTO",
310        // "IS",
311        // "ITERATE",
312        // "JOIN",
313        // "LANGUAGE",
314        // "LARGE",
315        // "LATERAL",
316        // "LEADING",
317        // "LEAVE",
318        // "LEFT",
319        // "LIKE",
320        // "LOCAL",
321        // "LOCALTIME",
322        // "LOCALTIMESTAMP",
323        // "LOOP",
324        // "MATCH",
325        // "MEMBER",
326        // "MERGE",
327        // "METHOD",
328        // "MINUTE",
329        // "MODIFIES",
330        // "MODULE",
331        // "MONTH",
332        // "MULTISET",
333        // "NATIONAL",
334        // "NATURAL",
335        // "NCHAR",
336        // "NCLOB",
337        // "NEW",
338        // "NO",
339        // "NONE",
340        // "NOT",
341        // "NULL",
342        // "NUMERIC",
343        // "OF",
344        // "OLD",
345        // "ON",
346        // "ONLY",
347        // "OPEN",
348        // "OR",
349        // "ORDER",
350        // "OUT",
351        // "OUTER",
352        // "OUTPUT",
353        // "OVER",
354        // "OVERLAPS",
355        // "PARAMETER",
356        // "PARTITION",
357        // "PRECISION",
358        // "PREPARE",
359        // "PRIMARY",
360        // "PROCEDURE",
361        // "RANGE",
362        // "READS",
363        // "REAL",
364        // "RECURSIVE",
365        // "REF",
366        // "REFERENCES",
367        // "REFERENCING",
368        // "RELEASE",
369        // "REPEAT",
370        // "RESIGNAL",
371        // "RESULT",
372        // "RETURN",
373        // "RETURNS",
374        // "REVOKE",
375        // "RIGHT",
376        // "ROLLBACK",
377        // "ROLLUP",
378        // "ROW",
379        // "ROWS",
380        // "SAVEPOINT",
381        // "SCOPE",
382        // "SCROLL",
383        // "SEARCH",
384        // "SECOND",
385        // "SELECT",
386        // "SENSITIVE",
387        // "SESSION_USER",
388        // "SET",
389        // "SIGNAL",
390        // "SIMILAR",
391        // "SMALLINT",
392        // "SOME",
393        // "SPECIFIC",
394        // "SPECIFICTYPE",
395        // "SQL",
396        // "SQLEXCEPTION",
397        // "SQLSTATE",
398        // "SQLWARNING",
399        // "START",
400        // "STATIC",
401        // "SUBMULTISET",
402        // "SYMMETRIC",
403        // "SYSTEM",
404        // "SYSTEM_USER",
405        // "TABLE",
406        // "TABLESAMPLE",
407        // "THEN",
408        // "TIME",
409        // "TIMESTAMP",
410        // "TIMEZONE_HOUR",
411        // "TIMEZONE_MINUTE",
412        // "TO",
413        // "TRAILING",
414        // "TRANSLATION",
415        // "TREAT",
416        // "TRIGGER",
417        // "TRUE",
418        // "UNDO",
419        // "UNION",
420        // "UNIQUE",
421        // "UNKNOWN",
422        // "UNNEST",
423        // "UNTIL",
424        // "UPDATE",
425                "USER",
426                // "USING",
427                // "VALUE",
428                // "VALUES",
429                // "VARCHAR",
430                // "VARYING",
431                // "WHEN",
432                // "WHENEVER",
433                // "WHERE",
434                // "WHILE",
435                // "WINDOW",
436                // "WITH",
437                // "WITHIN",
438                // "WITHOUT",
439                // "YEAR",
440                // "ALIAS",
441                // "AUTOCOMMIT",
442                // "CACHED",
443                // "CHECKPOINT",
444                // "EXPLAIN",
445                // "IGNORECASE",
446                // "INDEX",
447                // "LOGSIZE",
448                // "MATCHED",
449                // "MAXROWS",
450                // "MEMORY",
451                // "MINUS",
452                // "NEXT",
453                // "OPENBRACKET",
454                "PASSWORD"
455        // "PLAN",
456        // "PROPERTY",
457        // "READONLY",
458        // "REFERENTIAL_INTEGRITY",
459        // "RENAME",
460        // "RESTART",
461        // "SCRIPT",
462        // "SCRIPTFORMAT",
463        // "SEMICOLON",
464        // "SEQUENCE",
465        // "SHUTDOWN",
466        // "SOURCE",
467        // "TEMP",
468        // "TEXT",
469        // "VIEW",
470        // "WRITE_DELAY",
471        // "VAR_POP",
472        // "VAR_SAMP",
473        // "STDDEV_POP",
474        // "STDDEV_SAMP",
475        // "DEFRAG",
476        // "INCREMENT",
477        // "TOCHAR",
478        // "DATABASE",
479        // "SCHEMA",
480        // "ROLE",
481        // "DOW",
482        // "INITIAL"
483                );
484    
485        public boolean supportsInitiallyDeferrableColumns() {
486            return false;
487        }
488    
489        public String getCurrentDateTimeFunction() {
490            if (currentDateTimeFunction != null) {
491                return currentDateTimeFunction;
492            }
493    
494            return "NOW()";
495        }
496    
497    }