m_findblockCommand.CommandText=@"SELECT ""ID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ?";
m_findblockCommand.AddParameters(2);
m_findblocksetCommand.CommandText=@"SELECT ""ID"" FROM ""Blockset"" WHERE ""Fullhash"" = ? AND ""Length"" = ?";
m_findblocksetCommand.AddParameters(2);
m_findmetadatasetCommand.CommandText=@"SELECT ""A"".""ID"" FROM ""Metadataset"" A, ""BlocksetEntry"" B, ""Block"" C WHERE ""A"".""BlocksetID"" = ""B"".""BlocksetID"" AND ""B"".""BlockID"" = ""C"".""ID"" AND ""C"".""Hash"" = ? AND ""C"".""Size"" = ?";
m_findmetadatasetCommand.AddParameters(2);
m_findfilesetCommand.CommandText=@"SELECT ""ID"" FROM ""FileLookup"" WHERE ""BlocksetID"" = ? AND ""MetadataID"" = ? AND ""Path"" = ? AND ""PrefixID"" = ?";
m_insertblocksetentryCommand.CommandText=@"INSERT INTO ""BlocksetEntry"" (""BlocksetID"", ""Index"", ""BlockID"") SELECT ? AS A, ? AS B, ""ID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ?";
m_insertblocksetentryCommand.AddParameters(4);
m_insertblocklistHashesCommand.CommandText=@"INSERT INTO ""BlocklistHash"" (""BlocksetID"", ""Index"", ""Hash"") VALUES (?, ?, ?)";
m_insertblocklistHashesCommand.AddParameters(3);
m_insertmetadatasetCommand.CommandText=@"INSERT INTO ""Metadataset"" (""BlocksetID"") VALUES (?); SELECT last_insert_rowid();";
m_insertmetadatasetCommand.AddParameter();
m_selectfilelastmodifiedCommand.CommandText=@"SELECT ""A"".""ID"", ""B"".""LastModified"" FROM (SELECT ""ID"" FROM ""FileLookup"" WHERE ""PrefixID"" = ? AND ""Path"" = ?) ""A"" CROSS JOIN ""FilesetEntry"" ""B"" WHERE ""A"".""ID"" = ""B"".""FileID"" AND ""B"".""FilesetID"" = ?";
m_selectfilelastmodifiedCommand.AddParameters(3);
m_selectfilelastmodifiedWithSizeCommand.CommandText=@"SELECT ""C"".""ID"", ""C"".""LastModified"", ""D"".""Length"" FROM (SELECT ""A"".""ID"", ""B"".""LastModified"", ""A"".""BlocksetID"" FROM (SELECT ""ID"", ""BlocksetID"" FROM ""FileLookup"" WHERE ""PrefixID"" = ? AND ""Path"" = ?) ""A"" CROSS JOIN ""FilesetEntry"" ""B"" WHERE ""A"".""ID"" = ""B"".""FileID"" AND ""B"".""FilesetID"" = ?) AS ""C"", ""Blockset"" AS ""D"" WHERE ""C"".""BlocksetID"" == ""D"".""ID"" ";
m_selectfilemetadatahashandsizeCommand.CommandText=@"SELECT ""Blockset"".""Length"", ""Blockset"".""FullHash"" FROM ""Blockset"", ""Metadataset"", ""File"" WHERE ""File"".""ID"" = ? AND ""Blockset"".""ID"" = ""Metadataset"".""BlocksetID"" AND ""Metadataset"".""ID"" = ""File"".""MetadataID"" ";
Logging.Log.WriteWarningMessage(LOGTAG,"TestFileQuery",null,"Using performance test query version {0} as the TEST_QUERY_VERSION environment variable is set",testqueryversion);
// The original query (v==1) finds the most recent entry of the file in question,
// but it requires some large joins to extract the required information.
// To speed it up, we use a slightly simpler approach that only looks at the
// previous fileset, and uses information here.
// If there is a case where a file is sometimes there and sometimes not
// (i.e. filter file, remove filter) we will not find the file.
// We currently use this faster version,
// but allow users to switch back via an environment variable
// such that we can get performance feedback
switch(testqueryversion)
{
// The query used in Duplicati until 2.0.3.9
case1:
m_findfileCommand.CommandText=
@" SELECT ""FileLookup"".""ID"" AS ""FileID"", ""FilesetEntry"".""Lastmodified"", ""FileBlockset"".""Length"", ""MetaBlockset"".""Fullhash"" AS ""Metahash"", ""MetaBlockset"".""Length"" AS ""Metasize"" "+
@" SELECT FileLookup.ID as FileID, FilesetEntry.Lastmodified, FileBlockset.Length, "+
@" MetaBlockset.FullHash AS Metahash, MetaBlockset.Length as Metasize "+
@" FROM FilesetEntry "+
@"INNER JOIN Fileset ON (FileSet.ID = FilesetEntry.FilesetID) "+
@"INNER JOIN FileLookup ON (FileLookup.ID = FilesetEntry.FileID) "+
@"INNER JOIN Metadataset ON (Metadataset.ID = FileLookup.MetadataID) "+
@"INNER JOIN Blockset AS MetaBlockset ON (MetaBlockset.ID = Metadataset.BlocksetID) "+
@" LEFT JOIN Blockset AS FileBlockset ON (FileBlockset.ID = FileLookup.BlocksetID) "+
@" WHERE FileLookup.PrefixID = ? AND FileLookup.Path = ? AND FilesetID = ? "+
@" LIMIT 1 ";
break;
// The slow query used in Duplicati 2.0.3.10, but with "LIMIT 1" added
case4:
m_findfileCommand.CommandText=
@" SELECT ""FileLookup"".""ID"" AS ""FileID"", ""FilesetEntry"".""Lastmodified"", ""FileBlockset"".""Length"", ""MetaBlockset"".""Fullhash"" AS ""Metahash"", ""MetaBlockset"".""Length"" AS ""Metasize"" "+
@" WHERE ""FileLookup"".""PrefixID"" = ? AND ""FileLookup"".""Path"" = ? "+
@" AND ""Fileset"".""ID"" = ? "+
@" AND ""FilesetEntry"".""FileID"" = ""FileLookup"".""ID"" AND ""Fileset"".""ID"" = ""FilesetEntry"".""FilesetID"" "+
@" AND ""FileBlockset"".""ID"" = ""FileLookup"".""BlocksetID"" "+
@" AND ""Metadataset"".""ID"" = ""FileLookup"".""MetadataID"" AND ""MetaBlockset"".""ID"" = ""Metadataset"".""BlocksetID"" "+
@" LIMIT 1 ";
break;
}
m_findfileCommand.AddParameters(3);
m_selectfileHashCommand.CommandText=@"SELECT ""Blockset"".""Fullhash"" FROM ""Blockset"", ""FileLookup"" WHERE ""Blockset"".""ID"" = ""FileLookup"".""BlocksetID"" AND ""FileLookup"".""ID"" = ? ";
m_selectfileHashCommand.AddParameters(1);
m_selectblocklistHashesCommand.CommandText=@"SELECT ""Hash"" FROM ""BlocklistHash"" WHERE ""BlocksetID"" = ? ORDER BY ""Index"" ASC ";
m_selectblocklistHashesCommand.AddParameters(1);
}
/// <summary>
/// Builds the lookup tables. Call this method after deleting items, and before processing items
Logging.Log.WriteWarningMessage(LOGTAG,"BlockCacheFailure",ex,"Failed to create block cache, this could mean you have hash collisions in your table, the hash that failed is {0}. Error message: {1}.",failedhash,ex.Message);
Logging.Log.WriteWarningMessage(LOGTAG,"BlockCacheFailure",null,"Disabling block cache due to error");
}
}
}
/// <summary>
/// Probes to see if a block already exists
/// </summary>
/// <param name="key">The block key</param>
/// <param name="size">The size of the block</param>
/// <returns>True if the block should be added to the current output</returns>
Logging.Log.WriteWarningMessage(LOGTAG,"HashCollisionsFound",null,"Found hash collision on {0}, sizes {1} vs {2}. Disabling cache from now on.",key,size,exsize);
varbid=cmd.ExecuteScalarInt64(@"SELECT ""ID"" FROM ""Block"" WHERE ""Hash"" = ?",-1,h);
if(bid==-1)
thrownewException(string.Format("Could not find any blocks with the given hash: {0}",h));
foreach(varrdincmd.ExecuteReaderEnumerable(@"SELECT ""Size"" FROM ""Block"" WHERE ""Hash"" = ?",h))
Logging.Log.WriteErrorMessage(LOGTAG,"FoundIssue1400Error",null,"Found block with ID {0} and hash {1} and size {2}",bid,h,rd.ConvertValueToInt64(0,-1));
}
thrownewException(string.Format("Unexpected result count: {0}, expected {1}, check log for more messages",c,1));
}
ix++;
remainsize-=blocksize;
}
tr.Commit();
}
returntrue;
}
/// <summary>
/// Gets the metadataset ID from the filehash
/// </summary>
/// <returns><c>true</c>, if metadataset found, false if does not exist.</returns>
varlastFilesetId=cmd.ExecuteScalarInt64(@"SELECT ""ID"" FROM ""Fileset"" WHERE ""Timestamp"" < ? AND ""ID"" != ? ORDER BY ""Timestamp"" DESC ",-1,NormalizeDateTimeToEpochSeconds(timestamp),filesetid);
varlastFilesetId=cmd.ExecuteScalarInt64(@"SELECT ""ID"" FROM ""Fileset"" ORDER BY ""Timestamp"" DESC LIMIT 1");
varcount=cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""FileLookup"" INNER JOIN ""FilesetEntry"" ON ""FileLookup"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""FileLookup"".""BlocksetID"" NOT IN (?, ?)",-1,lastFilesetId,FOLDER_BLOCKSET_ID,SYMLINK_BLOCKSET_ID);
varsize=cmd.ExecuteScalarInt64(@"SELECT SUM(""Blockset"".""Length"") FROM ""FileLookup"", ""FilesetEntry"", ""Blockset"" WHERE ""FileLookup"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FileLookup"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FilesetEntry"".""FilesetID"" = ? AND ""FileLookup"".""BlocksetID"" NOT IN (?, ?)",-1,lastFilesetId,FOLDER_BLOCKSET_ID,SYMLINK_BLOCKSET_ID);
// TODO: Optimize these queries to not use the "File" view
varlastFilesetId=GetPreviousFilesetID(cmd);
results.AddedFolders=cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" = ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ?)",0,m_filesetId,FOLDER_BLOCKSET_ID,lastFilesetId);
results.AddedSymlinks=cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" = ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ?)",0,m_filesetId,SYMLINK_BLOCKSET_ID,lastFilesetId);
results.DeletedFolders=cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" = ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ?)",0,lastFilesetId,FOLDER_BLOCKSET_ID,m_filesetId);
results.DeletedSymlinks=cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" = ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ?)",0,lastFilesetId,SYMLINK_BLOCKSET_ID,m_filesetId);
varsubqueryNonFiles=@"SELECT ""File"".""Path"", ""Blockset"".""Fullhash"" FROM ""File"", ""FilesetEntry"", ""Metadataset"", ""Blockset"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""Metadataset"".""ID"" = ""File"".""MetadataID"" AND ""File"".""BlocksetID"" = ? AND ""Metadataset"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FilesetEntry"".""FilesetID"" = ? ";
results.ModifiedFolders=cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ("+subqueryNonFiles+@") A, ("+subqueryNonFiles+@") B WHERE ""A"".""Path"" = ""B"".""Path"" AND ""A"".""Fullhash"" != ""B"".""Fullhash"" ",0,lastFilesetId,FOLDER_BLOCKSET_ID,m_filesetId,FOLDER_BLOCKSET_ID);
results.ModifiedSymlinks=cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ("+subqueryNonFiles+@") A, ("+subqueryNonFiles+@") B WHERE ""A"".""Path"" = ""B"".""Path"" AND ""A"".""Fullhash"" != ""B"".""Fullhash"" ",0,lastFilesetId,SYMLINK_BLOCKSET_ID,m_filesetId,SYMLINK_BLOCKSET_ID);
varsubqueryFiles=@"SELECT ""File"".""Path"" AS ""Path"", ""A"".""Fullhash"" AS ""Filehash"", ""B"".""Fullhash"" AS ""Metahash"" FROM ""File"", ""FilesetEntry"", ""Blockset"" A, ""Blockset"" B, ""Metadataset"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""A"".""ID"" = ""File"".""BlocksetID"" AND ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""MetadataID"" = ""Metadataset"".""ID"" AND ""Metadataset"".""BlocksetID"" = ""B"".""ID"" ";
cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" AS "+subqueryFiles,tmpName1),lastFilesetId);
cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" AS "+subqueryFiles,tmpName2),m_filesetId);
results.AddedFiles=cmd.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" != ? AND ""File"".""BlocksetID"" != ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""{0}"")",tmpName1),0,m_filesetId,FOLDER_BLOCKSET_ID,SYMLINK_BLOCKSET_ID);
results.DeletedFiles=cmd.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM ""{0}"" WHERE ""{0}"".""Path"" NOT IN (SELECT ""Path"" FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ?)",tmpName1),0,m_filesetId);
results.ModifiedFiles=cmd.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM ""{0}"" A, ""{1}"" B WHERE ""A"".""Path"" = ""B"".""Path"" AND (""A"".""Filehash"" != ""B"".""Filehash"" OR ""A"".""Metahash"" != ""B"".""Metahash"")",tmpName1,tmpName2),0);
}
finally
{
try{cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"";",tmpName1));}
cmd.ExecuteNonQuery(@"INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") SELECT ? AS ""FilesetID"", ""FileID"", ""Lastmodified"" FROM (SELECT DISTINCT ""FilesetID"", ""FileID"", ""Lastmodified"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ? AND ""FileID"" NOT IN (SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ?)) ",filesetid,lastFilesetId,filesetid);
if(deleted!=null)
{
cmdDelete.Transaction=tr.Parent;
cmdDelete.CommandText=@"DELETE FROM ""FilesetEntry"" WHERE ""FilesetID"" = ? AND ""FileID"" IN (SELECT ""ID"" FROM ""File"" WHERE ""Path"" = ?) ";
cmdDelete.AddParameters(2);
cmdDelete.SetParameterValue(0,filesetid);
foreach(stringsindeleted)
{
cmdDelete.SetParameterValue(1,s);
cmdDelete.ExecuteNonQuery();
}
}
tr.Commit();
}
}
/// <summary>
/// Populates FilesetEntry table with files from previous fileset, which aren't
/// yet part of the new fileset, and which aren't excluded by the (optional) exclusion
using(varrd=cmd.ExecuteReader(@"SELECT DISTINCT ""Fileset"".""ID"", ""Fileset"".""Timestamp"" FROM ""Fileset"", ""RemoteVolume"" WHERE ""RemoteVolume"".""ID"" = ""Fileset"".""VolumeID"" AND ""Fileset"".""ID"" IN (SELECT ""FilesetID"" FROM ""FilesetEntry"") AND (""RemoteVolume"".""State"" = ""Uploading"" OR ""RemoteVolume"".""State"" = ""Temporary"")"))
using(varrd=cmd.ExecuteReader(@"SELECT ""Name"" FROM ""RemoteVolume"" WHERE ""Type"" = ? AND NOT ""ID"" IN (SELECT ""BlockVolumeID"" FROM ""IndexBlockLink"") AND ""State"" IN (?,?)",RemoteVolumeType.Blocks.ToString(),RemoteVolumeState.Uploaded.ToString(),RemoteVolumeState.Verified.ToString()))
varc=cmd.ExecuteNonQuery(@"UPDATE ""Block"" SET ""VolumeID"" = ? WHERE ""Hash"" = ? AND ""Size"" = ? AND ""VolumeID"" = ? ",targetvolumeid,blockkey,size,sourcevolumeid);
if(c!=1)
thrownewException(string.Format("Failed to move block {0}:{1} from volume {2}, count: {3}",blockkey,size,sourcevolumeid,c));
// Grab the strings and return as array to avoid concurrent access to the IEnumerable
returncmd.ExecuteReaderEnumerable(
@"SELECT DISTINCT ""Block"".""Hash"" FROM ""Block"" WHERE ""Block"".""VolumeID"" = ? AND ""Block"".""Hash"" IN (SELECT ""Hash"" FROM ""BlocklistHash"")",volumeid)
.Select(x=>x.ConvertValueToString(0))
.ToArray();
}
}
publicstringGetFirstPath()
{
using(varcmd=m_connection.CreateCommand())
{
cmd.CommandText=@"SELECT ""Path"" FROM ""File"" ORDER BY LENGTH(""Path"") DESC LIMIT 1";