merged 17Q1 temporary head
authorAsier Lostalé <asier.lostale@openbravo.com>
Thu, 09 Mar 2017 12:59:50 +0100
changeset 927 cb8b35695efb
parent 926 d3be29e49a89 (current diff)
parent 923 feb12dbe0440 (diff)
child 928 8c2f9c7b3ec8
merged 17Q1 temporary head
src-test/model/recreation/DATA_TYPE1.xml
--- a/.hgignore	Thu Mar 09 12:58:46 2017 +0100
+++ b/.hgignore	Thu Mar 09 12:59:50 2017 +0100
@@ -2,5 +2,6 @@
 
 build
 docs
+dbsm.properties
 src-test/config/db-config.json
 src-test/result.xml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/excludeFilter/excludePgTrgmFunctions.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+  <vector>
+    <excludedFunction name="GIN_EXTRACT_QUERY_TRGM"/>
+    <excludedFunction name="GIN_EXTRACT_VALUE_TRGM"/>
+    <excludedFunction name="GIN_TRGM_CONSISTENT"/>
+    <excludedFunction name="GTRGM_COMPRESS"/>
+    <excludedFunction name="GTRGM_CONSISTENT"/>
+    <excludedFunction name="GTRGM_DECOMPRESS"/>
+    <excludedFunction name="GTRGM_DISTANCE"/>
+    <excludedFunction name="GTRGM_IN"/>
+    <excludedFunction name="GTRGM_OUT"/>
+    <excludedFunction name="GTRGM_PENALTY"/>
+    <excludedFunction name="GTRGM_PICKSPLIT"/>
+    <excludedFunction name="GTRGM_SAME"/>
+    <excludedFunction name="GTRGM_UNION"/>
+    <excludedFunction name="SET_LIMIT"/>
+    <excludedFunction name="SHOW_LIMIT"/>
+    <excludedFunction name="SHOW_TRGM"/>
+    <excludedFunction name="SIMILARITY"/>
+    <excludedFunction name="SIMILARITY_DIST"/>
+    <excludedFunction name="SIMILARITY_OP"/>
+  </vector>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/indexes/BASIC_PARTIAL_INDEX4.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL1" primaryKey="false" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL2" primaryKey="false" required="true" type="CLOB" size="4000" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL3" primaryKey="false" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <index name="BASIC_INDEX" unique="false">
+        <index-column name="COL3"/>
+        <whereClause><![CDATA[COL3 IS NOT NULL]]></whereClause>
+      </index>
+    </table>
+  </database>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/indexes/CONTAINS_SEARCH_INDEX.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL1" primaryKey="false" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL2" primaryKey="false" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <index name="BASIC_INDEX" unique="false" containsSearch="true">
+        <index-column name="COL1"/>
+      </index>
+    </table>
+  </database>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/indexes/ICONTAINS_SEARCH_INDEX.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL1" primaryKey="false" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL2" primaryKey="false" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <index name="BASIC_INDEX" unique="false" containsSearch="true">
+        <index-column name="functionBasedColumn" functionExpression="LOWER(COL1)"/>
+      </index>
+    </table>
+  </database>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/indexes/MULTIPLE_CONTAINS_SEARCH_INDEX.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL1" primaryKey="false" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL2" primaryKey="false" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <index name="BASIC_INDEX" unique="false" containsSearch="true">
+        <index-column name="COL1"/>
+        <index-column name="COL2"/>
+      </index>
+    </table>
+  </database>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/indexes/PARTIAL_CONTAINS_SEARCH_INDEX.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL1" primaryKey="false" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL2" primaryKey="false" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <index name="PARTIAL_INDEX" unique="false" containsSearch="true">
+        <index-column name="COL1"/>
+        <whereClause><![CDATA[COL1 IS NOT NULL]]></whereClause>
+      </index>
+    </table>
+  </database>
--- a/src-test/model/recreation/DATA_TYPE1.xml	Thu Mar 09 12:58:46 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-<?xml version="1.0"?>
-  <database name="TABLE TEST">
-    <table name="TEST" primaryKey="TEST_ID">
-      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
-        <default/>
-        <onCreateDefault/>
-      </column>
-      <column name="COL1" primaryKey="false" required="true" type="DECIMAL" size="11,0" autoIncrement="false">
-        <default/>
-        <onCreateDefault/>
-      </column>
-      <column name="VC" primaryKey="true" required="true" type="VARCHAR" size="20" autoIncrement="false">
-        <default/>
-        <onCreateDefault/>
-      </column>
-      <column name="CH" primaryKey="true" required="true" type="CHAR" size="1" autoIncrement="false">
-        <default/>
-        <onCreateDefault/>
-      </column>
-    </table>
-  </database>
-  
\ No newline at end of file
--- a/src-test/model/recreation/DATA_TYPE2.xml	Thu Mar 09 12:58:46 2017 +0100
+++ b/src-test/model/recreation/DATA_TYPE2.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -9,11 +9,11 @@
         <default/>
         <onCreateDefault/>
       </column>
-      <column name="VC" primaryKey="true" required="true" type="VARCHAR" size="40" autoIncrement="false">
+      <column name="VC" primaryKey="false" required="true" type="VARCHAR" size="40" autoIncrement="false">
         <default/>
         <onCreateDefault/>
       </column>
-      <column name="CH" primaryKey="true" required="true" type="CHAR" size="1" autoIncrement="false">
+      <column name="CH" primaryKey="false" required="true" type="CHAR" size="1" autoIncrement="false">
         <default/>
         <onCreateDefault/>
       </column>
--- a/src-test/model/recreation/DATA_TYPE3.xml	Thu Mar 09 12:58:46 2017 +0100
+++ b/src-test/model/recreation/DATA_TYPE3.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -9,11 +9,11 @@
         <default/>
         <onCreateDefault/>
       </column>
-      <column name="VC" primaryKey="true" required="true" type="VARCHAR" size="20" autoIncrement="false">
+      <column name="VC" primaryKey="false" required="true" type="VARCHAR" size="20" autoIncrement="false">
         <default/>
         <onCreateDefault/>
       </column>
-      <column name="CH" primaryKey="true" required="true" type="CHAR" size="2" autoIncrement="false">
+      <column name="CH" primaryKey="false" required="true" type="CHAR" size="2" autoIncrement="false">
         <default/>
         <onCreateDefault/>
       </column>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/recreation/DATA_TYPE4.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL1" primaryKey="false" required="true" type="DECIMAL" size="10,0" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="VC" primaryKey="false" required="true" type="NVARCHAR" size="20" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="CH" primaryKey="false" required="true" type="NCHAR" size="1" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+    </table>
+  </database>
+  
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/recreation/DATA_TYPE5.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL1" primaryKey="false" required="true" type="DECIMAL" size="10,0" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="VC" primaryKey="false" required="true" type="CLOB" size="4000" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="CH" primaryKey="false" required="true" type="CHAR" size="1" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+    </table>
+  </database>
+  
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/recreation/DATA_TYPE6.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL1" primaryKey="false" required="true" type="DECIMAL" size="10,0" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="VC" primaryKey="false" required="true" type="VARCHAR" size="20" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="CH" primaryKey="false" required="true" type="CLOB" size="4000" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+    </table>
+  </database>
+  
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/recreation/DATA_TYPE7.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL1" primaryKey="false" required="true" type="DECIMAL" size="10,0" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="VC" primaryKey="false" required="true" type="NVARCHAR" size="40" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="CH" primaryKey="false" required="true" type="CHAR" size="1" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+    </table>
+  </database>
+  
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/recreation/DATA_TYPE8.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="COL1" primaryKey="false" required="true" type="DECIMAL" size="10,0" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="VC" primaryKey="false" required="true" type="NVARCHAR" size="40" autoIncrement="false">
+        <default/>
+        <onCreateDefault>'A'</onCreateDefault>
+      </column>
+      <column name="CH" primaryKey="false" required="true" type="CHAR" size="1" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+    </table>
+  </database>
+  
\ No newline at end of file
--- a/src-test/model/recreation/DATA_TYPE_BASE.xml	Thu Mar 09 12:58:46 2017 +0100
+++ b/src-test/model/recreation/DATA_TYPE_BASE.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -9,11 +9,11 @@
         <default/>
         <onCreateDefault/>
       </column>
-      <column name="VC" primaryKey="true" required="true" type="VARCHAR" size="20" autoIncrement="false">
+      <column name="VC" primaryKey="false" required="true" type="VARCHAR" size="20" autoIncrement="false">
         <default/>
         <onCreateDefault/>
       </column>
-      <column name="CH" primaryKey="true" required="true" type="CHAR" size="1" autoIncrement="false">
+      <column name="CH" primaryKey="false" required="true" type="CHAR" size="1" autoIncrement="false">
         <default/>
         <onCreateDefault/>
       </column>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/recreation/DATA_TYPE_NUMBERS1.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="N1" primaryKey="false" required="true" type="DECIMAL" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="N2" primaryKey="false" required="true" type="DECIMAL" size="20,0" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="N3" primaryKey="false" required="true" type="DECIMAL" size="10,5" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+    </table>
+  </database>
+  
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/recreation/DATA_TYPE_NUMBERS2.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="N1" primaryKey="false" required="true" type="DECIMAL" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="N2" primaryKey="false" required="true" type="DECIMAL" size="10,5" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="N3" primaryKey="false" required="true" type="DECIMAL" size="10,5" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+    </table>
+  </database>
+  
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/recreation/DATA_TYPE_NUMBERS3.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="N1" primaryKey="false" required="true" type="DECIMAL" size="10,0" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="N2" primaryKey="false" required="true" type="DECIMAL" size="10,0" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="N3" primaryKey="false" required="true" type="DECIMAL" size="10,5" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+    </table>
+  </database>
+  
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/model/recreation/DATA_TYPE_NUMBERS_BASE.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+  <database name="TABLE TEST">
+    <table name="TEST" primaryKey="TEST_ID">
+      <column name="TEST_ID" primaryKey="true" required="true" type="VARCHAR" size="32" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="N1" primaryKey="false" required="true" type="DECIMAL" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="N2" primaryKey="false" required="true" type="DECIMAL" size="10,0" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+      <column name="N3" primaryKey="false" required="true" type="DECIMAL" size="10,5" autoIncrement="false">
+        <default/>
+        <onCreateDefault/>
+      </column>
+    </table>
+  </database>
+  
\ No newline at end of file
--- a/src-test/src/org/openbravo/dbsm/test/base/DbsmTest.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src-test/src/org/openbravo/dbsm/test/base/DbsmTest.java	Thu Mar 09 12:59:50 2017 +0100
@@ -1,6 +1,6 @@
 /*
  ************************************************************************************
- * Copyright (C) 2015-2016 Openbravo S.L.U.
+ * Copyright (C) 2015-2017 Openbravo S.L.U.
  * Licensed under the Apache Software License version 2.0
  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to  in writing,  software  distributed
@@ -69,6 +69,9 @@
 import org.codehaus.jettison.json.JSONObject;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
@@ -100,6 +103,7 @@
   protected Logger log;
   protected String modelPath;
   private static BasicDataSource ds;
+  private static String previousDB;
   private Rdbms rdbms;
   private Platform platform;
   private SQLBatchEvaluator evaluator;
@@ -115,6 +119,15 @@
     standard, forced
   }
 
+  @Rule
+  public TestWatcher watcher = new TestWatcher() {
+    @Override
+    protected void starting(Description description) {
+      log.info("*** Starting test case: " + description.getClassName() + "."
+          + description.getMethodName());
+    }
+  };
+
   public DbsmTest(String rdbms, String driver, String url, String sid, String user,
       String password, String name) throws FileNotFoundException, IOException {
     initLogging();
@@ -138,6 +151,20 @@
     this.driver = driver;
     this.url = ownerUrl;
 
+    if (previousDB == null || !previousDB.equals(ownerUrl)) {
+      if (ds != null) {
+        try {
+          log.info("Closing datasource to switch DB from " + previousDB + " to " + ownerUrl);
+          ds.close();
+        } catch (SQLException e) {
+          log.error("Error closing ds", e);
+        }
+      }
+
+      ds = null;
+      previousDB = ownerUrl;
+    }
+
     if (ds == null) {
       ds = DBSMOBUtil.getDataSource(getDriver(), getUrl(), getUser(), getPassword());
     }
@@ -310,7 +337,7 @@
         platform.getSqlBuilder().setForcedRecreation("all");
       }
 
-      Database originalDB = platform.loadModelFromDatabase(getExcludeFilter());
+      Database originalDB = platform.loadModelFromDatabase(getExcludeFilter(), false);
       Database newDB = DatabaseUtils.readDatabase(dbModel);
 
       final DatabaseData databaseOrgData = new DatabaseData(newDB);
@@ -423,7 +450,7 @@
         ModelComparator comparator = new ModelComparator(platform.getPlatformInfo(),
             platform.isDelimitedIdentifierModeOn());
         @SuppressWarnings("unchecked")
-        List<Change> newChanges = comparator.compare(DatabaseUtils.readDatabase(dbModel),
+        List<ModelChange> newChanges = comparator.compare(DatabaseUtils.readDatabase(dbModel),
             platform.loadModelFromDatabase(getExcludeFilter()));
         assertThat("changes between updated db and target db", newChanges, is(empty()));
       }
@@ -482,8 +509,7 @@
 
     ModelComparator comparator = new ModelComparator(platform.getPlatformInfo(),
         platform.isDelimitedIdentifierModeOn());
-    @SuppressWarnings("unchecked")
-    List<Change> newChanges = comparator.compare(DatabaseUtils.readDatabase(dbModel),
+    List<ModelChange> newChanges = comparator.compare(DatabaseUtils.readDatabase(dbModel),
         platform.loadModelFromDatabase(getExcludeFilter()));
     assertThat("changes between updated db and target db", newChanges, is(empty()));
     return newDB;
@@ -672,16 +698,25 @@
         pkValue = RandomStringUtils.random(col.getSizeAsInt(), "0123456789ABCDEF");
         values += "'" + pkValue + "'";
       } else if ("VARCHAR".equals(col.getType()) || "NVARCHAR".equals(col.getType())
-          || "CHAR".equals(col.getType())) {
+          || "CHAR".equals(col.getType()) || "NCHAR".equals(col.getType())) {
         values += "'" + RandomStringUtils.randomAlphanumeric(col.getSizeAsInt()) + "'";
       } else if ("DECIMAL".equals(col.getType())) {
-        values += RandomStringUtils.randomNumeric(col.getSizeAsInt());
+        int scale = col.getScaleAsInt();
+        int precision = col.getPrecisionRadix();
+        if (scale == 0 && precision == 0) {
+          values += RandomStringUtils.randomNumeric(10);
+        } else {
+          values += RandomStringUtils.randomNumeric(precision - scale)
+              + (scale == 0 ? "" : ("." + RandomStringUtils.randomNumeric(scale)));
+        }
       } else if ("TIMESTAMP".equals(col.getType())) {
 
         Date date = new Date((long) (new Random().nextDouble() * (System.currentTimeMillis())));
         SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
 
         values += "to_date('" + sdf.format(date) + "', 'DD/MM/YYYY')";
+      } else if ("CLOB".equals(col.getType())) {
+        values += "'" + RandomStringUtils.randomAlphanumeric(10) + "'";
       }
     }
     sql += ") values (" + values + ")";
@@ -762,7 +797,46 @@
         cn.close();
       }
     }
+  }
 
+  protected void installPgTrgmExtension() {
+    if (getRdbms() != Rdbms.PG) {
+      return;
+    }
+    Connection connection = null;
+    try {
+      connection = getDataSource().getConnection();
+      StringBuilder query = new StringBuilder();
+      query.append("CREATE EXTENSION IF NOT EXISTS \"pg_trgm\"");
+      PreparedStatement st = connection.prepareStatement(query.toString());
+      st.execute();
+    } catch (SQLException e) {
+      log.error("Error while creating pg_trgm extension");
+    } finally {
+      getPlatform().returnConnection(connection);
+    }
+    // Configure the exclude filter
+    ExcludeFilter localExcludeFilter = new ExcludeFilter();
+    localExcludeFilter.fillFromFile(new File("model/excludeFilter/excludePgTrgmFunctions.xml"));
+    setExcludeFilter(localExcludeFilter);
+  }
+
+  protected void uninstallPgTrgmExtension() {
+    if (getRdbms() != Rdbms.PG) {
+      return;
+    }
+    Connection connection = null;
+    try {
+      connection = getDataSource().getConnection();
+      StringBuilder query = new StringBuilder();
+      query.append("DROP EXTENSION \"pg_trgm\" CASCADE");
+      PreparedStatement st = connection.prepareStatement(query.toString());
+      st.execute();
+    } catch (SQLException e) {
+      log.error("Error while deleting pg_trgm extension");
+    } finally {
+      getPlatform().returnConnection(connection);
+    }
   }
 
   /** Represents a DB row with its values */
--- a/src-test/src/org/openbravo/dbsm/test/base/TestBatchEvaluator.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src-test/src/org/openbravo/dbsm/test/base/TestBatchEvaluator.java	Thu Mar 09 12:59:50 2017 +0100
@@ -52,4 +52,14 @@
   public List<String> getSQLStatements() {
     return statements;
   }
+
+  @Override
+  public void setLogInfoSucessCommands(boolean logInfoSucessCommands) {
+
+  }
+
+  @Override
+  public boolean isLogInfoSucessCommands() {
+    return false;
+  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/src/org/openbravo/dbsm/test/model/ContainsSearchIndexes.java	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,202 @@
+/*
+ ************************************************************************************
+ * Copyright (C) 2016 Openbravo S.L.U.
+ * Licensed under the Apache Software License version 2.0
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to  in writing,  software  distributed
+ * under the License is distributed  on  an  "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR
+ * CONDITIONS OF ANY KIND, either  express  or  implied.  See  the  License  for  the
+ * specific language governing permissions and limitations under the License.
+ ************************************************************************************
+ */
+package org.openbravo.dbsm.test.model;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isEmptyString;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assume.assumeThat;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test cases to test the support of indexes intended to speed up searching using 'contains'
+ * operators.
+ * 
+ * @author caristu
+ *
+ */
+public class ContainsSearchIndexes extends IndexBaseTest {
+
+  public ContainsSearchIndexes(String rdbms, String driver, String url, String sid, String user,
+      String password, String name, TestType testType) throws FileNotFoundException, IOException {
+    super(rdbms, driver, url, sid, user, password, name, testType);
+  }
+
+  @Before
+  @Override
+  public void installPgTrgmExtension() {
+    super.installPgTrgmExtension();
+  }
+
+  @After
+  @Override
+  public void uninstallPgTrgmExtension() {
+    super.uninstallPgTrgmExtension();
+  }
+
+  @Test
+  // Tests that indexes for contains search are properly imported
+  public void importContainsSearchIndex() {
+    resetDB();
+    createDatabaseIfNeeded();
+    updateDatabase("indexes/CONTAINS_SEARCH_INDEX.xml");
+    if (Rdbms.PG.equals(getRdbms())) {
+      assertIsContainsSearchIndex("BASIC_INDEX");
+    } else if (Rdbms.ORA.equals(getRdbms())) {
+      // In Oracle, the contains search index definition should be stored in the comment of the
+      // table
+      assertThat(getCommentOfTableInOracle("TEST"), equalTo("BASIC_INDEX.containsSearch$"));
+    }
+  }
+
+  @Test
+  // Tests that indexes for icontains search are properly imported
+  // This index is a function based index which makes use of the icontains search feature
+  public void importIcontainsSearchIndex() {
+    resetDB();
+    createDatabaseIfNeeded();
+    updateDatabase("indexes/ICONTAINS_SEARCH_INDEX.xml");
+    if (Rdbms.PG.equals(getRdbms())) {
+      assertIsContainsSearchIndex("BASIC_INDEX");
+    } else if (Rdbms.ORA.equals(getRdbms())) {
+      // In Oracle, the contains search index definition should be stored in the comment of the
+      // table
+      assertThat(getCommentOfTableInOracle("TEST"), equalTo("BASIC_INDEX.containsSearch$"));
+    }
+  }
+
+  private void assertIsContainsSearchIndex(String indexName) {
+    final List<String> expectedConfiguration = Arrays.asList("gin", "gin_trgm_ops");
+    String accessMethod = getIndexAccessMethodFromDb(indexName);
+    String operatorClassName = getOperatorClassNameForIndexFromDb(indexName);
+    assertEquals(expectedConfiguration, Arrays.asList(accessMethod, operatorClassName));
+  }
+
+  @Test
+  // Tests that indexes for contains search are properly exported
+  public void exportContainsSearchIndex() throws IOException {
+    resetDB();
+    createDatabaseIfNeeded();
+    updateDatabase("indexes/CONTAINS_SEARCH_INDEX.xml");
+    assertExport("indexes/CONTAINS_SEARCH_INDEX.xml", "tables/TEST.xml");
+  }
+
+  @Test
+  // Tests that indexes for icontains search are properly exported
+  public void exportIcontainsSearchIndex() throws IOException {
+    resetDB();
+    createDatabaseIfNeeded();
+    updateDatabase("indexes/ICONTAINS_SEARCH_INDEX.xml");
+    assertExport("indexes/ICONTAINS_SEARCH_INDEX.xml", "tables/TEST.xml");
+  }
+
+  @Test
+  // Tests that an existing basic index can be changed as a contains search index
+  public void changeIndexFromBasicToContainsSearch() throws IOException {
+    assumeThat(getTestType(), is(TestType.onCreate));
+    resetDB();
+    updateDatabase("indexes/BASIC_INDEX.xml");
+    updateDatabase("indexes/CONTAINS_SEARCH_INDEX.xml");
+    assertExport("indexes/CONTAINS_SEARCH_INDEX.xml", "tables/TEST.xml");
+  }
+
+  @Test
+  // Tests that an existing contains search index can be changed as basic
+  public void changeIndexFromContainsSearchToBasic() throws IOException {
+    assumeThat(getTestType(), is(TestType.onCreate));
+    resetDB();
+    updateDatabase("indexes/CONTAINS_SEARCH_INDEX.xml");
+    updateDatabase("indexes/BASIC_INDEX.xml");
+    assertExport("indexes/BASIC_INDEX.xml", "tables/TEST.xml");
+  }
+
+  @Test
+  // Tests that if an index is changed as a contains search one, that index is recreated in postgres
+  // but not in oracle
+  public void recreationToChangeIndexAsContainsSearch() {
+    resetDB();
+    createDatabaseIfNeeded();
+    updateDatabase("indexes/BASIC_INDEX.xml");
+    List<String> commands = sqlStatmentsForUpdate("indexes/CONTAINS_SEARCH_INDEX.xml");
+    if (Rdbms.PG.equals(getRdbms())) {
+      assertThat("Index is dropped", commands, hasItem(containsString("DROP INDEX BASIC_INDEX")));
+      assertThat("Index is created", commands, hasItem(containsString("CREATE INDEX BASIC_INDEX")));
+    } else if (Rdbms.ORA.equals(getRdbms())) {
+      List<String> commentUpdateCommand = Arrays
+          .asList("COMMENT ON TABLE TEST IS 'BASIC_INDEX.containsSearch$'\n");
+      assertEquals("Not recreating index", commentUpdateCommand, commands);
+    }
+  }
+
+  @Test
+  // Tests that if a contains search index is changed to be basic, that index is recreated in
+  // postgres but not in oracle
+  public void recreationToChangeIndexAsBasic() {
+    resetDB();
+    createDatabaseIfNeeded();
+    updateDatabase("indexes/CONTAINS_SEARCH_INDEX.xml");
+    List<String> commands = sqlStatmentsForUpdate("indexes/BASIC_INDEX.xml");
+    if (Rdbms.PG.equals(getRdbms())) {
+      assertThat("Index is dropped", commands, hasItem(containsString("DROP INDEX BASIC_INDEX")));
+      assertThat("Index is created", commands, hasItem(containsString("CREATE INDEX BASIC_INDEX")));
+    } else if (Rdbms.ORA.equals(getRdbms())) {
+      List<String> commentUpdateCommand = Arrays.asList("COMMENT ON TABLE TEST IS ''\n");
+      assertEquals("Not recreating index", commentUpdateCommand, commands);
+    }
+  }
+
+  @Test
+  // Tests that if a contains search index is removed in Oracle, the comment associated with
+  // it is removed from its table
+  public void removeIndexShouldRemoveComment() {
+    assumeThat("not executing in Postgres", getRdbms(), is(Rdbms.ORA));
+    resetDB();
+    createDatabaseIfNeeded();
+    updateDatabase("indexes/CONTAINS_SEARCH_INDEX.xml");
+    updateDatabase("indexes/BASE_MODEL.xml");
+    String tableComment = getCommentOfTableInOracle("TEST");
+    assertThat(tableComment, anyOf(isEmptyString(), nullValue()));
+  }
+
+  @Test
+  // Tests that it is possible to define contains search indexes with multiple
+  // columns
+  public void exportMultipleContainsSearchIndex() throws IOException {
+    resetDB();
+    createDatabaseIfNeeded();
+    updateDatabase("indexes/MULTIPLE_CONTAINS_SEARCH_INDEX.xml");
+    assertExport("indexes/MULTIPLE_CONTAINS_SEARCH_INDEX.xml", "tables/TEST.xml");
+  }
+
+  @Test
+  // Tests that it is possible to define a partial index to be used for contains search
+  public void exportPartialContainsSearchIndex() throws IOException {
+    resetDB();
+    createDatabaseIfNeeded();
+    updateDatabase("indexes/PARTIAL_CONTAINS_SEARCH_INDEX.xml");
+    assertExport("indexes/PARTIAL_CONTAINS_SEARCH_INDEX.xml", "tables/TEST.xml");
+  }
+}
--- a/src-test/src/org/openbravo/dbsm/test/model/IndexBaseTest.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src-test/src/org/openbravo/dbsm/test/model/IndexBaseTest.java	Thu Mar 09 12:59:50 2017 +0100
@@ -101,6 +101,34 @@
   }
 
   /**
+   * Given a table, return its comment
+   * 
+   * @param tableName
+   *          the name of table
+   * @return the comment of the given table
+   */
+  protected String getCommentOfTableInOracle(String tableName) {
+    String tableComment = null;
+    Connection con = null;
+    try {
+      PreparedStatement st = null;
+      con = getPlatform().getDataSource().getConnection();
+      st = con
+          .prepareStatement("SELECT comments FROM all_tab_comments WHERE UPPER(table_name) = ?");
+      st.setString(1, tableName.toUpperCase());
+      ResultSet rs = st.executeQuery();
+      if (rs.next()) {
+        tableComment = rs.getString(1);
+      }
+    } catch (SQLException e) {
+      log.error("Error while getting the comment of the table " + tableName, e);
+    } finally {
+      getPlatform().returnConnection(con);
+    }
+    return tableComment;
+  }
+
+  /**
    * Given the name of a column and its table name, returns the comment of the column
    * 
    * @param tableName
@@ -131,4 +159,68 @@
     return columnComment;
   }
 
+  /**
+   * Given the name of an index, returns the operator class of its first column
+   * 
+   * @param indexName
+   *          the name of the index
+   * @return the operator class of the first column of the given index
+   */
+  protected String getOperatorClassNameForIndexFromDb(String indexName) {
+    String operatorClassName = null;
+    Connection cn = null;
+    try {
+      cn = getDataSource().getConnection();
+      StringBuilder query = new StringBuilder();
+      query.append("SELECT PG_OPCLASS.opcname ");
+      query.append("FROM PG_INDEX, PG_CLASS, PG_OPCLASS ");
+      query.append("WHERE PG_INDEX.indexrelid = PG_CLASS.OID ");
+      query.append("AND PG_OPCLASS.OID = PG_INDEX.indclass[0] ");
+      query.append("AND UPPER(PG_CLASS.relname) = ?");
+      PreparedStatement st = cn.prepareStatement(query.toString());
+      st.setString(1, indexName.toUpperCase());
+      ResultSet rs = st.executeQuery();
+      if (rs.next()) {
+        operatorClassName = rs.getString(1);
+      }
+    } catch (SQLException e) {
+      log.error("Error while getting the name of the operator class of the index " + indexName, e);
+    } finally {
+      getPlatform().returnConnection(cn);
+    }
+    return operatorClassName;
+  }
+
+  /**
+   * Given the name of an index, returns its access method (BTREE, GIN,...)
+   * 
+   * @param indexName
+   *          the name of the index
+   * @return the access method of the index.
+   */
+  protected String getIndexAccessMethodFromDb(String indexName) {
+    String indexWhereClause = null;
+    Connection connection = null;
+    try {
+      connection = getDataSource().getConnection();
+      StringBuilder query = new StringBuilder();
+      query.append("SELECT PG_AM.amname ");
+      query.append("FROM PG_INDEX, PG_CLASS, PG_AM ");
+      query.append("WHERE PG_INDEX.indexrelid = PG_CLASS.oid ");
+      query.append("AND PG_CLASS.relam = PG_AM.oid ");
+      query.append("AND UPPER(PG_CLASS.relname) = UPPER(?)");
+      PreparedStatement st = connection.prepareStatement(query.toString());
+      st.setString(1, indexName.toUpperCase());
+      ResultSet rs = st.executeQuery();
+      if (rs.next()) {
+        indexWhereClause = rs.getString(1);
+      }
+    } catch (SQLException e) {
+      log.error("Error while getting the access method of the index " + indexName, e);
+    } finally {
+      getPlatform().returnConnection(connection);
+    }
+    return indexWhereClause;
+  }
+
 }
--- a/src-test/src/org/openbravo/dbsm/test/model/ModelSuite.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src-test/src/org/openbravo/dbsm/test/model/ModelSuite.java	Thu Mar 09 12:59:50 2017 +0100
@@ -15,14 +15,15 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
 import org.junit.runners.Suite.SuiteClasses;
+import org.openbravo.dbsm.test.configscript.ConfigScriptSuite;
 import org.openbravo.dbsm.test.model.data.CreateDefault;
 import org.openbravo.dbsm.test.model.data.DefaultValuesTest;
 import org.openbravo.dbsm.test.model.data.OtherDefaults;
 import org.openbravo.dbsm.test.model.recreation.AddDropColumn;
 import org.openbravo.dbsm.test.model.recreation.AddDropConstraints;
-import org.openbravo.dbsm.test.model.recreation.DataTypeChanges;
 import org.openbravo.dbsm.test.model.recreation.OtherRecreations;
 import org.openbravo.dbsm.test.model.recreation.SQLCommands;
+import org.openbravo.dbsm.test.model.recreation.TypeChangeSuite;
 
 /**
  * Test suite grouping all cases for database model
@@ -43,6 +44,7 @@
     CheckIndexFunctionInPrescripts.class, //
     OperatorClassIndexes.class, //
     PartialIndexes.class, //
+    ContainsSearchIndexes.class, //
     CheckExcludeFilter.class, //
     CheckPlSqlStandardizationOnModelLoad.class, //
     CheckDisableAndEnableForeignKeysAndConstraints.class, //
@@ -51,12 +53,14 @@
     Views.class,//
 
     AddDropConstraints.class,//
-    DataTypeChanges.class, //
+    TypeChangeSuite.class, //
     OtherDefaults.class, //
     SQLCommands.class, //
     OtherRecreations.class, //
     PreventConstraintDeletion.class, //
-    PreventCascadeRowDeletion.class })
+    PreventCascadeRowDeletion.class,//
+
+    ConfigScriptSuite.class })
 public class ModelSuite {
 
 }
--- a/src-test/src/org/openbravo/dbsm/test/model/OperatorClassIndexes.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src-test/src/org/openbravo/dbsm/test/model/OperatorClassIndexes.java	Thu Mar 09 12:59:50 2017 +0100
@@ -25,10 +25,6 @@
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
 import java.util.List;
 
 import org.junit.Test;
@@ -175,61 +171,4 @@
     return commentCommands;
   }
 
-  /**
-   * Given a table, return its comment
-   * 
-   * @param tableName
-   *          the name of table
-   * @return the comment of the given table
-   */
-  private String getCommentOfTableInOracle(String tableName) {
-    String tableComment = null;
-    Connection con = null;
-    try {
-      PreparedStatement st = null;
-      con = getPlatform().getDataSource().getConnection();
-      st = con
-          .prepareStatement("SELECT comments FROM user_tab_comments WHERE UPPER(table_name) = ?");
-      st.setString(1, tableName.toUpperCase());
-      ResultSet rs = st.executeQuery();
-      if (rs.next()) {
-        tableComment = rs.getString(1);
-      }
-    } catch (SQLException e) {
-      log.error("Error while getting the comment of the table " + tableName, e);
-    } finally {
-      getPlatform().returnConnection(con);
-    }
-    return tableComment;
-  }
-
-  /**
-   * Given the name of an index, returns the operator class of its first column
-   * 
-   * @param indexName
-   *          the name of the index
-   * @return the operator class of the first column of the given index
-   */
-  private String getOperatorClassNameForIndexFromDb(String indexName) {
-    String operatorClassName = null;
-    try {
-      Connection cn = getDataSource().getConnection();
-      StringBuilder query = new StringBuilder();
-      query.append("SELECT PG_OPCLASS.opcname ");
-      query.append("FROM PG_INDEX, PG_CLASS, PG_OPCLASS ");
-      query.append("WHERE PG_INDEX.indexrelid = PG_CLASS.OID ");
-      query.append("AND PG_OPCLASS.OID = PG_INDEX.indclass[0] ");
-      query.append("AND UPPER(PG_CLASS.relname) = ?");
-      PreparedStatement st = cn.prepareStatement(query.toString());
-      st.setString(1, indexName.toUpperCase());
-      ResultSet rs = st.executeQuery();
-      if (rs.next()) {
-        operatorClassName = rs.getString(1);
-      }
-    } catch (SQLException e) {
-      log.error("Error while getting the name of the operator class of the index " + indexName, e);
-    }
-    return operatorClassName;
-  }
-
 }
--- a/src-test/src/org/openbravo/dbsm/test/model/PartialIndexes.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src-test/src/org/openbravo/dbsm/test/model/PartialIndexes.java	Thu Mar 09 12:59:50 2017 +0100
@@ -1,6 +1,6 @@
 /*
  ************************************************************************************
- * Copyright (C) 2016 Openbravo S.L.U.
+ * Copyright (C) 2016-2017 Openbravo S.L.U.
  * Licensed under the Apache Software License version 2.0
  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to  in writing,  software  distributed
@@ -394,4 +394,15 @@
     updateDatabase("indexes/BASE_MODEL_WITH_INDEXES.xml");
     assertExport("indexes/BASE_MODEL_WITH_INDEXES.xml", "tables/TEST.xml");
   }
+
+  @Test
+  // Tests that it is possible to create a new partial index on a newly added column having another
+  // column with changes that force table recreation
+  public void addNewPartialIndexHavingTableRecreation() throws IOException {
+    assumeThat(getTestType(), is(TestType.onCreate));
+    resetDB();
+    createDatabase("indexes/BASE_MODEL.xml");
+    updateDatabase("indexes/BASIC_PARTIAL_INDEX4.xml");
+    assertExport("indexes/BASIC_PARTIAL_INDEX4.xml", "tables/TEST.xml");
+  }
 }
--- a/src-test/src/org/openbravo/dbsm/test/model/recreation/AddDropConstraints.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src-test/src/org/openbravo/dbsm/test/model/recreation/AddDropConstraints.java	Thu Mar 09 12:59:50 2017 +0100
@@ -64,4 +64,52 @@
   public void addFunctionToExistingIndex() {
     assertTablesAreNotRecreated("IDX.xml", "FUNCTION_IDX.xml");
   }
+
+  @Test
+  public void partialIndexFromScratch() {
+    assertTablesAreNotRecreated("../indexes/BASE_MODEL.xml", "../indexes/BASIC_PARTIAL_INDEX.xml");
+  }
+
+  @Test
+  public void standardIndexToPartial() {
+    assertTablesAreNotRecreated("../indexes/BASIC_INDEX.xml", "../indexes/BASIC_PARTIAL_INDEX.xml");
+  }
+
+  @Test
+  public void partialIndexToStandard() {
+    assertTablesAreNotRecreated("../indexes/BASIC_PARTIAL_INDEX.xml", "../indexes/BASIC_INDEX.xml");
+  }
+
+  @Test
+  public void searchIndexFromScratch() {
+    installPgTrgmExtension();
+    try {
+      assertTablesAreNotRecreated("../indexes/BASE_MODEL.xml",
+          "../indexes/CONTAINS_SEARCH_INDEX.xml");
+    } finally {
+      uninstallPgTrgmExtension();
+    }
+  }
+
+  @Test
+  public void standardIndexToSearch() {
+    installPgTrgmExtension();
+    try {
+      assertTablesAreNotRecreated("../indexes/BASIC_INDEX.xml",
+          "../indexes/CONTAINS_SEARCH_INDEX.xml");
+    } finally {
+      uninstallPgTrgmExtension();
+    }
+  }
+
+  @Test
+  public void searchIndexToStandard() {
+    installPgTrgmExtension();
+    try {
+      assertTablesAreNotRecreated("../indexes/CONTAINS_SEARCH_INDEX.xml",
+          "../indexes/BASIC_INDEX.xml");
+    } finally {
+      uninstallPgTrgmExtension();
+    }
+  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/src/org/openbravo/dbsm/test/model/recreation/ColumnSizeChange.java	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,69 @@
+package org.openbravo.dbsm.test.model.recreation;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.junit.Test;
+
+public class ColumnSizeChange extends DataTypeChanges {
+
+  public ColumnSizeChange(String rdbms, String driver, String url, String sid, String user,
+      String password, String name, ActionType type, RecreationMode recMode)
+      throws FileNotFoundException, IOException {
+    super(rdbms, driver, url, sid, user, password, name, type, recMode);
+  }
+
+  @Test
+  public void increaseVarcharSize() {
+    assertTablesAreNotRecreated("DATA_TYPE_BASE.xml", "DATA_TYPE2.xml");
+  }
+
+  @Test
+  public void increaseCharSize() {
+    assertTablesAreNotRecreated("DATA_TYPE_BASE.xml", "DATA_TYPE3.xml");
+  }
+
+  @Test
+  public void increaseNumberPrecisionNoScale() {
+    assertTablesAreNotRecreated("DATA_TYPE_NUMBERS_BASE.xml", "DATA_TYPE_NUMBERS1.xml");
+  }
+
+  @Test
+  public void increaseScaleKeepPrecision() {
+    assertTablesAreRecreated("DATA_TYPE_NUMBERS_BASE.xml", "DATA_TYPE_NUMBERS2.xml", false);
+  }
+
+  @Test
+  public void decreaseScaleKeepPrecision() {
+    // ORA-01440: column to be modified must be empty to decrease precision or scale
+    worksOnlyIn(Rdbms.PG);
+
+    assertTablesAreNotRecreated("DATA_TYPE_NUMBERS2.xml", "DATA_TYPE_NUMBERS_BASE.xml");
+  }
+
+  @Test
+  public void decreaseScaleKeepPrecisionORA() {
+    // this case is not supported in ORA, so table should always be recreated
+    worksOnlyIn(Rdbms.ORA);
+
+    assertTablesAreRecreated("DATA_TYPE_NUMBERS2.xml", "DATA_TYPE_NUMBERS_BASE.xml");
+  }
+
+  @Test
+  public void fromAnyPrecisionToFixedPrecision() {
+    // ORA-01440: column to be modified must be empty to decrease precision or scale
+    // it works in PG as far as data doesn't overflow new restriction
+    worksOnlyIn(Rdbms.PG);
+
+    assertTablesAreNotRecreated("DATA_TYPE_NUMBERS_BASE.xml", "DATA_TYPE_NUMBERS3.xml");
+  }
+
+  @Test
+  public void fromAnyPrecisionToFixedPrecisionORA() {
+    // this case is not supported in ORA, so table should always be recreated
+    worksOnlyIn(Rdbms.ORA);
+
+    assertTablesAreRecreated("DATA_TYPE_NUMBERS_BASE.xml", "DATA_TYPE_NUMBERS3.xml");
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/src/org/openbravo/dbsm/test/model/recreation/ColumnTypeChange.java	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,61 @@
+package org.openbravo.dbsm.test.model.recreation;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.junit.Test;
+
+public class ColumnTypeChange extends DataTypeChanges {
+
+  public ColumnTypeChange(String rdbms, String driver, String url, String sid, String user,
+      String password, String name, ActionType type, RecreationMode recMode)
+      throws FileNotFoundException, IOException {
+    super(rdbms, driver, url, sid, user, password, name, type, recMode);
+  }
+
+  @Test
+  public void changeVarcharToNVarchar() {
+    assertTablesAreNotRecreated("DATA_TYPE_BASE.xml", "DATA_TYPE4.xml");
+  }
+
+  @Test
+  public void changeNVarcharToVarchar() {
+    worksOnlyIn(Rdbms.PG);
+    assertTablesAreNotRecreated("DATA_TYPE4.xml", "DATA_TYPE_BASE.xml");
+  }
+
+  @Test
+  public void changeNVarcharToVarcharORA() {
+    worksOnlyIn(Rdbms.ORA);
+    assertTablesAreRecreated("DATA_TYPE4.xml", "DATA_TYPE_BASE.xml");
+  }
+
+  @Test
+  public void changeVarcharToText() {
+    worksOnlyIn(Rdbms.PG);
+    assertTablesAreNotRecreated("DATA_TYPE_BASE.xml", "DATA_TYPE5.xml");
+  }
+
+  @Test
+  public void changeVarcharToORA() {
+    worksOnlyIn(Rdbms.ORA);
+    assertTablesAreRecreated("DATA_TYPE_BASE.xml", "DATA_TYPE5.xml");
+  }
+
+  @Test
+  public void changeCharToText() {
+    worksOnlyIn(Rdbms.PG);
+    assertTablesAreNotRecreated("DATA_TYPE_BASE.xml", "DATA_TYPE6.xml");
+  }
+
+  @Test
+  public void changeCharToTextORA() {
+    worksOnlyIn(Rdbms.ORA);
+    assertTablesAreRecreated("DATA_TYPE_BASE.xml", "DATA_TYPE6.xml");
+  }
+
+  @Test
+  public void changeTextToVarchar() {
+    assertTablesAreRecreated("DATA_TYPE5.xml", "DATA_TYPE_BASE.xml");
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/src/org/openbravo/dbsm/test/model/recreation/CombinedTypeChanges.java	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,25 @@
+package org.openbravo.dbsm.test.model.recreation;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.junit.Test;
+
+public class CombinedTypeChanges extends DataTypeChanges {
+
+  public CombinedTypeChanges(String rdbms, String driver, String url, String sid, String user,
+      String password, String name, ActionType type, RecreationMode recMode)
+      throws FileNotFoundException, IOException {
+    super(rdbms, driver, url, sid, user, password, name, type, recMode);
+  }
+
+  @Test
+  public void fromVarcharToNVarcharAndSize() {
+    assertTablesAreNotRecreated("DATA_TYPE_BASE.xml", "DATA_TYPE7.xml");
+  }
+
+  @Test
+  public void fromVarcharToNVarcharAndOnCreateDefault() {
+    assertTablesAreNotRecreated("DATA_TYPE_BASE.xml", "DATA_TYPE8.xml");
+  }
+}
--- a/src-test/src/org/openbravo/dbsm/test/model/recreation/DataTypeChanges.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src-test/src/org/openbravo/dbsm/test/model/recreation/DataTypeChanges.java	Thu Mar 09 12:59:50 2017 +0100
@@ -11,7 +11,7 @@
  */
 package org.openbravo.dbsm.test.model.recreation;
 
-import static org.hamcrest.Matchers.is;
+import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assume.assumeThat;
 
 import java.io.FileNotFoundException;
@@ -19,8 +19,6 @@
 import java.util.Collection;
 
 import org.codehaus.jettison.json.JSONException;
-import org.junit.Before;
-import org.junit.Test;
 import org.junit.runners.Parameterized.Parameters;
 import org.openbravo.dbsm.test.base.DbsmTest;
 
@@ -37,28 +35,18 @@
     super(rdbms, driver, url, sid, user, password, name, type, recMode);
   }
 
-  @Before
-  public void willCauseRecreation() {
-    assumeThat(recreationMode, is(DbsmTest.RecreationMode.forced));
-  }
-
   @Parameters(name = "DB: {6} - recreation {8}")
   public static Collection<Object[]> parameters() throws IOException, JSONException {
     return TableRecreationBaseTest.parameters();
   }
 
-  @Test
-  public void changeDecimalTypeSize() {
-    assertTablesAreNotRecreated("DATA_TYPE_BASE.xml", "DATA_TYPE1.xml");
+  protected void notWorkingYet() {
+    assumeThat("Feature not implemented yet", recreationMode, is(DbsmTest.RecreationMode.forced));
   }
 
-  @Test
-  public void changeVarcharTypeSize() {
-    assertTablesAreNotRecreated("DATA_TYPE_BASE.xml", "DATA_TYPE2.xml");
-  }
-
-  @Test
-  public void changeCharTypeSize() {
-    assertTablesAreNotRecreated("DATA_TYPE_BASE.xml", "DATA_TYPE3.xml");
+  protected void worksOnlyIn(Rdbms dbSpecific) {
+    if (recreationMode == RecreationMode.standard) {
+      assumeThat("Feature supported only for " + dbSpecific, getRdbms(), is(dbSpecific));
+    }
   }
 }
--- a/src-test/src/org/openbravo/dbsm/test/model/recreation/TableRecreationBaseTest.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src-test/src/org/openbravo/dbsm/test/model/recreation/TableRecreationBaseTest.java	Thu Mar 09 12:59:50 2017 +0100
@@ -11,6 +11,7 @@
  */
 package org.openbravo.dbsm.test.model.recreation;
 
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.Matchers.contains;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
@@ -76,8 +77,28 @@
     assertTablesAreNotRecreated(fromModel, toModel, true);
   }
 
+  protected void assertTablesAreRecreated(String fromModel, String toModel) {
+    boolean generateDummyData = true;
+    assertTablesAreRecreated(fromModel, toModel, generateDummyData);
+  }
+
+  protected void assertTablesAreRecreated(String fromModel, String toModel,
+      boolean generateDummyData) {
+    ModelOids oids = updateModel(fromModel, toModel, generateDummyData);
+    assertThat("Table OID changed", oids.newTableInternalId,
+        not(contains(oids.oldTableInternalId.toArray())));
+  }
+
   protected void assertTablesAreNotRecreated(String fromModel, String toModel,
       boolean generateDummyData) {
+    ModelOids oids = updateModel(fromModel, toModel, generateDummyData);
+    if (recreationMode == RecreationMode.standard) {
+      assertThat("Table OID changed", oids.newTableInternalId,
+          contains(oids.oldTableInternalId.toArray()));
+    }
+  }
+
+  private ModelOids updateModel(String fromModel, String toModel, boolean generateDummyData) {
     resetDB();
     try {
       String initialModel = MODEL_DIRECTORY
@@ -96,13 +117,12 @@
       Database newModel = updateDatabase(targetModel);
 
       log.info("Updating to " + newModel);
-      if (recreationMode == RecreationMode.standard) {
-        List<String> newTableInternalId = getOIds(newModel);
-        assertThat("Table OID changed", newTableInternalId, contains(oldTableInternalId.toArray()));
-      }
+
+      return new ModelOids(oldTableInternalId, getOIds(newModel));
     } catch (Exception e) {
       e.printStackTrace();
       fail("Exception " + e.getMessage());
+      return null;
     }
   }
 
@@ -138,4 +158,14 @@
     }
   }
 
+  private static class ModelOids {
+    List<String> oldTableInternalId;
+    List<String> newTableInternalId;
+
+    public ModelOids(List<String> oldTableInternalId, List<String> newTableInternalId) {
+      this.oldTableInternalId = oldTableInternalId;
+      this.newTableInternalId = newTableInternalId;
+    }
+  }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src-test/src/org/openbravo/dbsm/test/model/recreation/TypeChangeSuite.java	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,14 @@
+package org.openbravo.dbsm.test.model.recreation;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses({ //
+ColumnSizeChange.class, //
+    ColumnTypeChange.class, //
+    CombinedTypeChanges.class })
+public class TypeChangeSuite {
+
+}
--- a/src/mapping.xml	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/mapping.xml	Thu Mar 09 12:59:50 2017 +0100
@@ -13,7 +13,7 @@
 Unless required by applicable law or agreed to in writing,
 software distributed under the License is distributed on an
 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
+KIND, either express or implied.  See tshe License for the
 specific language governing permissions and limitations
 under the License.
 -->
@@ -94,6 +94,7 @@
     <element name="index">
       <attribute name="name" property="name"/>
       <attribute name="unique" property="unique"/>
+      <attribute name="containsSearch" property="containsSearch"/>
 
       <element name="index-column" property="columns" updater="addColumn"/>
       <element name="whereClause" property="whereClause">
--- a/src/org/apache/ddlutils/Platform.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/Platform.java	Thu Mar 09 12:59:50 2017 +0100
@@ -22,7 +22,6 @@
 import java.io.Writer;
 import java.sql.Connection;
 import java.sql.ResultSet;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
@@ -35,6 +34,7 @@
 import org.apache.commons.beanutils.DynaBean;
 import org.apache.ddlutils.alteration.Change;
 import org.apache.ddlutils.model.Database;
+import org.apache.ddlutils.model.StructureObject;
 import org.apache.ddlutils.model.Table;
 import org.apache.ddlutils.model.Trigger;
 import org.apache.ddlutils.platform.CreationParameters;
@@ -1464,7 +1464,7 @@
 
   public void executeOnCreateDefaultForMandatoryColumns(Database database, OBDataset ad);
 
-  public ArrayList checkTranslationConsistency(Database database, Database fullDatabase);
+  public List<StructureObject> checkTranslationConsistency(Database database, Database fullDatabase);
 
   public String disableNOTNULLColumnsSql(Database database, OBDataset dataset);
 
@@ -1506,4 +1506,10 @@
 
   public SQLBatchEvaluator getBatchEvaluator();
 
+  /** Sets the maximum number of threads parallelizable tasks can use */
+  public void setMaxThreads(int threads);
+
+  /** Returns the maximum number of threads parallelizable tasks can use */
+  public int getMaxThreads();
+
 }
--- a/src/org/apache/ddlutils/PlatformInfo.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/PlatformInfo.java	Thu Mar 09 12:59:50 2017 +0100
@@ -86,6 +86,9 @@
   /** Whether partial indexes are supported. */
   private boolean _partialIndexesSupported = false;
 
+  /** Whether contains search indexes are supported. */
+  private boolean _containsSearchIndexesSupported = false;
+
   /** Whether triggers are supported. */
   private boolean _triggersSupported = true;
 
@@ -565,6 +568,25 @@
   }
 
   /**
+   * Determines whether contains search indexes are supported.
+   * 
+   * @return <code>true</code> if contains search indexes are supported
+   */
+  public boolean isContainsSearchIndexesSupported() {
+    return _containsSearchIndexesSupported;
+  }
+
+  /**
+   * Specifies whether contains search indexes are supported.
+   * 
+   * @param containsSearchIndexesSupported
+   *          <code>true</code> if contains search indexes are supported
+   */
+  public void setContainsSearchIndexesSupported(boolean containsSearchIndexesSupported) {
+    _containsSearchIndexesSupported = containsSearchIndexesSupported;
+  }
+
+  /**
    * Determines whether triggers are supported.
    * 
    * @return <code>true</code> if triggers are supported
--- a/src/org/apache/ddlutils/alteration/ColumnSizeChange.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/alteration/ColumnSizeChange.java	Thu Mar 09 12:59:50 2017 +0100
@@ -99,9 +99,7 @@
    * {@inheritDoc}
    */
   public void apply(Database database, boolean caseSensitive) {
-    Table table = _table;
-    if (table == null)
-      table = database.findTable(_tablename, caseSensitive);
+    Table table = database.findTable(_tablename, caseSensitive);
 
     // We will not try to apply the change if the table doesn't exist in the model
     // This could happen in update.database.mod if a configuration script has this change
@@ -109,9 +107,7 @@
     if (table == null) {
       return;
     }
-    Column column = _column;
-    if (column == null)
-      column = table.findColumn(_columnname, caseSensitive);
+    Column column = table.findColumn(_columnname, caseSensitive);
     if (column != null)
       column.setSizeAndScale(_newSize, _newScale);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/apache/ddlutils/alteration/ContainsSearchIndexInformationChange.java	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,74 @@
+/*
+ ************************************************************************************
+ * Copyright (C) 2016 Openbravo S.L.U.
+ * Licensed under the Apache Software License version 2.0
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to  in writing,  software  distributed
+ * under the License is distributed  on  an  "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR
+ * CONDITIONS OF ANY KIND, either  express  or  implied.  See  the  License  for  the
+ * specific language governing permissions and limitations under the License.
+ ************************************************************************************
+ */
+package org.apache.ddlutils.alteration;
+
+import org.apache.ddlutils.model.Database;
+import org.apache.ddlutils.model.Index;
+import org.apache.ddlutils.model.Table;
+
+/**
+ * This change class will be used for those platforms which does not support contains search
+ * indexes, in order to apply the alternative actions (for example updating comments). This helps to
+ * keep the consistency between XML and DB if the containsSearch property is changed on an existing
+ * index.
+ * 
+ * In platforms where contains search indexes are supported, changes on these kind of indexes are
+ * handled with {@link RemoveIndexChange} and {@link AddIndexChange} classes.
+ */
+public class ContainsSearchIndexInformationChange extends TableChangeImplBase {
+
+  /** The index whose containsSearch property has changed */
+  private Index _index;
+  /** The new containsSearch property */
+  private boolean _newContainsSearchValue;
+
+  /**
+   * Creates a new contains search index change. This means that the containsSearch property of the
+   * index has been modified.
+   * 
+   * @param table
+   *          The table where the index belongs to
+   * @param index
+   *          The modified index
+   * @param newContainsSearchValue
+   *          The new value of the containsSearch property
+   */
+  public ContainsSearchIndexInformationChange(Table table, Index index,
+      boolean newContainsSearchValue) {
+    super(table);
+    _index = index;
+    _newContainsSearchValue = newContainsSearchValue;
+  }
+
+  /**
+   * Returns the modified index.
+   * 
+   * @return The index
+   */
+  public Index getIndex() {
+    return _index;
+  }
+
+  /**
+   * Returns the new containsSearch property value.
+   * 
+   * @return The new value of the containsSearch property
+   */
+  public boolean getNewContainsSearch() {
+    return _newContainsSearchValue;
+  }
+
+  @Override
+  public void apply(Database database, boolean caseSensitive) {
+    _index.setContainsSearch(_newContainsSearchValue);
+  }
+}
--- a/src/org/apache/ddlutils/alteration/ModelComparator.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/alteration/ModelComparator.java	Thu Mar 09 12:59:50 2017 +0100
@@ -22,6 +22,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.logging.Log;
@@ -80,8 +81,8 @@
    *          The target model
    * @return The changes
    */
-  public List compare(Database sourceModel, Database targetModel) {
-    ArrayList changes = new ArrayList();
+  public List<ModelChange> compare(Database sourceModel, Database targetModel) {
+    List<ModelChange> changes = new ArrayList<>();
 
     for (int trIdx = 0; trIdx < sourceModel.getTriggerCount(); trIdx++) {
       Trigger sourceTrigger = sourceModel.getTrigger(trIdx);
@@ -222,9 +223,9 @@
    *          The target table
    * @return The changes
    */
-  public List compareTables(Database sourceModel, Table sourceTable, Database targetModel,
-      Table targetTable) {
-    ArrayList changes = new ArrayList();
+  public List<ModelChange> compareTables(Database sourceModel, Table sourceTable,
+      Database targetModel, Table targetTable) {
+    List<ModelChange> changes = new ArrayList<>();
 
     for (int fkIdx = 0; fkIdx < sourceTable.getForeignKeyCount(); fkIdx++) {
       ForeignKey sourceFk = sourceTable.getForeignKey(fkIdx);
@@ -308,9 +309,6 @@
       }
     }
 
-    // Only in some platforms do the operator classes of the index column matter
-    // Take this into account when comparing the indexes
-    boolean operatorClassMatters = _platformInfo.isOperatorClassesSupported();
     for (int indexIdx = 0; indexIdx < sourceTable.getIndexCount(); indexIdx++) {
       Index sourceIndex = sourceTable.getIndex(indexIdx);
       Index targetIndex = findCorrespondingIndex(targetTable, sourceIndex);
@@ -327,6 +325,12 @@
         // index information stored for platforms which does not support partial indexing.
         changes.add(new PartialIndexInformationChange(sourceTable, sourceIndex, sourceIndex
             .getWhereClause(), targetIndex.getWhereClause()));
+      } else if (!_platformInfo.isContainsSearchIndexesSupported()
+          && targetIndex.isContainsSearch() != sourceIndex.isContainsSearch()) {
+        // keep track of changes in the containsSearch property in order to update the information
+        // stored for platforms which does not support contains search indexes.
+        changes.add(new ContainsSearchIndexInformationChange(sourceTable, sourceIndex, targetIndex
+            .isContainsSearch()));
       }
     }
     for (int indexIdx = 0; indexIdx < targetTable.getIndexCount(); indexIdx++) {
@@ -344,7 +348,7 @@
       }
     }
 
-    HashMap addColumnChanges = new HashMap();
+    Map<Column, AddColumnChange> addColumnChanges = new HashMap<>();
 
     for (int columnIdx = 0; columnIdx < targetTable.getColumnCount(); columnIdx++) {
       Column targetColumn = targetTable.getColumn(columnIdx);
@@ -424,7 +428,7 @@
       }
     }
 
-    HashMap columnPosChanges = new HashMap();
+    Map<Column, Integer> columnPosChanges = new HashMap<>();
     int diffPos = 0;
 
     for (int columnIdx = 0; columnIdx < sourceTable.getColumnCount(); columnIdx++) {
@@ -467,9 +471,9 @@
    *          The target column
    * @return The changes
    */
-  public List compareColumns(Table sourceTable, Column sourceColumn, Table targetTable,
-      Column targetColumn) {
-    ArrayList changes = new ArrayList();
+  public List<ModelChange> compareColumns(Table sourceTable, Column sourceColumn,
+      Table targetTable, Column targetColumn) {
+    List<ModelChange> changes = new ArrayList<>();
 
     // if (_platformInfo.getTargetJdbcType(targetColumn.getTypeCode()) !=
     // sourceColumn.getTypeCode())
--- a/src/org/apache/ddlutils/io/DBSMValueSuppressionStrategy.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/io/DBSMValueSuppressionStrategy.java	Thu Mar 09 12:59:50 2017 +0100
@@ -12,10 +12,14 @@
 
 package org.apache.ddlutils.io;
 
+import java.lang.reflect.Method;
+
 import org.apache.commons.betwixt.AttributeDescriptor;
 import org.apache.commons.betwixt.ElementDescriptor;
+import org.apache.commons.betwixt.expression.MethodExpression;
 import org.apache.commons.betwixt.strategy.ValueSuppressionStrategy;
 import org.apache.ddlutils.model.Index;
+import org.openbravo.ddlutils.util.DBSMContants;
 
 /**
  * This class defines the strategy used by DBSourceManager to decide which attributes or elements
@@ -23,8 +27,6 @@
  */
 public class DBSMValueSuppressionStrategy extends ValueSuppressionStrategy {
 
-  private static final String WHERE_CLAUSE = "whereClause";
-
   /**
    * Determines if the given attribute value be suppressed
    * 
@@ -36,7 +38,11 @@
    */
   @Override
   public boolean suppressAttribute(AttributeDescriptor attributeDescriptor, String value) {
-    // For attributes, use default strategy: suppress all null values
+    if (isIndexContainsSearchAttribute(attributeDescriptor)) {
+      // Do not export Index containsSearch attribute if it is false
+      return !Boolean.valueOf(value);
+    }
+    // For the rest of attributes, use default strategy: suppress all null values
     return ValueSuppressionStrategy.DEFAULT.suppressAttribute(attributeDescriptor, value);
   }
 
@@ -60,9 +66,25 @@
   public boolean suppressElement(ElementDescriptor element, String namespaceUri, String localName,
       String qualifiedName, Object value) {
     // Do not export Index empty whereClause element
-    if (WHERE_CLAUSE.equals(localName) && value != null && value instanceof Index) {
+    if (DBSMContants.WHERE_CLAUSE.equals(localName) && value != null && value instanceof Index) {
       return ((Index) value).getWhereClause() == null;
     }
     return false;
   }
+
+  private boolean isIndexContainsSearchAttribute(AttributeDescriptor attributeDescriptor) {
+    if (!DBSMContants.CONTAINS_SEARCH.equals(attributeDescriptor.getLocalName())) {
+      return false;
+    }
+    return Index.class.getName().equals(getAttributeOwnerClassName(attributeDescriptor));
+  }
+
+  private String getAttributeOwnerClassName(AttributeDescriptor attributeDescriptor) {
+    MethodExpression methodExpression = (MethodExpression) attributeDescriptor.getTextExpression();
+    Method method = methodExpression.getMethod();
+    if (method == null) {
+      return null;
+    }
+    return method.getDeclaringClass().getName();
+  }
 }
--- a/src/org/apache/ddlutils/model/Index.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/model/Index.java	Thu Mar 09 12:59:50 2017 +0100
@@ -36,6 +36,8 @@
   protected String _name;
   /** Whether the index is unique */
   protected boolean _unique = false;
+  /** Whether the index is used to speed up searching using 'contains' operators */
+  protected boolean _containsSearch = false;
   /** The where clause expression used for partial indexing **/
   protected String _whereClause;
   /** The columns making up the index. */
@@ -129,6 +131,14 @@
     _unique = unique;
   }
 
+  public boolean isContainsSearch() {
+    return _containsSearch;
+  }
+
+  public void setContainsSearch(boolean containsSearch) {
+    _containsSearch = containsSearch;
+  }
+
   /**
    * {@inheritDoc}
    */
@@ -151,7 +161,8 @@
       Index other = (Index) obj;
 
       return new EqualsBuilder().append(_name, other._name).append(_unique, other._unique)
-          .append(_whereClause, other._whereClause).append(_columns, other._columns).isEquals();
+          .append(_containsSearch, other._containsSearch).append(_whereClause, other._whereClause)
+          .append(_columns, other._columns).isEquals();
     } else {
       return false;
     }
@@ -174,6 +185,9 @@
     } else {
       EqualsBuilder equalsBuilder = new EqualsBuilder().append(_name, other._name).append(_unique,
           other._unique);
+      if (platformInfo.isContainsSearchIndexesSupported()) {
+        equalsBuilder.append(_containsSearch, other._containsSearch);
+      }
       if (platformInfo.isPartialIndexesSupported()) {
         equalsBuilder.append(_whereClause, other._whereClause);
       }
@@ -231,7 +245,12 @@
         if (_unique != other._unique) {
           return false;
         }
-        if (platformInfo.isPartialIndexesSupported() && !isSameWhereClause(other)) {
+        if (platformInfo != null && platformInfo.isContainsSearchIndexesSupported()
+            && _containsSearch != other._containsSearch) {
+          return false;
+        }
+        if (platformInfo != null && platformInfo.isPartialIndexesSupported()
+            && !isSameWhereClause(other)) {
           return false;
         }
         for (int idx = 0; idx < getColumnCount(); idx++) {
@@ -256,8 +275,8 @@
    * {@inheritDoc}
    */
   public int hashCode() {
-    return new HashCodeBuilder(17, 37).append(_name).append(_unique).append(_whereClause)
-        .append(_columns).toHashCode();
+    return new HashCodeBuilder(17, 37).append(_name).append(_unique).append(_containsSearch)
+        .append(_whereClause).append(_columns).toHashCode();
   }
 
   /**
@@ -270,6 +289,8 @@
     result.append(getName());
     result.append("; unique =");
     result.append(isUnique());
+    result.append("; containsSearch =");
+    result.append(isContainsSearch());
     result.append("; where clause =");
     result.append(getWhereClause());
     result.append("; ");
@@ -289,6 +310,8 @@
     result.append(getName());
     result.append("; unique =");
     result.append(isUnique());
+    result.append("; containsSearch =");
+    result.append(isContainsSearch());
     result.append("; where clause =");
     result.append(getWhereClause());
     result.append("] columns:");
@@ -308,6 +331,7 @@
 
     result._name = _name;
     result._unique = _unique;
+    result._containsSearch = _containsSearch;
     result._whereClause = _whereClause;
     result._columns = (ArrayList) _columns.clone();
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/apache/ddlutils/platform/ConcurrentSqlEvaluator.java	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,76 @@
+/*
+ ************************************************************************************
+ * Copyright (C) 2016 Openbravo S.L.U.
+ * Licensed under the Apache Software License version 2.0
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to  in writing,  software  distributed
+ * under the License is distributed  on  an  "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR
+ * CONDITIONS OF ANY KIND, either  express  or  implied.  See  the  License  for  the
+ * specific language governing permissions and limitations under the License.
+ ************************************************************************************
+ */
+
+package org.apache.ddlutils.platform;
+
+import java.sql.Connection;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.ddlutils.util.JdbcSupport;
+
+/**
+ * Wrapper for SQLBatchEvaluator that allows concurrent executions. It allows to execute a single
+ * sql statement handling DB connection acquisition and return from pool.
+ * 
+ * @author alostale
+ *
+ */
+public class ConcurrentSqlEvaluator implements Callable<Integer> {
+  private final static Log log = LogFactory.getLog(ConcurrentSqlEvaluator.class);
+
+  private SQLBatchEvaluator evaluator;
+  private String sql;
+  private JdbcSupport dbConPool;
+  private boolean continueOnError;
+
+  /**
+   * 
+   * @param evaluator
+   *          SQLBatchEvaluator to process the sql
+   * @param sql
+   *          A single sql statement to execute
+   * @param dbConPool
+   *          DB connection pool to borrow the connection from
+   * @param continueOnError
+   *          should an exception be thrown if sql fails
+   */
+  public ConcurrentSqlEvaluator(SQLBatchEvaluator evaluator, String sql, JdbcSupport dbConPool,
+      boolean continueOnError) {
+    this.evaluator = evaluator;
+    this.sql = sql;
+    this.dbConPool = dbConPool;
+    this.continueOnError = continueOnError;
+  }
+
+  /**
+   * Executes the sql borrowing the connection from pool and returning it afterwards.
+   * 
+   * @return number of errors the sql execution caused
+   */
+  @Override
+  public Integer call() {
+    Connection con = null;
+    try {
+      con = dbConPool.borrowConnection();
+      log.debug("[" + Thread.currentThread().getName() + "] - executing " + sql);
+      return evaluator.evaluateBatch(con, Arrays.asList(sql), continueOnError, 0);
+    } catch (Exception e) {
+      log.error("Error while executing " + sql, e);
+      return 1;
+    } finally {
+      dbConPool.returnConnection(con);
+    }
+  }
+}
--- a/src/org/apache/ddlutils/platform/ExcludeFilter.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/ExcludeFilter.java	Thu Mar 09 12:59:50 2017 +0100
@@ -1,6 +1,6 @@
 /*
  ************************************************************************************
- * Copyright (C) 2001-2006 Openbravo S.L.U.
+ * Copyright (C) 2001-2017 Openbravo S.L.U.
  * Licensed under the Apache Software License version 2.0
  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to  in writing,  software  distributed
@@ -387,7 +387,7 @@
   private List<String> getNonWildcardExcludedObjects(String[] excludedObjects) {
     List<String> nonWildcardExcludedObjects = new ArrayList<String>();
     for (String excludedObject : excludedObjects) {
-      if (!excludedObject.contains("%")) {
+      if (excludedObject != null && !excludedObject.contains("%")) {
         nonWildcardExcludedObjects.add(excludedObject);
       }
     }
@@ -397,7 +397,7 @@
   private List<String> getWildcardExcludedObjects(String[] excludedObjects) {
     List<String> wildcardExcludedObjects = new ArrayList<String>();
     for (String excludedObject : excludedObjects) {
-      if (excludedObject.contains("%")) {
+      if (excludedObject != null && excludedObject.contains("%")) {
         wildcardExcludedObjects.add(excludedObject);
       }
     }
--- a/src/org/apache/ddlutils/platform/JdbcModelReader.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/JdbcModelReader.java	Thu Mar 09 12:59:50 2017 +0100
@@ -540,7 +540,8 @@
    *          The table metadata values as defined by {@link #getColumnsForTable()}
    * @return The table or <code>null</code> if the result set row did not contain a valid table
    */
-  protected Table readTable(DatabaseMetaDataWrapper metaData, Map values) throws SQLException {
+  protected Table readTable(DatabaseMetaDataWrapper metaData, Map<String, Object> values)
+      throws SQLException {
     String tableName = (String) values.get("TABLE_NAME");
     Table table = null;
 
@@ -755,7 +756,8 @@
    *          The column meta data values as defined by {@link #getColumnsForColumn()}
    * @return The column
    */
-  protected Column readColumn(DatabaseMetaDataWrapper metaData, Map values) throws SQLException {
+  protected Column readColumn(DatabaseMetaDataWrapper metaData, Map<String, Object> values)
+      throws SQLException {
     Column column = new Column();
 
     column.setName((String) values.get("COLUMN_NAME"));
--- a/src/org/apache/ddlutils/platform/ModelLoader.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/ModelLoader.java	Thu Mar 09 12:59:50 2017 +0100
@@ -1,6 +1,6 @@
 /*
  ************************************************************************************
- * Copyright (C) 2001-2006 Openbravo S.L.U.
+ * Copyright (C) 2001-2016 Openbravo S.L.U.
  * Licensed under the Apache Software License version 2.0
  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to  in writing,  software  distributed
@@ -40,4 +40,7 @@
 
   public void addAdditionalTableIfExists(Connection connection, Database model, String tablename);
 
+  /** Defines how many threads can be used to execute parallelizable tasks */
+  public void setMaxThreads(int threads);
+
 }
--- a/src/org/apache/ddlutils/platform/ModelLoaderBase.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/ModelLoaderBase.java	Thu Mar 09 12:59:50 2017 +0100
@@ -106,6 +106,7 @@
   protected String _moduleId;
 
   private boolean onlyLoadTableColumns = false;
+  private int maxThreads = 1;
 
   /** Creates a new instance of BasicModelLoader */
   public ModelLoaderBase() {
@@ -914,4 +915,13 @@
       getLog().error(e);
     }
   }
+
+  @Override
+  public void setMaxThreads(int maxThreads) {
+    this.maxThreads = maxThreads;
+  }
+
+  protected int getMaxThreads() {
+    return maxThreads;
+  }
 }
--- a/src/org/apache/ddlutils/platform/PlatformImplBase.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/PlatformImplBase.java	Thu Mar 09 12:59:50 2017 +0100
@@ -40,6 +40,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -48,6 +49,11 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.Vector;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.commons.beanutils.DynaBean;
 import org.apache.commons.beanutils.PropertyUtils;
@@ -60,11 +66,13 @@
 import org.apache.ddlutils.Platform;
 import org.apache.ddlutils.PlatformInfo;
 import org.apache.ddlutils.alteration.AddColumnChange;
+import org.apache.ddlutils.alteration.AddIndexChange;
 import org.apache.ddlutils.alteration.AddRowChange;
 import org.apache.ddlutils.alteration.Change;
 import org.apache.ddlutils.alteration.ColumnChange;
 import org.apache.ddlutils.alteration.ColumnDataChange;
 import org.apache.ddlutils.alteration.ColumnSizeChange;
+import org.apache.ddlutils.alteration.ModelChange;
 import org.apache.ddlutils.alteration.RemoveRowChange;
 import org.apache.ddlutils.dynabean.SqlDynaClass;
 import org.apache.ddlutils.dynabean.SqlDynaProperty;
@@ -72,6 +80,8 @@
 import org.apache.ddlutils.model.Database;
 import org.apache.ddlutils.model.ForeignKey;
 import org.apache.ddlutils.model.Function;
+import org.apache.ddlutils.model.Index;
+import org.apache.ddlutils.model.StructureObject;
 import org.apache.ddlutils.model.Table;
 import org.apache.ddlutils.model.Trigger;
 import org.apache.ddlutils.model.TypeMap;
@@ -96,7 +106,7 @@
   protected static final String MODEL_DEFAULT_NAME = "default";
 
   /** The log for this platform. */
-  private final Log _log = LogFactory.getLog(getClass());
+  private static final Log _log = LogFactory.getLog(PlatformImplBase.class);
 
   /** The platform info. */
   private PlatformInfo _info = new PlatformInfo();
@@ -123,6 +133,8 @@
 
   private SQLBatchEvaluator batchEvaluator = new StandardBatchEvaluator(this);
 
+  private int maxThreads = 0;
+
   /**
    * {@inheritDoc}
    */
@@ -314,12 +326,7 @@
    */
   public int evaluateBatch(Connection connection, String sql, boolean continueOnError)
       throws DatabaseOperationException {
-    List<String> commands = new ArrayList<String>();
-    SqlTokenizer tokenizer = new SqlTokenizer(sql);
-
-    while (tokenizer.hasMoreStatements()) {
-      commands.add(tokenizer.getNextStatement());
-    }
+    List<String> commands = getCommands(sql);
     return evaluateBatch(connection, commands, continueOnError);
   }
 
@@ -334,18 +341,70 @@
 
   public int evaluateBatchRealBatch(Connection connection, String sql, boolean continueOnError)
       throws DatabaseOperationException {
-    ArrayList<String> commands = new ArrayList<String>();
+    List<String> commands = getCommands(sql);
+    return evaluateBatchRealBatch(connection, commands, continueOnError);
+  }
+
+  public int evaluateBatchRealBatch(Connection connection, List<String> sql, boolean continueOnError)
+      throws DatabaseOperationException {
+    return batchEvaluator.evaluateBatchRealBatch(connection, sql, continueOnError);
+  }
+
+  private int evaluateConcurrentBatch(String sql, boolean continueOnError)
+      throws DatabaseOperationException {
+    List<String> commands = getCommands(sql);
+    int numOfThreads = Math.min(getMaxThreads(), commands.size());
+    if (numOfThreads <= 1) {
+      // use standard batch evaluator
+      Connection con = borrowConnection();
+      try {
+        return evaluateBatch(con, commands, continueOnError);
+      } finally {
+        returnConnection(con);
+      }
+    }
+
+    boolean wasLoggingSuccessCommands = batchEvaluator.isLogInfoSucessCommands();
+    batchEvaluator.setLogInfoSucessCommands(false);
+
+    ExecutorService executor = Executors.newFixedThreadPool(numOfThreads);
+    List<ConcurrentSqlEvaluator> tasks = new ArrayList<>();
+    for (String command : commands) {
+      tasks.add(new ConcurrentSqlEvaluator(batchEvaluator, command, this, continueOnError));
+    }
+
+    int errors = 0;
+    try {
+      for (Future<Integer> executionErrors : executor.invokeAll(tasks)) {
+        errors += executionErrors.get();
+      }
+    } catch (InterruptedException | ExecutionException e1) {
+      errors += 1;
+      _log.error("Error executing concurrent batch " + sql, e1);
+    } finally {
+      executor.shutdown();
+      try {
+        // wait till finished, it should be fast as awaited for actual execution in invokeAll
+        executor.awaitTermination(30L, TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        _log.error("Error shutting down thread pool", e);
+      }
+    }
+
+    batchEvaluator.setLogInfoSucessCommands(wasLoggingSuccessCommands);
+    _log.info("Executed " + commands.size() + " SQL commnand(s) in " + numOfThreads + " threads "
+        + (errors == 0 ? "successfully" : ("with " + errors + " errors")));
+    return errors;
+  }
+
+  private List<String> getCommands(String sql) {
+    List<String> commands = new ArrayList<>();
     SqlTokenizer tokenizer = new SqlTokenizer(sql);
 
     while (tokenizer.hasMoreStatements()) {
       commands.add(tokenizer.getNextStatement());
     }
-    return evaluateBatchRealBatch(connection, commands, continueOnError);
-  }
-
-  public int evaluateBatchRealBatch(Connection connection, List<String> sql, boolean continueOnError)
-      throws DatabaseOperationException {
-    return batchEvaluator.evaluateBatchRealBatch(connection, sql, continueOnError);
+    return commands;
   }
 
   /**
@@ -675,28 +734,61 @@
   }
 
   public boolean alterTablesPostScript(Connection connection, Database currentModel,
-      Database desiredModel, boolean continueOnError, List changes, Database fullModel, OBDataset ad)
-      throws DatabaseOperationException {
+      Database desiredModel, boolean continueOnError, List<ModelChange> changes,
+      Database fullModel, OBDataset ad) throws DatabaseOperationException {
     String sql = null;
 
+    List<AddIndexChange> newIndexes = null;
+    SqlBuilder sqlBuilder = getSqlBuilder();
     try {
       StringWriter buffer = new StringWriter();
 
-      getSqlBuilder().setWriter(buffer);
-      getSqlBuilder().alterDatabasePostScript(currentModel, desiredModel, null, changes, fullModel,
-          ad);
+      sqlBuilder.setWriter(buffer);
+      newIndexes = sqlBuilder.alterDatabasePostScript(currentModel, desiredModel, null, changes,
+          fullModel, ad);
       sql = buffer.toString();
     } catch (IOException ex) {
       // won't happen because we're using a string writer
     }
     _ignoreWarns = false;
     int numErrors = evaluateBatch(connection, sql, continueOnError);
+
+    if (newIndexes != null && !newIndexes.isEmpty()) {
+      _log.info(newIndexes.size() + " indexes to create");
+      StringWriter buffer = new StringWriter();
+      sqlBuilder.setWriter(buffer);
+
+      try {
+        createIndexes(currentModel, desiredModel, newIndexes);
+      } catch (IOException ignore) {
+      }
+      sql = buffer.toString();
+      numErrors += evaluateConcurrentBatch(sql, continueOnError);
+    }
+
     if (numErrors > 0) {
       return false;
     }
     return true;
   }
 
+  private void createIndexes(Database currentModel, Database desiredModel,
+      List<AddIndexChange> indexes) throws IOException {
+    Map<Table, List<Index>> newIndexesMap = new HashMap<>();
+
+    for (AddIndexChange indexChg : indexes) {
+      getSqlBuilder().processChange(currentModel, desiredModel, null, indexChg);
+
+      List<Index> indexesForTable = newIndexesMap.get(indexChg.getChangedTable());
+      if (indexesForTable == null) {
+        indexesForTable = new ArrayList<Index>();
+      }
+      indexesForTable.add(indexChg.getNewIndex());
+      newIndexesMap.put(indexChg.getChangedTable(), indexesForTable);
+    }
+    getSqlBuilder().newIndexesPostAction(newIndexesMap);
+  }
+
   public List alterTablesRecreatePKs(Connection connection, Database currentModel,
       Database desiredModel, boolean continueOnError) throws DatabaseOperationException {
     String sql = null;
@@ -3367,15 +3459,19 @@
     return true;
   }
 
-  public ArrayList checkTranslationConsistency(Database database, Database fullDatabase) {
-    return new ArrayList();
+  public List<StructureObject> checkTranslationConsistency(Database database, Database fullDatabase) {
+    return Collections.emptyList();
   }
 
-  protected void printDiff(String s1, String s2) {
-    getLog().warn("********************************************************");
+  /**
+   * Logs differences between two strings, used to highlight differences while standardization. It
+   * is synchronized to avoid messing two different outputs while working in parallel.
+   */
+  public static synchronized void printDiff(String str1, String str2) {
+    _log.warn("********************************************************");
     diff_match_patch diffClass = new diff_match_patch();
-    s1 = s1.replaceAll("\r\n", "\n");
-    s2 = s2.replaceAll("\r\n", "\n");
+    String s1 = str1.replaceAll("\r\n", "\n");
+    String s2 = str2.replaceAll("\r\n", "\n");
     LinkedList<Diff> diffs = diffClass.diff_main(s1, s2);
     boolean initial = true;
     String fullDiff = "";
@@ -3407,8 +3503,8 @@
         fullDiff += "[" + diff.text + "]";
       }
     }
-    getLog().warn(fullDiff);
-    getLog().warn("********************************************************");
+    _log.warn(fullDiff);
+    _log.warn("********************************************************");
   }
 
   @Override
@@ -3430,4 +3526,19 @@
   public void updateDBStatistics() {
     // do not update statistics in default implementation
   }
+
+  @Override
+  public void setMaxThreads(int threads) {
+    maxThreads = threads;
+    getModelLoader().setMaxThreads(getMaxThreads());
+  }
+
+  @Override
+  public int getMaxThreads() {
+    // rule of thumb: if max threads is not set, use one half of available processors
+    if (maxThreads < 1) {
+      maxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 2, 1);
+    }
+    return maxThreads;
+  }
 }
--- a/src/org/apache/ddlutils/platform/SQLBatchEvaluator.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/SQLBatchEvaluator.java	Thu Mar 09 12:59:50 2017 +0100
@@ -1,6 +1,6 @@
 /*
  ************************************************************************************
- * Copyright (C) 2015 Openbravo S.L.U.
+ * Copyright (C) 2015-2016 Openbravo S.L.U.
  * Licensed under the Apache Software License version 2.0
  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to  in writing,  software  distributed
@@ -32,4 +32,10 @@
 
   public int evaluateBatchRealBatch(Connection connection, List<String> sql, boolean continueOnError)
       throws DatabaseOperationException;
+
+  /** Should successful commands be logged */
+  public void setLogInfoSucessCommands(boolean logInfoSucessCommands);
+
+  /** Should successful commands be logged */
+  public boolean isLogInfoSucessCommands();
 }
--- a/src/org/apache/ddlutils/platform/SqlBuilder.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/SqlBuilder.java	Thu Mar 09 12:59:50 2017 +0100
@@ -73,6 +73,7 @@
 import org.apache.ddlutils.alteration.ColumnOrderChange;
 import org.apache.ddlutils.alteration.ColumnRequiredChange;
 import org.apache.ddlutils.alteration.ColumnSizeChange;
+import org.apache.ddlutils.alteration.ContainsSearchIndexInformationChange;
 import org.apache.ddlutils.alteration.ModelChange;
 import org.apache.ddlutils.alteration.ModelComparator;
 import org.apache.ddlutils.alteration.PartialIndexInformationChange;
@@ -556,14 +557,14 @@
 
     ModelComparator comparator = new ModelComparator(getPlatformInfo(), getPlatform()
         .isDelimitedIdentifierModeOn());
-    List changes = comparator.compare(currentModel, desiredModel);
+    List<ModelChange> changes = comparator.compare(currentModel, desiredModel);
 
     alterDatabase(currentModel, desiredModel, params, changes);
 
   }
 
   public void alterDatabase(Database currentModel, Database desiredModel,
-      CreationParameters params, List changes) throws IOException {
+      CreationParameters params, List<ModelChange> changes) throws IOException {
     _PLSQLFunctionTranslation = createPLSQLFunctionTranslation(desiredModel);
     _PLSQLTriggerTranslation = createPLSQLTriggerTranslation(desiredModel);
     _SQLTranslation = createSQLTranslation(desiredModel);
@@ -730,18 +731,15 @@
     return changes;
   }
 
-  public void alterDatabasePostScript(Database currentModel, Database desiredModel,
-      CreationParameters params, List changes, Database fullModel, OBDataset ad) throws IOException {
-    Iterator it = changes.iterator();
-
+  /** Returns a list of new indexes to be processed afterwards */
+  public List<AddIndexChange> alterDatabasePostScript(Database currentModel, Database desiredModel,
+      CreationParameters params, List<ModelChange> changes, Database fullModel, OBDataset ad)
+      throws IOException {
     Vector<AddColumnChange> newColumns = new Vector<AddColumnChange>();
-    while (it.hasNext()) {
-      Object change = it.next();
-
+    for (ModelChange change : changes) {
       if (change instanceof AddColumnChange) {
         newColumns.add((AddColumnChange) change);
       }
-
     }
 
     // We will now create the primary keys from recreated tables
@@ -752,6 +750,7 @@
         ColumnDefaultValueChange.class, ColumnOnCreateDefaultValueChange.class,
         ColumnRequiredChange.class, ColumnDataTypeChange.class, ColumnSizeChange.class });
 
+    List<AddIndexChange> newIndexes = new ArrayList<>();
     Collection tableChanges = CollectionUtils.select(changes, predicate);
     ArrayList<String> recreatedTables = new ArrayList<String>();
     for (int i = 0; i < desiredModel.getTableCount(); i++) {
@@ -789,7 +788,8 @@
               && (!changedNewColumn.isRequired() || !changedNewColumn.isSameDefaultAndOCD())) {
             executeOnCreateDefault(table, tempTable, change.getNewColumn(), recreated, true);
           }
-          writeColumnCommentStmt(currentModel, change.getChangedTable(), change.getNewColumn());
+          writeColumnCommentStmt(currentModel, change.getChangedTable(), change.getNewColumn(),
+              true);
         }
       }
       if (recreated) {
@@ -798,6 +798,7 @@
 
           if (fullModel != null) {
             // We have the full model. We will activate foreign keys pointing to recreated tables
+            Table recreatedTable = desiredModel.getTable(i);
             for (int idxTable = 0; idxTable < fullModel.getTableCount(); idxTable++) {
               for (int idxFk = 0; idxFk < fullModel.getTable(idxTable).getForeignKeyCount(); idxFk++) {
                 ForeignKey fk = fullModel.getTable(idxTable).getForeignKey(idxFk);
@@ -811,19 +812,17 @@
           }
         }
       } else {
-        Iterator it2 = changes.iterator();
-        List<Index> indexesForTable = new ArrayList<Index>();
-        while (it2.hasNext()) {
-          Object change = it2.next();
-          if (change instanceof AddIndexChange) {
-            AddIndexChange ichange = ((AddIndexChange) change);
-            if (ichange.getChangedTable().getName().equalsIgnoreCase(currentTable.getName())) {
-              processChange(currentModel, desiredModel, params, ichange);
-              indexesForTable.add(ichange.getNewIndex());
-            }
+        // obtain new indexes to be returned to process them afterwards
+        for (ModelChange change : changes) {
+          if (!(change instanceof AddIndexChange)) {
+            continue;
+          }
+          AddIndexChange ichange = (AddIndexChange) change;
+          if (ichange.getChangedTable().getName()
+              .equalsIgnoreCase(desiredModel.getTable(i).getName())) {
+            newIndexes.add((AddIndexChange) change);
           }
         }
-        newIndexesPostAction(currentTable, indexesForTable);
       }
     }
 
@@ -832,7 +831,6 @@
     }
 
     // We will now recreate the unchanged foreign keys
-
     ListOrderedMap changesPerTable = new ListOrderedMap();
     ListOrderedMap unchangedFKs = new ListOrderedMap();
     boolean caseSensitive = getPlatform().isDelimitedIdentifierModeOn();
@@ -875,10 +873,7 @@
       }
     }
 
-    it = changes.iterator();
-    while (it.hasNext()) {
-      Object change = it.next();
-
+    for (ModelChange change : changes) {
       if (change instanceof AddForeignKeyChange) {
         ForeignKey fk = ((AddForeignKeyChange) change).getNewForeignKey();
         if (!recreatedFKs.contains(fk.getName())) {
@@ -894,31 +889,30 @@
       }
     }
 
+    return newIndexes;
+  }
+
+  /**
+   * Hook that is executed after all the NewIndexChanges for a list of tables have been created
+   * 
+   * @param newIndexesMap
+   *          a map of all the tables that have new indexes, along with the new indexes
+   * @throws IOException
+   */
+  protected void newIndexesPostAction(Map<Table, List<Index>> newIndexesMap) throws IOException {
   }
 
   /**
    * Hook that is executed after all the NewIndexChanges for a table have been created
    * 
-   * @param table
-   *          the table owner of the new indexes
-   * @param newIndexesList
-   *          a list with the new indexes of the table
-   * @throws IOException
-   */
-  protected void newIndexesPostAction(Table table, List<Index> newIndexesList) throws IOException {
-  }
-
-  /**
-   * Hook that is executed after all the NewIndexChanges for a table have been created
-   * 
-   * @param table
-   *          the table owner of the new indexes
-   * @param indexes
-   *          an array with the new indexes of the table
+   * @param newIndexesMap
+   *          a map of all the tables that have new indexes, along with the new indexes
    * @throws IOException
    */
   private void newIndexesPostAction(Table table, Index[] indexes) throws IOException {
-    newIndexesPostAction(table, Arrays.asList(indexes));
+    Map<Table, List<Index>> newIndexesMap = new HashMap<Table, List<Index>>();
+    newIndexesMap.put(table, Arrays.asList(indexes));
+    newIndexesPostAction(newIndexesMap);
   }
 
   public boolean hasBeenRecreated(Table table) {
@@ -1026,8 +1020,9 @@
    *          The parameters used in the creation of new tables. Note that for existing tables, the
    *          parameters won't be applied
    */
-  protected void processChanges(Database currentModel, Database desiredModel, List changes,
-      CreationParameters params, boolean createConstraints) throws IOException {
+  protected void processChanges(Database currentModel, Database desiredModel,
+      List<ModelChange> changes, CreationParameters params, boolean createConstraints)
+      throws IOException {
     CallbackClosure callbackClosure = new CallbackClosure(this, "processChange", new Class[] {
         Database.class, Database.class, CreationParameters.class, null }, new Object[] {
         currentModel, desiredModel, params, null });
@@ -1105,6 +1100,12 @@
           callbackClosure);
       updatePartialIndexesPostAction();
     }
+
+    if (!getPlatformInfo().isContainsSearchIndexesSupported()) {
+      applyForSelectedChanges(changes, new Class[] { ContainsSearchIndexInformationChange.class },
+          callbackClosure);
+      updateContainsSearchIndexesPostAction();
+    }
   }
 
   /**
@@ -1160,6 +1161,31 @@
   }
 
   /**
+   * Action to be executed when a change on the containsSearch property of an index is detected. It
+   * must be implemented for those platforms where contains search indexes are not supported.
+   * 
+   * @param table
+   *          the table where the changed index belongs
+   * @param index
+   *          the modified index
+   * @param newContainsSearchValue
+   *          the new value of the containsSearch property
+   * @throws IOException
+   */
+  protected void updateContainsSearchIndexAction(Table table, Index index,
+      boolean newContainsSearchValue) throws IOException {
+  }
+
+  /**
+   * Action to be executed once all changes in contains search indexes have been applied in the
+   * model
+   * 
+   * @throws IOException
+   */
+  protected void updateContainsSearchIndexesPostAction() throws IOException {
+  }
+
+  /**
    * This is a fall-through callback which generates a warning because a specific change type wasn't
    * handled.
    * 
@@ -1233,9 +1259,10 @@
       CreationParameters params, RemoveIndexChange change) throws IOException {
     Index removedIndex = change.getIndex();
     writeExternalIndexDropStmt(change.getChangedTable(), removedIndex);
-    if (indexHasColumnWithOperatorClass(removedIndex)) {
-      // keep track of the removed indexes that use operator classes, as in some platforms is it
-      // required to update the comments of the tables that own them
+    if (indexHasColumnWithOperatorClass(removedIndex) || removedIndex.isContainsSearch()) {
+      // keep track of the removed indexes that use operator classes (including indexes flagged as
+      // contains search), as in some platforms is it required to update the comments of the tables
+      // that own them
       putRemovedIndex(_removedIndexesWithOperatorClassMap, change);
     }
     if (removedIndex.getWhereClause() != null && !removedIndex.getWhereClause().isEmpty()) {
@@ -1280,6 +1307,29 @@
   }
 
   /**
+   * Processes the change representing modifications in the information of contains search indexes
+   * which is stored to maintain consistency between the XML model and the database. This changes
+   * only apply for those platforms where contains search indexes are not supported as they are used
+   * just to keep updated that information.
+   * 
+   * @param currentModel
+   *          The current database schema
+   * @param desiredModel
+   *          The desired database schema
+   * @param params
+   *          The parameters used in the creation of new tables. Note that for existing tables, the
+   *          parameters won't be applied
+   * @param change
+   *          The change object
+   */
+  protected void processChange(Database currentModel, Database desiredModel,
+      CreationParameters params, ContainsSearchIndexInformationChange change) throws IOException {
+    updateContainsSearchIndexAction(change.getChangedTable(), change.getIndex(),
+        change.getNewContainsSearch());
+    change.apply(currentModel, getPlatform().isDelimitedIdentifierModeOn());
+  }
+
+  /**
    * Processes the change representing the removal of an check.
    * 
    * @param currentModel
@@ -1746,7 +1796,7 @@
    *          The change objects for this table
    */
   protected void processTableStructureChanges(Database currentModel, Database desiredModel,
-      String tableName, Map parameters, List changes, Set<String> unchangedtriggers)
+      String tableName, Map parameters, List<TableChange> changes, Set<String> unchangedtriggers)
       throws IOException {
     Table sourceTable = currentModel.findTable(tableName, getPlatform()
         .isDelimitedIdentifierModeOn());
@@ -2231,7 +2281,7 @@
 
   protected void processChange(Database currentModel, Database desiredModel,
       ColumnOnCreateDefaultValueChange change) throws IOException {
-    writeColumnCommentStmt(currentModel, change.getChangedTable(), change.getChangedColumn());
+    writeColumnCommentStmt(currentModel, change.getChangedTable(), change.getChangedColumn(), false);
     change.apply(currentModel, getPlatform().isDelimitedIdentifierModeOn());
   }
 
@@ -2349,8 +2399,8 @@
 
   }
 
-  public void writeColumnCommentStmt(Database database, Table table, Column column)
-      throws IOException {
+  public void writeColumnCommentStmt(Database database, Table table, Column column,
+      boolean keepComments) throws IOException {
 
   }
 
@@ -2853,8 +2903,8 @@
    * @param parameters
    *          Additional platform-specific parameters for the table creation
    */
-  protected void writeTableCreationStmt(Database database, Table table, Map parameters)
-      throws IOException {
+  protected void writeTableCreationStmt(Database database, Table table,
+      Map<String, Object> parameters) throws IOException {
     printStartOfStatement("TABLE", getStructureObjectName(table));
     printScriptOptions("CRITICAL = TRUE");
 
@@ -2963,9 +3013,8 @@
    */
   protected void writeColumn(Table table, Column column, boolean deferNotNull) throws IOException {
     // see comments in columnsDiffer about null/"" defaults
-    printIdentifier(getColumnName(column));
-    print(" ");
-    print(getSqlType(column));
+
+    writeColumnType(column);
 
     String value;
     String onCreateDefault = column.getLiteralOnCreateDefault();
@@ -2993,6 +3042,12 @@
     }
   }
 
+  protected void writeColumnType(Column column) throws IOException {
+    printIdentifier(getColumnName(column));
+    print(" ");
+    print(getSqlType(column));
+  }
+
   /**
    * Returns the full SQL type specification (including size and precision/scale) for the given
    * column.
@@ -3494,7 +3549,6 @@
    *          The table
    */
   protected void writeExternalIndicesCreateStmt(Table table) throws IOException {
-    Map<String, String> indexColumnsWithOperatorClass = new HashMap<String, String>();
     for (int idx = 0; idx < table.getIndexCount(); idx++) {
       Index index = table.getIndex(idx);
 
@@ -3542,6 +3596,7 @@
         printIdentifier(getConstraintObjectName(index));
         print(" ON ");
         printIdentifier(getStructureObjectName(table));
+        writeMethod(index);
         print(" (");
 
         List<IndexColumn> columnsWithOperatorClass = new ArrayList<IndexColumn>();
@@ -3569,7 +3624,7 @@
             // included in the comments of the table in Oracle
             columnsWithOperatorClass.add(idxColumn);
           }
-          writeOperatorClass(idxColumn);
+          writeOperatorClass(index, idxColumn);
         }
 
         print(")");
@@ -3581,13 +3636,26 @@
   }
 
   /**
+   * Writes the access method used by the index
+   * 
+   * @param index
+   *          the index
+   * @throws IOException
+   */
+  protected void writeMethod(Index index) throws IOException {
+  }
+
+  /**
    * Writes the operator class of the index column, if any.
    * 
+   * @param index
+   *          the index owner of the index column, it can be used to retrieve information about the
+   *          index itself
    * @param idxColumn
    *          the index column
    * @throws IOException
    */
-  protected void writeOperatorClass(IndexColumn idxColumn) throws IOException {
+  protected void writeOperatorClass(Index index, IndexColumn idxColumn) throws IOException {
   }
 
   /**
@@ -3668,14 +3736,12 @@
    * @return true if any columns if the provided index defines an operator class, false otherwise
    */
   protected boolean indexHasColumnWithOperatorClass(Index index) {
-    boolean hasColumnWithOperatorClass = false;
     for (IndexColumn indexColumn : index.getColumns()) {
       if (indexColumn.getOperatorClass() != null && !indexColumn.getOperatorClass().isEmpty()) {
-        hasColumnWithOperatorClass = true;
-        break;
+        return true;
       }
     }
-    return hasColumnWithOperatorClass;
+    return false;
   }
 
   /**
@@ -4748,14 +4814,15 @@
    * remaining changes will be in that list.
    */
   protected void processTableStructureChanges(Database currentModel, Database desiredModel,
-      Table sourceTable, Table targetTable, Map parameters, List changes) throws IOException {
+      Table sourceTable, Table targetTable, Map parameters, List<TableChange> changes)
+      throws IOException {
 
     if (requiresRecreation(targetTable, changes, true)) {
       return;
     }
 
     // // First we drop primary keys as necessary
-    for (Iterator changeIt = changes.iterator(); changeIt.hasNext();) {
+    for (Iterator<TableChange> changeIt = changes.iterator(); changeIt.hasNext();) {
       TableChange change = (TableChange) changeIt.next();
 
       if (change instanceof RemovePrimaryKeyChange) {
@@ -4776,6 +4843,12 @@
       } else if (change instanceof ColumnDefaultValueChange) {
         processChange(currentModel, desiredModel, (ColumnDefaultValueChange) change);
         changeIt.remove();
+      } else if (change instanceof ColumnDataTypeChange) {
+        processChange(currentModel, desiredModel, (ColumnDataTypeChange) change);
+        changeIt.remove();
+      } else if (change instanceof ColumnSizeChange) {
+        processChange(currentModel, desiredModel, (ColumnSizeChange) change);
+        changeIt.remove();
       }
     }
 
@@ -4957,6 +5030,16 @@
     // no default implementation
   }
 
+  protected void processChange(Database currentModel, Database desiredModel,
+      ColumnDataTypeChange change) throws IOException {
+    // no default implementation
+  }
+
+  protected void processChange(Database currentModel, Database desiredModel, ColumnSizeChange change)
+      throws IOException {
+    // no default implementation
+  }
+
   /**
    * Creates the sequence necessary for the auto-increment of the given column. To be implemented
    * platform specific: default unimplemented
--- a/src/org/apache/ddlutils/platform/StandardBatchEvaluator.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/StandardBatchEvaluator.java	Thu Mar 09 12:59:50 2017 +0100
@@ -38,6 +38,7 @@
   private static final int MAX_LOOPS_OF_FORCED = 5;
   protected final Log _log = LogFactory.getLog(getClass());
   private Platform platform;
+  private boolean logInfoSucessCommands = true;
 
   public StandardBatchEvaluator(Platform platform) {
     this.platform = platform;
@@ -148,13 +149,8 @@
           }
         }
       } while (changedSomething);
-      String errorNumber = "";
-      if (errors > 0)
-        errorNumber = " with " + errors + " error(s)";
-      else
-        errorNumber = " successfully";
-      if (commandCount > 0)
-        _log.info("Executed " + commandCount + " SQL command(s)" + errorNumber);
+
+      logCommands(errors, commandCount);
 
       // execute the forced commands
       int loops = 0;
@@ -231,6 +227,21 @@
     return errors;
   }
 
+  private void logCommands(int errors, int commandCount) {
+    String errorNumber = "";
+    if (errors > 0)
+      errorNumber = " with " + errors + " error(s)";
+    else
+      errorNumber = " successfully";
+    if (commandCount > 0) {
+      if (errors == 0 && !logInfoSucessCommands) {
+        _log.debug("Executed " + commandCount + " SQL command(s)" + errorNumber);
+      } else {
+        _log.info("Executed " + commandCount + " SQL command(s)" + errorNumber);
+      }
+    }
+  }
+
   @Override
   public int evaluateBatchRealBatch(Connection connection, List<String> sql, boolean continueOnError)
       throws DatabaseOperationException {
@@ -314,13 +325,7 @@
       }
     }
 
-    String errorNumber = "";
-    if (errors > 0)
-      errorNumber = " with " + errors + " error(s)";
-    else
-      errorNumber = " successfully";
-    if (commandCount > 0)
-      _log.info("Executed " + commandCount + " SQL command(s)" + errorNumber);
+    logCommands(errors, commandCount);
 
     return errors;
   }
@@ -329,4 +334,14 @@
       boolean continueOnError, long indexFailedStatement) {
     return evaluateBatch(connection, sql, continueOnError, 0);
   }
+
+  @Override
+  public void setLogInfoSucessCommands(boolean logInfoSucessCommands) {
+    this.logInfoSucessCommands = logInfoSucessCommands;
+  }
+
+  @Override
+  public boolean isLogInfoSucessCommands() {
+    return logInfoSucessCommands;
+  }
 }
--- a/src/org/apache/ddlutils/platform/oracle/Oracle8Builder.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/oracle/Oracle8Builder.java	Thu Mar 09 12:59:50 2017 +0100
@@ -37,6 +37,8 @@
 import org.apache.ddlutils.DdlUtilsException;
 import org.apache.ddlutils.Platform;
 import org.apache.ddlutils.alteration.AddColumnChange;
+import org.apache.ddlutils.alteration.ColumnChange;
+import org.apache.ddlutils.alteration.ColumnDataTypeChange;
 import org.apache.ddlutils.alteration.ColumnDefaultValueChange;
 import org.apache.ddlutils.alteration.ColumnRequiredChange;
 import org.apache.ddlutils.alteration.ColumnSizeChange;
@@ -58,6 +60,7 @@
 import org.apache.oro.text.regex.PatternCompiler;
 import org.apache.oro.text.regex.Perl5Compiler;
 import org.apache.oro.text.regex.Perl5Matcher;
+import org.openbravo.ddlutils.util.DBSMContants;
 
 /**
  * The SQL Builder for Oracle.
@@ -76,6 +79,7 @@
 
   private Map<String, String> _onCreateDefaultColumns;
   private Map<String, String> _columnsWithUpdatedComments;
+  private Map<String, String> _tablesWithUpdatedComments;
 
   /**
    * Creates a new builder instance.
@@ -139,13 +143,13 @@
     // Create comments for onCreateDefault
     for (int idx = 0; idx < table.getColumnCount(); idx++) {
       Column column = table.getColumn(idx);
-      writeColumnCommentStmt(database, table, column);
+      writeColumnCommentStmt(database, table, column, false);
     }
   }
 
   @Override
-  public void writeColumnCommentStmt(Database database, Table table, Column column)
-      throws IOException {
+  public void writeColumnCommentStmt(Database database, Table table, Column column,
+      boolean keepComments) throws IOException {
     String comment = "";
     if (column.getOnCreateDefault() != null && !column.getOnCreateDefault().equals("")) {
       String oncreatedefaultp = column.getOnCreateDefault();
@@ -168,6 +172,13 @@
       _onCreateDefaultColumns = new HashMap<String, String>();
     }
     _onCreateDefaultColumns.put(table.getName() + "." + column.getName(), comment);
+    if (keepComments) {
+      // Retrieve existing column comments from database
+      String commentFromDatabase = getCommentOfColumn(table.getName(), column.getName());
+      if (commentFromDatabase != null) {
+        comment += transformInOracleComment(commentFromDatabase);
+      }
+    }
     println("COMMENT ON COLUMN " + table.getName() + "." + column.getName() + " IS '" + comment
         + "'");
     printEndOfStatement();
@@ -623,24 +634,26 @@
    * {@inheritDoc}
    */
   @Override
-  protected void newIndexesPostAction(Table table, List<Index> newIndexesList) throws IOException {
-    // Updates the comments of a table that have new indexes, to prevent losing the info about
+  protected void newIndexesPostAction(Map<Table, List<Index>> newIndexesMap) throws IOException {
+    // Updates the comments of the tables that have new indexes, to prevent losing the info about
     // the operator class or partial indexing of the indexed columns
-    List<Index> indexesWithOperatorClass = new ArrayList<Index>();
-    List<Index> partialIndexes = new ArrayList<Index>();
-    for (Index index : newIndexesList) {
-      if (indexHasColumnWithOperatorClass(index)) {
-        indexesWithOperatorClass.add(index);
+    for (Table table : newIndexesMap.keySet()) {
+      List<Index> indexesWithOperatorClass = new ArrayList<Index>();
+      List<Index> partialIndexes = new ArrayList<Index>();
+      for (Index index : newIndexesMap.get(table)) {
+        if (indexHasColumnWithOperatorClass(index) || index.isContainsSearch()) {
+          indexesWithOperatorClass.add(index);
+        }
+        if (index.getWhereClause() != null && !index.getWhereClause().isEmpty()) {
+          partialIndexes.add(index);
+        }
       }
-      if (index.getWhereClause() != null && !index.getWhereClause().isEmpty()) {
-        partialIndexes.add(index);
+      if (!indexesWithOperatorClass.isEmpty()) {
+        includeOperatorClassInTableComment(table, indexesWithOperatorClass);
       }
-    }
-    if (!indexesWithOperatorClass.isEmpty()) {
-      includeOperatorClassInTableComment(table, indexesWithOperatorClass);
-    }
-    if (!partialIndexes.isEmpty()) {
-      includeWhereClauseInColumnComment(table, partialIndexes);
+      if (!partialIndexes.isEmpty()) {
+        includeWhereClauseInColumnComment(table, partialIndexes);
+      }
     }
 
     if (_onCreateDefaultColumns != null) {
@@ -686,6 +699,8 @@
           if (indexColumn.getOperatorClass() != null && !indexColumn.getOperatorClass().isEmpty()) {
             tableComment.append(index.getName() + "." + indexColumn.getName() + ".operatorClass="
                 + indexColumn.getOperatorClass() + "$");
+          } else if (index.isContainsSearch()) {
+            tableComment.append(index.getName() + "." + DBSMContants.CONTAINS_SEARCH + "$");
           }
         }
       }
@@ -766,7 +781,7 @@
     for (String tableName : removedIndexesMap.keySet()) {
       List<Index> indexesWithOperatorClass = new ArrayList<Index>();
       for (Index index : removedIndexesMap.get(tableName)) {
-        if (indexHasColumnWithOperatorClass(index)) {
+        if (indexHasColumnWithOperatorClass(index) || index.isContainsSearch()) {
           indexesWithOperatorClass.add(index);
         }
       }
@@ -873,6 +888,57 @@
     _columnsWithUpdatedComments.clear();
   }
 
+  @Override
+  protected void updateContainsSearchIndexAction(Table table, Index index,
+      boolean newContainsSearchValue) throws IOException {
+
+    String tableName = table.getName();
+    String currentComments;
+    String updatedComments;
+
+    if (_tablesWithUpdatedComments == null) {
+      _tablesWithUpdatedComments = new HashMap<String, String>();
+    }
+
+    if (_tablesWithUpdatedComments.containsKey(tableName)) {
+      currentComments = _tablesWithUpdatedComments.get(tableName);
+    } else {
+      currentComments = transformInOracleComment(getCommentOfTable(tableName));
+    }
+
+    if (currentComments == null) {
+      currentComments = "";
+    }
+
+    if (newContainsSearchValue) {
+      updatedComments = currentComments + index.getName() + "." + DBSMContants.CONTAINS_SEARCH
+          + "$";
+    } else {
+      updatedComments = currentComments.replace(index.getName() + "."
+          + DBSMContants.CONTAINS_SEARCH + "$", "");
+    }
+    _tablesWithUpdatedComments.put(tableName, updatedComments);
+  }
+
+  /**
+   * Action to be executed when a change on the containsSearch property of an index is detected.
+   * Here this method is used to update at once all the table comments affected by changes on
+   * contains search indexes.
+   * 
+   * @throws IOException
+   */
+  @Override
+  protected void updateContainsSearchIndexesPostAction() throws IOException {
+    if (_tablesWithUpdatedComments == null) {
+      return;
+    }
+    for (String table : _tablesWithUpdatedComments.keySet()) {
+      print("COMMENT ON TABLE " + table + " IS '" + _tablesWithUpdatedComments.get(table) + "'");
+      printEndOfStatement();
+    }
+    _tablesWithUpdatedComments.clear();
+  }
+
   /**
    * Given a table and a list of removed indexes that define operator classes, updates the comments
    * of the table to delete the info associated with the deleted indexes
@@ -893,7 +959,8 @@
           // Find the line that corresponds with the deleted indexColumn, that is, the line that
           // starts with "indexName.columnName."
           for (String commentLine : commentLines) {
-            if (commentLine.startsWith(index.getName() + "." + indexColumn.getName() + ".")) {
+            if (commentLine.startsWith(index.getName() + "." + indexColumn.getName() + ".")
+                || commentLine.startsWith(index.getName() + ".containsSearch")) {
               commentLines.remove(commentLine);
               break;
             }
@@ -1020,4 +1087,83 @@
     }
     return tableComment;
   }
+
+  /** Returns {@code true} if table requires to be recreated */
+  public boolean requiresRecreation(ColumnDataTypeChange change) {
+    if (isSupportedTypeChange(change)) {
+      return false;
+    }
+    return true;
+  }
+
+  private boolean isSupportedTypeChange(ColumnDataTypeChange change) {
+    String oldType = TypeMap.getJdbcTypeName(change.getChangedColumn().getTypeCode());
+    String newType = TypeMap.getJdbcTypeName(change.getNewTypeCode());
+
+    // it is allowed to change from (var)char to n(var)char but not in the other way around
+    boolean charToNChar = TypeMap.CHAR.equals(oldType) && TypeMap.NCHAR.equals(newType);
+    boolean varcharToNVarchar = TypeMap.VARCHAR.equals(oldType) && TypeMap.NVARCHAR.equals(newType);
+
+    return charToNChar || varcharToNVarchar;
+  }
+
+  @Override
+  protected void processChange(Database currentModel, Database desiredModel,
+      ColumnDataTypeChange change) throws IOException {
+    if (isSupportedTypeChange(change)) {
+      change.apply(currentModel, getPlatform().isDelimitedIdentifierModeOn());
+      printColumnTypeChange(currentModel, change);
+    }
+  }
+
+  /** Returns {@code true} if table requires to be recreated */
+  public boolean requiresRecreation(ColumnSizeChange change) {
+    String type = TypeMap.getJdbcTypeName(change.getChangedColumn().getTypeCode());
+    boolean supportedChange = canResizeType(type);
+
+    boolean madeLonger;
+    if (TypeMap.DECIMAL.equals(type)) {
+      int oldPrecision = change.getOldSize() == 0 ? Integer.MAX_VALUE : change.getOldSize();
+      int oldScale = change.getOldScale();
+      int newPrecision = change.getNewSize() == 0 ? Integer.MAX_VALUE : change.getNewSize();
+      int newScale = change.getNewScale();
+      if (oldPrecision == newPrecision) {
+        // can't change scale keeping same precision
+        madeLonger = oldScale == newScale;
+      } else {
+        madeLonger = oldPrecision <= newPrecision && oldScale <= newScale;
+      }
+    } else {
+      madeLonger = change.getOldSize() <= change.getNewSize();
+    }
+    return !(supportedChange && madeLonger);
+  }
+
+  private boolean canResizeType(String type) {
+    switch (type) {
+    case TypeMap.NVARCHAR:
+    case TypeMap.VARCHAR:
+    case TypeMap.NCHAR:
+    case TypeMap.CHAR:
+    case TypeMap.DECIMAL:
+      return true;
+    default:
+      return false;
+    }
+  }
+
+  @Override
+  protected void processChange(Database currentModel, Database desiredModel, ColumnSizeChange change)
+      throws IOException {
+    change.apply(currentModel, getPlatform().isDelimitedIdentifierModeOn());
+    printColumnTypeChange(currentModel, change);
+  }
+
+  private void printColumnTypeChange(Database database, ColumnChange change) throws IOException {
+    Table table = database.findTable(change.getChangedTable().getName());
+    Column column = table.findColumn(change.getChangedColumn().getName());
+    print("ALTER TABLE " + table.getName() + " MODIFY ");
+    writeColumnType(column);
+    printEndOfStatement();
+  }
 }
--- a/src/org/apache/ddlutils/platform/oracle/Oracle8Platform.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/oracle/Oracle8Platform.java	Thu Mar 09 12:59:50 2017 +0100
@@ -38,6 +38,7 @@
 import org.apache.ddlutils.PlatformInfo;
 import org.apache.ddlutils.model.Database;
 import org.apache.ddlutils.model.Function;
+import org.apache.ddlutils.model.StructureObject;
 import org.apache.ddlutils.model.Table;
 import org.apache.ddlutils.model.Trigger;
 import org.apache.ddlutils.platform.Oracle8StandardBatchEvaluator;
@@ -413,16 +414,19 @@
     }
   }
 
-  public ArrayList checkTranslationConsistency(Database database, Database fullDatabase) {
-    ArrayList inconsistentObjects = new ArrayList();
+  @Override
+  public List<StructureObject> checkTranslationConsistency(Database database, Database fullDatabase) {
+    List<StructureObject> inconsistentObjects = new ArrayList<>();
     PostgrePLSQLStandarization.generateOutPatterns(database);
     for (int i = 0; i < database.getFunctionCount(); i++) {
       PostgrePLSQLFunctionTranslation funcTrans = new PostgrePLSQLFunctionTranslation(fullDatabase);
       int indF = -1;
       Function f = database.getFunction(i);
       for (int j = 0; indF == -1 && j < fullDatabase.getFunctionCount(); j++) {
-        if (fullDatabase.getFunction(j).equals(f))
+        if (fullDatabase.getFunction(j).equals(f)) {
           indF = j;
+          break;
+        }
       }
       PostgrePLSQLFunctionStandarization funcStand = new PostgrePLSQLFunctionStandarization(
           fullDatabase, indF);
@@ -456,8 +460,10 @@
       int indF = -1;
       Trigger trg = database.getTrigger(i);
       for (int j = 0; indF == -1 && j < fullDatabase.getTriggerCount(); j++) {
-        if (fullDatabase.getTrigger(j).equals(trg))
+        if (fullDatabase.getTrigger(j).equals(trg)) {
           indF = j;
+          break;
+        }
       }
       PostgrePLSQLTriggerStandarization triggerStand = new PostgrePLSQLTriggerStandarization(
           fullDatabase, indF);
--- a/src/org/apache/ddlutils/platform/oracle/OracleModelLoader.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/oracle/OracleModelLoader.java	Thu Mar 09 12:59:50 2017 +0100
@@ -31,6 +31,7 @@
 import org.apache.ddlutils.platform.ModelLoaderBase;
 import org.apache.ddlutils.platform.RowFiller;
 import org.apache.ddlutils.util.ExtTypes;
+import org.openbravo.ddlutils.util.DBSMContants;
 
 /**
  * 
@@ -346,7 +347,11 @@
         }
         String operatorClass = getIndexOperatorClass(indexName, inxcol.getName());
         if (operatorClass != null && !operatorClass.isEmpty()) {
-          inxcol.setOperatorClass(operatorClass);
+          if (DBSMContants.CONTAINS_SEARCH.equals(operatorClass)) {
+            inx.setContainsSearch(true);
+          } else {
+            inxcol.setOperatorClass(operatorClass);
+          }
         }
         inx.addColumn(inxcol);
       }
@@ -417,6 +422,8 @@
         for (String commentLine : commentLines) {
           if (commentLine.startsWith(indexName + "." + indexColumnName)) {
             operatorClass = commentLine.substring(commentLine.indexOf("=") + 1);
+          } else if (commentLine.startsWith(indexName + "." + DBSMContants.CONTAINS_SEARCH)) {
+            operatorClass = DBSMContants.CONTAINS_SEARCH;
           }
         }
       }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/apache/ddlutils/platform/postgresql/PostgrePLSQLConsistencyChecker.java	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,85 @@
+/*
+ ************************************************************************************
+ * Copyright (C) 2016 Openbravo S.L.U.
+ * Licensed under the Apache Software License version 2.0
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to  in writing,  software  distributed
+ * under the License is distributed  on  an  "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR
+ * CONDITIONS OF ANY KIND, either  express  or  implied.  See  the  License  for  the
+ * specific language governing permissions and limitations under the License.
+ ************************************************************************************
+ */
+
+package org.apache.ddlutils.platform.postgresql;
+
+import java.util.concurrent.Callable;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.ddlutils.model.Database;
+import org.apache.ddlutils.model.Function;
+import org.apache.ddlutils.model.StructureObject;
+import org.apache.ddlutils.model.Trigger;
+import org.apache.ddlutils.platform.PlatformImplBase;
+import org.apache.ddlutils.translation.CommentFilter;
+import org.apache.ddlutils.translation.LiteralFilter;
+
+/**
+ * Checks consistency of exported PL code in PostgreSQL. It compares the body of a PL object
+ * (Function or Trigger) to ensure after importing it will remain as it was exported.
+ * 
+ * It allows parallel execution.
+ * 
+ * @see PostgreSqlPlatform#checkTranslationConsistency
+ * 
+ * @author alostale
+ *
+ */
+class PostgrePLSQLConsistencyChecker implements Callable<StructureObject> {
+  private static final Log log = LogFactory.getLog(PostgrePLSQLConsistencyChecker.class);
+  private Database fullDatabase;
+  private StructureObject plObject;
+
+  PostgrePLSQLConsistencyChecker(Database fullDatabase, StructureObject plObject) {
+    this.fullDatabase = fullDatabase;
+    this.plObject = plObject;
+  }
+
+  /** Checks PL consistency returning {@code null} if it is consistent or the object if it is not */
+  @Override
+  public StructureObject call() throws Exception {
+    PostgrePLSQLFunctionTranslation funcTrans = new PostgrePLSQLFunctionTranslation(fullDatabase);
+    String originalBody;
+    String body;
+    if (plObject instanceof Function) {
+      originalBody = ((Function) plObject).getOriginalBody();
+      body = ((Function) plObject).getBody();
+    } else if (plObject instanceof Trigger) {
+      originalBody = ((Trigger) plObject).getOriginalBody();
+      body = ((Function) plObject).getBody();
+    } else {
+      throw new IllegalArgumentException("Expected a Function or a Trigger got a "
+          + plObject.getClass().getName());
+    }
+
+    if (originalBody == null) {
+      return null;
+    }
+    LiteralFilter litFilter1 = new LiteralFilter();
+    CommentFilter comFilter1 = new CommentFilter();
+    String s1 = litFilter1.removeLiterals(body);
+    s1 = comFilter1.removeComments(s1);
+    s1 = funcTrans.exec(s1);
+    s1 = comFilter1.restoreComments(s1);
+    s1 = litFilter1.restoreLiterals(s1);
+    String s1r = s1.replaceAll("\\s", "");
+    String s2 = originalBody;
+    String s2r = s2.replaceAll("\\s", "");
+    if (!s1r.equals(s2r)) {
+      log.warn("Found differences in " + plObject.getName());
+      PlatformImplBase.printDiff(s1, s2);
+      return plObject;
+    }
+    return null;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/apache/ddlutils/platform/postgresql/PostgrePLSQLFunctionConcurrentStandardization.java	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,68 @@
+/*
+ ************************************************************************************
+ * Copyright (C) 2016 Openbravo S.L.U.
+ * Licensed under the Apache Software License version 2.0
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to  in writing,  software  distributed
+ * under the License is distributed  on  an  "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR
+ * CONDITIONS OF ANY KIND, either  express  or  implied.  See  the  License  for  the
+ * specific language governing permissions and limitations under the License.
+ ************************************************************************************
+ */
+
+package org.apache.ddlutils.platform.postgresql;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.ddlutils.model.Database;
+import org.apache.ddlutils.model.Function;
+import org.apache.ddlutils.translation.CommentFilter;
+import org.apache.ddlutils.translation.LiteralFilter;
+
+/**
+ * Allows to concurrently standardize PL functions.
+ * 
+ * @see PostgreSqlModelLoader#standardizePlSql
+ * 
+ * @author alostale
+ */
+class PostgrePLSQLFunctionConcurrentStandardization implements Runnable {
+  private static final Log log = LogFactory
+      .getLog(PostgrePLSQLFunctionConcurrentStandardization.class);
+
+  private Database db;
+  private int idx;
+
+  PostgrePLSQLFunctionConcurrentStandardization(Database db, int idx) {
+    this.db = db;
+    this.idx = idx;
+  }
+
+  @Override
+  public void run() {
+    Function f = db.getFunction(idx);
+    log.debug("Standardizing function: " + f.getName());
+
+    f.setOriginalBody(f.getBody());
+    PostgrePLSQLFunctionStandarization functionStandarization = new PostgrePLSQLFunctionStandarization(
+        db, idx);
+    String body = f.getBody();
+
+    LiteralFilter litFilter = new LiteralFilter();
+    CommentFilter comFilter = new CommentFilter();
+
+    body = litFilter.removeLiterals(body);
+    body = comFilter.removeComments(body);
+    String standardizedBody = functionStandarization.exec(body).trim();
+
+    standardizedBody = comFilter.restoreComments(standardizedBody);
+    standardizedBody = litFilter.restoreLiterals(standardizedBody);
+
+    while (standardizedBody.charAt(standardizedBody.length() - 1) == '\n'
+        || standardizedBody.charAt(standardizedBody.length() - 1) == ' ')
+      standardizedBody = standardizedBody.substring(0, standardizedBody.length() - 1);
+    f.setBody(standardizedBody + "\n");
+    log.debug("  ...standardized function: " + f.getName());
+  }
+
+}
--- a/src/org/apache/ddlutils/platform/postgresql/PostgrePLSQLStandarization.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/postgresql/PostgrePLSQLStandarization.java	Thu Mar 09 12:59:50 2017 +0100
@@ -1,3 +1,15 @@
+/*
+ ************************************************************************************
+ * Copyright (C) 2008-2017 Openbravo S.L.U.
+ * Licensed under the Apache Software License version 2.0
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to  in writing,  software  distributed
+ * under the License is distributed  on  an  "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR
+ * CONDITIONS OF ANY KIND, either  express  or  implied.  See  the  License  for  the
+ * specific language governing permissions and limitations under the License.
+ ************************************************************************************
+ */
+
 package org.apache.ddlutils.platform.postgresql;
 
 import java.sql.Types;
@@ -8,6 +20,7 @@
 import org.apache.ddlutils.model.Parameter;
 import org.apache.ddlutils.translation.ByLineTranslation;
 import org.apache.ddlutils.translation.CombinedTranslation;
+import org.apache.ddlutils.translation.ReplaceOutFunctionParams;
 import org.apache.ddlutils.translation.ReplacePatTranslation;
 import org.apache.ddlutils.translation.ReplaceStrTranslation;
 
@@ -89,8 +102,7 @@
       if (database.getFunction(i).getTypeCode() == Types.NULL) {
         append(new ReplacePatTranslation("[Pp][Ee][Rr][Ff][Oo][Rr][Mm][\\s|\\t]*"
             + generateStringPat(database.getFunction(i).getName()) + "[\\s]*\\(", database
-            .getFunction(i).getName()
-            + "("));
+            .getFunction(i).getName() + "("));
       }
 
     }
@@ -202,11 +214,11 @@
           String patternOut = "$" + (numParamsOut + 1) + "(" + paramPos + ")";
           if (!outFunctions.contains(f.getName()))
             outFunctions.add(f.getName());
-          patternsOutFunctions.add(new ByLineTranslation(new ReplacePatTranslation(patternIn,
-              patternOut)));
+          patternsOutFunctions.add(new ByLineTranslation(new ReplaceOutFunctionParams(patternIn,
+              patternOut, f.getName())));
 
         }
-      } while (defAct > 0 && f.getParameter(defAct).isDefaultFunction());
+      } while (defAct > 0 && f.getParameter(defAct).getDefaultValue() != null);
     }
   }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/apache/ddlutils/platform/postgresql/PostgrePLSQLTriggerConcurrentStandardization.java	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,68 @@
+/*
+ ************************************************************************************
+ * Copyright (C) 2016 Openbravo S.L.U.
+ * Licensed under the Apache Software License version 2.0
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to  in writing,  software  distributed
+ * under the License is distributed  on  an  "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR
+ * CONDITIONS OF ANY KIND, either  express  or  implied.  See  the  License  for  the
+ * specific language governing permissions and limitations under the License.
+ ************************************************************************************
+ */
+
+package org.apache.ddlutils.platform.postgresql;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.ddlutils.model.Database;
+import org.apache.ddlutils.model.Trigger;
+import org.apache.ddlutils.translation.CommentFilter;
+import org.apache.ddlutils.translation.LiteralFilter;
+
+/**
+ * Allows to concurrently standardize PL triggers.
+ * 
+ * @see PostgreSqlModelLoader#standardizePlSql
+ * 
+ * @author alostale
+ */
+class PostgrePLSQLTriggerConcurrentStandardization implements Runnable {
+  private static final Log log = LogFactory
+      .getLog(PostgrePLSQLTriggerConcurrentStandardization.class);
+
+  private Database db;
+  private int idx;
+
+  PostgrePLSQLTriggerConcurrentStandardization(Database db, int idx) {
+    this.db = db;
+    this.idx = idx;
+  }
+
+  @Override
+  public void run() {
+    Trigger trg = db.getTrigger(idx);
+    log.debug("Standardizing trigger: " + trg.getName());
+    trg.setOriginalBody(trg.getBody());
+    PostgrePLSQLTriggerStandarization triggerStandarization = new PostgrePLSQLTriggerStandarization(
+        db, idx);
+    String body = trg.getBody();
+
+    LiteralFilter litFilter = new LiteralFilter();
+    CommentFilter comFilter = new CommentFilter();
+
+    body = litFilter.removeLiterals(body);
+    body = comFilter.removeComments(body);
+
+    String standardizedBody = triggerStandarization.exec(body);
+
+    standardizedBody = comFilter.restoreComments(standardizedBody);
+    standardizedBody = litFilter.restoreLiterals(standardizedBody);
+
+    while (standardizedBody.charAt(standardizedBody.length() - 1) == '\n'
+        || standardizedBody.charAt(standardizedBody.length() - 1) == ' ')
+      standardizedBody = standardizedBody.substring(0, standardizedBody.length() - 1);
+    trg.setBody(standardizedBody + '\n');
+    log.debug("  ...standardized trigger: " + trg.getName());
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/apache/ddlutils/platform/postgresql/PostgrePLSQLViewConcurrentStandardization.java	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,54 @@
+/*
+ ************************************************************************************
+ * Copyright (C) 2016 Openbravo S.L.U.
+ * Licensed under the Apache Software License version 2.0
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to  in writing,  software  distributed
+ * under the License is distributed  on  an  "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR
+ * CONDITIONS OF ANY KIND, either  express  or  implied.  See  the  License  for  the
+ * specific language governing permissions and limitations under the License.
+ ************************************************************************************
+ */
+
+package org.apache.ddlutils.platform.postgresql;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.ddlutils.model.Database;
+import org.apache.ddlutils.model.View;
+
+/**
+ * Allows to concurrently standardize DB views.
+ * 
+ * @see PostgreSqlModelLoader#standardizePlSql
+ * 
+ * @author alostale
+ */
+class PostgrePLSQLViewConcurrentStandardization implements Runnable {
+  private static final Log log = LogFactory.getLog(PostgrePLSQLViewConcurrentStandardization.class);
+
+  private Database db;
+  private int idx;
+
+  PostgrePLSQLViewConcurrentStandardization(Database db, int idx) {
+    this.db = db;
+    this.idx = idx;
+  }
+
+  @Override
+  public void run() {
+    View view = db.getView(idx);
+    log.debug("Standardizing view: " + view.getName());
+    PostgreSQLStandarization viewStandarization = new PostgreSQLStandarization();
+    String body = db.getView(idx).getStatement();
+
+    String standardizedBody = viewStandarization.exec(body);
+    if (standardizedBody.endsWith("\n"))
+      standardizedBody = standardizedBody.substring(0, standardizedBody.length() - 1);
+    standardizedBody = standardizedBody.trim();
+    db.getView(idx).setStatement(standardizedBody);
+
+    log.debug("  ...standardized view: " + view.getName());
+  }
+
+}
--- a/src/org/apache/ddlutils/platform/postgresql/PostgreSqlBuilder.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/postgresql/PostgreSqlBuilder.java	Thu Mar 09 12:59:50 2017 +0100
@@ -1,23 +1,23 @@
+/*
+ ************************************************************************************
+ * Copyright (C) 2001-2017 Openbravo S.L.U.
+ * Licensed under the Apache Software License version 2.0
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to  in writing,  software  distributed
+ * under the License is distributed  on  an  "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR
+ * CONDITIONS OF ANY KIND, either  express  or  implied.  See  the  License  for  the
+ * specific language governing permissions and limitations under the License.
+ ************************************************************************************
+ */
+
 package org.apache.ddlutils.platform.postgresql;
 
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
+import static org.apache.ddlutils.model.TypeMap.CHAR;
+import static org.apache.ddlutils.model.TypeMap.CLOB;
+import static org.apache.ddlutils.model.TypeMap.DECIMAL;
+import static org.apache.ddlutils.model.TypeMap.NCHAR;
+import static org.apache.ddlutils.model.TypeMap.NVARCHAR;
+import static org.apache.ddlutils.model.TypeMap.VARCHAR;
 
 import java.io.IOException;
 import java.sql.Types;
@@ -27,6 +27,7 @@
 import org.apache.ddlutils.Platform;
 import org.apache.ddlutils.alteration.AddColumnChange;
 import org.apache.ddlutils.alteration.ColumnChange;
+import org.apache.ddlutils.alteration.ColumnDataTypeChange;
 import org.apache.ddlutils.alteration.ColumnDefaultValueChange;
 import org.apache.ddlutils.alteration.ColumnRequiredChange;
 import org.apache.ddlutils.alteration.ColumnSizeChange;
@@ -40,6 +41,7 @@
 import org.apache.ddlutils.model.Parameter;
 import org.apache.ddlutils.model.Table;
 import org.apache.ddlutils.model.Trigger;
+import org.apache.ddlutils.model.TypeMap;
 import org.apache.ddlutils.model.View;
 import org.apache.ddlutils.platform.SqlBuilder;
 import org.apache.ddlutils.translation.CommentFilter;
@@ -54,6 +56,9 @@
  */
 public class PostgreSqlBuilder extends SqlBuilder {
 
+  private static final String GIN_ACCESS_METHOD = "gin";
+  private static final String GIN_OPERATOR_CLASS = "gin_trgm_ops";
+
   private Translation plsqltranslation = null;
   private Translation sqltranslation = null;
 
@@ -116,8 +121,20 @@
    * {@inheritDoc}
    */
   @Override
-  protected void writeOperatorClass(IndexColumn idxColumn) throws IOException {
-    if (idxColumn.getOperatorClass() != null && !idxColumn.getOperatorClass().isEmpty()) {
+  protected void writeMethod(Index index) throws IOException {
+    if (index.isContainsSearch()) {
+      print(" USING " + GIN_ACCESS_METHOD);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected void writeOperatorClass(Index index, IndexColumn idxColumn) throws IOException {
+    if (index.isContainsSearch()) {
+      print(" " + GIN_OPERATOR_CLASS);
+    } else if (idxColumn.getOperatorClass() != null && !idxColumn.getOperatorClass().isEmpty()) {
       print(" " + idxColumn.getOperatorClass());
     }
   }
@@ -156,13 +173,13 @@
 
     for (int idx = 0; idx < table.getColumnCount(); idx++) {
       Column column = table.getColumn(idx);
-      writeColumnCommentStmt(database, table, column);
+      writeColumnCommentStmt(database, table, column, false);
     }
   }
 
   @Override
-  public void writeColumnCommentStmt(Database database, Table table, Column column)
-      throws IOException {
+  public void writeColumnCommentStmt(Database database, Table table, Column column,
+      boolean keepComments) throws IOException {
     String comment = "";
 
     if (column.getTypeCode() == ExtTypes.NVARCHAR) {
@@ -954,4 +971,106 @@
     }
     printEndOfStatement();
   }
+
+  /** Returns {@code true} if table requires to be recreated */
+  public boolean requiresRecreation(ColumnDataTypeChange change) {
+    boolean supportedChange = isCommentChange(change) || isAllowedChange(change);
+    return !supportedChange;
+  }
+
+  /** PG doesn't support NChar nor NVarchar types, so only change is to add a comment */
+  private boolean isCommentChange(ColumnDataTypeChange change) {
+    String oldType = TypeMap.getJdbcTypeName(change.getChangedColumn().getTypeCode());
+    String newType = TypeMap.getJdbcTypeName(change.getNewTypeCode());
+
+    boolean varcharToNVarchar = (NVARCHAR.equals(oldType) || VARCHAR.equals(oldType))
+        && (NVARCHAR.equals(newType) || VARCHAR.equals(newType));
+    boolean charToNchar = (NCHAR.equals(oldType) || CHAR.equals(oldType))
+        && (NCHAR.equals(newType) || CHAR.equals(newType));
+
+    return varcharToNVarchar || charToNchar;
+  }
+
+  private boolean isAllowedChange(ColumnDataTypeChange change) {
+    String oldType = TypeMap.getJdbcTypeName(change.getChangedColumn().getTypeCode());
+    String newType = TypeMap.getJdbcTypeName(change.getNewTypeCode());
+    boolean wasTxtType = (NVARCHAR.equals(oldType) || VARCHAR.equals(oldType)
+        || NCHAR.equals(oldType) || CHAR.equals(oldType));
+    return wasTxtType && CLOB.equals(newType);
+  }
+
+  @Override
+  protected void processChange(Database currentModel, Database desiredModel,
+      ColumnDataTypeChange change) throws IOException {
+    if (isCommentChange(change)) {
+      change.apply(currentModel, getPlatform().isDelimitedIdentifierModeOn());
+      Column modifiedColumn = currentModel.findTable(change.getChangedTable().getName())
+          .findColumn(change.getChangedColumn().getName());
+      writeColumnCommentStmt(desiredModel, change.getChangedTable(), modifiedColumn, false);
+    } else if (isAllowedChange(change)) {
+      change.apply(currentModel, getPlatform().isDelimitedIdentifierModeOn());
+      Table table = currentModel.findTable(change.getChangedTable().getName());
+      Column column = table.findColumn(change.getChangedColumn().getName());
+      print("ALTER TABLE " + table.getName() + " ALTER COLUMN ");
+
+      printIdentifier(getColumnName(column));
+      print(" TYPE ");
+      print(getSqlType(column));
+
+      printEndOfStatement();
+    }
+  }
+
+  /** Returns {@code true} if table requires to be recreated */
+  public boolean requiresRecreation(ColumnSizeChange change) {
+    boolean supportedChange = canResizeType(change.getChangedColumn().getTypeCode());
+    boolean madeLonger;
+    String type = TypeMap.getJdbcTypeName(change.getChangedColumn().getTypeCode());
+    if (DECIMAL.equals(type)) {
+      int oldPrecision = change.getOldSize() == 0 ? Integer.MAX_VALUE : change.getOldSize();
+      int newPrecision = change.getNewSize() == 0 ? Integer.MAX_VALUE : change.getNewSize();
+
+      if (oldPrecision == newPrecision) {
+        int oldScale = change.getOldScale() == null ? Integer.MAX_VALUE : change.getOldScale();
+        int newScale = change.getNewScale() == null ? Integer.MAX_VALUE : change.getNewScale();
+        // keeping same precision: to avoid recreation, scale can not be increased
+        madeLonger = oldScale >= newScale;
+      } else {
+        madeLonger = change.getOldSize() <= change.getNewSize();
+      }
+    } else {
+      madeLonger = change.getOldSize() <= change.getNewSize();
+    }
+
+    return !(supportedChange && madeLonger);
+  }
+
+  private boolean canResizeType(int typeCode) {
+    String type = TypeMap.getJdbcTypeName(typeCode);
+    switch (type) {
+    case NVARCHAR:
+    case VARCHAR:
+    case NCHAR:
+    case CHAR:
+    case DECIMAL:
+      return true;
+    default:
+      return false;
+    }
+  }
+
+  @Override
+  protected void processChange(Database currentModel, Database desiredModel, ColumnSizeChange change)
+      throws IOException {
+    change.apply(currentModel, getPlatform().isDelimitedIdentifierModeOn());
+    Table table = currentModel.findTable(change.getChangedTable().getName());
+    Column column = table.findColumn(change.getChangedColumn().getName());
+    print("ALTER TABLE " + table.getName() + " ALTER COLUMN ");
+
+    printIdentifier(getColumnName(column));
+    print(" TYPE ");
+    print(getSqlType(column));
+
+    printEndOfStatement();
+  }
 }
--- a/src/org/apache/ddlutils/platform/postgresql/PostgreSqlModelLoader.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/postgresql/PostgreSqlModelLoader.java	Thu Mar 09 12:59:50 2017 +0100
@@ -26,6 +26,9 @@
 import java.util.Map;
 import java.util.StringTokenizer;
 import java.util.Vector;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -36,13 +39,10 @@
 import org.apache.ddlutils.model.Parameter;
 import org.apache.ddlutils.model.Sequence;
 import org.apache.ddlutils.model.Table;
-import org.apache.ddlutils.model.Trigger;
 import org.apache.ddlutils.model.Unique;
 import org.apache.ddlutils.platform.ModelLoaderBase;
 import org.apache.ddlutils.platform.RowConstructor;
 import org.apache.ddlutils.platform.RowFiller;
-import org.apache.ddlutils.translation.CommentFilter;
-import org.apache.ddlutils.translation.LiteralFilter;
 import org.apache.ddlutils.translation.Translation;
 import org.apache.ddlutils.util.ExtTypes;
 
@@ -95,66 +95,38 @@
   }
 
   private void standardizePlSql(Database db) {
-    _log.info("Starting function and trigger standardization.");
+    long t = System.currentTimeMillis();
+    _log.info("Starting function trigger and view standardization in " + getMaxThreads()
+        + " threads");
     PostgrePLSQLStandarization.generateOutPatterns(db);
-    for (int i = 0; i < db.getFunctionCount(); i++) {
-      Function f = db.getFunction(i);
-      _log.debug("Translating function: " + f.getName());
-      f.setOriginalBody(f.getBody());
-      PostgrePLSQLFunctionStandarization functionStandarization = new PostgrePLSQLFunctionStandarization(
-          db, i);
-      String body = f.getBody();
 
-      LiteralFilter litFilter = new LiteralFilter();
-      CommentFilter comFilter = new CommentFilter();
+    ExecutorService executor = Executors.newFixedThreadPool(getMaxThreads());
 
-      body = litFilter.removeLiterals(body);
-      body = comFilter.removeComments(body);
-      String standardizedBody = functionStandarization.exec(body).trim();
+    int functionCnt = db.getFunctionCount();
+    for (int i = 0; i < functionCnt; i++) {
+      executor.execute(new PostgrePLSQLFunctionConcurrentStandardization(db, i));
+    }
 
-      standardizedBody = comFilter.restoreComments(standardizedBody);
-      standardizedBody = litFilter.restoreLiterals(standardizedBody);
+    int trgCnt = db.getTriggerCount();
+    for (int i = 0; i < trgCnt; i++) {
+      executor.execute(new PostgrePLSQLTriggerConcurrentStandardization(db, i));
+    }
 
-      while (standardizedBody.charAt(standardizedBody.length() - 1) == '\n'
-          || standardizedBody.charAt(standardizedBody.length() - 1) == ' ')
-        standardizedBody = standardizedBody.substring(0, standardizedBody.length() - 1);
-      f.setBody(standardizedBody + "\n");// initialBlanks+initialComments+
+    int viewCnt = db.getViewCount();
+    for (int i = 0; i < viewCnt; i++) {
+      executor.execute(new PostgrePLSQLViewConcurrentStandardization(db, i));
     }
-    for (int i = 0; i < db.getTriggerCount(); i++) {
-      Trigger trg = db.getTrigger(i);
-      _log.debug("Translating trigger: " + trg.getName());
-      trg.setOriginalBody(trg.getBody());
-      PostgrePLSQLTriggerStandarization triggerStandarization = new PostgrePLSQLTriggerStandarization(
-          db, i);
-      String body = trg.getBody();
 
-      LiteralFilter litFilter = new LiteralFilter();
-      CommentFilter comFilter = new CommentFilter();
+    executor.shutdown();
+    try {
+      // await till actual standardization occurs, this should take few seconds at most
+      executor.awaitTermination(20L, TimeUnit.MINUTES);
+    } catch (InterruptedException e) {
+      _log.error("Error completing pl standardization", e);
+    }
 
-      body = litFilter.removeLiterals(body);
-      body = comFilter.removeComments(body);
-
-      String standardizedBody = triggerStandarization.exec(body);
-
-      standardizedBody = comFilter.restoreComments(standardizedBody);
-      standardizedBody = litFilter.restoreLiterals(standardizedBody);
-
-      // System.out.println(db.getTrigger(i).getName()+"trad:::::::::::"+standardizedBody);
-      while (standardizedBody.charAt(standardizedBody.length() - 1) == '\n'
-          || standardizedBody.charAt(standardizedBody.length() - 1) == ' ')
-        standardizedBody = standardizedBody.substring(0, standardizedBody.length() - 1);
-      trg.setBody(standardizedBody + '\n');// initialBlanks+initialComments+
-    }
-    for (int i = 0; i < db.getViewCount(); i++) {
-      PostgreSQLStandarization viewStandarization = new PostgreSQLStandarization();
-      String body = db.getView(i).getStatement();
-
-      String standardizedBody = viewStandarization.exec(body);
-      if (standardizedBody.endsWith("\n"))
-        standardizedBody = standardizedBody.substring(0, standardizedBody.length() - 1);
-      standardizedBody = standardizedBody.trim();
-      db.getView(i).setStatement(standardizedBody);
-    }
+    _log.info("Standardidized " + functionCnt + " functions, " + trgCnt + " triggers and "
+        + viewCnt + " views in " + (System.currentTimeMillis() - t) + " ms");
   }
 
   @Override
@@ -1029,12 +1001,17 @@
     // Obtain the operatorClassOids of each index column
     String[] operatorClassOids = rs.getString(4).split(" ");
     int i = 0;
+    int trgmOperators = 0;
     for (IndexColumn indexColumn : inx.getColumns()) {
       String operatorClassName = getOperatorClassName(operatorClassOids[i++]);
-      if (operatorClassName.toUpperCase().contains("PATTERN")) {
+      String operatorClassNameUpperCased = operatorClassName.toUpperCase();
+      if (operatorClassNameUpperCased.contains("PATTERN")) {
         indexColumn.setOperatorClass(operatorClassName);
+      } else if (operatorClassNameUpperCased.equals("GIN_TRGM_OPS")) {
+        trgmOperators++;
       }
     }
+    inx.setContainsSearch(trgmOperators == inx.getColumns().length);
     return inx;
   }
 
--- a/src/org/apache/ddlutils/platform/postgresql/PostgreSqlModelReader.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/postgresql/PostgreSqlModelReader.java	Thu Mar 09 12:59:50 2017 +0100
@@ -52,17 +52,16 @@
     setDefaultTablePattern(null);
   }
 
-  /**
-   * {@inheritDoc}
-   */
-  protected Table readTable(DatabaseMetaDataWrapper metaData, Map values) throws SQLException {
+  @Override
+  protected Table readTable(DatabaseMetaDataWrapper metaData, Map<String, Object> values)
+      throws SQLException {
     Table table = super.readTable(metaData, values);
 
     if (table != null) {
       // PostgreSQL also returns unique indics for non-pk auto-increment
       // columns
       // which are of the form "[table]_[column]_key"
-      HashMap uniquesByName = new HashMap();
+      Map<String, Index> uniquesByName = new HashMap<>();
 
       for (int indexIdx = 0; indexIdx < table.getIndexCount(); indexIdx++) {
         Index index = table.getIndex(indexIdx);
@@ -86,10 +85,9 @@
     return table;
   }
 
-  /**
-   * {@inheritDoc}
-   */
-  protected Column readColumn(DatabaseMetaDataWrapper metaData, Map values) throws SQLException {
+  @Override
+  protected Column readColumn(DatabaseMetaDataWrapper metaData, Map<String, Object> values)
+      throws SQLException {
     Column column = super.readColumn(metaData, values);
 
     if (column.getSize() != null) {
--- a/src/org/apache/ddlutils/platform/postgresql/PostgreSqlPlatform.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/apache/ddlutils/platform/postgresql/PostgreSqlPlatform.java	Thu Mar 09 12:59:50 2017 +0100
@@ -32,19 +32,21 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.commons.beanutils.DynaBean;
 import org.apache.ddlutils.DatabaseOperationException;
 import org.apache.ddlutils.PlatformInfo;
 import org.apache.ddlutils.dynabean.SqlDynaProperty;
 import org.apache.ddlutils.model.Database;
-import org.apache.ddlutils.model.Function;
+import org.apache.ddlutils.model.StructureObject;
 import org.apache.ddlutils.model.Table;
-import org.apache.ddlutils.model.Trigger;
 import org.apache.ddlutils.platform.PGStandardBatchEvaluator;
 import org.apache.ddlutils.platform.PlatformImplBase;
-import org.apache.ddlutils.translation.CommentFilter;
-import org.apache.ddlutils.translation.LiteralFilter;
 import org.apache.ddlutils.util.ExtTypes;
 import org.openbravo.ddlutils.util.OBDataset;
 
@@ -115,6 +117,7 @@
     info.setNcharsupported(true);
     info.setOperatorClassesSupported(true);
     info.setPartialIndexesSupported(true);
+    info.setContainsSearchIndexesSupported(true);
 
     info.setColumnOrderManaged(false);
 
@@ -500,53 +503,36 @@
     }
   }
 
-  public ArrayList checkTranslationConsistency(Database database, Database fullDatabase) {
-    ArrayList inconsistentObjects = new ArrayList();
-
+  @Override
+  public List<StructureObject> checkTranslationConsistency(Database database, Database fullDatabase) {
+    List<PostgrePLSQLConsistencyChecker> tasks = new ArrayList<>();
     for (int i = 0; i < database.getFunctionCount(); i++) {
-      PostgrePLSQLFunctionTranslation funcTrans = new PostgrePLSQLFunctionTranslation(fullDatabase);
-      Function f = database.getFunction(i);
-      if (f.getOriginalBody() != null) {
-        LiteralFilter litFilter1 = new LiteralFilter();
-        CommentFilter comFilter1 = new CommentFilter();
-        String s1 = litFilter1.removeLiterals(f.getBody());
-        s1 = comFilter1.removeComments(s1);
-        s1 = funcTrans.exec(s1);
-        s1 = comFilter1.restoreComments(s1);
-        s1 = litFilter1.restoreLiterals(s1);
-        String s1r = s1.replaceAll("\\s", "");
-        String s2 = f.getOriginalBody();
-        String s2r = s2.replaceAll("\\s", "");
-        if (!s1r.equals(s2r)) {
-          getLog().warn("Found differences in " + f.getName());
-          printDiff(s1, s2);
-          inconsistentObjects.add(f);
-        }
-      }
+      tasks.add(new PostgrePLSQLConsistencyChecker(fullDatabase, database.getFunction(i)));
     }
 
     for (int i = 0; i < database.getTriggerCount(); i++) {
-      PostgrePLSQLTriggerTranslation triggerTrans = new PostgrePLSQLTriggerTranslation(fullDatabase);
-      Trigger trg = database.getTrigger(i);
-      if (trg.getOriginalBody() != null) {
-        LiteralFilter litFilter1 = new LiteralFilter();
-        CommentFilter comFilter1 = new CommentFilter();
-        String s1 = litFilter1.removeLiterals(trg.getBody());
-        s1 = comFilter1.removeComments(s1);
-        s1 = triggerTrans.exec(s1);
-        s1 = comFilter1.restoreComments(s1);
-        s1 = litFilter1.restoreLiterals(s1);
-        String s1r = s1.replaceAll("\\s", "");
-        String s2 = trg.getOriginalBody();
-        String s2r = s2.replaceAll("\\s", "");
-        if (!s1r.equals(s2r)) {
-          getLog().warn("Found differences in " + trg.getName());
-          printDiff(s1, s2);
-          inconsistentObjects.add(trg);
+      tasks.add(new PostgrePLSQLConsistencyChecker(fullDatabase, database.getTrigger(i)));
+    }
+
+    List<StructureObject> inconsistentObjects = new ArrayList<>();
+    ExecutorService executor = Executors.newFixedThreadPool(getMaxThreads());
+    try {
+      for (Future<StructureObject> result : executor.invokeAll(tasks)) {
+        StructureObject inconsistentObject = result.get();
+        if (inconsistentObject != null) {
+          inconsistentObjects.add(inconsistentObject);
         }
       }
+    } catch (InterruptedException | ExecutionException e) {
+      getLog().error("Error checking translation consistency", e);
+    } finally {
+      executor.shutdown();
+      try {
+        executor.awaitTermination(5L, TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        getLog().error("Error shutting down thread pool", e);
+      }
     }
-
     return inconsistentObjects;
   }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/apache/ddlutils/translation/ReplaceOutFunctionParams.java	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,44 @@
+/*
+ ************************************************************************************
+ * Copyright (C) 2017 Openbravo S.L.U.
+ * Licensed under the Apache Software License version 2.0
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to  in writing,  software  distributed
+ * under the License is distributed  on  an  "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR
+ * CONDITIONS OF ANY KIND, either  express  or  implied.  See  the  License  for  the
+ * specific language governing permissions and limitations under the License.
+ ************************************************************************************
+ */
+
+package org.apache.ddlutils.translation;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * Replacements for functions with out parameters, treated as special to skip to apply regexp when
+ * it's known it will not find any result as in some cases regexp can be very slow for non matching
+ * expressions.
+ */
+public class ReplaceOutFunctionParams extends ReplacePatTranslation {
+  private static final String PARAM_SEPARATOR = ",";
+  private String functionName;
+  private int numOfParams;
+
+  public ReplaceOutFunctionParams(String pattern, String replace, String functionName) {
+    super(pattern, replace);
+    this.functionName = functionName.toLowerCase();
+    numOfParams = StringUtils.countMatches(_p.pattern(), PARAM_SEPARATOR);
+  }
+
+  @Override
+  public String exec(String s) {
+    if (!s.toLowerCase().contains(functionName)) {
+      return s;
+    } else if (numOfParams > StringUtils.countMatches(s, PARAM_SEPARATOR)) {
+      // Pattern has more parameters than actual String, as regexp won't match, we can skip it
+      return s;
+    }
+    return super.exec(s);
+  }
+
+}
--- a/src/org/openbravo/ddlutils/task/AlterDatabaseDataAll.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/openbravo/ddlutils/task/AlterDatabaseDataAll.java	Thu Mar 09 12:59:50 2017 +0100
@@ -1,6 +1,6 @@
 /*
  ************************************************************************************
- * Copyright (C) 2001-2016 Openbravo S.L.U.
+ * Copyright (C) 2001-2017 Openbravo S.L.U.
  * Licensed under the Apache Software License version 2.0
  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to  in writing,  software  distributed
@@ -77,6 +77,8 @@
 
   private boolean executeModuleScripts = true;
 
+  private int threads = 0;
+
   public AlterDatabaseDataAll() {
     super();
   }
@@ -108,6 +110,8 @@
         getPassword());
 
     final Platform platform = PlatformFactory.createNewPlatformInstance(ds);
+    platform.setMaxThreads(threads);
+    getLog().info("Max threads " + platform.getMaxThreads());
 
     if (!StringUtils.isEmpty(forcedRecreation)) {
       getLog().info("Forced recreation: " + forcedRecreation);
@@ -124,7 +128,7 @@
 
       Database originaldb;
       if (getOriginalmodel() == null) {
-        originaldb = platform.loadModelFromDatabase(excludeFilter);
+        originaldb = platform.loadModelFromDatabase(excludeFilter, false);
         getLog().info("Checking datatypes from the model loaded from the database");
         if (originaldb == null) {
           originaldb = new Database();
@@ -510,4 +514,9 @@
   public void setExecuteModuleScripts(boolean executeModuleScripts) {
     this.executeModuleScripts = executeModuleScripts;
   }
+
+  /** Defines how many threads can be used to execute parallelizable tasks */
+  public void setThreads(int threads) {
+    this.threads = threads;
+  }
 }
--- a/src/org/openbravo/ddlutils/task/AlterDatabaseJava.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/openbravo/ddlutils/task/AlterDatabaseJava.java	Thu Mar 09 12:59:50 2017 +0100
@@ -45,7 +45,15 @@
           || "on".equals(args[18]));
     }
 
+    if (args.length > 19) {
+      int maxThreads;
+      try {
+        maxThreads = Integer.parseInt(args[19]);
+      } catch (NumberFormatException e) {
+        maxThreads = -1;
+      }
+      ada.setThreads(maxThreads);
+    }
     ada.execute();
-
   }
 }
--- a/src/org/openbravo/ddlutils/task/ExportDatabase.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/openbravo/ddlutils/task/ExportDatabase.java	Thu Mar 09 12:59:50 2017 +0100
@@ -15,7 +15,7 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
+import java.util.List;
 import java.util.Vector;
 
 import org.apache.commons.beanutils.DynaBean;
@@ -30,6 +30,7 @@
 import org.apache.ddlutils.io.DatabaseIO;
 import org.apache.ddlutils.model.Database;
 import org.apache.ddlutils.model.DatabaseData;
+import org.apache.ddlutils.model.StructureObject;
 import org.apache.ddlutils.platform.ExcludeFilter;
 import org.apache.tools.ant.BuildException;
 import org.openbravo.base.exception.OBException;
@@ -64,6 +65,7 @@
 
   private boolean rd;
   private ExcludeFilter excludeFilter;
+  private int threads = 0;
 
   /** Creates a new instance of ExportDatabase */
   public ExportDatabase() {
@@ -82,6 +84,7 @@
         getPassword());
 
     final Platform platform = PlatformFactory.createNewPlatformInstance(ds);
+    platform.setMaxThreads(threads);
     // platform.setDelimitedIdentifierModeOn(true);
     // DBSMOBUtil.verifyRevision(platform, getCodeRevision(), getLog());
     if (!DBSMOBUtil.verifyCheckSum(new File(model.getAbsolutePath() + "/../../../")
@@ -128,14 +131,16 @@
         if (checkTranslationConsistency) {
 
           log.info("Checking translation consistency");
-          ArrayList inconsistentObjects = platform.checkTranslationConsistency(dbI, db);
+          long t = System.currentTimeMillis();
+          List<StructureObject> inconsistentObjects = platform.checkTranslationConsistency(dbI, db);
           if (inconsistentObjects.size() > 0) {
             log.warn("Warning: Some of the functions and triggers which are being exported have been detected to change if they are inserted in a PostgreSQL database again. If you are working on an Oracle-only environment, you should not worry about this. If you are working with PostgreSQL, you should check that the functions and triggers are inserted in a correct way when applying the exported module. The affected objects are: ");
             for (int numObj = 0; numObj < inconsistentObjects.size(); numObj++) {
               log.warn(inconsistentObjects.get(numObj).toString());
             }
           } else {
-            log.info("Translation consistency check finished succesfully");
+            log.info("Translation consistency check finished succesfully in "
+                + (System.currentTimeMillis() - t) + " ms");
           }
         }
         getLog().info(db.toString());
@@ -411,4 +416,9 @@
   public void setCheckTranslationConsistency(boolean checkTranslationConsistency) {
     this.checkTranslationConsistency = checkTranslationConsistency;
   }
+
+  /** Defines how many threads can be used to execute parallelizable tasks */
+  public void setThreads(int threads) {
+    this.threads = threads;
+  }
 }
--- a/src/org/openbravo/ddlutils/task/ImportSampledata.java	Thu Mar 09 12:58:46 2017 +0100
+++ b/src/org/openbravo/ddlutils/task/ImportSampledata.java	Thu Mar 09 12:59:50 2017 +0100
@@ -1,6 +1,6 @@
 /*
  ************************************************************************************
- * Copyright (C) 2013-2015 Openbravo S.L.U.
+ * Copyright (C) 2013-2016 Openbravo S.L.U.
  * Licensed under the Apache Software License version 2.0
  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to  in writing,  software  distributed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/openbravo/ddlutils/util/DBSMContants.java	Thu Mar 09 12:59:50 2017 +0100
@@ -0,0 +1,23 @@
+/*
+ ************************************************************************************
+ * Copyright (C) 2016 Openbravo S.L.U.
+ * Licensed under the Apache Software License version 2.0
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to  in writing,  software  distributed
+ * under the License is distributed  on  an  "AS IS"  BASIS,  WITHOUT  WARRANTIES  OR
+ * CONDITIONS OF ANY KIND, either  express  or  implied.  See  the  License  for  the
+ * specific language governing permissions and limitations under the License.
+ ************************************************************************************
+ */
+package org.openbravo.ddlutils.util;
+
+/**
+ * Contains constants used for DBSM operations
+ */
+public class DBSMContants {
+
+  public static final String WHERE_CLAUSE = "whereClause";
+
+  public static final String CONTAINS_SEARCH = "containsSearch";
+
+}