2025-03-13 23:06:32 +01:00
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
2024-02-28 15:45:30 +01:00
// DEALINGS IN THE SOFTWARE.
2013-06-20 20:17:10 +02:00
2025-04-03 15:24:57 +02:00
#nullable enable
2013-06-20 20:17:10 +02:00
using System ;
2025-03-14 14:34:56 +01:00
using System.Data ;
2013-06-20 20:17:10 +02:00
using System.Collections.Generic ;
2019-09-29 20:16:28 -07:00
using Duplicati.Library.Utility ;
2013-06-20 20:17:10 +02:00
namespace Duplicati.Library.Main.Database
{
2013-08-22 20:52:54 +02:00
internal class LocalListChangesDatabase : LocalDatabase
2013-06-20 20:17:10 +02:00
{
2025-04-22 14:26:46 +02:00
public LocalListChangesDatabase ( string path , long pagecachesize )
: base ( path , "ListChanges" , false , pagecachesize )
2013-06-20 20:17:10 +02:00
{
2016-04-06 20:40:34 +02:00
ShouldCloseConnection = true ;
2013-06-20 20:17:10 +02:00
}
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
public interface IStorageHelper : IDisposable
{
2025-03-14 14:34:56 +01:00
void AddElement ( string path , string filehash , string metahash , long size , Interface . ListChangesElementType type , bool asNew );
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
void AddFromDb ( long filesetId , bool asNew , IFilter filter );
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
IChangeCountReport CreateChangeCountReport ();
IChangeSizeReport CreateChangeSizeReport ();
2025-03-14 14:34:56 +01:00
IEnumerable < Tuple < Interface . ListChangesChangeType , Interface . ListChangesElementType , string >> CreateChangedFileReport ();
2013-06-20 20:17:10 +02:00
}
public interface IChangeCountReport
{
long AddedFolders { get ; }
long AddedSymlinks { get ; }
long AddedFiles { get ; }
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
long DeletedFolders { get ; }
long DeletedSymlinks { get ; }
long DeletedFiles { get ; }
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
long ModifiedFolders { get ; }
long ModifiedSymlinks { get ; }
long ModifiedFiles { get ; }
}
public interface IChangeSizeReport
{
long AddedSize { get ; }
long DeletedSize { get ; }
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
long PreviousSize { get ; }
long CurrentSize { get ; }
}
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
internal class ChangeCountReport : IChangeCountReport
{
2025-03-14 12:13:17 +01:00
public long AddedFolders { get ; internal set ; }
public long AddedSymlinks { get ; internal set ; }
2013-06-20 20:17:10 +02:00
public long AddedFiles { get ; internal set ; }
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
public long DeletedFolders { get ; internal set ; }
2025-03-14 12:13:17 +01:00
public long DeletedSymlinks { get ; internal set ; }
public long DeletedFiles { get ; internal set ; }
2013-06-20 20:17:10 +02:00
public long ModifiedFolders { get ; internal set ; }
public long ModifiedSymlinks { get ; internal set ; }
public long ModifiedFiles { get ; internal set ; }
}
internal class ChangeSizeReport : IChangeSizeReport
{
public long AddedSize { get ; internal set ; }
public long DeletedSize { get ; internal set ; }
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
public long PreviousSize { get ; internal set ; }
public long CurrentSize { get ; internal set ; }
}
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
private class StorageHelper : IStorageHelper
{
2025-03-14 14:34:56 +01:00
private readonly IDbConnection m_connection ;
private IDbTransaction m_transaction ;
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
private IDbCommand m_insertPreviousElementCommand ;
private IDbCommand m_insertCurrentElementCommand ;
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
private string m_previousTable ;
private string m_currentTable ;
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
public StorageHelper ( IDbConnection con )
2013-06-20 20:17:10 +02:00
{
m_connection = con ;
m_previousTable = "Previous-" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
m_currentTable = "Current-" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
2025-03-14 12:13:17 +01:00
2025-04-14 12:04:00 +02:00
m_transaction = m_connection . BeginTransactionSafe ();
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
using ( var cmd = m_connection . CreateCommand ( m_transaction ))
2013-06-20 20:17:10 +02:00
{
2025-03-14 12:13:17 +01:00
cmd . ExecuteNonQuery ( FormatInvariant ( $@"CREATE TEMPORARY TABLE ""{m_previousTable}"" (""Path"" TEXT NOT NULL, ""FileHash"" TEXT NULL, ""MetaHash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL, ""Type"" INTEGER NOT NULL) " ));
cmd . ExecuteNonQuery ( FormatInvariant ( $@"CREATE TEMPORARY TABLE ""{m_currentTable}"" (""Path"" TEXT NOT NULL, ""FileHash"" TEXT NULL, ""MetaHash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL, ""Type"" INTEGER NOT NULL) " ));
2013-06-20 20:17:10 +02:00
}
2025-03-14 12:13:17 +01:00
2025-04-02 14:32:39 +02:00
m_insertPreviousElementCommand = m_connection . CreateCommand ( m_transaction , FormatInvariant ( $@"INSERT INTO ""{m_previousTable}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") VALUES (@Path,@FileHash,@MetaHash,@Size,@Type)" ));
m_insertCurrentElementCommand = m_connection . CreateCommand ( FormatInvariant ( $@"INSERT INTO ""{m_currentTable}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") VALUES (@Path,@FileHash,@MetaHash,@Size,@Type)" ));
2013-06-20 20:17:10 +02:00
}
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
public void AddFromDb ( long filesetId , bool asNew , IFilter filter )
2013-06-20 20:17:10 +02:00
{
var tablename = asNew ? m_currentTable : m_previousTable ;
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
var folders = FormatInvariant ( $@"SELECT ""File"".""Path"" AS ""Path"", NULL AS ""FileHash"", ""Blockset"".""Fullhash"" AS ""MetaHash"", -1 AS ""Size"", {(int)Interface.ListChangesElementType.Folder} AS ""Type"", ""FilesetEntry"".""FilesetID"" AS ""FilesetID"" FROM ""File"",""FilesetEntry"",""Metadataset"",""Blockset"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""File"".""BlocksetID"" = -100 AND ""Metadataset"".""ID""=""File"".""MetadataID"" AND ""Metadataset"".""BlocksetID"" = ""Blockset"".""ID"" " );
var symlinks = FormatInvariant ( $@"SELECT ""File"".""Path"" AS ""Path"", NULL AS ""FileHash"", ""Blockset"".""Fullhash"" AS ""MetaHash"", -1 AS ""Size"", {(int)Interface.ListChangesElementType.Symlink} AS ""Type"", ""FilesetEntry"".""FilesetID"" AS ""FilesetID"" FROM ""File"",""FilesetEntry"",""Metadataset"",""Blockset"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""File"".""BlocksetID"" = -200 AND ""Metadataset"".""ID""=""File"".""MetadataID"" AND ""Metadataset"".""BlocksetID"" = ""Blockset"".""ID"" " );
var files = FormatInvariant ( $@"SELECT ""File"".""Path"" AS ""Path"", ""FB"".""FullHash"" AS ""FileHash"", ""MB"".""Fullhash"" AS ""MetaHash"", ""FB"".""Length"" AS ""Size"", {(int)Interface.ListChangesElementType.File} AS ""Type"", ""FilesetEntry"".""FilesetID"" AS ""FilesetID"" FROM ""File"",""FilesetEntry"",""Metadataset"",""Blockset"" MB, ""Blockset"" FB WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""File"".""BlocksetID"" >= 0 AND ""Metadataset"".""ID""=""File"".""MetadataID"" AND ""Metadataset"".""BlocksetID"" = ""MB"".""ID"" AND ""File"".""BlocksetID"" = ""FB"".""ID"" " );
2013-06-20 20:17:10 +02:00
var combined = "(" + folders + " UNION " + symlinks + " UNION " + files + ")" ;
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
using ( var cmd = m_connection . CreateCommand ( m_transaction ))
2013-06-20 20:17:10 +02:00
{
if ( filter == null || filter . Empty )
{
// Simple case, select everything
2025-04-02 14:32:39 +02:00
cmd . SetCommandAndParameters ( FormatInvariant ( $@"INSERT INTO ""{tablename}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") SELECT ""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"" FROM {combined} A WHERE ""A"".""FilesetID"" = @FilesetId " ))
. SetParameterValue ( "@FilesetId" , filesetId )
. ExecuteNonQuery ();
2013-06-20 20:17:10 +02:00
}
2019-09-29 20:16:28 -07:00
else if ( Library . Utility . Utility . IsFSCaseSensitive && filter is FilterExpression expression && expression . Type == Duplicati . Library . Utility . FilterType . Simple )
2013-06-20 20:17:10 +02:00
{
// File list based
2015-09-15 19:29:58 +02:00
// unfortunately we cannot do this if the filesystem is case sensitive as
// SQLite only supports ASCII compares
2019-09-29 20:16:28 -07:00
var p = expression . GetSimpleList ();
2013-06-20 20:17:10 +02:00
var filenamestable = "Filenames-" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
2025-03-14 12:13:17 +01:00
cmd . ExecuteNonQuery ( FormatInvariant ( $@"CREATE TEMPORARY TABLE ""{filenamestable}"" (""Path"" TEXT NOT NULL) " ));
2025-04-02 14:32:39 +02:00
cmd . SetCommandAndParameters ( FormatInvariant ( $@"INSERT INTO ""{filenamestable}"" (""Path"") VALUES (@Path)" ));
2025-03-14 12:13:17 +01:00
foreach ( var s in p )
2025-04-02 14:32:39 +02:00
cmd . SetParameterValue ( "@Path" , s )
. ExecuteNonQuery ();
2024-06-10 16:00:39 +02:00
string whereClause ;
if ( expression . Result )
{
// Include filter
2025-04-02 14:32:39 +02:00
whereClause = FormatInvariant ( $@"""A"".""FilesetID"" = @FilesetId AND ""A"".""Path"" IN (SELECT DISTINCT ""Path"" FROM ""{filenamestable}"")" );
2024-06-10 16:00:39 +02:00
}
else
{
// Exclude filter
2025-04-02 14:32:39 +02:00
whereClause = FormatInvariant ( $@"""A"".""FilesetID"" = @FilesetId AND ""A"".""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""{filenamestable}"")" );
2024-06-10 16:00:39 +02:00
}
2025-04-02 14:32:39 +02:00
cmd . SetCommandAndParameters ( FormatInvariant ( $@"INSERT INTO ""{tablename}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") SELECT ""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"" FROM {combined} A WHERE {whereClause} " ))
. SetParameterValue ( "@FilesetId" , filesetId )
. ExecuteNonQuery ();
2025-03-14 12:13:17 +01:00
cmd . ExecuteNonQuery ( FormatInvariant ( $@"DROP TABLE IF EXISTS ""{filenamestable}"" " ));
2013-06-20 20:17:10 +02:00
}
else
{
// Do row-wise iteration
2025-03-14 14:34:56 +01:00
var values = new object [ 5 ];
2025-04-02 14:32:39 +02:00
cmd . SetCommandAndParameters ( FormatInvariant ( $@"SELECT ""A"".""Path"", ""A"".""FileHash"", ""A"".""MetaHash"", ""A"".""Size"", ""A"".""Type"" FROM {combined} A WHERE ""A"".""FilesetID"" = @FilesetId" ))
. SetParameterValue ( "@FilesetId" , filesetId );
using ( var cmd2 = m_connection . CreateCommand ( m_transaction , FormatInvariant ( $@"INSERT INTO ""{tablename}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") VALUES (@Path,@FileHash,@MetaHash,@Size,@Type)" )))
using ( var rd = cmd . ExecuteReader ())
2025-03-14 14:34:56 +01:00
while ( rd . Read ())
{
rd . GetValues ( values );
if ( values [ 0 ] != null && values [ 0 ] != DBNull . Value && FilterExpression . Matches ( filter , values [ 0 ]. ToString ()))
2013-06-20 20:17:10 +02:00
{
2025-04-02 14:32:39 +02:00
cmd2 . SetParameterValue ( "@Path" , values [ 0 ])
. SetParameterValue ( "@FileHash" , values [ 1 ])
. SetParameterValue ( "@MetaHash" , values [ 2 ])
. SetParameterValue ( "@Size" , values [ 3 ])
. SetParameterValue ( "@Type" , values [ 4 ])
. ExecuteNonQuery ();
2013-06-20 20:17:10 +02:00
}
2025-03-14 14:34:56 +01:00
}
2013-06-20 20:17:10 +02:00
}
}
}
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
public void AddElement ( string path , string filehash , string metahash , long size , Interface . ListChangesElementType type , bool asNew )
2013-06-20 20:17:10 +02:00
{
var cmd = asNew ? m_insertCurrentElementCommand : m_insertPreviousElementCommand ;
2025-04-02 14:32:39 +02:00
cmd . SetParameterValue ( "@Path" , path );
cmd . SetParameterValue ( "@FileHash" , filehash );
2025-05-09 11:13:10 +02:00
cmd . SetParameterValue ( "@MetaHash" , metahash );
2025-04-02 14:32:39 +02:00
cmd . SetParameterValue ( "@Size" , size );
cmd . SetParameterValue ( "@Type" , ( int ) type );
2013-06-20 20:17:10 +02:00
cmd . ExecuteNonQuery ();
}
2025-04-03 15:24:57 +02:00
private static IEnumerable < string? > ReaderToStringList ( IDataReader rd )
2013-06-20 20:17:10 +02:00
{
2025-03-14 12:13:17 +01:00
using ( rd )
2013-06-20 20:17:10 +02:00
while ( rd . Read ())
{
var v = rd . GetValue ( 0 );
if ( v == null || v == DBNull . Value )
yield return null ;
else
yield return v . ToString ();
}
}
2025-03-14 12:13:17 +01:00
2025-04-02 14:32:39 +02:00
private ( string Added , string Deleted , string Modified ) GetSqls ( bool allTypes )
2013-06-20 20:17:10 +02:00
{
2025-04-02 14:32:39 +02:00
return (
2025-04-03 22:33:09 +02:00
FormatInvariant ( $@"SELECT ""Path"" FROM ""{m_currentTable}"" WHERE {(allTypes ? "" : FormatInvariant(@$" "" { m_currentTable } "" . "" Type "" = @Type AND "))} ""{m_currentTable}"".""Path"" NOT IN (SELECT ""Path"" FROM ""{m_previousTable}"")" ),
FormatInvariant ( $@"SELECT ""Path"" FROM ""{m_previousTable}"" WHERE {(allTypes ? "" : FormatInvariant(@$" "" { m_previousTable } "" . "" Type "" = @Type AND "))} ""{m_previousTable}"".""Path"" NOT IN (SELECT ""Path"" FROM ""{m_currentTable}"")" ),
FormatInvariant ( $@"SELECT ""{m_currentTable}"".""Path"" FROM ""{m_currentTable}"",""{m_previousTable}"" WHERE {(allTypes ? "" : FormatInvariant($@" "" { m_currentTable } "" . "" Type "" = @Type AND "))} ""{m_currentTable}"".""Path"" = ""{m_previousTable}"".""Path"" AND (""{m_currentTable}"".""FileHash"" != ""{m_previousTable}"".""FileHash"" OR ""{m_currentTable}"".""MetaHash"" != ""{m_previousTable}"".""MetaHash"" OR ""{m_currentTable}"".""Type"" != ""{m_previousTable}"".""Type"") " )
2013-06-20 20:17:10 +02:00
);
}
2015-01-24 21:59:53 +01:00
2013-06-20 20:17:10 +02:00
public IChangeSizeReport CreateChangeSizeReport ()
{
var sqls = GetSqls ( true );
2025-04-02 14:32:39 +02:00
var added = sqls . Added ;
var deleted = sqls . Deleted ;
//var modified = sqls.Modified;
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
using ( var cmd = m_connection . CreateCommand ( m_transaction ))
2013-06-20 20:17:10 +02:00
{
var result = new ChangeSizeReport ();
2025-03-14 12:13:17 +01:00
result . PreviousSize = cmd . ExecuteScalarInt64 ( FormatInvariant ( $@"SELECT SUM(""Size"") FROM ""{m_previousTable}"" " ), 0 );
result . CurrentSize = cmd . ExecuteScalarInt64 ( FormatInvariant ( $@"SELECT SUM(""Size"") FROM ""{m_currentTable}"" " ), 0 );
result . AddedSize = cmd . ExecuteScalarInt64 ( FormatInvariant ( $@"SELECT SUM(""Size"") FROM ""{m_currentTable}"" WHERE ""{m_currentTable}"".""Path"" IN ({added}) " ), 0 );
result . DeletedSize = cmd . ExecuteScalarInt64 ( FormatInvariant ( $@"SELECT SUM(""Size"") FROM ""{m_previousTable}"" WHERE ""{m_previousTable}"".""Path"" IN ({deleted}) " ), 0 );
2013-06-20 20:17:10 +02:00
return result ;
}
}
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
public IChangeCountReport CreateChangeCountReport ()
{
var sqls = GetSqls ( false );
2025-04-02 14:32:39 +02:00
var added = @"SELECT COUNT(*) FROM (" + sqls . Added + ")" ;
var deleted = @"SELECT COUNT(*) FROM (" + sqls . Deleted + ")" ;
var modified = @"SELECT COUNT(*) FROM (" + sqls . Modified + ")" ;
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
using ( var cmd = m_connection . CreateCommand ( m_transaction ))
2013-06-20 20:17:10 +02:00
{
var result = new ChangeCountReport ();
2025-04-02 14:32:39 +02:00
result . AddedFolders = cmd . SetCommandAndParameters ( added )
. SetParameterValue ( "@Type" , ( int ) Interface . ListChangesElementType . Folder )
. ExecuteScalarInt64 ( 0 );
result . AddedSymlinks = cmd . SetCommandAndParameters ( added )
. SetParameterValue ( "@Type" , ( int ) Interface . ListChangesElementType . Symlink )
. ExecuteScalarInt64 ( 0 );
result . AddedFiles = cmd . SetCommandAndParameters ( added )
. SetParameterValue ( "@Type" , ( int ) Interface . ListChangesElementType . File )
. ExecuteScalarInt64 ( 0 );
result . DeletedFolders = cmd . SetCommandAndParameters ( deleted )
. SetParameterValue ( "@Type" , ( int ) Interface . ListChangesElementType . Folder )
. ExecuteScalarInt64 ( 0 );
result . DeletedSymlinks = cmd . SetCommandAndParameters ( deleted )
. SetParameterValue ( "@Type" , ( int ) Interface . ListChangesElementType . Symlink )
. ExecuteScalarInt64 ( 0 );
result . DeletedFiles = cmd . SetCommandAndParameters ( deleted )
. SetParameterValue ( "@Type" , ( int ) Interface . ListChangesElementType . File )
. ExecuteScalarInt64 ( 0 );
result . ModifiedFolders = cmd . SetCommandAndParameters ( modified )
. SetParameterValue ( "@Type" , ( int ) Interface . ListChangesElementType . Folder )
. ExecuteScalarInt64 ( 0 );
result . ModifiedSymlinks = cmd . SetCommandAndParameters ( modified )
. SetParameterValue ( "@Type" , ( int ) Interface . ListChangesElementType . Symlink )
. ExecuteScalarInt64 ( 0 );
result . ModifiedFiles = cmd . SetCommandAndParameters ( modified )
. SetParameterValue ( "@Type" , ( int ) Interface . ListChangesElementType . File )
. ExecuteScalarInt64 ( 0 );
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
return result ;
2025-03-14 12:13:17 +01:00
}
2013-06-20 20:17:10 +02:00
}
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
public IEnumerable < Tuple < Interface . ListChangesChangeType , Interface . ListChangesElementType , string >> CreateChangedFileReport ()
2013-06-20 20:17:10 +02:00
{
var sqls = GetSqls ( false );
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
using ( var cmd = m_connection . CreateCommand ( m_transaction ))
2013-06-20 20:17:10 +02:00
{
2025-04-02 14:32:39 +02:00
var elTypes = new [] {
Interface . ListChangesElementType . Folder ,
Interface . ListChangesElementType . Symlink ,
Interface . ListChangesElementType . File
};
IEnumerable < Tuple < Interface . ListChangesChangeType , Interface . ListChangesElementType , string >> BuildResult ( IDbCommand cmd , string sql , Interface . ListChangesChangeType changeType )
{
cmd . SetCommandAndParameters ( sql );
foreach ( var type in elTypes )
foreach ( var s in ReaderToStringList ( cmd . SetParameterValue ( "@Type" , ( int ) type ). ExecuteReader ()))
2025-04-03 15:24:57 +02:00
yield return new Tuple < Interface . ListChangesChangeType , Interface . ListChangesElementType , string >( changeType , type , s ?? "" );
2025-04-02 14:32:39 +02:00
}
foreach ( var r in BuildResult ( cmd , sqls . Added , Interface . ListChangesChangeType . Added ))
yield return r ;
foreach ( var r in BuildResult ( cmd , sqls . Deleted , Interface . ListChangesChangeType . Deleted ))
yield return r ;
foreach ( var r in BuildResult ( cmd , sqls . Modified , Interface . ListChangesChangeType . Modified ))
yield return r ;
2013-06-20 20:17:10 +02:00
}
}
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
public void Dispose ()
{
if ( m_insertPreviousElementCommand != null )
{
try { m_insertPreviousElementCommand . Dispose (); }
2025-03-14 12:13:17 +01:00
catch { }
2025-04-03 15:24:57 +02:00
finally { m_insertPreviousElementCommand = null !; }
2013-06-20 20:17:10 +02:00
}
if ( m_insertCurrentElementCommand != null )
{
try { m_insertCurrentElementCommand . Dispose (); }
2025-03-14 12:13:17 +01:00
catch { }
2025-04-03 15:24:57 +02:00
finally { m_insertCurrentElementCommand = null !; }
2013-06-20 20:17:10 +02:00
}
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
if ( m_transaction != null )
{
try { m_transaction . Rollback (); }
2025-03-14 12:13:17 +01:00
catch { }
2013-06-20 20:17:10 +02:00
finally
{
2025-04-03 15:24:57 +02:00
m_previousTable = null !;
m_currentTable = null !;
m_transaction = null !;
2013-06-20 20:17:10 +02:00
}
}
}
}
2025-03-14 12:13:17 +01:00
2013-06-20 20:17:10 +02:00
public IStorageHelper CreateStorageHelper ()
{
return new StorageHelper ( m_connection );
}
}
}