package org.tmatesoft.svn.core.internal.wc17.db; import org.tmatesoft.sqljet.core.SqlJetException; import org.tmatesoft.sqljet.core.SqlJetTransactionMode; import org.tmatesoft.sqljet.core.table.ISqlJetCursor; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.db.SVNSqlJetDb; import org.tmatesoft.svn.core.internal.db.SVNSqlJetInsertStatement; import org.tmatesoft.svn.core.internal.db.SVNSqlJetSelectStatement; import org.tmatesoft.svn.core.internal.db.SVNSqlJetStatement; import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryUtil; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNExternal; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbKind; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbStatus; import org.tmatesoft.svn.core.internal.wc17.db.statement.*; import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbSchema.*; import org.tmatesoft.svn.core.wc2.ISvnObjectReceiver; import org.tmatesoft.svn.core.wc2.SvnTarget; import org.tmatesoft.svn.util.SVNLogType; import java.io.File; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import static org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbStatementUtil.*; public class SvnWcDbProperties extends SvnWcDbShared { private static final int WC__NO_REVERT_FILES = 4; public static SVNProperties readProperties(SVNWCDbRoot root, File relpath) throws SVNException { SVNProperties props = null; SVNSqlJetStatement stmt = null; try { stmt = root.getSDb().getStatement(SVNWCDbStatements.SELECT_ACTUAL_PROPS); stmt.bindf("is", root.getWcId(), relpath); if (stmt.next() && !isColumnNull(stmt, ACTUAL_NODE__Fields.properties)) { props = getColumnProperties(stmt, ACTUAL_NODE__Fields.properties); } if (props != null) { return props; } props = readPristineProperties(root, relpath); if (props == null) { return new SVNProperties(); } } finally { reset(stmt); } return props; } public static SVNProperties readPristineProperties(SVNWCDbRoot root, File relpath) throws SVNException { SVNSqlJetStatement stmt = root.getSDb().getStatement(SVNWCDbStatements.SELECT_NODE_PROPS); try { stmt.bindf("is", root.getWcId(), relpath); if (!stmt.next()) { nodeNotFound(root.getAbsPath(relpath)); } SVNWCDbStatus presence = getColumnPresence(stmt); if (presence == SVNWCDbStatus.BaseDeleted) { boolean haveRow = stmt.next(); assert (haveRow); presence = getColumnPresence(stmt); } if (presence == SVNWCDbStatus.Normal || presence == SVNWCDbStatus.Incomplete) { SVNProperties props = getColumnProperties(stmt, SVNWCDbSchema.NODES__Fields.properties); if (props == null) { props = new SVNProperties(); } return props; } return null; } finally { reset(stmt); } } public static void readPropertiesRecursively(SVNWCDbRoot root, File relpath, SVNDepth depth, boolean baseProperties, boolean pristineProperties, Collection<String> changelists, ISvnObjectReceiver<SVNProperties> receiver) throws SVNException { SVNSqlJetSelectStatement stmt = null; root.getSDb().getTemporaryDb().beginTransaction(SqlJetTransactionMode.WRITE); try { try { cacheProperties(root, relpath, depth, baseProperties, pristineProperties, changelists); stmt = new SVNSqlJetSelectStatement(root.getSDb().getTemporaryDb(), SVNWCDbSchema.NODE_PROPS_CACHE); while(stmt.next()) { SVNProperties props = getColumnProperties(stmt, NODE_PROPS_CACHE__Fields.properties); File target = getColumnPath(stmt, NODE_PROPS_CACHE__Fields.local_Relpath); File absolutePath = root.getAbsPath(target); receiver.receive(SvnTarget.fromFile(absolutePath), props); } } finally { reset(stmt); SVNSqlJetStatement dropCache = new SVNWCDbCreateSchema(root.getSDb().getTemporaryDb(), SVNWCDbCreateSchema.DROP_NODE_PROPS_CACHE, -1); try { dropCache.done(); } finally { dropCache.reset(); } } } catch (SVNException e) { root.getSDb().getTemporaryDb().rollback(); throw e; } finally { root.getSDb().getTemporaryDb().commit(); } } /* Set the ACTUAL_NODE properties column for (WC_ID, LOCAL_RELPATH) to * PROPS. */ private static void setActualProps(SVNWCDbRoot root, File localRelPath, SVNProperties properties) throws SVNException { SVNSqlJetStatement stmt = root.getSDb().getStatement(SVNWCDbStatements.UPDATE_ACTUAL_PROPS); long affectedRows = 0; try { stmt.bindf("is", root.getWcId(), localRelPath); stmt.bindProperties(3, properties); affectedRows = stmt.exec(); } finally { stmt.reset(); } if (affectedRows == 1 || properties.size() == 0) return; /* We have to insert a row in ACTUAL */ try { stmt = root.getSDb().getStatement(SVNWCDbStatements.INSERT_ACTUAL_PROPS); stmt.bindf("is", root.getWcId(), localRelPath); if (localRelPath != null) { stmt.bindString(3, SVNFileUtil.getFilePath(SVNFileUtil.getFileDir(localRelPath))); } stmt.bindProperties(4, properties); stmt.exec(); } finally { stmt.reset(); } } public static void upgradeApplyProperties(SVNWCDbRoot root, File dirAbsPath, File localRelPath, SVNProperties baseProps, SVNProperties workingProps, SVNProperties revertProps, int originalFormat) throws SVNException { long topOpDepth = -1; long belowOpDepth = -1; SVNWCDbStatus topPresence = SVNWCDbStatus.NotPresent; SVNWCDbStatus belowPresence = SVNWCDbStatus.NotPresent; SVNWCDbKind kind = SVNWCDbKind.Unknown; long affectedRows; SVNSqlJetStatement stmt = root.getSDb().getStatement(SVNWCDbStatements.SELECT_NODE_INFO); try { stmt.bindf("is", root.getWcId(), localRelPath); boolean haveRow = stmt.next(); if (haveRow) { topOpDepth = getColumnInt64(stmt, SVNWCDbSchema.NODES__Fields.op_depth); topPresence = getColumnPresence(stmt, SVNWCDbSchema.NODES__Fields.presence); kind = getColumnKind(stmt, SVNWCDbSchema.NODES__Fields.kind); haveRow = stmt.next(); if (haveRow) { belowOpDepth = getColumnInt64(stmt, SVNWCDbSchema.NODES__Fields.op_depth); belowPresence = getColumnPresence(stmt, SVNWCDbSchema.NODES__Fields.presence); } } } finally { stmt.reset(); } if (originalFormat > WC__NO_REVERT_FILES && revertProps == null && topOpDepth != -1 && topPresence == SVNWCDbStatus.Normal && belowOpDepth != -1 && belowPresence != SVNWCDbStatus.NotPresent) { /* There should be REVERT_PROPS, so it appears that we just ran into the described bug. Sigh. */ SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CORRUPT, "The properties of ''{0}'' are in an indeterminate state and cannot be upgraded. See issue #2530.", SVNFileUtil.createFilePath(dirAbsPath, localRelPath)); SVNErrorManager.error(err, SVNLogType.WC); return; } /* Need at least one row, or two rows if there are revert props */ if (topOpDepth == -1 || (belowOpDepth == -1 && revertProps != null)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CORRUPT, "Insufficient NODES rows for ''{0}''", SVNFileUtil.createFilePath(dirAbsPath, localRelPath)); SVNErrorManager.error(err, SVNLogType.WC); return; } /* one row, base props only: upper row gets base props two rows, base props only: lower row gets base props two rows, revert props only: lower row gets revert props two rows, base and revert props: upper row gets base, lower gets revert */ if (revertProps != null || belowOpDepth == -1) { stmt = root.getSDb().getStatement(SVNWCDbStatements.UPDATE_NODE_PROPS); try { stmt.bindf("isi", root.getWcId(), localRelPath, topOpDepth); stmt.bindProperties(4, baseProps); affectedRows = stmt.exec(); assert(affectedRows == 1); } finally { stmt.reset(); } } if (belowOpDepth != -1) { SVNProperties props = revertProps != null ? revertProps : baseProps; stmt = root.getSDb().getStatement(SVNWCDbStatements.UPDATE_NODE_PROPS); try { stmt.bindf("isi", root.getWcId(), localRelPath, belowOpDepth); stmt.bindProperties(4, props); affectedRows = stmt.exec(); assert(affectedRows == 1); } finally { stmt.reset(); } } if (workingProps != null && baseProps != null) { SVNProperties diffs = FSRepositoryUtil.getPropsDiffs(workingProps, baseProps); if (diffs.isEmpty()) workingProps.clear(); } if (workingProps != null) { setActualProps(root, localRelPath, workingProps); } if (kind == SVNWCDbKind.Dir) { SVNProperties props = workingProps; if (props == null) { props = baseProps; } String externals = props != null ? props.getStringValue(SVNProperty.EXTERNALS) : null; if (externals != null && !"".equals(externals)) { SVNExternal[] externalsList = SVNExternal.parseExternals( SVNFileUtil.createFilePath(dirAbsPath, localRelPath), externals); for (SVNExternal externalItem : externalsList) { File itemRelPah = SVNFileUtil.createFilePath(localRelPath, externalItem.getPath()); stmt = root.getSDb().getStatement(SVNWCDbStatements.INSERT_EXTERNAL_UPGRADE); try { stmt.bindf("issssis", root.getWcId(), itemRelPah, SVNFileUtil.getFilePath(SVNFileUtil.getFileDir(itemRelPah)), SvnWcDbStatementUtil.getPresenceText(SVNWCDbStatus.Normal), localRelPath, 1, /* repos_id */ "" /* repos_relpath */); stmt.exec(); } finally { stmt.reset(); } } } } } public static void upgradeApplyDavCache(SVNWCDbRoot root, File dirRelPath, Map<String, SVNProperties> cacheValues) throws SVNException { SVNSqlJetStatement selectRoot = root.getSDb().getStatement(SVNWCDbStatements.SELECT_WCROOT_NULL); long wcId = 0; try { if (selectRoot.next()) wcId = selectRoot.getColumnLong(WCROOT__Fields.id); } finally { selectRoot.reset(); } /* Iterate over all the wcprops, writing each one to the wc_db. */ for (Iterator<String> names = cacheValues.keySet().iterator(); names.hasNext();) { String name = (String) names.next(); SVNProperties props = (SVNProperties) cacheValues.get(name); if (props.size() > 0) { File localRelPath = SVNFileUtil.createFilePath(dirRelPath, name); SVNSqlJetStatement stmt = root.getSDb().getStatement(SVNWCDbStatements.UPDATE_BASE_NODE_DAV_CACHE); stmt.bindf("is", wcId, localRelPath); stmt.bindProperties(3, props); try { stmt.exec(); } finally { stmt.reset(); } } } } private static void cacheProperties(SVNWCDbRoot root, File relpath, SVNDepth depth, boolean baseProperties, boolean pristineProperties, Collection<String> changelists) throws SVNException { SVNSqlJetStatement stmt = null; InsertIntoPropertiesCache insertStmt = null; SVNSqlJetSelectStatement propertiesSelectStmt = null; root.getSDb().beginTransaction(SqlJetTransactionMode.READ_ONLY); try { collectTargets(root, relpath, depth, changelists); stmt = new SVNWCDbCreateSchema(root.getSDb().getTemporaryDb(), SVNWCDbCreateSchema.NODE_PROPS_CACHE, -1); try { stmt.done(); } finally { stmt.reset(); } if (baseProperties) { propertiesSelectStmt = new SVNWCDbNodesBase(root.getSDb()); } else if (pristineProperties) { propertiesSelectStmt = new SVNWCDbNodesCurrent(root.getSDb()); } else { propertiesSelectStmt = new SVNWCDbNodesCurrent(root.getSDb()); } insertStmt = new InsertIntoPropertiesCache(root.getSDb().getTemporaryDb()); stmt = new SVNSqlJetSelectStatement(root.getSDb().getTemporaryDb(), SVNWCDbSchema.TARGETS_LIST); stmt.bindf("i", root.getWcId()); while(stmt.next()) { String localRelpath = getColumnText(stmt, TARGETS_LIST__Fields.local_relpath); long wcId = getColumnInt64(stmt, TARGETS_LIST__Fields.wc_id); String kind = getColumnText(stmt, TARGETS_LIST__Fields.kind); propertiesSelectStmt.bindf("is", wcId, localRelpath); byte[] props = null; try { if (propertiesSelectStmt.next()) { SVNWCDbStatus presence = getColumnPresence(propertiesSelectStmt); if (baseProperties) { if (presence == SVNWCDbStatus.Normal || presence == SVNWCDbStatus.Incomplete) { props = getColumnBlob(propertiesSelectStmt, SVNWCDbSchema.NODES__Fields.properties); } } else if (pristineProperties) { if (presence == SVNWCDbStatus.Normal || presence == SVNWCDbStatus.Incomplete || presence == SVNWCDbStatus.BaseDeleted) { props = getColumnBlob(propertiesSelectStmt, NODES__Fields.properties); } if (props == null) { long rowOpDepth = getColumnInt64(propertiesSelectStmt, NODES__Fields.op_depth); if (rowOpDepth > 0 && getColumnPresence(propertiesSelectStmt, NODES__Fields.presence) == SVNWCDbStatus.BaseDeleted) { SelectRowWithMaxOpDepth query = new SelectRowWithMaxOpDepth(root.getSDb(), rowOpDepth); try { query.bindf("is", wcId, localRelpath); if (query.next()) { props = getColumnBlob(query, NODES__Fields.properties); } } finally { reset(query); } } } } else { if (presence == SVNWCDbStatus.Normal || presence == SVNWCDbStatus.Incomplete) { props = getColumnBlob(propertiesSelectStmt, NODES__Fields.properties); } SVNSqlJetSelectStatement query = new SVNSqlJetSelectStatement(root.getSDb(), SVNWCDbSchema.ACTUAL_NODE); byte[] actualProps = null; try { query.bindf("is", wcId, localRelpath); if (query.next()) { actualProps = getColumnBlob(query, ACTUAL_NODE__Fields.properties); } } finally { reset(query); } props = actualProps != null ? actualProps : props; } } } finally { reset(propertiesSelectStmt); } if (props != null && props.length > 2) { try { insertStmt.putInsertValue(NODE_PROPS_CACHE__Fields.local_Relpath, localRelpath); insertStmt.putInsertValue(NODE_PROPS_CACHE__Fields.kind, kind); insertStmt.putInsertValue(NODE_PROPS_CACHE__Fields.properties, props); insertStmt.exec(); } finally { insertStmt.reset(); } } } } finally { try { reset(stmt); reset(insertStmt); reset(propertiesSelectStmt); SVNSqlJetStatement dropTargets = new SVNWCDbCreateSchema(root.getSDb().getTemporaryDb(), SVNWCDbCreateSchema.DROP_TARGETS_LIST, -1); try { dropTargets.done(); } finally { dropTargets.reset(); } } finally { root.getSDb().commit(); } } } /* * SELECT properties FROM nodes nn WHERE //n.presence = 'base-deleted' AND nn.wc_id = n.wc_id AND nn.local_relpath = n.local_relpath AND nn.op_depth < n.op_depth ORDER BY op_depth DESC */ private static class SelectRowWithMaxOpDepth extends SVNSqlJetSelectStatement { private long opDepth; public SelectRowWithMaxOpDepth(SVNSqlJetDb sDb, long opDepth) throws SVNException { super(sDb, SVNWCDbSchema.NODES); this.opDepth = opDepth; } @Override protected ISqlJetCursor openCursor() throws SVNException { try { return super.openCursor().reverse(); } catch (SqlJetException e) { return null; } } @Override protected boolean isFilterPassed() throws SVNException { long rowOpDepth = getColumnLong(NODES__Fields.op_depth); return rowOpDepth < opDepth; } } private static class InsertIntoPropertiesCache extends SVNSqlJetInsertStatement { private HashMap<String, Object> insertValues; public InsertIntoPropertiesCache(SVNSqlJetDb sDb) throws SVNException { super(sDb, SVNWCDbSchema.NODE_PROPS_CACHE); insertValues = new HashMap<String, Object>(); } public void putInsertValue(Enum<?> f, Object value) { insertValues.put(f.toString(), value); } @Override public void reset() throws SVNException { super.reset(); insertValues.clear(); } @Override protected Map<String, Object> getInsertValues() throws SVNException { return insertValues; } } }