package org.tmatesoft.svn.core.internal.wc17.db; import static org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbStatementUtil.getColumnBlob; import static org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbStatementUtil.getColumnInheritedProperties; import static org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbStatementUtil.getColumnInt64; import static org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbStatementUtil.getColumnKind; import static org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbStatementUtil.getColumnPath; import static org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbStatementUtil.getColumnPresence; import static org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbStatementUtil.getColumnProperties; import static org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbStatementUtil.isColumnNull; import static org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbStatementUtil.reset; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; 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.SVNDepth; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; 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.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.StructureFields.InheritedProperties; import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbNodesBase; import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbNodesCurrent; 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.SVNWCDbSchema.WCROOT__Fields; import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbSelectIPropsNode; import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbStatements; import org.tmatesoft.svn.core.wc2.ISvnObjectReceiver; import org.tmatesoft.svn.core.wc2.SvnTarget; import org.tmatesoft.svn.util.SVNLogType; 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 readChangedProperties(SVNWCDbRoot root, File relpath) throws SVNException { 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)) { return getColumnProperties(stmt, ACTUAL_NODE__Fields.properties); } } finally { reset(stmt); } return null; } 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 { final Collection<Properties> propsList = cacheProperties(root, relpath, depth, baseProperties, pristineProperties, changelists); for (Properties properties : propsList) { SVNProperties props = SVNSqlJetStatement.parseProperties(properties.properties); File target = new File(properties.relPath); File absolutePath = root.getAbsPath(target); receiver.receive(SvnTarget.fromFile(absolutePath), props); } } /* 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 class Properties { public Properties(String path, byte[] props) { this.relPath = path; this.properties = props; } public String relPath; public byte[] properties; } private static Collection<SvnWcDbProperties.Properties> cacheProperties(SVNWCDbRoot root, File relpath, SVNDepth depth, boolean baseProperties, boolean pristineProperties, Collection<String> changelists) throws SVNException { SVNSqlJetSelectStatement propertiesSelectStmt = null; root.getSDb().beginTransaction(SqlJetTransactionMode.READ_ONLY); Collection<Properties> result = new ArrayList<SvnWcDbProperties.Properties>(); try { // 1. get targets list (relpath, wcid, kind) final Collection<Target> targets = collectTargets(root, relpath, depth, changelists); if (baseProperties) { propertiesSelectStmt = new SVNWCDbNodesBase(root.getSDb()); } else if (pristineProperties) { propertiesSelectStmt = new SVNWCDbNodesCurrent(root.getSDb()); } else { propertiesSelectStmt = new SVNWCDbNodesCurrent(root.getSDb()); } // 2. for each target select properties. for (Target target : targets) { String localRelpath = target.relPath; long wcId = target.wcId; 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); } // 3. put props into result map. if (props != null && props.length > 2) { result.add(new Properties(localRelpath, props)); } } } finally { try { reset(propertiesSelectStmt); } finally { root.getSDb().commit(); } } return result; } public static Map<File, File> getInheritedPropertiesNodes(SVNWCDbRoot root, File localRelPath, SVNDepth depth) throws SVNException { final Map<File, File> result = new HashMap<File, File>(); SVNWCDbSelectIPropsNode stmt = null; stmt = (SVNWCDbSelectIPropsNode) root.getSDb().getStatement(SVNWCDbStatements.SELECT_IPROPS_NODE); try { stmt.setDepth(SVNDepth.EMPTY); stmt.bindf("is", root.getWcId(), localRelPath); if (stmt.next()) { final File path = root.getAbsPath(getColumnPath(stmt, NODES__Fields.local_relpath)); result.put(path, getColumnPath(stmt, NODES__Fields.repos_path)); } } finally { reset(stmt); } if (depth == SVNDepth.EMPTY) { return result; } stmt = (SVNWCDbSelectIPropsNode) root.getSDb().getStatement(SVNWCDbStatements.SELECT_IPROPS_NODE); try { stmt.setDepth(depth); stmt.bindf("is", root.getWcId(), localRelPath); while(stmt.next()) { final File path = root.getAbsPath(getColumnPath(stmt, NODES__Fields.local_relpath)); result.put(path, getColumnPath(stmt, NODES__Fields.repos_path)); } } finally { reset(stmt); } return result; } public static List<Structure<InheritedProperties>> readInheritedProperties(SVNWCDbRoot root, File localRelPath, String propertyName) throws SVNException { SVNSqlJetStatement stmt = null; File relPath = localRelPath; File parentRelPath = null; File expectedParentReposRelPath = null; final List<Structure<InheritedProperties>> inheritedProperties = new ArrayList<Structure<InheritedProperties>>(); List<Structure<InheritedProperties>> cachedProperties = null; try { stmt = root.getSDb().getStatement(SVNWCDbStatements.SELECT_NODE_INFO); while(relPath != null) { SVNProperties nodeProps = null; parentRelPath = "".equals(relPath.getPath()) ? null : SVNFileUtil.getFileDir(relPath); stmt.bindf("is", root.getWcId(), relPath); if (!stmt.next()) { nodeNotFound(root, relPath); } final long opDepth = getColumnInt64(stmt, NODES__Fields.op_depth); SVNWCDbStatus status = getColumnPresence(stmt, NODES__Fields.presence); if (status != SVNWCDbStatus.Normal && status != SVNWCDbStatus.Incomplete) { final SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_PATH_UNEXPECTED_STATUS, "The node ''{0}'' has a status that has no properites", root.getAbsPath(relPath)); SVNErrorManager.error(err, SVNLogType.WC); } if (opDepth > 0) { } else if (expectedParentReposRelPath != null) { final File reposRelPath = getColumnPath(stmt, NODES__Fields.repos_path); if (!expectedParentReposRelPath.equals(reposRelPath)) { reset(stmt); break; } expectedParentReposRelPath = SVNFileUtil.getFileDir(expectedParentReposRelPath); } else { final File reposRelPath = getColumnPath(stmt, NODES__Fields.repos_path); expectedParentReposRelPath = SVNFileUtil.getFileDir(reposRelPath); } if (opDepth == 0 && !isColumnNull(stmt, NODES__Fields.inherited_props)) { final byte[] inheritedPropsBlob = getColumnBlob(stmt, NODES__Fields.inherited_props); if (inheritedPropsBlob != null && !Arrays.equals(SvnWcDbShared.EMPTY_PROPS_BLOB, inheritedPropsBlob)) { cachedProperties = getColumnInheritedProperties(stmt, NODES__Fields.inherited_props); parentRelPath = null; } } nodeProps = getColumnProperties(stmt, NODES__Fields.properties); reset(stmt); if (!relPath.equals(localRelPath)) { final SVNProperties changedProps = readChangedProperties(root, relPath); if (changedProps != null) { nodeProps = changedProps; } if (nodeProps != null && !nodeProps.isEmpty()) { if (propertyName != null) { final SVNProperties filteredProperites = new SVNProperties(); if (nodeProps.containsName(propertyName)) { filteredProperites.put(propertyName, nodeProps.getSVNPropertyValue(propertyName)); } nodeProps = filteredProperites; } if (nodeProps != null && !nodeProps.isEmpty()) { final Structure<InheritedProperties> inheritedProperitesElement = Structure.obtain(InheritedProperties.class); inheritedProperitesElement.set(InheritedProperties.pathOrURL, SVNFileUtil.getFilePath(root.getAbsPath(relPath))); inheritedProperitesElement.set(InheritedProperties.properties, nodeProps); inheritedProperties.add(0, inheritedProperitesElement); } } } relPath = parentRelPath; } if (cachedProperties != null) { for (Structure<InheritedProperties> element : cachedProperties) { SVNProperties props = element.get(InheritedProperties.properties); if (props == null || props.isEmpty()) { continue; } if (propertyName != null) { if (!props.containsName(propertyName)) { continue; } final SVNProperties filteredProperties = new SVNProperties(); filteredProperties.put(propertyName, props.getSVNPropertyValue(propertyName)); props = filteredProperties; } if (!props.isEmpty()) { element.set(InheritedProperties.properties, props); inheritedProperties.add(0, element); } } } } finally { reset(stmt); } return inheritedProperties; } /* * 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; } } }