You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by re...@apache.org on 2017/05/17 14:42:34 UTC

svn commit: r1795422 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/plugins/document/rdb/ test/java/org/apache/jackrabbit/oak/plugins/document/rdb/

Author: reschke
Date: Wed May 17 14:42:34 2017
New Revision: 1795422

URL: http://svn.apache.org/viewvc?rev=1795422&view=rev
Log:
OAK-6207: RDBDocumentStore: allow schema evolution part 2: record schema version when updating/inserting rows

Added:
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreSchemaUpgradeTest.java   (with props)
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreDB.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreJDBC.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBHelper.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBOptions.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java?rev=1795422&r1=1795421&r2=1795422&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java Wed May 17 14:42:34 2017
@@ -109,9 +109,9 @@ import com.google.common.collect.Sets;
  * <li>Apache Derby</li>
  * <li>IBM DB2</li>
  * <li>PostgreSQL</li>
- * <li>MariaDB (MySQL) (experimental)</li>
- * <li>Microsoft SQL Server (experimental)</li>
- * <li>Oracle (experimental)</li>
+ * <li>MariaDB (MySQL)</li>
+ * <li>Microsoft SQL Server</li>
+ * <li>Oracle</li>
  * </ul>
  * 
  * <h3>Table Layout</h3>
@@ -164,6 +164,13 @@ import com.google.common.collect.Sets;
  * purposes)</td>
  * </tr>
  * <tr>
+ * <th>VERSION</th>
+ * <td>smallint</td>
+ * <td>the schema version the code writing to a row (or inserting it) was aware
+ * of (introduced with schema version 1). Not set for rows written by version 0
+ * client code.</td>
+ * </tr>
+ * <tr>
  * <th>DATA</th>
  * <td>varchar(16384)</td>
  * <td>the document's JSON serialization (only used for small document sizes, in
@@ -183,13 +190,26 @@ import com.google.common.collect.Sets;
  * testing, as tables can also be dropped automatically when the store is
  * disposed (this only happens for those tables that have been created on
  * demand).
+ * <h4>Versioning</h4>
+ * <p>
+ * The initial database layout used in OAK 1.0 through 1.6 is version 0.
+ * <p>
+ * Version 1 introduces an additional "version" column, which records the schema
+ * version of the code writing to the database (upon insert and update). This is
+ * in preparation of future layout changes which might introduce new columns.
+ * <p>
+ * The code deals with both version 0 and version 1 table layouts. By default,
+ * it tries to create version 1 tables, and also tries to upgrade existing
+ * version 0 tables to version 1.
+ * <h4>DB-specific information</h4>
  * <p>
- * <em>Note that the database needs to be created/configured to support all Unicode
- * characters in text fields, and to collate by Unicode code point (in DB2: "collate using identity",
- * in Postgres: "C").
- * THIS IS NOT THE DEFAULT!</em>
+ * <em>Note that the database needs to be created/configured to support all
+ * Unicode characters in text fields, and to collate by Unicode code point (in
+ * DB2: "collate using identity", in Postgres: "C"). THIS IS NOT THE
+ * DEFAULT!</em>
  * <p>
- * <em>For MySQL, the database parameter "max_allowed_packet" needs to be increased to support ~16M blobs.</em>
+ * <em>For MySQL, the database parameter "max_allowed_packet" needs to be
+ * increased to support ~16M blobs.</em>
  * 
  * <h3>Caching</h3>
  * <p>
@@ -197,9 +217,9 @@ import com.google.common.collect.Sets;
  * 
  * <h3>Queries</h3>
  * <p>
- * The implementation currently supports only three indexed properties:
- * "_bin", "deletedOnce", and "_modified". Attempts to use a different indexed property will
- * cause a {@link DocumentStoreException}.
+ * The implementation currently supports only three indexed properties: "_bin",
+ * "deletedOnce", and "_modified". Attempts to use a different indexed property
+ * will cause a {@link DocumentStoreException}.
  */
 public class RDBDocumentStore implements DocumentStore {
 
@@ -609,6 +629,7 @@ public class RDBDocumentStore implements
 
         private final String name;
         private boolean idIsBinary = false;
+        private boolean hasVersion = false;
         private int dataLimitInOctets = 16384;
 
         public RDBTableMetaData(String name) {
@@ -627,10 +648,18 @@ public class RDBDocumentStore implements
             return this.idIsBinary;
         }
 
+        public boolean hasVersion() {
+            return this.hasVersion;
+        }
+
         public void setIdIsBinary(boolean idIsBinary) {
             this.idIsBinary = idIsBinary;
         }
 
+        public void setHasVersion(boolean hasVersion) {
+            this.hasVersion = hasVersion;
+        }
+
         public void setDataLimitInOctets(int dataLimitInOctets) {
             this.dataLimitInOctets = dataLimitInOctets;
         }
@@ -766,6 +795,10 @@ public class RDBDocumentStore implements
     private static final Set<String> REQUIREDCOLUMNS = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
             new String[] { "id", "dsize", "deletedonce", "bdata", "data", "cmodcount", "modcount", "hasbinary", "modified" })));
 
+    // set of optional table columns
+    private static final Set<String> OPTIONALCOLUMNS = Collections
+            .unmodifiableSet(new HashSet<String>(Arrays.asList(new String[] { "version" })));
+
     // set of properties not serialized to JSON
     // when adding new columns also update UNHANDLEDPROPS!
     private static final Set<String> COLUMNPROPERTIES = new HashSet<String>(Arrays.asList(
@@ -838,13 +871,13 @@ public class RDBDocumentStore implements
         StringBuilder tableDiags = new StringBuilder();
         try {
             createTableFor(con, Collection.CLUSTER_NODES, this.tableMeta.get(Collection.CLUSTER_NODES), tablesCreated,
-                    tablesPresent, tableDiags);
+                    tablesPresent, tableDiags, options.getInitialSchema(), options.getUpgradeToSchema());
             createTableFor(con, Collection.NODES, this.tableMeta.get(Collection.NODES), tablesCreated, tablesPresent,
-                    tableDiags);
+                    tableDiags, options.getInitialSchema(), options.getUpgradeToSchema());
             createTableFor(con, Collection.SETTINGS, this.tableMeta.get(Collection.SETTINGS), tablesCreated, tablesPresent,
-                    tableDiags);
+                    tableDiags, options.getInitialSchema(), options.getUpgradeToSchema());
             createTableFor(con, Collection.JOURNAL, this.tableMeta.get(Collection.JOURNAL), tablesCreated, tablesPresent,
-                    tableDiags);
+                    tableDiags, options.getInitialSchema(), options.getUpgradeToSchema());
         } finally {
             con.commit();
             con.close();
@@ -885,6 +918,9 @@ public class RDBDocumentStore implements
             if ("data".equals(lcName)) {
                 tmd.setDataLimitInOctets(met.getPrecision(i));
             }
+            if ("version".equals(lcName)) {
+                tmd.setHasVersion(true);
+            }
         }
     }
 
@@ -999,7 +1035,7 @@ public class RDBDocumentStore implements
     }
 
     private void createTableFor(Connection con, Collection<? extends Document> col, RDBTableMetaData tmd, List<String> tablesCreated,
-            List<String> tablesPresent, StringBuilder diagnostics) throws SQLException {
+            List<String> tablesPresent, StringBuilder diagnostics, int initialSchema, int upgradeToSchema) throws SQLException {
         String dbname = this.dbInfo.toString();
         if (con.getMetaData().getURL() != null) {
             dbname += " (" + con.getMetaData().getURL() + ")";
@@ -1010,6 +1046,7 @@ public class RDBDocumentStore implements
 
         ResultSet checkResultSet = null;
         Statement creatStatement = null;
+        Statement upgradeStatement = null;
         try {
             checkStatement = con.prepareStatement("select * from " + tableName + " where ID = ?");
             checkStatement.setString(1, "0:/");
@@ -1022,10 +1059,16 @@ public class RDBDocumentStore implements
             // check that all required columns are present
             Set<String> requiredColumns = new HashSet<String>(REQUIREDCOLUMNS);
             Set<String> unknownColumns = new HashSet<String>();
+            boolean hasVersionColumn = false;
             for (int i = 1; i <= met.getColumnCount(); i++) {
                 String cname = met.getColumnName(i).toLowerCase(Locale.ENGLISH);
                 if (!requiredColumns.remove(cname)) {
-                    unknownColumns.add(cname);
+                    if (!OPTIONALCOLUMNS.contains(cname)) {
+                        unknownColumns.add(cname);
+                    }
+                }
+                if (cname.equals("version")) {
+                    hasVersionColumn = true;
                 }
             }
 
@@ -1050,6 +1093,22 @@ public class RDBDocumentStore implements
                     diagnostics.append(" ").append(indexInfo);
                 }
             }
+
+            if (!hasVersionColumn && upgradeToSchema >= 1) {
+                String upStatement1 = this.dbInfo.getTableUpgradeStatement(tableName, 1);
+                try {
+                    upgradeStatement = con.createStatement();
+                    upgradeStatement.execute(upStatement1);
+                    upgradeStatement.close();
+                    con.commit();
+                    LOG.info("Upgraded " + tableName + " to DB level 1 using '" + upStatement1 + "'");
+                } catch (SQLException exup) {
+                    con.rollback();
+                    LOG.info("Attempted to upgrade " + tableName + " to DB level 1 using '" + upStatement1
+                            + "', but failed - will continue without.", exup);
+                }
+            }
+
             tablesPresent.add(tableName);
         } catch (SQLException ex) {
             // table does not appear to exist
@@ -1060,7 +1119,7 @@ public class RDBDocumentStore implements
 
             try {
                 creatStatement = con.createStatement();
-                creatStatement.execute(this.dbInfo.getTableCreationStatement(tableName));
+                creatStatement.execute(this.dbInfo.getTableCreationStatement(tableName, initialSchema));
                 creatStatement.close();
 
                 for (String ic : this.dbInfo.getIndexCreationStatements(tableName)) {
@@ -1071,6 +1130,21 @@ public class RDBDocumentStore implements
 
                 con.commit();
 
+                if (initialSchema < 1 && upgradeToSchema >= 1) {
+                    String upStatement1 = this.dbInfo.getTableUpgradeStatement(tableName, 1);
+                    try {
+                        upgradeStatement = con.createStatement();
+                        upgradeStatement.execute(upStatement1);
+                        upgradeStatement.close();
+                        con.commit();
+                        LOG.info("Upgraded " + tableName + " to DB level 1 using '" + upStatement1 + "'");
+                    } catch (SQLException exup) {
+                        con.rollback();
+                        LOG.info("Attempted to upgrade " + tableName + " to DB level 1 using '" + upStatement1
+                                + "', but failed - will continue without.", exup);
+                    }
+                }
+
                 tablesCreated.add(tableName);
 
                 checkStatement2 = con.prepareStatement("select * from " + tableName + " where ID = ?");
@@ -1102,6 +1176,7 @@ public class RDBDocumentStore implements
             closeResultSet(checkResultSet);
             closeStatement(checkStatement);
             closeStatement(creatStatement);
+            closeStatement(upgradeStatement);
         }
     }
 

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreDB.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreDB.java?rev=1795422&r1=1795421&r2=1795422&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreDB.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreDB.java Wed May 17 14:42:34 2017
@@ -37,6 +37,8 @@ import org.apache.jackrabbit.oak.plugins
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Preconditions;
+
 /**
  * Defines variation in the capabilities of different RDBs.
  */
@@ -106,8 +108,10 @@ public enum RDBDocumentStoreDB {
         }
 
         @Override
-        public String getTableCreationStatement(String tableName) {
-            return ("create table " + tableName + " (ID varchar(512) not null primary key, MODIFIED bigint, HASBINARY smallint, DELETEDONCE smallint, MODCOUNT bigint, CMODCOUNT bigint, DSIZE bigint, DATA varchar(16384), BDATA bytea)");
+        public String getTableCreationStatement(String tableName, int schema) {
+            return ("create table " + tableName
+                    + " (ID varchar(512) not null primary key, MODIFIED bigint, HASBINARY smallint, DELETEDONCE smallint, MODCOUNT bigint, CMODCOUNT bigint, DSIZE bigint, "
+                    + (schema >= 1 ? "VERSION smallint, " : "") + "DATA varchar(16384), BDATA bytea)");
         }
 
         @Override
@@ -150,10 +154,10 @@ public enum RDBDocumentStoreDB {
         }
 
         @Override
-        public String getTableCreationStatement(String tableName) {
+        public String getTableCreationStatement(String tableName, int schema) {
             return "create table " + tableName
-                    + " (ID varchar(512) not null, MODIFIED bigint, HASBINARY smallint, DELETEDONCE smallint, MODCOUNT bigint, CMODCOUNT bigint, DSIZE bigint, DATA varchar(16384), BDATA blob("
-                    + 1024 * 1024 * 1024 + "))";
+                    + " (ID varchar(512) not null, MODIFIED bigint, HASBINARY smallint, DELETEDONCE smallint, MODCOUNT bigint, CMODCOUNT bigint, DSIZE bigint, "
+                    + (schema >= 1 ? "VERSION smallint, " : "") + "DATA varchar(16384), BDATA blob(" + 1024 * 1024 * 1024 + "))";
         }
 
         @Override
@@ -230,9 +234,11 @@ public enum RDBDocumentStoreDB {
         }
 
         @Override
-        public String getTableCreationStatement(String tableName) {
+        public String getTableCreationStatement(String tableName, int schema) {
             // see https://issues.apache.org/jira/browse/OAK-1914
-            return ("create table " + tableName + " (ID varchar(512) not null primary key, MODIFIED number, HASBINARY number, DELETEDONCE number, MODCOUNT number, CMODCOUNT number, DSIZE number, DATA varchar(4000), BDATA blob)");
+            return ("create table " + tableName
+                    + " (ID varchar(512) not null primary key, MODIFIED number, HASBINARY number, DELETEDONCE number, MODCOUNT number, CMODCOUNT number, DSIZE number, "
+                    + (schema >= 1 ? "VERSION number, " : "") + "DATA varchar(4000), BDATA blob)");
         }
 
         @Override
@@ -274,9 +280,11 @@ public enum RDBDocumentStoreDB {
         }
 
         @Override
-        public String getTableCreationStatement(String tableName) {
+        public String getTableCreationStatement(String tableName, int schema) {
             // see https://issues.apache.org/jira/browse/OAK-1913
-            return ("create table " + tableName + " (ID varbinary(512) not null primary key, MODIFIED bigint, HASBINARY smallint, DELETEDONCE smallint, MODCOUNT bigint, CMODCOUNT bigint, DSIZE bigint, DATA varchar(16000), BDATA longblob)");
+            return ("create table " + tableName
+                    + " (ID varbinary(512) not null primary key, MODIFIED bigint, HASBINARY smallint, DELETEDONCE smallint, MODCOUNT bigint, CMODCOUNT bigint, DSIZE bigint, "
+                    + (schema >= 1 ? "VERSION smallint, " : "") + "DATA varchar(16000), BDATA longblob)");
         }
 
         @Override
@@ -344,9 +352,11 @@ public enum RDBDocumentStoreDB {
         }
 
         @Override
-        public String getTableCreationStatement(String tableName) {
+        public String getTableCreationStatement(String tableName, int schema) {
             // see https://issues.apache.org/jira/browse/OAK-2395
-            return ("create table " + tableName + " (ID varbinary(512) not null primary key, MODIFIED bigint, HASBINARY smallint, DELETEDONCE smallint, MODCOUNT bigint, CMODCOUNT bigint, DSIZE bigint, DATA nvarchar(4000), BDATA varbinary(max))");
+            return ("create table " + tableName
+                    + " (ID varbinary(512) not null primary key, MODIFIED bigint, HASBINARY smallint, DELETEDONCE smallint, MODCOUNT bigint, CMODCOUNT bigint, DSIZE bigint, "
+                    + (schema >= 1 ? "VERSION smallint, " : "") + "DATA nvarchar(4000), BDATA varbinary(max))");
         }
 
         @Override
@@ -483,11 +493,10 @@ public enum RDBDocumentStoreDB {
      * @param tableName
      * @return the table creation string
      */
-    public String getTableCreationStatement(String tableName) {
-        return "create table "
-                + tableName
-                + " (ID varchar(512) not null primary key, MODIFIED bigint, HASBINARY smallint, DELETEDONCE smallint, MODCOUNT bigint, CMODCOUNT bigint, DSIZE bigint, DATA varchar(16384), BDATA blob("
-                + 1024 * 1024 * 1024 + "))";
+    public String getTableCreationStatement(String tableName, int schema) {
+        return "create table " + tableName
+                + " (ID varchar(512) not null primary key, MODIFIED bigint, HASBINARY smallint, DELETEDONCE smallint, MODCOUNT bigint, CMODCOUNT bigint, DSIZE bigint, "
+                + (schema >= 1 ? "VERSION smallint, " : "") + "DATA varchar(16384), BDATA blob(" + 1024 * 1024 * 1024 + "))";
     }
 
     public List<String> getIndexCreationStatements(String tableName) {
@@ -506,6 +515,16 @@ public enum RDBDocumentStoreDB {
         return "";
     }
 
+    /**
+     * Statements needed to upgrade the DB
+     *
+     * @return the table modification string
+     */
+    public String getTableUpgradeStatement(String tableName, int level) {
+        Preconditions.checkArgument(level == 1, "level must be 1");
+        return "alter table " + tableName + " add VERSION smallint";
+    }
+
     protected String description;
 
     private RDBDocumentStoreDB(String description) {

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreJDBC.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreJDBC.java?rev=1795422&r1=1795421&r2=1795422&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreJDBC.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreJDBC.java Wed May 17 14:42:34 2017
@@ -76,6 +76,8 @@ public class RDBDocumentStoreJDBC {
     private static final String MODCOUNT = NodeDocument.MOD_COUNT;
     private static final String MODIFIED = NodeDocument.MODIFIED_IN_SECS;
 
+    private static final int SCHEMAVERSION = 1;
+
     private final RDBDocumentStoreDB dbInfo;
     private final RDBDocumentSerializer ser;
     private final int queryHitsLimit, queryTimeLimit;
@@ -96,6 +98,9 @@ public class RDBDocumentStoreJDBC {
         t.append("update " + tmd.getName() + " set ");
         t.append(setModifiedConditionally ? "MODIFIED = case when ? > MODIFIED then ? else MODIFIED end, " : "MODIFIED = ?, ");
         t.append("HASBINARY = ?, DELETEDONCE = ?, MODCOUNT = ?, CMODCOUNT = ?, DSIZE = DSIZE + ?, ");
+        if (tmd.hasVersion()) {
+            t.append("VERSION = " + SCHEMAVERSION + ", ");
+        }
         t.append("DATA = " + stringAppend.getStatementComponent() + " ");
         t.append("where ID = ?");
         if (oldmodcount != null) {
@@ -140,6 +145,9 @@ public class RDBDocumentStoreJDBC {
             t.append("update " + tmd.getName() + " set ");
             t.append(setModifiedConditionally ? "MODIFIED = case when ? > MODIFIED then ? else MODIFIED end, " : "MODIFIED = ?, ");
             t.append("MODCOUNT = MODCOUNT + 1, DSIZE = DSIZE + ?, ");
+            if (tmd.hasVersion()) {
+                t.append("VERSION = " + SCHEMAVERSION + ", ");
+            }
             t.append("DATA = " + stringAppend.getStatementComponent() + " ");
             t.append("where ").append(inClause.getStatementComponent());
             PreparedStatement stmt = connection.prepareStatement(t.toString());
@@ -292,8 +300,9 @@ public class RDBDocumentStoreJDBC {
 
     public <T extends Document> Set<String> insert(Connection connection, RDBTableMetaData tmd, List<T> documents) throws SQLException {
         PreparedStatement stmt = connection.prepareStatement(
-                "insert into " + tmd.getName() + "(ID, MODIFIED, HASBINARY, DELETEDONCE, MODCOUNT, CMODCOUNT, DSIZE, DATA, BDATA) "
-                        + "values (?, ?, ?, ?, ?, ?, ?, ?, ?)");
+                "insert into " + tmd.getName() + "(ID, MODIFIED, HASBINARY, DELETEDONCE, MODCOUNT, CMODCOUNT, DSIZE, DATA, "
+                        + (tmd.hasVersion() ? "VERSION, " : "") + " BDATA) " + "values (?, ?, ?, ?, ?, ?, ?, ?,"
+                        + (tmd.hasVersion() ? (" " + SCHEMAVERSION + ", ") : "") + " ?)");
 
         List<T> sortedDocs = sortDocuments(documents);
         int[] results;
@@ -370,7 +379,8 @@ public class RDBDocumentStoreJDBC {
         int[] batchResults = new int[0];
 
         PreparedStatement stmt = connection.prepareStatement("update " + tmd.getName()
-            + " set MODIFIED = ?, HASBINARY = ?, DELETEDONCE = ?, MODCOUNT = ?, CMODCOUNT = ?, DSIZE = ?, DATA = ?, BDATA = ? where ID = ? and MODCOUNT = ?");
+                + " set MODIFIED = ?, HASBINARY = ?, DELETEDONCE = ?, MODCOUNT = ?, CMODCOUNT = ?, DSIZE = ?, DATA = ?, "
+                + (tmd.hasVersion() ? (" VERSION = " + SCHEMAVERSION + ", ") : "") + "BDATA = ? where ID = ? and MODCOUNT = ?");
         try {
             boolean batchIsEmpty = true;
             for (T document : sortDocuments(documents)) {
@@ -847,7 +857,8 @@ public class RDBDocumentStoreJDBC {
 
         StringBuilder t = new StringBuilder();
         t.append("update " + tmd.getName() + " set ");
-        t.append("MODIFIED = ?, HASBINARY = ?, DELETEDONCE = ?, MODCOUNT = ?, CMODCOUNT = ?, DSIZE = ?, DATA = ?, BDATA = ? ");
+        t.append("MODIFIED = ?, HASBINARY = ?, DELETEDONCE = ?, MODCOUNT = ?, CMODCOUNT = ?, DSIZE = ?, DATA = ?, "
+                + (tmd.hasVersion() ? (" VERSION = " + SCHEMAVERSION + ", ") : "") + "BDATA = ? ");
         t.append("where ID = ?");
         if (oldmodcount != null) {
             t.append(" and MODCOUNT = ?");

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBHelper.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBHelper.java?rev=1795422&r1=1795421&r2=1795422&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBHelper.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBHelper.java Wed May 17 14:42:34 2017
@@ -34,7 +34,7 @@ public class RDBHelper {
             RDBBlobStoreDB bdb = RDBBlobStoreDB.getValue(database);
 
             for (String table : RDBDocumentStore.getTableNames()) {
-                System.out.println("  " + ddb.getTableCreationStatement(table));
+                System.out.println("  " + ddb.getTableCreationStatement(table, new RDBOptions().getInitialSchema()));
                 for (String s : ddb.getIndexCreationStatements(table)) {
                     System.out.println("    " + s);
                 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBOptions.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBOptions.java?rev=1795422&r1=1795421&r2=1795422&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBOptions.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBOptions.java Wed May 17 14:42:34 2017
@@ -25,6 +25,9 @@ public class RDBOptions {
 
     private boolean dropTablesOnClose = false;
     private String tablePrefix = "";
+    private int initialSchema = Integer.getInteger("org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions.INITIALSCHEMA", 0);
+    private int upgradeToSchema = Integer.getInteger("org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions.UPGRADETOSCHEMA",
+            1);
 
     public RDBOptions() {
     }
@@ -37,6 +40,10 @@ public class RDBOptions {
         return this;
     }
 
+    public boolean isDropTablesOnClose() {
+        return this.dropTablesOnClose;
+    }
+
     /**
      * Prefix for table names.
      */
@@ -49,7 +56,27 @@ public class RDBOptions {
         return this.tablePrefix;
     }
 
-    public boolean isDropTablesOnClose() {
-        return this.dropTablesOnClose;
+    /**
+     * Control over initial DB schema
+     */
+    public RDBOptions initialSchema(int initialSchema) {
+        this.initialSchema = initialSchema;
+        return this;
+    }
+
+    public int getInitialSchema() {
+        return this.initialSchema;
+    }
+
+    /**
+     * Control over DB schema to upgrade to
+     */
+    public RDBOptions upgradeToSchema(int upgradeToSchema) {
+        this.upgradeToSchema = upgradeToSchema;
+        return this;
+    }
+
+    public int getUpgradeToSchema() {
+        return this.upgradeToSchema;
     }
 }

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreSchemaUpgradeTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreSchemaUpgradeTest.java?rev=1795422&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreSchemaUpgradeTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreSchemaUpgradeTest.java Wed May 17 14:42:34 2017
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+package org.apache.jackrabbit.oak.plugins.document.rdb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+
+import javax.sql.DataSource;
+
+import org.apache.jackrabbit.oak.commons.junit.LogCustomizer;
+import org.apache.jackrabbit.oak.plugins.document.Collection;
+import org.apache.jackrabbit.oak.plugins.document.DocumentMK;
+import org.apache.jackrabbit.oak.plugins.document.DocumentStoreFixture;
+import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDocumentStore.RDBTableMetaData;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import ch.qos.logback.classic.Level;
+
+@RunWith(Parameterized.class)
+public class RDBDocumentStoreSchemaUpgradeTest {
+
+    @Parameterized.Parameters(name = "{0}")
+    public static java.util.Collection<Object[]> fixtures() {
+        java.util.Collection<Object[]> result = new ArrayList<Object[]>();
+        DocumentStoreFixture candidates[] = new DocumentStoreFixture[] { DocumentStoreFixture.RDB_H2,
+                DocumentStoreFixture.RDB_DERBY, DocumentStoreFixture.RDB_PG, DocumentStoreFixture.RDB_DB2,
+                DocumentStoreFixture.RDB_MYSQL, DocumentStoreFixture.RDB_ORACLE, DocumentStoreFixture.RDB_MSSQL };
+
+        for (DocumentStoreFixture dsf : candidates) {
+            if (dsf.isAvailable()) {
+                result.add(new Object[] { dsf });
+            }
+        }
+
+        return result;
+    }
+
+    private DataSource ds;
+
+    public RDBDocumentStoreSchemaUpgradeTest(DocumentStoreFixture dsf) {
+        this.ds = dsf.getRDBDataSource();
+    }
+
+    @Test
+    public void initDefault() {
+        RDBOptions op = new RDBOptions().tablePrefix("T00").initialSchema(0).upgradeToSchema(0).dropTablesOnClose(true);
+        RDBDocumentStore rdb = null;
+        try {
+            rdb = new RDBDocumentStore(this.ds, new DocumentMK.Builder(), op);
+            RDBTableMetaData meta = rdb.getTable(Collection.NODES);
+            assertEquals(op.getTablePrefix() + "_NODES", meta.getName());
+            assertFalse(meta.hasVersion());
+        } finally {
+            if (rdb != null) {
+                rdb.dispose();
+            }
+        }
+    }
+
+    @Test
+    public void init01() {
+        LogCustomizer logCustomizer = LogCustomizer.forLogger(RDBDocumentStore.class.getName()).enable(Level.INFO)
+                .contains("to DB level 1").create();
+        logCustomizer.starting();
+
+        RDBOptions op = new RDBOptions().tablePrefix("T01").initialSchema(0).upgradeToSchema(1).dropTablesOnClose(true);
+        RDBDocumentStore rdb = null;
+        try {
+            rdb = new RDBDocumentStore(this.ds, new DocumentMK.Builder(), op);
+            RDBTableMetaData meta = rdb.getTable(Collection.NODES);
+            assertEquals(op.getTablePrefix() + "_NODES", meta.getName());
+            assertTrue(meta.hasVersion());
+            assertEquals("unexpected # of log entries: " + logCustomizer.getLogs(), RDBDocumentStore.getTableNames().size(),
+                    logCustomizer.getLogs().size());
+        } finally {
+            logCustomizer.finished();
+            if (rdb != null) {
+                rdb.dispose();
+            }
+        }
+    }
+
+    @Test
+    public void init11() {
+        LogCustomizer logCustomizer = LogCustomizer.forLogger(RDBDocumentStore.class.getName()).enable(Level.INFO)
+                .contains("to DB level 1").create();
+        logCustomizer.starting();
+
+        RDBOptions op = new RDBOptions().tablePrefix("T11").initialSchema(1).upgradeToSchema(1).dropTablesOnClose(true);
+        RDBDocumentStore rdb = null;
+        try {
+            rdb = new RDBDocumentStore(this.ds, new DocumentMK.Builder(), op);
+            RDBTableMetaData meta = rdb.getTable(Collection.NODES);
+            assertEquals(op.getTablePrefix() + "_NODES", meta.getName());
+            assertTrue(meta.hasVersion());
+            assertEquals("unexpected # of log entries: " + logCustomizer.getLogs(), 0, logCustomizer.getLogs().size());
+        } finally {
+            logCustomizer.finished();
+            if (rdb != null) {
+                rdb.dispose();
+            }
+        }
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreSchemaUpgradeTest.java
------------------------------------------------------------------------------
    svn:eol-style = native