1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * The contents of this file are subject to the terms of the Liferay Enterprise
5    * Subscription License ("License"). You may not use this file except in
6    * compliance with the License. You can obtain a copy of the License by
7    * contacting Liferay, Inc. See the License for the specific language governing
8    * permissions and limitations under the License, including but not limited to
9    * distribution rights of the Software.
10   *
11   *
12   *
13   */
14  
15  package com.liferay.portal.dao.db;
16  
17  import com.liferay.counter.service.CounterLocalServiceUtil;
18  import com.liferay.portal.SystemException;
19  import com.liferay.portal.kernel.dao.db.DB;
20  import com.liferay.portal.kernel.dao.db.DBFactoryUtil;
21  import com.liferay.portal.kernel.dao.db.Index;
22  import com.liferay.portal.kernel.dao.jdbc.DataAccess;
23  import com.liferay.portal.kernel.io.unsync.UnsyncBufferedReader;
24  import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
25  import com.liferay.portal.kernel.log.Log;
26  import com.liferay.portal.kernel.log.LogFactoryUtil;
27  import com.liferay.portal.kernel.util.FileUtil;
28  import com.liferay.portal.kernel.util.GetterUtil;
29  import com.liferay.portal.kernel.util.PropertiesUtil;
30  import com.liferay.portal.kernel.util.StringBundler;
31  import com.liferay.portal.kernel.util.StringPool;
32  import com.liferay.portal.kernel.util.StringUtil;
33  import com.liferay.portal.kernel.util.Validator;
34  import com.liferay.portal.velocity.VelocityUtil;
35  import com.liferay.util.SimpleCounter;
36  
37  import java.io.File;
38  import java.io.FileReader;
39  import java.io.IOException;
40  import java.io.InputStream;
41  
42  import java.sql.Connection;
43  import java.sql.SQLException;
44  import java.sql.Statement;
45  
46  import java.util.Collections;
47  import java.util.Enumeration;
48  import java.util.HashMap;
49  import java.util.HashSet;
50  import java.util.List;
51  import java.util.Map;
52  import java.util.Properties;
53  import java.util.Set;
54  
55  import javax.naming.NamingException;
56  
57  /**
58   * <a href="BaseDB.java.html"><b><i>View Source</i></b></a>
59   *
60   * @author Alexander Chow
61   * @author Ganesh Ram
62   * @author Brian Wing Shun Chan
63   */
64  public abstract class BaseDB implements DB {
65  
66      public void buildCreateFile(String sqlDir, String databaseName)
67          throws IOException {
68  
69          buildCreateFile(sqlDir, databaseName, POPULATED);
70          buildCreateFile(sqlDir, databaseName, MINIMAL);
71          buildCreateFile(sqlDir, databaseName, SHARDED);
72      }
73  
74      public void buildCreateFile(
75              String sqlDir, String databaseName, int population)
76          throws IOException {
77  
78          String suffix = getSuffix(population);
79  
80          File file = new File(
81              sqlDir + "/create" + suffix + "/create" + suffix + "-" +
82                  getServerName() + ".sql");
83  
84          if (population != SHARDED) {
85              String content = buildCreateFileContent(
86                  sqlDir, databaseName, population);
87  
88              if (content != null) {
89                  FileUtil.write(file, content);
90              }
91          }
92          else {
93              String content = buildCreateFileContent(
94                  sqlDir, databaseName, MINIMAL);
95  
96              if (content != null) {
97                  FileUtil.write(file, content);
98              }
99  
100             content = buildCreateFileContent(
101                 sqlDir, databaseName + "1", MINIMAL);
102 
103             if (content != null) {
104                 FileUtil.write(file, content, false, true);
105             }
106 
107             content = buildCreateFileContent(
108                 sqlDir, databaseName + "2", MINIMAL);
109 
110             if (content != null) {
111                 FileUtil.write(file, content, false, true);
112             }
113         }
114     }
115 
116     public abstract String buildSQL(String template) throws IOException;
117 
118     public void buildSQLFile(String sqlDir, String fileName)
119         throws IOException {
120 
121         String template = buildTemplate(sqlDir, fileName);
122 
123         if (Validator.isNull(template)) {
124             return;
125         }
126 
127         template = buildSQL(template);
128 
129         FileUtil.write(
130             sqlDir + "/" + fileName + "/" + fileName + "-" + getServerName() +
131                 ".sql",
132             template);
133     }
134 
135     @SuppressWarnings("unused")
136     public List<Index> getIndexes() throws SQLException {
137         return Collections.EMPTY_LIST;
138     }
139 
140     public String getTemplateFalse() {
141         return getTemplate()[2];
142     }
143 
144     public String getTemplateTrue() {
145         return getTemplate()[1];
146     }
147 
148     public String getType() {
149         return _type;
150     }
151 
152     public long increment() throws SystemException {
153         return CounterLocalServiceUtil.increment();
154     }
155 
156     public boolean isSupportsAlterColumnName() {
157         return _SUPPORTS_ALTER_COLUMN_NAME;
158     }
159 
160     public boolean isSupportsAlterColumnType() {
161         return _SUPPORTS_ALTER_COLUMN_TYPE;
162     }
163 
164     public boolean isSupportsDateMilliseconds() {
165         return _SUPPORTS_DATE_MILLISECONDS;
166     }
167 
168     public boolean isSupportsInlineDistinct() {
169         return _SUPPORTS_INLINE_DISTINCT;
170     }
171 
172     public boolean isSupportsScrollableResults() {
173         return _SUPPORTS_SCROLLABLE_RESULTS;
174     }
175 
176     public boolean isSupportsStringCaseSensitiveQuery() {
177         return _supportsStringCaseSensitiveQuery;
178     }
179 
180     public boolean isSupportsUpdateWithInnerJoin() {
181         return _SUPPORTS_UPDATE_WITH_INNER_JOIN;
182     }
183 
184     public void runSQL(String sql) throws IOException, SQLException {
185         runSQL(new String[] {sql});
186     }
187 
188     public void runSQL(Connection con, String sql)
189         throws IOException, SQLException {
190 
191         runSQL(con, new String[] {sql});
192     }
193 
194     public void runSQL(String[] sqls) throws IOException, SQLException {
195         Connection con = DataAccess.getConnection();
196 
197         try {
198             runSQL(con, sqls);
199         }
200         finally {
201             DataAccess.cleanUp(con);
202         }
203     }
204 
205     public void runSQL(Connection con, String[] sqls)
206         throws IOException, SQLException {
207 
208         Statement s = null;
209 
210         try {
211             s = con.createStatement();
212 
213             for (int i = 0; i < sqls.length; i++) {
214                 String sql = buildSQL(sqls[i]);
215 
216                 sql = sql.trim();
217 
218                 if (sql.endsWith(";")) {
219                     sql = sql.substring(0, sql.length() - 1);
220                 }
221 
222                 if (sql.endsWith("go")) {
223                     sql = sql.substring(0, sql.length() - 2);
224                 }
225 
226                 if (_log.isDebugEnabled()) {
227                     _log.debug(sql);
228                 }
229 
230                 try {
231                     s.executeUpdate(sql);
232                 }
233                 catch (SQLException sqle) {
234                     throw sqle;
235                 }
236             }
237         }
238         finally {
239             DataAccess.cleanUp(s);
240         }
241     }
242 
243     public void runSQLTemplate(String path)
244         throws IOException, NamingException, SQLException {
245 
246         runSQLTemplate(path, true);
247     }
248 
249     public void runSQLTemplate(String path, boolean failOnError)
250         throws IOException, NamingException, SQLException {
251 
252         Thread currentThread = Thread.currentThread();
253 
254         ClassLoader classLoader = currentThread.getContextClassLoader();
255 
256         InputStream is = classLoader.getResourceAsStream(
257             "com/liferay/portal/tools/sql/dependencies/" + path);
258 
259         if (is == null) {
260             is = classLoader.getResourceAsStream(path);
261         }
262 
263         if (is == null) {
264             _log.error("Invalid path " + path);
265 
266             if (failOnError) {
267                 throw new IOException("Invalid path " + path);
268             }
269             else {
270                 return;
271             }
272         }
273 
274         String template = StringUtil.read(is);
275 
276         is.close();
277 
278         boolean evaluate = path.endsWith(".vm");
279 
280         runSQLTemplateString(template, evaluate, failOnError);
281     }
282 
283     public void runSQLTemplateString(
284             String template, boolean evaluate, boolean failOnError)
285         throws IOException, NamingException, SQLException {
286 
287         if (evaluate) {
288             try {
289                 template = evaluateVM(template);
290             }
291             catch (Exception e) {
292                 _log.error(e, e);
293             }
294         }
295 
296         StringBundler sb = new StringBundler();
297 
298         UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
299             new UnsyncStringReader(template));
300 
301         String line = null;
302 
303         while ((line = unsyncBufferedReader.readLine()) != null) {
304             if (!line.startsWith("##")) {
305                 if (line.startsWith("@include ")) {
306                     int pos = line.indexOf(" ");
307 
308                     String includeFileName = line.substring(pos + 1);
309 
310                     Thread currentThread = Thread.currentThread();
311 
312                     ClassLoader classLoader =
313                         currentThread.getContextClassLoader();
314 
315                     InputStream is = classLoader.getResourceAsStream(
316                         "com/liferay/portal/tools/sql/dependencies/" +
317                             includeFileName);
318 
319                     if (is == null) {
320                         is = classLoader.getResourceAsStream(includeFileName);
321                     }
322 
323                     String include = StringUtil.read(is);
324 
325                     is.close();
326 
327                     if (includeFileName.endsWith(".vm")) {
328                         try {
329                             include = evaluateVM(include);
330                         }
331                         catch (Exception e) {
332                             _log.error(e, e);
333                         }
334                     }
335 
336                     include = convertTimestamp(include);
337                     include = replaceTemplate(include, getTemplate());
338 
339                     runSQLTemplateString(include, false, true);
340                 }
341                 else{
342                     sb.append(line);
343 
344                     if (line.endsWith(";")) {
345                         String sql = sb.toString();
346 
347                         sb.setIndex(0);
348 
349                         try {
350                             if (!sql.equals("COMMIT_TRANSACTION;")) {
351                                 runSQL(sql);
352                             }
353                             else {
354                                 if (_log.isDebugEnabled()) {
355                                     _log.debug("Skip commit sql");
356                                 }
357                             }
358                         }
359                         catch (IOException ioe) {
360                             if (failOnError) {
361                                 throw ioe;
362                             }
363                             else if (_log.isWarnEnabled()) {
364                                 _log.warn(ioe.getMessage());
365                             }
366                         }
367                         catch (SQLException sqle) {
368                             if (failOnError) {
369                                 throw sqle;
370                             }
371                             else if (_log.isWarnEnabled()) {
372                                 String message = GetterUtil.getString(
373                                     sqle.getMessage());
374 
375                                 if (!message.startsWith("Duplicate key name")) {
376                                     _log.warn(message + ": " + sql);
377                                 }
378 
379                                 if (message.startsWith("Duplicate entry") ||
380                                     message.startsWith(
381                                         "Specified key was too long")) {
382 
383                                     _log.error(line);
384                                 }
385                             }
386                         }
387                     }
388                 }
389             }
390         }
391 
392         unsyncBufferedReader.close();
393     }
394 
395     public void setSupportsStringCaseSensitiveQuery(
396         boolean supportsStringCaseSensitiveQuery) {
397 
398         if (_log.isInfoEnabled()) {
399             if (supportsStringCaseSensitiveQuery) {
400                 _log.info("Database supports case sensitive queries");
401             }
402             else {
403                 _log.info("Database does not support case sensitive queries");
404             }
405         }
406 
407         _supportsStringCaseSensitiveQuery = supportsStringCaseSensitiveQuery;
408     }
409 
410     public void updateIndexes(
411             String tablesSQL, String indexesSQL, String indexesProperties,
412             boolean dropIndexes)
413         throws IOException, SQLException {
414 
415         List<Index> indexes = getIndexes();
416 
417         Set<String> validIndexNames = null;
418 
419         if (dropIndexes) {
420             validIndexNames = dropIndexes(
421                 tablesSQL, indexesSQL, indexesProperties, indexes);
422         }
423         else {
424             validIndexNames = new HashSet<String>();
425 
426             for (Index index : indexes) {
427                 String indexName = index.getIndexName().toUpperCase();
428 
429                 validIndexNames.add(indexName);
430             }
431         }
432 
433         addIndexes(indexesSQL, validIndexNames);
434     }
435 
436     protected BaseDB(String type) {
437         _type = type;
438     }
439 
440     protected void addIndexes(String indexesSQL, Set<String> validIndexNames)
441         throws IOException {
442 
443         if (_log.isInfoEnabled()) {
444             _log.info("Adding indexes");
445         }
446 
447         DB db = DBFactoryUtil.getDB();
448 
449         UnsyncBufferedReader bufferedReader = new UnsyncBufferedReader(
450             new UnsyncStringReader(indexesSQL));
451 
452         String sql = null;
453 
454         while ((sql = bufferedReader.readLine()) != null) {
455             if (Validator.isNull(sql)) {
456                 continue;
457             }
458 
459             int y = sql.indexOf(" on ");
460             int x = sql.lastIndexOf(" ", y - 1);
461 
462             String indexName = sql.substring(x + 1, y);
463 
464             if (validIndexNames.contains(indexName)) {
465                 continue;
466             }
467 
468             if (_log.isInfoEnabled()) {
469                 _log.info(sql);
470             }
471 
472             try {
473                 db.runSQL(sql);
474             }
475             catch (Exception e) {
476                 if (_log.isWarnEnabled()) {
477                     _log.warn(e.getMessage());
478                 }
479             }
480         }
481     }
482 
483     protected abstract String buildCreateFileContent(
484             String sqlDir, String databaseName, int population)
485         throws IOException;
486 
487     protected String[] buildColumnNameTokens(String line) {
488         String[] words = StringUtil.split(line, " ");
489 
490         if (words.length == 7) {
491             words[5] = "not null;";
492         }
493 
494         String[] template = {
495             words[1], words[2], words[3], words[4], words[5]
496         };
497 
498         return template;
499     }
500 
501     protected String[] buildColumnTypeTokens(String line) {
502         String[] words = StringUtil.split(line, " ");
503 
504         String nullable = "";
505 
506         if (words.length == 6) {
507             nullable = "not null;";
508         }
509         else if (words.length == 5) {
510             nullable = words[4];
511         }
512         else if (words.length == 4) {
513             nullable = "not null;";
514 
515             if (words[3].endsWith(";")) {
516                 words[3] = words[3].substring(0, words[3].length() - 1);
517             }
518         }
519 
520         String[] template = {
521             words[1], words[2], "", words[3], nullable
522         };
523 
524         return template;
525     }
526 
527     protected String buildTemplate(String sqlDir, String fileName)
528         throws IOException {
529 
530         String template = readFile(sqlDir + "/" + fileName + ".sql");
531 
532         if (fileName.equals("portal") || fileName.equals("portal-minimal") ||
533             fileName.equals("update-5.0.1-5.1.0")) {
534 
535             UnsyncBufferedReader unsyncBufferedReader =
536                 new UnsyncBufferedReader(new UnsyncStringReader(template));
537 
538             StringBundler sb = new StringBundler();
539 
540             String line = null;
541 
542             while ((line = unsyncBufferedReader.readLine()) != null) {
543                 if (line.startsWith("@include ")) {
544                     int pos = line.indexOf(" ");
545 
546                     String includeFileName = line.substring(pos + 1);
547 
548                     File includeFile = new File(
549                         sqlDir + "/" + includeFileName);
550 
551                     if (!includeFile.exists()) {
552                         continue;
553                     }
554 
555                     String include = FileUtil.read(includeFile);
556 
557                     if (includeFileName.endsWith(".vm")) {
558                         try {
559                             include = evaluateVM(include);
560                         }
561                         catch (Exception e) {
562                             _log.error(e, e);
563                         }
564                     }
565 
566                     include = convertTimestamp(include);
567                     include = replaceTemplate(include, getTemplate());
568 
569                     sb.append(include);
570                     sb.append("\n\n");
571                 }
572                 else {
573                     sb.append(line);
574                     sb.append("\n");
575                 }
576             }
577 
578             unsyncBufferedReader.close();
579 
580             template = sb.toString();
581         }
582 
583         if (fileName.equals("indexes") && (this instanceof SybaseDB)) {
584             template = removeBooleanIndexes(sqlDir, template);
585         }
586 
587         return template;
588     }
589 
590     protected String convertTimestamp(String data) {
591         String s = null;
592 
593         if (this instanceof MySQLDB) {
594             s = StringUtil.replace(data, "SPECIFIC_TIMESTAMP_", "");
595         }
596         else {
597             s = data.replaceAll(
598                 "SPECIFIC_TIMESTAMP_" + "\\d+", "CURRENT_TIMESTAMP");
599         }
600 
601         return s;
602     }
603 
604     protected Set<String> dropIndexes(
605             String tablesSQL, String indexesSQL, String indexesProperties,
606             List<Index> indexes)
607         throws IOException, SQLException {
608 
609         if (_log.isInfoEnabled()) {
610             _log.info("Dropping stale indexes");
611         }
612 
613         Set<String> validIndexNames = new HashSet<String>();
614 
615         if (indexes.isEmpty()) {
616             return validIndexNames;
617         }
618 
619         DB db = DBFactoryUtil.getDB();
620 
621         String tablesSQLLowerCase = tablesSQL.toLowerCase();
622         String indexesSQLLowerCase = indexesSQL.toLowerCase();
623 
624         Properties indexesPropertiesObj = PropertiesUtil.load(
625             indexesProperties);
626 
627         Enumeration<String> enu =
628             (Enumeration<String>)indexesPropertiesObj.propertyNames();
629 
630         while (enu.hasMoreElements()) {
631             String key = enu.nextElement();
632 
633             String value = indexesPropertiesObj.getProperty(key);
634 
635             indexesPropertiesObj.setProperty(key.toLowerCase(), value);
636         }
637 
638         for (Index index : indexes) {
639             String indexNameUpperCase = index.getIndexName().toUpperCase();
640             String indexNameLowerCase = indexNameUpperCase.toLowerCase();
641             String tableName = index.getTableName();
642             String tableNameLowerCase = tableName.toLowerCase();
643             boolean unique = index.isUnique();
644 
645             validIndexNames.add(indexNameUpperCase);
646 
647             if (indexesPropertiesObj.containsKey(indexNameLowerCase)) {
648                 if (unique &&
649                     indexesSQLLowerCase.contains(
650                         "create unique index " + indexNameLowerCase + " ")) {
651 
652                     continue;
653                 }
654 
655                 if (!unique &&
656                     indexesSQLLowerCase.contains(
657                         "create index " + indexNameLowerCase + " ")) {
658 
659                     continue;
660                 }
661             }
662             else {
663                 if (!tablesSQLLowerCase.contains(
664                         "create table " + tableNameLowerCase + " (")) {
665 
666                     continue;
667                 }
668             }
669 
670             validIndexNames.remove(indexNameUpperCase);
671 
672             db.runSQL("drop index " + indexNameUpperCase + " on " + tableName);
673         }
674 
675         return validIndexNames;
676     }
677 
678     protected String evaluateVM(String template) throws Exception {
679         Map<String, Object> variables = new HashMap<String, Object>();
680 
681         variables.put("counter", new SimpleCounter());
682 
683         template = VelocityUtil.evaluate(template, variables);
684 
685         // Trim insert statements because it breaks MySQL Query Browser
686 
687         UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
688             new UnsyncStringReader(template));
689 
690         StringBundler sb = new StringBundler();
691 
692         String line = null;
693 
694         while ((line = unsyncBufferedReader.readLine()) != null) {
695             line = line.trim();
696 
697             sb.append(line);
698             sb.append("\n");
699         }
700 
701         unsyncBufferedReader.close();
702 
703         template = sb.toString();
704         template = StringUtil.replace(template, "\n\n\n", "\n\n");
705 
706         return template;
707     }
708 
709     protected abstract String getServerName();
710 
711     protected String getSuffix(int type) {
712         if (type == MINIMAL) {
713             return "-minimal";
714         }
715         else if (type == SHARDED) {
716             return "-sharded";
717         }
718         else {
719             return StringPool.BLANK;
720         }
721     }
722 
723     protected abstract String[] getTemplate();
724 
725     protected String readFile(String fileName) throws IOException {
726         if (FileUtil.exists(fileName)) {
727             return FileUtil.read(fileName);
728         }
729         else {
730             return StringPool.BLANK;
731         }
732     }
733 
734     protected String readSQL(String fileName, String comments, String eol)
735         throws IOException {
736 
737         if (!FileUtil.exists(fileName)) {
738             return StringPool.BLANK;
739         }
740 
741         UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
742             new FileReader(new File(fileName)));
743 
744         StringBundler sb = new StringBundler();
745 
746         String line = null;
747 
748         while ((line = unsyncBufferedReader.readLine()) != null) {
749             if (!line.startsWith(comments)) {
750                 line = StringUtil.replace(
751                     line,
752                     new String[] {"\n", "\t"},
753                     new String[] {"", ""});
754 
755                 if (line.endsWith(";")) {
756                     sb.append(line.substring(0, line.length() - 1));
757                     sb.append(eol);
758                 }
759                 else {
760                     sb.append(line);
761                 }
762             }
763         }
764 
765         unsyncBufferedReader.close();
766 
767         return sb.toString();
768     }
769 
770     protected String removeBooleanIndexes(String sqlDir, String data)
771         throws IOException {
772 
773         String portalData = readFile(sqlDir + "/portal-tables.sql");
774 
775         if (Validator.isNull(portalData)) {
776             return StringPool.BLANK;
777         }
778 
779         UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
780             new UnsyncStringReader(data));
781 
782         StringBundler sb = new StringBundler();
783 
784         String line = null;
785 
786         while ((line = unsyncBufferedReader.readLine()) != null) {
787             boolean append = true;
788 
789             int x = line.indexOf(" on ");
790 
791             if (x != -1) {
792                 int y = line.indexOf(" (", x);
793 
794                 String table = line.substring(x + 4, y);
795 
796                 x = y + 2;
797                 y = line.indexOf(")", x);
798 
799                 String[] columns = StringUtil.split(line.substring(x, y));
800 
801                 x = portalData.indexOf("create table " + table + " (");
802                 y = portalData.indexOf(");", x);
803 
804                 String portalTableData = portalData.substring(x, y);
805 
806                 for (int i = 0; i < columns.length; i++) {
807                     if (portalTableData.indexOf(
808                             columns[i].trim() + " BOOLEAN") != -1) {
809 
810                         append = false;
811 
812                         break;
813                     }
814                 }
815             }
816 
817             if (append) {
818                 sb.append(line);
819                 sb.append("\n");
820             }
821         }
822 
823         unsyncBufferedReader.close();
824 
825         return sb.toString();
826     }
827 
828     protected String removeInserts(String data) throws IOException {
829         UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
830             new UnsyncStringReader(data));
831 
832         StringBundler sb = new StringBundler();
833 
834         String line = null;
835 
836         while ((line = unsyncBufferedReader.readLine()) != null) {
837             if (!line.startsWith("insert into ") &&
838                 !line.startsWith("update ")) {
839 
840                 sb.append(line);
841                 sb.append("\n");
842             }
843         }
844 
845         unsyncBufferedReader.close();
846 
847         return sb.toString();
848     }
849 
850     protected String removeLongInserts(String data) throws IOException {
851         UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
852             new UnsyncStringReader(data));
853 
854         StringBundler sb = new StringBundler();
855 
856         String line = null;
857 
858         while ((line = unsyncBufferedReader.readLine()) != null) {
859             if (!line.startsWith("insert into Image (") &&
860                 !line.startsWith("insert into JournalArticle (") &&
861                 !line.startsWith("insert into JournalStructure (") &&
862                 !line.startsWith("insert into JournalTemplate (")) {
863 
864                 sb.append(line);
865                 sb.append("\n");
866             }
867         }
868 
869         unsyncBufferedReader.close();
870 
871         return sb.toString();
872     }
873 
874     protected String removeNull(String content) {
875         content = StringUtil.replace(content, " is null", " IS NULL");
876         content = StringUtil.replace(content, " not null", " not_null");
877         content = StringUtil.replace(content, " null", "");
878         content = StringUtil.replace(content, " not_null", " not null");
879 
880         return content;
881     }
882 
883     protected String replaceTemplate(String template, String[] actual) {
884         if ((template == null) || (TEMPLATE == null) || (actual == null)) {
885             return null;
886         }
887 
888         if (TEMPLATE.length != actual.length) {
889             return template;
890         }
891 
892         for (int i = 0; i < TEMPLATE.length; i++) {
893             if (TEMPLATE[i].equals("##") ||
894                 TEMPLATE[i].equals("'01/01/1970'")) {
895 
896                 template = template.replaceAll(TEMPLATE[i], actual[i]);
897             }
898             else {
899                 template = template.replaceAll(
900                     "\\b" + TEMPLATE[i] + "\\b", actual[i]);
901             }
902         }
903 
904         return template;
905     }
906 
907     protected abstract String reword(String data) throws IOException;
908 
909     protected static String ALTER_COLUMN_TYPE = "alter_column_type ";
910 
911     protected static String ALTER_COLUMN_NAME = "alter_column_name ";
912 
913     protected static String DROP_INDEX = "drop index";
914 
915     protected static String DROP_PRIMARY_KEY = "drop primary key";
916 
917     protected static String[] REWORD_TEMPLATE = {
918         "@table@", "@old-column@", "@new-column@", "@type@", "@nullable@"
919     };
920 
921     protected static String[] TEMPLATE = {
922         "##", "TRUE", "FALSE",
923         "'01/01/1970'", "CURRENT_TIMESTAMP",
924         " BLOB", " BOOLEAN", " DATE",
925         " DOUBLE", " INTEGER", " LONG",
926         " STRING", " TEXT", " VARCHAR",
927         " IDENTITY", "COMMIT_TRANSACTION"
928     };
929 
930     private static final boolean _SUPPORTS_ALTER_COLUMN_NAME = true;
931 
932     private static final boolean _SUPPORTS_ALTER_COLUMN_TYPE = true;
933 
934     private static final boolean _SUPPORTS_DATE_MILLISECONDS = true;
935 
936     private static final boolean _SUPPORTS_INLINE_DISTINCT = true;
937 
938     private static final boolean _SUPPORTS_SCROLLABLE_RESULTS = true;
939 
940     private static final boolean _SUPPORTS_UPDATE_WITH_INNER_JOIN = true;
941 
942     private static Log _log = LogFactoryUtil.getLog(BaseDB.class);
943 
944     private String _type;
945     private boolean _supportsStringCaseSensitiveQuery;
946 
947 }