package org.tmatesoft.svn.core.internal.wc2.ng;
 
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.tmatesoft.sqljet.core.SqlJetException;
import org.tmatesoft.sqljet.core.SqlJetTransactionMode;
import org.tmatesoft.sqljet.core.table.ISqlJetCursor;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetDb;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetInsertStatement;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetSelectFieldsStatement;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetSelectStatement;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetStatement;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetUpdateStatement;
import org.tmatesoft.svn.core.internal.util.SVNSkel;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNEventFactory;
import org.tmatesoft.svn.core.internal.wc.SVNExternal;
import org.tmatesoft.svn.core.internal.wc.SVNFileListUtil;
import org.tmatesoft.svn.core.internal.wc.SVNFileType;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc.SVNTreeConflictUtil;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext;
import org.tmatesoft.svn.core.internal.wc17.SVNWCUtils;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb;
import org.tmatesoft.svn.core.internal.wc17.db.SVNWCDb;
import org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbConflicts;
import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbSchema;
import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbSchema.ACTUAL_NODE__Fields;
import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbSchema.NODES__Fields;
import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbStatements;
import org.tmatesoft.svn.core.internal.wc2.old.SvnOldUpgrade;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.SVNEvent;
import org.tmatesoft.svn.core.wc.SVNEventAction;
import org.tmatesoft.svn.core.wc.SVNTreeConflictDescription;
import org.tmatesoft.svn.util.SVNLogType;
 
public class SvnNgUpgradeSDb {
	private static String PRISTINE_STORAGE_EXT = ".svn-base";
	/* Number of characters in a pristine file basename, in WC format <= 28. */
	private static int PRISTINE_BASENAME_OLD_LEN = 40;
	
    /* Return a string indicating the released version (or versions) of Subversion that used WC format number WC_FORMAT, or some other
     * suitable string if no released version used WC_FORMAT.
     *
     * ##It's not ideal to encode this sort of knowledge in this low-level library.  On the other hand, it doesn't need to be updated often and
     * should be easily found when it does need to be updated.  */
    private static String versionStringFromFormat(int wcFormat) {
        switch (wcFormat) {
            case 4: return "<=1.3";
            case 8: return "1.4";
            case 9: return "1.5";
            case 10: return "1.6";
        }
      return "(unreleased development version)";
    }
    
    public static int upgrade(final File wcRootAbsPath, SVNWCDb db, final SVNSqlJetDb sDb, int startFormat, ISVNEventHandler eventHandler) throws SVNException {
        int resultFormat = 0;
        if (startFormat < SVNWCContext.WC_NG_VERSION /* 12 */) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "Working copy ''{0}'' is too old (format {1}, created by Subversion {2})", 
            		wcRootAbsPath, startFormat, versionStringFromFormat(startFormat));
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        
        /* Early WCNG formats no longer supported. */
        if (startFormat < 19) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, 
                    "Working copy ''{0}'' is an old development version (format {1}); to upgrade it, use a format 18 client, then " +
                    "use ''tools/dev/wc-ng/bump-to-19.py'', then use the current client",
                    wcRootAbsPath, startFormat);
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        
        /* ##need lock-out. only one upgrade at a time. note that other code cannot use this un-upgraded database until we finish the upgrade.  */
 
        /* Note: none of these have "break" statements; the fall-through is intentional. */
        switch (startFormat)
        {
          case 19:
        	  runBump(db, sDb, wcRootAbsPath, new bumpTo20());
        	  resultFormat = 20;
          case 20:
        	  runBump(db, sDb, wcRootAbsPath, new bumpTo21());
        	  resultFormat = 21;
          case 21:
        	  runBump(db, sDb, wcRootAbsPath, new bumpTo22());
        	  resultFormat = 22;
          case 22:
        	  runBump(db, sDb, wcRootAbsPath, new bumpTo23());
        	  resultFormat = 23;
          case 23:
        	  runBump(db, sDb, wcRootAbsPath, new bumpTo24());
        	  resultFormat = 24;
          case 24:
        	  runBump(db, sDb, wcRootAbsPath, new bumpTo25());
        	  resultFormat = 25;
          case 25:
        	  runBump(db, sDb, wcRootAbsPath, new bumpTo26());
        	  resultFormat = 26;
          case 26:
        	  runBump(db, sDb, wcRootAbsPath, new bumpTo27());
        	  resultFormat = 27;
          case 27:
        	  runBump(db, sDb, wcRootAbsPath, new bumpTo28());
        	  resultFormat = 28;
          case 28:
        	  runBump(db, sDb, wcRootAbsPath, new bumpTo29());
        	  resultFormat = 29;
          case 29:
              runBump(db, sDb, wcRootAbsPath, new bumpTo30());
              resultFormat = 30;
          case 30:
              runBump(db, sDb, wcRootAbsPath, new bumpTo31());
              resultFormat = 31;
 
          /* ##future bumps go here.  */
          //#if 0
                //case XXX-1:
                /* Revamp the recording of tree conflicts.  */
                //SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_XXX, &bb, scratch_pool));
                //*result_format = XXX;
                /* FALLTHROUGH  */
           //#endif*/
           
        }
 
        /*
      #ifdef SVN_DEBUG
      if (*result_format != start_format)
        {
          int schema_version;
          SVN_ERR(svn_sqlite__read_schema_version(&schema_version, sdb, scratch_pool));
 
          /* If this assertion fails the schema isn't updated correctly /
          SVN_ERR_ASSERT(schema_version == *result_format);
        }
      #endif
      */
 
      /* Zap anything that might be remaining or escaped our notice.  */
      SvnOldUpgrade.wipeObsoleteFiles(wcRootAbsPath);

      if (resultFormat != 0 && eventHandler != null) {
          SVNEvent event = SVNEventFactory.createSVNEvent(wcRootAbsPath, SVNNodeKind.UNKNOWN, null, SVNRepository.INVALID_REVISION, SVNEventAction.UPGRADED_PATH, SVNEventAction.UPGRADED_PATH, null, null);
          eventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN);
      }
 
      return resultFormat;
    }
    
    private static void migrateTreeConflictData(SVNSqlJetDb sDb) throws SVNException
    {
        SVNSqlJetStatement stmt = new SVNSqlJetSelectFieldsStatement<SVNWCDbSchema.ACTUAL_NODE__Fields>(sDb, SVNWCDbSchema.ACTUAL_NODE) {
            protected void defineFields() {
                fields.add(SVNWCDbSchema.ACTUAL_NODE__Fields.wc_id);
                fields.add(SVNWCDbSchema.ACTUAL_NODE__Fields.local_relpath);
                fields.add(SVNWCDbSchema.ACTUAL_NODE__Fields.tree_conflict_data);
            }
            protected boolean isFilterPassed() throws SVNException {
                return !isColumnNull(SVNWCDbSchema.ACTUAL_NODE__Fields.tree_conflict_data);
            }
        };
        /* Iterate over each node which has a set of tree conflicts, then insert all of them into the new schema.  */
        try {
            while (stmt.next()) {
                migrateSingleTreeConflictData(sDb, 
                        stmt.getColumnString(SVNWCDbSchema.ACTUAL_NODE__Fields.tree_conflict_data),
                        stmt.getColumnLong(SVNWCDbSchema.ACTUAL_NODE__Fields.wc_id),
                        SVNFileUtil.createFilePath(stmt.getColumnString(SVNWCDbSchema.ACTUAL_NODE__Fields.local_relpath)));
            }
        } finally {
            stmt.reset();
        }
        
        /* Erase all the old tree conflict data.  */
        stmt = new SVNSqlJetUpdateStatement(sDb, SVNWCDbSchema.ACTUAL_NODE) {
            public Map<String, Object> getUpdateValues() throws SVNException {
                Map<String, Object> rowValues = getRowValues();
                rowValues.put(SVNWCDbSchema.ACTUAL_NODE__Fields.tree_conflict_data.toString(), null);
                return rowValues;
            }
        };
        try {
            stmt.exec();
        } finally {
            stmt.reset();
        }
    }
        
    private static void migrateSingleTreeConflictData(SVNSqlJetDb sDb, String treeConflictData, long wcId, File localRelPath) throws SVNException {
        @SuppressWarnings("unchecked")
        Map<File, SVNTreeConflictDescription> conflicts = SVNTreeConflictUtil.readTreeConflicts(localRelPath, treeConflictData);
        for (Iterator<File> keys = conflicts.keySet().iterator(); keys.hasNext();) {
            File entryPath = keys.next();
            SVNTreeConflictDescription conflict = conflicts.get(entryPath);
            
            /* See if we need to update or insert an ACTUAL node. */
            SVNSqlJetStatement stmt = sDb.getStatement(SVNWCDbStatements.SELECT_ACTUAL_NODE); 
            stmt.bindf("is", wcId, conflict.getPath());
            
            boolean haveRow = false;
            try {
                haveRow = stmt.next();
            } finally {
                stmt.reset();
            }
            
            if (haveRow) {
                /* There is an existing ACTUAL row, so just update it. */
                stmt = sDb.getStatement(SVNWCDbStatements.UPDATE_ACTUAL_CONFLICT_DATA);
            } else {
                 /* We need to insert an ACTUAL row with the tree conflict data. */
                stmt = sDb.getStatement(SVNWCDbStatements.INSERT_ACTUAL_CONFLICT_DATA);
            }
            
            stmt.bindf("iss", wcId, conflict.getPath(), SVNTreeConflictUtil.getSingleTreeConflictData(conflict));
            if (!haveRow)
                stmt.bindString(4, SVNFileUtil.getFilePath(localRelPath));
            
            try {
                stmt.exec();
            } finally {
                stmt.reset();
            }
            
        }
    }
    
    public static void setVersion(SVNSqlJetDb sDb, int version) throws SVNException {
        try {
            sDb.getDb().pragma("pragma user_version = " + version);
        } catch (SqlJetException e) {
            SVNSqlJetDb.createSqlJetError(e);
        }
    }
    
    private interface Bumpable {
    	public void bumpTo(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException;
    }
    
    private static void runBump(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath, Bumpable bump) throws SVNException {
    	sDb.beginTransaction(SqlJetTransactionMode.WRITE);
        try {
        	bump.bumpTo(db, sDb, wcRootAbsPath);
        } catch (SVNException e) {
            sDb.rollback();
            throw e;
        }
        finally {
            sDb.commit();
        }
    }
    
    /* UPDATE tableName SET checksum=(SELECT checksum FROM pristine WHERE md5_checksum=tableName.checksum)
	   WHERE EXISTS(SELECT 1 FROM pristine WHERE md5_checksum=WORKING_NODE.checksum); */
    private static class UpdateChecksum {
    	private SVNSqlJetDb sDb;
    	private Enum<?> tableName;
    	
    	public UpdateChecksum(SVNSqlJetDb sDb, Enum<?> tableName) {
            this.sDb = sDb;
            this.tableName = tableName;
        }
    	
    	public void run() throws SVNException {
	    	SVNSqlJetUpdateStatement stmt = new SVNSqlJetUpdateStatement(sDb, tableName) {
	    		private SVNSqlJetSelectFieldsStatement<SVNWCDbSchema.PRISTINE__Fields> select = 
	    				new SVNSqlJetSelectFieldsStatement<SVNWCDbSchema.PRISTINE__Fields>(sDb, SVNWCDbSchema.PRISTINE) {
	    					protected boolean isFilterPassed() throws SVNException {
	    						return ((String)getBind(1)).equals(getColumnString(SVNWCDbSchema.PRISTINE__Fields.md5_checksum));
	    					}
	    					protected void defineFields() {
	    		                fields.add(SVNWCDbSchema.PRISTINE__Fields.checksum);
	    		            }
	    					protected Object[] getWhere() throws SVNException {
	    				        return new Object[] {};
	    				    }
	    				};
	    		
	    		public Map<String, Object> getUpdateValues() throws SVNException {
	    			return null;
	    		}
	    				
	    		public long exec() throws SVNException {
	    	        long n = 0;
	    	        try {
	    	            statementStarted();
	    	            while (next()) {
	    	            	Map<String, Object> rowValues = getRowValues();
	    	            	String checksum = (String)rowValues.get("checksum");
	    	            	if (checksum == null)
	    	            		continue;
	    	            	select.bindString(1, checksum);
	    	            	try {
	    	            		if (select.next()) {
	    	            			rowValues.put("checksum", select.getColumnString(SVNWCDbSchema.PRISTINE__Fields.checksum));
	    	            		} else {
	    	            			continue;
	    	            		}
	    	            	} finally {
	    	            		select.reset();
	    	            	}
	    	            	
	    	                update(rowValues);
	    	                n++;
	    	            }
	    	            statementCompleted(null);
	    	        } catch (SqlJetException e) {
	    	            statementCompleted(e);
	    	            SVNSqlJetDb.createSqlJetError(e);
	    	        }
	    	        return n;
	    	    }
	    	};
	    	
	    	try {
				stmt.exec();
			} finally {
				stmt.reset();
			}
    	}
    }
    
    private static class bumpTo20 implements Bumpable {
    	public void bumpTo(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException {
	    	try {
	    		sDb.getDb().createTable("CREATE TABLE NODES ( wc_id  INTEGER NOT NULL REFERENCES WCROOT (id), local_relpath  TEXT NOT NULL, op_depth INTEGER NOT NULL, "
	                + "  parent_relpath  TEXT, repos_id  INTEGER REFERENCES REPOSITORY (id), repos_path  TEXT, revision  INTEGER, presence  TEXT NOT NULL, "
	                + "  moved_here  INTEGER, moved_to  TEXT, kind  TEXT NOT NULL, properties  BLOB, depth  TEXT, checksum  TEXT REFERENCES PRISTINE (checksum), symlink_target  TEXT, "
	                + "  changed_revision  INTEGER, changed_date INTEGER, changed_author TEXT, translated_size  INTEGER, last_mod_time  INTEGER, "
	                + "  dav_cache  BLOB, file_external  TEXT, PRIMARY KEY (wc_id, local_relpath, op_depth) ); ");
	    		sDb.getDb().createIndex("CREATE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, op_depth); ");
	    		
	    		/*
	    		UPDATE BASE_NODE SET checksum=(SELECT checksum FROM pristine WHERE md5_checksum=BASE_NODE.checksum)
				WHERE EXISTS(SELECT 1 FROM pristine WHERE md5_checksum=BASE_NODE.checksum);
	    		 */
	    		UpdateChecksum uc = new UpdateChecksum(sDb, SVNWCDbSchema.BASE_NODE);
	    		uc.run();
	    
		    	/*
	    		UPDATE WORKING_NODE SET checksum=(SELECT checksum FROM pristine WHERE md5_checksum=WORKING_NODE.checksum)
				WHERE EXISTS(SELECT 1 FROM pristine WHERE md5_checksum=WORKING_NODE.checksum);
		    	 */
	    		uc = new UpdateChecksum(sDb, SVNWCDbSchema.WORKING_NODE);
	    		uc.run();
	    		
	    		/*
				INSERT INTO NODES (
					wc_id, local_relpath, op_depth, parent_relpath,
					repos_id, repos_path, revision,
					presence, depth, moved_here, moved_to, kind,
					changed_revision, changed_date, changed_author,
					checksum, properties, translated_size, last_mod_time,
					dav_cache, symlink_target, file_external )
					SELECT wc_id, local_relpath, 0 /*op_depth/, parent_relpath,
					repos_id, repos_relpath, revnum,
					presence, depth, NULL /*moved_here/, NULL /*moved_to/, kind,
					changed_rev, changed_date, changed_author,
					checksum, properties, translated_size, last_mod_time,
					dav_cache, symlink_target, file_external
				FROM BASE_NODE;
				*/
	    		SVNSqlJetInsertStatement stmt = new SVNSqlJetInsertStatement(sDb, SVNWCDbSchema.NODES) {
	    			private SVNSqlJetSelectStatement select = new SVNSqlJetSelectStatement(sDb.getTemporaryDb(), SVNWCDbSchema.BASE_NODE) {};

	    		    public long exec() throws SVNException {
	    		        try {
	    		            int n = 0;
	    		            while (select.next()) {
	    		                try {
	    		                    table.insertByFieldNamesOr(null, getInsertValues());
	    		                    n++;
	    		                } catch (SqlJetException e) {
	    		                    SVNSqlJetDb.createSqlJetError(e);
	    		                    return -1;
	    		                }
	    		            }
	    		            return n;
	    		        } finally {
	    		            select.reset();
	    		        }
	    		    }
	    		    
	    		    protected Map<String, Object> getInsertValues() throws SVNException {
	    		    	Map<String,Object> selectedRow = select.getRowValues();
	    		    	Map<String, Object> insertValues = new HashMap<String, Object>();
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.wc_id.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.wc_id.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.local_relpath.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.local_relpath.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.op_depth.toString(), 0);
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.parent_relpath.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.parent_relpath.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.repos_id.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.repos_id.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.repos_path.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.repos_path.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.revision.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.revnum.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.presence.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.presence.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.depth.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.depth.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.moved_here.toString(), null);
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.moved_to.toString(), null);
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.kind.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.kind.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.changed_revision.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.changed_rev.toString()));	    		    	
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.changed_date.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.changed_date.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.changed_author.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.changed_author.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.checksum.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.checksum.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.properties.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.properties.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.translated_size.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.translated_size.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.last_mod_time.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.last_mod_time.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.dav_cache.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.dav_cache.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.symlink_target.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.symlink_target.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.file_external.toString(), selectedRow.get(SVNWCDbSchema.BASE_NODE__Fields.file_external.toString()));
	    		    	return insertValues;
	    		    }

	    		};
	    		
	    		try {
	    			stmt.exec();
	    		} finally {
	    			stmt.reset();
	    		}
	    		/*INSERT INTO NODES (
					wc_id, local_relpath, op_depth, parent_relpath,
					repos_id, repos_path, revision,
					presence, depth, moved_here, moved_to, kind,
					changed_revision, changed_date, changed_author,
					checksum, properties, translated_size, last_mod_time,
					dav_cache, symlink_target, file_external )
				SELECT wc_id, local_relpath, 2 /*op_depth/, parent_relpath,
					copyfrom_repos_id, copyfrom_repos_path, copyfrom_revnum,
					presence, depth, NULL /*moved_here/, NULL /*moved_to/, kind,
					changed_rev, changed_date, changed_author,
					checksum, properties, translated_size, last_mod_time,
					NULL /*dav_cache/, symlink_target, NULL /*file_external/
				FROM WORKING_NODE;
				 */
	    		stmt = new SVNSqlJetInsertStatement(sDb, SVNWCDbSchema.NODES) {
	    			private SVNSqlJetSelectStatement select = new SVNSqlJetSelectStatement(sDb.getTemporaryDb(), SVNWCDbSchema.BASE_NODE) {};

	    		    public long exec() throws SVNException {
	    		        try {
	    		            int n = 0;
	    		            while (select.next()) {
	    		                try {
	    		                    table.insertByFieldNamesOr(null, getInsertValues());
	    		                    n++;
	    		                } catch (SqlJetException e) {
	    		                    SVNSqlJetDb.createSqlJetError(e);
	    		                    return -1;
	    		                }
	    		            }
	    		            return n;
	    		        } finally {
	    		            select.reset();
	    		        }
	    		    }
	    		    
	    		    protected Map<String, Object> getInsertValues() throws SVNException {
	    		    	Map<String,Object> selectedRow = select.getRowValues();
	    		    	Map<String, Object> insertValues = new HashMap<String, Object>();
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.wc_id.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.wc_id.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.local_relpath.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.local_relpath.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.op_depth.toString(), 2);
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.parent_relpath.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.parent_relpath.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.repos_id.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.copyfrom_repos_id.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.repos_path.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.copyfrom_repos_path.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.revision.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.copyfrom_revnum.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.presence.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.presence.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.depth.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.depth.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.moved_here.toString(), null);
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.moved_to.toString(), null);
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.kind.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.kind.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.changed_revision.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.changed_rev.toString()));	    		    	
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.changed_date.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.changed_date.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.changed_author.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.changed_author.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.checksum.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.checksum.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.properties.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.properties.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.translated_size.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.translated_size.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.last_mod_time.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.last_mod_time.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.dav_cache.toString(), null);
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.symlink_target.toString(), selectedRow.get(SVNWCDbSchema.WORKING_NODE__Fields.symlink_target.toString()));
	    		    	insertValues.put(SVNWCDbSchema.NODES__Fields.file_external.toString(), null);
	    		    	return insertValues;
	    		    }

	    		};
	    		
	    		try {
	    			stmt.exec();
	    		} finally {
	    			stmt.reset();
	    		}
	    		
	    		sDb.getDb().dropTable("BASE_NODE");
				sDb.getDb().dropTable("WORKING_NODE");
				
	    	} catch (SqlJetException e) {
	            SVNSqlJetDb.createSqlJetError(e);
	        }
        
	    	setVersion(sDb, (int)21);    
    	}
    }
    
    private static class bumpTo21 implements Bumpable {
    	public void bumpTo(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException {
	        setVersion(sDb, (int)21);
	        migrateTreeConflictData(sDb);
    	}
    }
    
    private static class bumpTo22 implements Bumpable {
    	public void bumpTo(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException {
	    	/*-- STMT_UPGRADE_TO_22
	        UPDATE actual_node SET tree_conflict_data = conflict_data;
	        UPDATE actual_node SET conflict_data = NULL;
	        */
	        SVNSqlJetUpdateStatement stmt = new SVNSqlJetUpdateStatement(sDb, SVNWCDbSchema.ACTUAL_NODE) {
	            public Map<String, Object> getUpdateValues() throws SVNException {
	                Map<String, Object> rowValues = getRowValues();
	                rowValues.put(SVNWCDbSchema.ACTUAL_NODE__Fields.tree_conflict_data.toString(), 
	                        rowValues.get(SVNWCDbSchema.ACTUAL_NODE__Fields.conflict_data.toString()));
	                return rowValues;
	            }
	        };
	        try {
	            stmt.exec();
	        } finally {
	            stmt.reset();
	        }
	        
	        stmt = new SVNSqlJetUpdateStatement(sDb, SVNWCDbSchema.ACTUAL_NODE) {
	            public Map<String, Object> getUpdateValues() throws SVNException {
	                Map<String, Object> rowValues = getRowValues();
	                rowValues.put(SVNWCDbSchema.ACTUAL_NODE__Fields.conflict_data.toString(), null);
	                return rowValues;
	            }
	        };
	        try {
	            stmt.exec();
	        } finally {
	            stmt.reset();
	        }
	 
	        setVersion(sDb, (int)22);
    	}
    }
    
    private static class bumpTo23 implements Bumpable {
    	public void bumpTo(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException {
	        //-- STMT_HAS_WORKING_NODES
	        //SELECT 1 FROM nodes WHERE op_depth > 0
	        //LIMIT 1
	        SVNSqlJetSelectFieldsStatement<SVNWCDbSchema.NODES__Fields> stmt = new SVNSqlJetSelectFieldsStatement<SVNWCDbSchema.NODES__Fields>(sDb, SVNWCDbSchema.NODES) {
	            protected void defineFields() {
	                fields.add(SVNWCDbSchema.NODES__Fields.wc_id);
	            }
	            protected boolean isFilterPassed() throws SVNException {
	                return getColumnLong(SVNWCDbSchema.NODES__Fields.op_depth) > 0;
	            }
	        };
	        
	        boolean haveRow = false;
	        try {
	            haveRow = stmt.next();
	        } finally {
	            stmt.reset();
	        }
	        
	        if (haveRow) {
	            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, 
	                    "The working copy at ''{0}'' is format 22 with WORKING nodes; use a format 22 client to diff/revert before using this client", 
	                    wcRootAbsPath);
	            SVNErrorManager.error(err, SVNLogType.WC);
	        }
	        
	 
	        setVersion(sDb, (int)23);
    	}
    
    }
    
    private static class bumpTo24 implements Bumpable {
    	public void bumpTo(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException {
	    	/*-- STMT_UPGRADE_TO_25
	    	UPDATE pristine SET refcount = (SELECT COUNT(*) FROM nodes WHERE checksum = pristine.checksum /*OR checksum = pristine.md5_checksum/);
	    	*/
	    	SVNSqlJetUpdateStatement stmt = new SVNSqlJetUpdateStatement(sDb, SVNWCDbSchema.PRISTINE) {
	    		private SVNSqlJetSelectFieldsStatement<SVNWCDbSchema.NODES__Fields> select = 
	    				new SVNSqlJetSelectFieldsStatement<SVNWCDbSchema.NODES__Fields>(sDb, SVNWCDbSchema.NODES) {
	    					protected boolean isFilterPassed() throws SVNException {
	    						return ((String)getBind(1)).equals(getColumnString(SVNWCDbSchema.NODES__Fields.checksum));
	    					}
	    					protected void defineFields() {
	    		                fields.add(SVNWCDbSchema.NODES__Fields.wc_id);
	    		            }
	    					protected Object[] getWhere() throws SVNException {
	    				        return new Object[] {};
	    				    }
	    				};
	    				
	    		public Map<String, Object> getUpdateValues() throws SVNException {
	    			Map<String, Object> rowValues = getRowValues();
	    			
	    			select.bindString(1, (String)rowValues.get(SVNWCDbSchema.PRISTINE__Fields.checksum.toString()));
	    			long rowCount = 0;
	    			try {
	    				while (select.next())
	    					rowCount++;
	    			} finally {
	    				select.reset();
	    			}
					rowValues.put(SVNWCDbSchema.PRISTINE__Fields.refcount.toString(), rowCount);
					return rowValues;
				}
	    	};
	    	
	    	try {
				stmt.exec();
			} finally {
				stmt.reset();
			}
	    	
	    	setVersion(sDb, (int)24);
	    	
	    	try {
	    		sDb.getDb().createTrigger("CREATE TRIGGER nodes_insert_trigger AFTER INSERT ON nodes WHEN NEW.checksum IS NOT NULL BEGIN UPDATE pristine SET refcount = refcount + 1 WHERE checksum = NEW.checksum; END;");
	    		sDb.getDb().createTrigger("CREATE TRIGGER nodes_delete_trigger AFTER DELETE ON nodes WHEN OLD.checksum IS NOT NULL BEGIN UPDATE pristine SET refcount = refcount - 1 WHERE checksum = OLD.checksum; END;");
	    		sDb.getDb().createTrigger("CREATE TRIGGER nodes_update_checksum_trigger AFTER UPDATE OF checksum ON nodes WHEN NEW.checksum IS NOT OLD.checksum BEGIN UPDATE pristine SET refcount = refcount + 1 WHERE checksum = NEW.checksum; UPDATE pristine SET refcount = refcount - 1 WHERE checksum = OLD.checksum; END;");
	    	} catch (SqlJetException e) {
	            SVNSqlJetDb.createSqlJetError(e);
	        }
    	}
    }
    
    private static class bumpTo25 implements Bumpable {
    	public void bumpTo(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException {
	    	/*-- STMT_UPGRADE_TO_25
	    	//DROP VIEW IF EXISTS NODES_CURRENT;
	    	CREATE VIEW NODES_CURRENT AS
	    	  SELECT * FROM nodes JOIN (SELECT wc_id, local_relpath, MAX(op_depth) AS op_depth FROM nodes GROUP BY wc_id, local_relpath) AS filter
	    	    ON nodes.wc_id = filter.wc_id AND nodes.local_relpath = filter.local_relpath AND nodes.op_depth = filter.op_depth;
	    	*/
	    	
	    	try {
	    		sDb.getDb().createView(
	    				"CREATE VIEW NODES_CURRENT AS " + 
	    				"SELECT * FROM nodes JOIN (SELECT wc_id, local_relpath, MAX(op_depth) AS op_depth FROM nodes GROUP BY wc_id, local_relpath) AS filter " +
	    				"ON nodes.wc_id = filter.wc_id AND nodes.local_relpath = filter.local_relpath AND nodes.op_depth = filter.op_depth;");
	    	} catch (SqlJetException e) {
	            SVNSqlJetDb.createSqlJetError(e);
	        }
	
	    	setVersion(sDb, (int)25);
    	}
    }
    
    private static class bumpTo26 implements Bumpable {
    	public void bumpTo(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException {
	    	/*-- STMT_UPGRADE_TO_26
	    	* DROP VIEW IF EXISTS NODES_BASE;
	    	* CREATE VIEW NODES_BASE AS
	    	* SELECT * FROM nodes
	    	*  WHERE op_depth = 0;
	    	*/
	    	
	    	try {
	    		if (sDb.getDb().getSchema().getViewNames().contains("NODES_BASE")) {
	    			sDb.getDb().dropView("NODES_BASE");
	    		}
	    		sDb.getDb().createView("CREATE VIEW NODES_BASE AS SELECT * FROM nodes WHERE op_depth = 0;"); 
	    	} catch (SqlJetException e) {
	            SVNSqlJetDb.createSqlJetError(e);
	        }
	    	
	    	setVersion(sDb, (int)26);
	    }
    }
    
    private static class bumpTo27 implements Bumpable {
	    public void bumpTo(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException {
	    	/*
	    	-- STMT_HAS_ACTUAL_NODES_CONFLICTS
	    	SELECT 1 FROM actual_node
	    	WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL)
	    	           AND (conflict_new IS NULL) AND (conflict_working IS NULL)
	    	           AND (tree_conflict_data IS NULL))
	    	LIMIT 1
	    	*/
	
	    	  SVNSqlJetSelectFieldsStatement<SVNWCDbSchema.ACTUAL_NODE__Fields> stmt = new SVNSqlJetSelectFieldsStatement<SVNWCDbSchema.ACTUAL_NODE__Fields>(sDb, SVNWCDbSchema.ACTUAL_NODE) {
	              protected void defineFields() {
	                  fields.add(SVNWCDbSchema.ACTUAL_NODE__Fields.wc_id);
	              }
	              protected boolean isFilterPassed() throws SVNException {
	            	  return !(isColumnNull(SVNWCDbSchema.ACTUAL_NODE__Fields.prop_reject) &&
	            			  isColumnNull(SVNWCDbSchema.ACTUAL_NODE__Fields.conflict_old) &&
	            			  isColumnNull(SVNWCDbSchema.ACTUAL_NODE__Fields.conflict_new) &&
	            			  isColumnNull(SVNWCDbSchema.ACTUAL_NODE__Fields.conflict_working) &&
	            			  isColumnNull(SVNWCDbSchema.ACTUAL_NODE__Fields.tree_conflict_data)); 
	              }
	          };
	          
	          boolean haveRow = false;
	          try {
	              haveRow = stmt.next();
	          } finally {
	              stmt.reset();
	          }

	    	  if (haveRow) {
	              SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, 
	                      "The working copy at ''{0}'' is format 26 with conflicts; use a format 26 client to resolve before using this client", 
	                      wcRootAbsPath);
	              SVNErrorManager.error(err, SVNLogType.WC);
	          }
	         	    	  
	    	  setVersion(sDb, (int)27);
	    }
    }
    
    private static class bumpTo28 implements Bumpable {
	    public void bumpTo(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException {
	    	/*-- STMT_UPGRADE_TO_28
	    	 * UPDATE NODES SET checksum=(SELECT checksum FROM pristine WHERE md5_checksum=nodes.checksum)
			 * WHERE EXISTS(SELECT 1 FROM pristine WHERE md5_checksum=nodes.checksum);
	    	 */
	    	UpdateChecksum uc = new UpdateChecksum(sDb, SVNWCDbSchema.NODES);
	    	uc.run();
	    	
	    	setVersion(sDb, 28);
	    }
    }
    
    private static class bumpTo29 implements Bumpable {
	    public void bumpTo(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException {
	    	/* Rename all pristine files, adding a ".svn-base" suffix. */
	    	File pristineDirAbsPath = SVNWCUtils.admChild(wcRootAbsPath, ISVNWCDb.PRISTINE_STORAGE_RELPATH);
	    	for (File dir: SVNFileListUtil.listFiles(pristineDirAbsPath)) {
	    		for (File file: SVNFileListUtil.listFiles(dir)) {
		    		/* If FINFO indicates that ABSPATH names a file, rename it to '<ABSPATH>.svn-base'.
		    	    *
		    	    * Ignore any file whose name is not the expected length, in order to make life easier for any developer 
		    	    * who runs this code twice or has some non-standard files in the pristine directory.
		    	    */
		    		if (SVNFileType.getType(file) == SVNFileType.FILE && SVNFileUtil.getFileName(file).length() == PRISTINE_BASENAME_OLD_LEN) {
		    		   File newAbsPath = SVNFileUtil.createFilePath(SVNFileUtil.getFilePath(file) + PRISTINE_STORAGE_EXT);
		    		   SVNFileUtil.rename(file, newAbsPath);
	   		       }
	    		}
	    	}
	    	
	    	/* Externals */
	    	try {
	    		sDb.getDb().createTable("CREATE TABLE EXTERNALS ( " +
		                "  wc_id  INTEGER NOT NULL REFERENCES WCROOT (id), " +
		                "  local_relpath  TEXT NOT NULL, " +
		                "  parent_relpath  TEXT NOT NULL, " +
		                "  repos_id  INTEGER NOT NULL REFERENCES REPOSITORY (id), " +
		                "  presence  TEXT NOT NULL, " +
		                "  kind  TEXT NOT NULL, " +
		                "  def_local_relpath         TEXT NOT NULL, " +
		                "  def_repos_relpath         TEXT NOT NULL, " +
		                "  def_operational_revision  TEXT, " +
		                "  def_revision              TEXT, " +
		                "  PRIMARY KEY (wc_id, local_relpath) " +
		                "); "); 
	    		sDb.getDb().createIndex("CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath);");
	    		sDb.getDb().createIndex("CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id, def_local_relpath, local_relpath);");
	    	} catch (SqlJetException e) {
	            SVNSqlJetDb.createSqlJetError(e);
	        }
	    	
	    	upgradeExternals(sDb, wcRootAbsPath);
	    	  
         	 /* Format 29 introduces the EXTERNALS table (See STMT_CREATE_TRIGGERS) and optimizes a few trigger definitions. ... */
         	 //-- STMT_UPGRADE_TO_29
         	 try {
         		if (sDb.getDb().getSchema().getTriggerNames().contains("nodes_update_checksum_trigger")) {
         			sDb.getDb().dropTrigger("nodes_update_checksum_trigger");
                }
         		if (sDb.getDb().getSchema().getTriggerNames().contains("nodes_insert_trigger")) {
         			sDb.getDb().dropTrigger("nodes_insert_trigger");
                }
         		if (sDb.getDb().getSchema().getTriggerNames().contains("nodes_delete_trigger")) {
         			sDb.getDb().dropTrigger("nodes_delete_trigger");
                }
 	    		sDb.getDb().createTrigger("CREATE TRIGGER nodes_update_checksum_trigger AFTER UPDATE OF checksum ON nodes WHEN NEW.checksum IS NOT OLD.checksum BEGIN UPDATE pristine SET refcount = refcount + 1 WHERE checksum = NEW.checksum; UPDATE pristine SET refcount = refcount - 1 WHERE checksum = OLD.checksum; END;");
 	    		sDb.getDb().createTrigger("CREATE TRIGGER nodes_insert_trigger AFTER INSERT ON nodes WHEN NEW.checksum IS NOT NULL BEGIN UPDATE pristine SET refcount = refcount + 1 WHERE checksum = NEW.checksum; END;");
 	    		sDb.getDb().createTrigger("CREATE TRIGGER nodes_delete_trigger AFTER DELETE ON nodes WHEN OLD.checksum IS NOT NULL BEGIN UPDATE pristine SET refcount = refcount - 1 WHERE checksum = OLD.checksum; END;");
 	    	} catch (SqlJetException e) {
 	            SVNSqlJetDb.createSqlJetError(e);
 	        }

         	setVersion(sDb, 29);
	    }
    }

    private static class bumpTo30 implements Bumpable {

        public void bumpTo(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException {
            try {
                if (sDb.getDb().getSchema().getTable("NODES").getColumn("inherited_props") == null) {
                    sDb.getDb().createIndex("CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth);");
                    sDb.getDb().createIndex("CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum);");
    
                    final ISqlJetCursor actulaNode = sDb.getDb().getTable("ACTUAL_NODE").open();
                    while(!actulaNode.eof()) {
                        final String conflictOld = actulaNode.getString(ACTUAL_NODE__Fields.conflict_old.toString());
                        final String conflictWorking = actulaNode.getString(ACTUAL_NODE__Fields.conflict_working.toString());
                        final String conflictNew = actulaNode.getString(ACTUAL_NODE__Fields.conflict_new.toString());
                        final String propReject = actulaNode.getString(ACTUAL_NODE__Fields.prop_reject.toString());
                        final byte[] treeConflictData = actulaNode.getBlobAsArray(ACTUAL_NODE__Fields.tree_conflict_data.toString());
                        
                        if (conflictOld == null && conflictWorking == null && conflictNew == null && propReject == null && treeConflictData == null) {
                            actulaNode.next();
                            continue;
                        }
                        final String localRelpath = actulaNode.getString(ACTUAL_NODE__Fields.local_relpath.toString());
                        final SVNSkel conflictData = SvnWcDbConflicts.convertToConflictSkel(wcRootAbsPath, db, localRelpath, conflictOld, conflictWorking, conflictNew, propReject, treeConflictData);
                        
                        final Map<String, Object> newRowValues = new HashMap<String, Object>();
                        if (conflictData != null) {
                            newRowValues.put(ACTUAL_NODE__Fields.conflict_data.toString(), conflictData.unparse());
                        }
    
                        newRowValues.put(ACTUAL_NODE__Fields.conflict_old.toString(), null);
                        newRowValues.put(ACTUAL_NODE__Fields.conflict_working.toString(), null);
                        newRowValues.put(ACTUAL_NODE__Fields.conflict_new.toString(), null);
                        newRowValues.put(ACTUAL_NODE__Fields.prop_reject.toString(), null);
                        newRowValues.put(ACTUAL_NODE__Fields.tree_conflict_data.toString(), null);
                        actulaNode.updateByFieldNames(newRowValues);
                        
                        actulaNode.next();
                    }
                }
            } catch (SqlJetException e) {
                SVNSqlJetDb.createSqlJetError(e);
            } 
            setVersion(sDb, 30);
        }
    }

    private static class bumpTo31 implements Bumpable {

        public void bumpTo(SVNWCDb db, SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException {
            try {
                if (sDb.getDb().getSchema().getTable("NODES").getColumn("inherited_props") != null) {
                    setVersion(sDb, 31);
                    return;
                } else {    
                    if (sDb.getDb().getSchema().getIndex("I_ACTUAL_CHANGELIST") != null) {
                        sDb.getDb().dropIndex("I_ACTUAL_CHANGELIST");
                    }
                    if (sDb.getDb().getSchema().getIndex("I_EXTERNALS_PARENT") != null) {
                        sDb.getDb().dropIndex("I_EXTERNALS_PARENT");
                    }
                    sDb.getDb().alterTable("ALTER TABLE NODES ADD COLUMN inherited_props BLOB;");
    
                    sDb.getDb().dropIndex("I_NODES_PARENT");
                    sDb.getDb().createIndex("CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, local_relpath, op_depth);");
                    sDb.getDb().dropIndex("I_ACTUAL_PARENT");
                    sDb.getDb().createIndex("CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, local_relpath);");
                }
            } catch (SqlJetException e) {
                SVNSqlJetDb.createSqlJetError(e);
            }
            
            final SVNSqlJetStatement stmt = sDb.getStatement(SVNWCDbStatements.SELECT_WCROOT_NODES);
            final SVNSqlJetStatement markRootStmt = sDb.getStatement(SVNWCDbStatements.UPDATE_IPROPS);
            final byte[] emptyInheritedProps = SVNSkel.createEmptyList().unparse();
            try {
                while(stmt.next()) {
                    final long wcId = stmt.getColumnLong(NODES__Fields.wc_id);
                    final String localRelPath = stmt.getColumnString(NODES__Fields.local_relpath);
                    
                    try {
                        markRootStmt.bindf("isb", wcId, localRelPath, emptyInheritedProps);
                        markRootStmt.done();
                    } finally {
                        markRootStmt.reset();
                    }                    
                }
            } finally {
                stmt.reset();
            }
            
            setVersion(sDb, 31);
        }
        
    }
    
    private static void upgradeExternals(SVNSqlJetDb sDb, File wcRootAbsPath) throws SVNException {
        SVNSqlJetStatement stmt = sDb.getStatement(SVNWCDbStatements.SELECT_EXTERNAL_PROPERTIES);

    	/* ### For this intermediate upgrade we just assume WC_ID = 1.
        ### Before this bump we lost track of externals all the time, so lets keep this easy. */
    	stmt.bindf("is", 1, "");
    	try {
            while (stmt.next()) {
            	SVNProperties props = stmt.getColumnProperties(SVNWCDbSchema.NODES__Fields.properties);
            	String externalsValues = props.getStringValue(SVNProperty.EXTERNALS);
            	File localRelPath = SVNFileUtil.createFilePath(stmt.getColumnString(SVNWCDbSchema.NODES__Fields.properties));
            	File localAbsPath = SVNFileUtil.createFilePath(wcRootAbsPath, localRelPath);
                
            	if (externalsValues != null) {
            		SVNExternal[] externalDefs = SVNExternal.parseExternals(localAbsPath, externalsValues);
        		    for (SVNExternal externalDef : externalDefs) {
        		    	File externalPath = SVNFileUtil.createFilePath(localAbsPath, externalDef.getPath());
        		    	
        		    	/* Insert dummy externals definitions: Insert an unknown external, to make sure it will be cleaned up when it is not
                           updated on the next update. */
                        SVNSqlJetStatement addStmt = sDb.getStatement(SVNWCDbStatements.INSERT_EXTERNAL);
                        try {
                            addStmt.bindf("isssssis",
                                    1, /* wc_id*/
                                    SVNFileUtil.getFilePath(externalPath),
                                    SVNFileUtil.getFilePath(SVNFileUtil.getFileDir(externalPath)),
                                    "normal",
                                    "unknown",
                                    SVNFileUtil.getFilePath(localRelPath),
                                    1, /* repos_id */
                                    "" /* repos_relpath */);
                            addStmt.exec();
                        } finally {
                            addStmt.reset();
                        }
                    }
            	}
            }
        } finally {
            stmt.reset();
        }
    }
}
