Click here to Skip to main content
15,892,697 members
Articles / Desktop Programming / Windows Forms

SQLite Compare Utility

Rate me:
Please Sign up or sign in to vote.
4.89/5 (68 votes)
21 Feb 2015LGPL35 min read 284.8K   37.2K   131  
Utility for comparing two SQLite database files for both structure and data
using System;
using System.IO;
using System.Threading;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Data.SQLite;
using Common;

namespace SQLiteTurbo
{
    /// <summary>
    /// This class provides utility methods for accessing BLOB fields in a way that
    /// doesn't force their contents to be loaded into main memory (aka BLOB streaming).
    /// </summary>
    /// <remarks>This class is not thread-safe. You should never call WriteBlob/ReadBlob on the same
    /// instance from two different threads simultaneously.</remarks>
    public class BlobReaderWriter : IDisposable
    {
        #region Constructors & Destructors
        /// <summary>
        /// Initialize the BlobReaderWriter object using the specified database file path.
        /// </summary>
        /// <param name="dbpath">The path to the database file that contains BLOB fields</param>
        public BlobReaderWriter(string dbpath, bool readOnly)
        {
            // Form a connection to the database file
            if (readOnly)
                _errorCode = sqlite3_open_v2(dbpath, ref _conn1, SQLITE_OPEN_READONLY, null);
            else
                _errorCode = sqlite3_open(dbpath, ref _conn1);
            if (_errorCode > 0)
                throw new SQLiteException(_errorCode, "can't open file: " + dbpath);
        }

        /// <summary>
        /// Initialize a dual database BlobReaderWriter object using the two database files
        /// that are specified in the parameters.
        /// </summary>
        /// <param name="dbpath1">The path to the first database file</param>
        /// <param name="dbpath2">The path to the second database file</param>
        public BlobReaderWriter(string dbpath1, string dbpath2)
        {
            // Form read-only connections to the database
            _errorCode = sqlite3_open_v2(dbpath1, ref _conn1, SQLITE_OPEN_READONLY, null);
            if (_errorCode > 0)
                throw new SQLiteException(_errorCode, "can't open file: " + dbpath1);
            _errorCode = sqlite3_open_v2(dbpath2, ref _conn2, SQLITE_OPEN_READONLY, null);
            if (_errorCode > 0)
                throw new SQLiteException(_errorCode, "can't open file: " + dbpath2);
        }

        ~BlobReaderWriter()
        {
            Dispose(false);
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Write to the BLOB field specified by the table, column and row id. The BLOB will have
        /// a size of <paramref name="blobLength"/> and its contents will be fetched by calling the
        /// <paramref name="writer"/>delegate.
        /// </summary>
        /// <param name="tableName">The name of the table where the BLOB field exists.</param>
        /// <param name="columnName">The name of the column where the BLOB field exists.</param>
        /// <param name="rowId">The Row ID of the row where the BLOB field exists.</param>
        /// <param name="blobLength">The size of the BLOB to be written. This has to be known in advance!</param>
        /// <param name="writer">A delegate that will be called in order to fetch the bytes needed for writing
        /// into the BLOB field.</param>
        public void WriteBlob(string tableName, string columnName, long rowId, int blobLength, BlobWriter writer)
        {
            if (_disposed)
                throw new ObjectDisposedException("Object was disposed.");

            tableName = SQLiteParser.Utils.Chop(tableName);
            columnName = SQLiteParser.Utils.Chop(columnName);

            // Prepare an empty blob to which I can write the contents of the file.
            long len = blobLength;            

            // Open the BLOB handle and decide if the BLOB needs zeroing.
            _blob1 = IntPtr.Zero;
            bool needsZeroing = false;
            _errorCode = sqlite3_blob_open(_conn1, "main", tableName, columnName, rowId, 1, ref _blob1);
            if (_errorCode > 0)
            {
                _blob1 = IntPtr.Zero;
                needsZeroing = true;
            }
            else
            {
                // Query how big is that BLOB and decide if the BLOB needs zeroing or not.            
                int blobSize = sqlite3_blob_bytes(_blob1);
                if (blobSize != blobLength)
                    needsZeroing = true;
            } // else

            if (needsZeroing)
            {
                IntPtr stmt = IntPtr.Zero;
                IntPtr tail = IntPtr.Zero;

                // Close the BLOB handle if necessary
                if (_blob1 != IntPtr.Zero)
                {
                    _errorCode = sqlite3_blob_close(_blob1);
                    _blob1 = IntPtr.Zero;
                    if (_errorCode > 0)
                        throw new SQLiteException(_errorCode, "can't close BLOB handle");
                }

                // Prepare SQL statement for zeroing the BLOB field in the correct size for the BLOB
                // length we want to write to it.
                _errorCode = sqlite3_prepare_v2(_conn1, "UPDATE " + SQLiteParser.Utils.QuoteIfNeeded(tableName) + 
                    " SET " + SQLiteParser.Utils.QuoteIfNeeded(columnName) +
                    " = @blob WHERE RowID = @rowid", -1, ref stmt, ref tail);
                if (_errorCode > 0)
                    throw new SQLiteException(_errorCode, "sqlite3_prepare_v2 failed");

                try
                {
                    // Bind the BLOB parameter
                    _errorCode = sqlite3_bind_zeroblob(stmt, 1, (int)len);
                    if (_errorCode > 0)
                        throw new SQLiteException(_errorCode, "failed to bind zero-blob");

                    // Bind the RowID parameter
                    _errorCode = sqlite3_bind_int64(stmt, 2, rowId);
                    if (_errorCode > 0)
                        throw new SQLiteException(_errorCode, "failed to bind rowid");

                    // Execute the prepared statement
                    _errorCode = sqlite3_step(stmt);
                    if (_errorCode > 0 && _errorCode < 100)
                        throw new SQLiteException(_errorCode, "failed to execute zeroblob command");
                }
                finally
                {
                    // Finalize the prepared statement (we don't need it for more executions).
                    sqlite3_finalize(stmt);
                } // catch

                // Note: SQLite library won't let us open a BLOB with size zero so we simply return here.
                if (len == 0)
                {
                    _blob1 = IntPtr.Zero;
                    return;
                }

                // Open the BLOB handle
                _blob1 = IntPtr.Zero;
                _errorCode = sqlite3_blob_open(_conn1, "main", tableName, columnName, rowId, 1, ref _blob1);
                if (_errorCode > 0)
                    throw new SQLiteException(_errorCode, "sqlite3_blob_open failed");
            } // if

            // Write into the BLOB field by calling the blob writer delegate
            try
            {
                int offset = 0;
                int written = 0;
                while (written < len)
                {
                    int count = (int)(len - written);
                    if (count > PAGESIZE)
                        count = PAGESIZE;

                    // Call the writer delegate to fill my buffer
                    bool cancel = false;
                    int nbytes = writer(_buffer1, count, written, (int)len, ref cancel);
                    if (cancel)
                        throw new UserCancellationException();

                    // Write the new chunk to the BLOB
                    _errorCode = sqlite3_blob_write(_blob1, _buffer1, nbytes, offset);
                    if (_errorCode > 0)
                        throw new SQLiteException(_errorCode, "sqlite3_blob_write failed");

                    offset += nbytes;
                    written += nbytes;
                } // while
            }
            finally
            {
                // Close the BLOB handle
                sqlite3_blob_close(_blob1);

                // Reset the BLOB handle
                _blob1 = IntPtr.Zero;
            } // finally
        }

        /// <summary>
        /// Reads the entire BLOB field into memory
        /// </summary>
        /// <param name="tableName">The name of the table where the BLOB field is located</param>
        /// <param name="colName">The name of the column where the BLOB field is located</param>
        /// <param name="rowId">The ID of the row where the BLOB field is located</param>
        /// <returns>The BLOB data bytes</returns>
        public byte[] ReadBlob(string tableName, string colName, long rowId)
        {
            byte[] res = GetFieldBytes(_conn1, SQLiteParser.Utils.Chop(tableName), SQLiteParser.Utils.Chop(colName), rowId);
            return res;
        }

        /// <summary>
        /// Read the BLOB field in the specified table, column name and in row with the specified
        /// ROWID. The reading is performed in chunks that are passed to the provided reader delegate.
        /// </summary>
        /// <remarks>Never call this method when the value of the BLOB field is NULL - it won't work!</remarks>
        /// <param name="tableName">The name of the table where the BLOB field exists.</param>
        /// <param name="columnName">The name of the column where the BLOB field exists.</param>
        /// <param name="rowId">The ID of the row where the BLOB field exists.</param>
        /// <param name="reader">The BLOB reader delegate</param>
        public void ReadBlob(string tableName, string columnName, long rowId, BlobReader reader)
        {
            if (_disposed)
                throw new ObjectDisposedException("Object was disposed.");

            tableName = SQLiteParser.Utils.Chop(tableName);
            columnName = SQLiteParser.Utils.Chop(columnName);

            // Open the BLOB handle
            _errorCode = sqlite3_blob_open(_conn1, "main", tableName, columnName, rowId, 0, ref _blob1);
            if (_errorCode > 0)
            {
                // Sometimes - the database contains a non-BLOB object in a column that is declared a BLOB.
                // IN such cases - the call to sqlite3_blob_open will fail. When this happens we'll deal
                // with it by reading the contents of the field directly without using the BLOB API.

                IntPtr stmt = IntPtr.Zero;
                IntPtr tail = IntPtr.Zero;

                _errorCode = sqlite3_prepare_v2(_conn1, "SELECT "+
                    SQLiteParser.Utils.QuoteIfNeeded(columnName)+" FROM " + 
                    SQLiteParser.Utils.QuoteIfNeeded(tableName) + 
                    " WHERE RowID = @rowid", -1, ref stmt, ref tail);
                if (_errorCode > 0)
                    throw new SQLiteException(_errorCode, "failed to prepare SELECT statement");

                try
                {
                    // Bind the RowID parameter
                    _errorCode = sqlite3_bind_int64(stmt, 1, rowId);
                    if (_errorCode > 0)
                        throw new SQLiteException(_errorCode, "failed to bind rowid");

                    // Execute the prepared statement
                    _errorCode = sqlite3_step(stmt);
                    if (_errorCode > 0 && _errorCode < 100)
                        throw new SQLiteException(_errorCode, "failed to execute SELECT command");                    

                    string txt = sqlite3_column_text16(stmt, 0);
                    byte[] tmp = Encoding.ASCII.GetBytes(txt);

                    bool cancel = false;
                    reader(tmp, tmp.Length, 0, tmp.Length, ref cancel);
                    if (cancel)
                        throw new UserCancellationException();
                }
                finally
                {
                    // Finalize the prepared statement (we don't need it for more executions).
                    sqlite3_finalize(stmt);
                } // catch

                return;
            }

            try
            {
                // Query how big is that BLOB
                int count = sqlite3_blob_bytes(_blob1);
                int total = count;
                int offset = 0;
                while (count > 0)
                {
                    int toread = (count > PAGESIZE ? PAGESIZE : count);
                    _errorCode = sqlite3_blob_read(_blob1, _buffer1, toread, offset);
                    if (_errorCode > 0)
                        throw new SQLiteException(_errorCode, "sqlite3_blob_read failed");

                    bool cancel = false;
                    reader(_buffer1, toread, offset + toread, total, ref cancel);
                    if (cancel)
                        throw new UserCancellationException();

                    offset += toread;
                    count -= toread;
                } // while
            }
            finally
            {
                // Close the BLOB handle and reset it.
                sqlite3_blob_close(_blob1);

                // Reset the BLOB handle
                _blob1 = IntPtr.Zero;
            } // finally
        }

        /// <summary>
        /// Convenience method to write the BLOB field specified by the table name, column name and rowid
        /// to the file specified by the <paramref name="fpath"/> parameter.
        /// </summary>
        /// <param name="tableName">The table name where the BLOB exists</param>
        /// <param name="columnName">The column name where the BLOB exists.</param>
        /// <param name="rowId">The ROwID where the BLOB exists.</param>
        /// <param name="fpath">The path to the file that will contain the BLOB data.</param>
        /// <param name="reader">An optional delegate that will be called while reading the BLOB and can be used
        /// to notify about the progress of the write process.</param>
        public void ReadBlobToFile(string tableName, string columnName, long rowId, string fpath, BlobReader reader)
        {
            if (_disposed)
                throw new ObjectDisposedException("Object was disposed.");

            if (File.Exists(fpath))
                File.Delete(fpath);

            using (FileStream fs = File.Create(fpath, PAGESIZE))
            {
                bool first = true;

                // Use my own reader to get the BLOB chunks from the database and insert them into the file.
                BlobReader myreader = new BlobReader(delegate(byte[] buffer, int length, int bytesRead, int totalBytes, ref bool cancel)
                {
                    if (first)
                    {
                        // Set the overal size of the file before starting to write its contents.
                        fs.SetLength(totalBytes);
                        first = false;
                    }

                    // Write this chunk to the file.
                    fs.Write(buffer, 0, length);

                    // Call the actual delegate if necessary
                    if (reader != null)
                        reader(buffer, length, bytesRead, totalBytes, ref cancel);
                });

                ReadBlob(tableName, columnName, rowId, myreader);
            } // using
        }

        /// <summary>
        /// Write the BLOB data from the specified file <paramref name="fpath"/> to the BLOB field as 
        /// specified by the table name, column name and row id parameters. During the write-process - notify
        /// the optional <paramref name="writer"/> delegate about the progress.
        /// </summary>
        /// <param name="tableName">The table name where the BLOB exists</param>
        /// <param name="columnName">The column name where the BLOB exists.</param>
        /// <param name="rowId">The ROwID where the BLOB exists.</param>
        /// <param name="fpath">The path to the file that contains the BLOB data that should be written to the database.</param>
        /// <param name="writer">An optional writer delegate that will be informed throughout the process.</param>
        public void WriteBlobFromFile(string tableName, string columnName, long rowId, string fpath, BlobWriter writer)
        {
            if (_disposed)
                throw new ObjectDisposedException("Object was disposed.");

            using (FileStream fs = File.Open(fpath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                long len = fs.Length;

                // Ensure that we don't have too large files
                if (len > int.MaxValue)
                    throw new SQLiteException("The file is too large to be written as a BLOB");

                BlobWriter mywriter = new BlobWriter(delegate(byte[] buffer, int max, int written, int total, ref bool cancel)
                {
                    // Read the bytes from the file
                    int nbytes = fs.Read(buffer, 0, max);

                    // Call the actual delegate if necessary (but ignore its outputs).
                    if (writer != null)
                        writer(buffer, max, written, total, ref cancel);

                    // Return the number of bytes that were actually read from the file.
                    return nbytes;
                });

                WriteBlob(tableName, columnName, rowId, (int)len, mywriter);
            } // using
        }

        /// <summary>
        /// Copy a BLOB field from the source row to the target row
        /// </summary>
        /// <param name="targetdb">The path to the target row database</param>
        /// <param name="tableName">The name of the table where the row resides</param>
        /// <param name="columnName">The name of the BLOB column</param>
        /// <param name="fromRowId">The ID of the source row</param>
        /// <param name="toRowId">The ID of the target row</param>
        /// <param name="handler">An optional progress notifications handler</param>
        public void CopyBlob(string targetdb, string tableName, string columnName, long fromRowId, long toRowId, BlobProgressHandler handler)
        {
            if (_disposed)
                throw new ObjectDisposedException("Object was disposed.");

            if (_conn2 != IntPtr.Zero)
                throw new InvalidOperationException("You need to use a non-dual database constructor");

            // Open conncetion to the target database as read-write
            _errorCode = sqlite3_open(targetdb, ref _conn2);
            if (_errorCode > 0)
                throw new SQLiteException(_errorCode, "can't open file: " + targetdb);

            try
            {
                // Open source BLOB handle and compute its size
                _errorCode = sqlite3_blob_open(_conn1, "main", tableName, columnName, fromRowId, 0, ref _blob1);
                if (_errorCode > 0)
                    throw new SQLiteException(_errorCode, "failed to open BLOB handle");
                int count1 = sqlite3_blob_bytes(_blob1);

                bool needsResizing = false;

                // Open target BLOB handle and check if it needs to be resized first
                _errorCode = sqlite3_blob_open(_conn2, "main", tableName, columnName, toRowId, 1, ref _blob2);
                if (_errorCode > 0)
                    needsResizing = true;
                else
                {
                    int count2 = sqlite3_blob_bytes(_blob2);
                    if (count1 != count2)
                        needsResizing = true;
                } // else
                                
                // If the target BLOB needs resizing do it now.
                if (needsResizing)
                {
                    // We'll need to resize the target BLOB field
                    IntPtr stmt = IntPtr.Zero;
                    IntPtr tail = IntPtr.Zero;

                    // Close the BLOB handle if necessary
                    if (_blob2 != IntPtr.Zero)
                    {
                        _errorCode = sqlite3_blob_close(_blob2);
                        _blob2 = IntPtr.Zero;
                        if (_errorCode > 0)
                            throw new SQLiteException(_errorCode, "can't close BLOB handle");
                    }

                    // Prepare SQL statement for zeroing the BLOB field in the correct size for the BLOB
                    // length we want to write to it.
                    _errorCode = sqlite3_prepare_v2(_conn2, "UPDATE " + 
                        SQLiteParser.Utils.QuoteIfNeeded(tableName) + " SET " + 
                        SQLiteParser.Utils.QuoteIfNeeded(columnName) +
                        " = @blob WHERE RowID = @rowid", -1, ref stmt, ref tail);
                    if (_errorCode > 0)
                        throw new SQLiteException(_errorCode, "sqlite3_prepare_v2 failed");

                    try
                    {
                        // Bind the BLOB parameter
                        _errorCode = sqlite3_bind_zeroblob(stmt, 1, count1);
                        if (_errorCode > 0)
                            throw new SQLiteException(_errorCode, "failed to bind zero-blob");

                        // Bind the RowID parameter
                        _errorCode = sqlite3_bind_int64(stmt, 2, toRowId);
                        if (_errorCode > 0)
                            throw new SQLiteException(_errorCode, "failed to bind rowid");

                        // Execute the prepared statement
                        _errorCode = sqlite3_step(stmt);
                        if (_errorCode > 0 && _errorCode < 100)
                            throw new SQLiteException(_errorCode, "failed to execute zeroblob command");
                    }
                    finally
                    {
                        // Finalize the prepared statement (we don't need it for more executions).
                        sqlite3_finalize(stmt);
                    } // catch

                    // Re-open the target BLOB field
                    _errorCode = sqlite3_blob_open(_conn2, "main", tableName, columnName, toRowId, 1, ref _blob2);
                    if (_errorCode > 0)
                        throw new SQLiteException(_errorCode, "failed to re-open target BLOB handle");
                } // if

                int count = count1;
                int offset = 0;
                while (count > 0)
                {
                    int toread = (count > PAGESIZE ? PAGESIZE : count);

                    // Read the BLOB data from the source database
                    _errorCode = sqlite3_blob_read(_blob1, _buffer1, toread, offset);
                    if (_errorCode > 0)
                        throw new SQLiteException(_errorCode, "sqlite3_blob_read failed");

                    // Write it to the target database
                    _errorCode = sqlite3_blob_write(_blob2, _buffer1, toread, offset);
                    if (_errorCode > 0)
                        throw new SQLiteException(_errorCode, "sqlite3_blob_read failed");

                    offset += toread;
                    count -= toread;

                    if (handler != null)
                    {
                        bool cancel = false;
                        handler(offset, count1, ref cancel);
                        if (cancel)
                            throw new UserCancellationException();
                    }
                } // while

                // Close the blob handles
                sqlite3_blob_close(_blob1); _blob1 = IntPtr.Zero;
                sqlite3_blob_close(_blob2); _blob2 = IntPtr.Zero;

                // Close the target database connection
                sqlite3_close(_conn2); _conn2 = IntPtr.Zero;
            }
            finally
            {
                if (_blob1 != IntPtr.Zero)
                {
                    sqlite3_blob_close(_blob1);
                    _blob1 = IntPtr.Zero;
                }
                if (_blob2 != IntPtr.Zero)
                {
                    sqlite3_blob_close(_blob2);
                    _blob2 = IntPtr.Zero;
                }
                if (_conn2 != IntPtr.Zero)
                {
                    sqlite3_close(_conn2);
                    _conn2 = IntPtr.Zero;
                }
            } // finally
        }

        /// <summary>
        /// Compares two blob fields that reside in the two databases on which the blob reader writer object
        /// was opened (you need to use the dual databases constructor in order to use this method).
        /// </summary>
        /// <param name="tableName">The name of the table that contains the blob field</param>
        /// <param name="columnName">The name of the column that contains the blob field</param>
        /// <param name="rowid1">The row id in the first database where the blob field is located.</param>
        /// <param name="rowid2">The row id in the second database where the blob field is located.</param>
        /// <param name="comparer">An optional delegate to get progress notifications and be allowed to
        /// cancel the comparison process.</param>
        /// <returns>TRUE if the two blobs are the same, FALSE if they are different.</returns>
        public bool CompareBlobs(string tableName, string columnName, long rowid1, long rowid2, BlobProgressHandler comparer)
        {
            if (_disposed)
                throw new ObjectDisposedException("Object was disposed.");

            if (_conn2 == IntPtr.Zero)
                throw new InvalidOperationException("You need to use a dual database constructor");

            tableName = SQLiteParser.Utils.Chop(tableName);
            columnName = SQLiteParser.Utils.Chop(columnName);

            try
            {
                byte[] bbytes1 = null;
                byte[] bbytes2 = null;

                _errorCode = sqlite3_blob_open(_conn1, "main", tableName, columnName, rowid1, 0, ref _blob1);
                if (_errorCode > 0)
                {
                    bbytes1 = GetFieldBytes(_conn1, tableName, columnName, rowid1);
                }
                _errorCode = sqlite3_blob_open(_conn2, "main", tableName, columnName, rowid2, 0, ref _blob2);
                if (_errorCode > 0)
                {
                    bbytes2 = GetFieldBytes(_conn2, tableName, columnName, rowid2);
                }

                if (bbytes1 != null && bbytes2 != null)
                {
                    if (bbytes1.Length != bbytes2.Length)
                        return false;
                    for (int i = 0; i < bbytes1.Length; i++)
                    {
                        if (bbytes1[i] != bbytes2[i])
                            return false;
                    }
                    return true;
                }

                int count1;
                if (bbytes1 != null)
                    count1 = bbytes1.Length;
                else
                    count1 = sqlite3_blob_bytes(_blob1);

                int count2;
                if (bbytes2 != null)
                    count2 = bbytes2.Length;
                else
                    count2 = sqlite3_blob_bytes(_blob2);

                if (count1 != count2)
                    return false;

                int count = count1;

                int offset = 0;
                while (count > 0)
                {
                    int toread = (count > PAGESIZE ? PAGESIZE : count);

                    if (bbytes1 == null)
                    {
                        _errorCode = sqlite3_blob_read(_blob1, _buffer1, toread, offset);
                        if (_errorCode > 0)
                            throw new SQLiteException(_errorCode, "sqlite3_blob_read failed");
                    }

                    if (bbytes2 == null)
                    {
                        _errorCode = sqlite3_blob_read(_blob2, _buffer2, toread, offset);
                        if (_errorCode > 0)
                            throw new SQLiteException(_errorCode, "sqlite3_blob_read failed");
                    }

                    if (bbytes1 == null && bbytes2 == null)
                    {
                        for (int i = 0; i < toread; i++)
                        {
                            if (_buffer1[i] != _buffer2[i])
                                return false;
                        }
                    }
                    else if (bbytes1 != null)
                    {
                        for (int i = 0; i < bbytes1.Length; i++)
                        {
                            if (bbytes1[i] != _buffer2[i])
                                return false;
                        }
                    }
                    else
                    {
                        for (int i = 0; i < bbytes2.Length; i++)
                        {
                            if (bbytes2[i] != _buffer1[i])
                                return false;
                        }
                    }

                    if (comparer != null)
                    {
                        bool cancel = false;
                        comparer(offset + toread, count1, ref cancel);
                        if (cancel)
                            throw new UserCancellationException();
                    }

                    offset += toread;
                    count -= toread;
                } // while

                // Close the blob handles
                if (bbytes1 == null)
                {
                    sqlite3_blob_close(_blob1); 
                    _blob1 = IntPtr.Zero;
                }

                if (bbytes2 == null)
                {
                    sqlite3_blob_close(_blob2); 
                    _blob2 = IntPtr.Zero;
                }

                return true;
            }
            finally
            {
                if (_blob1 != IntPtr.Zero)
                {
                    sqlite3_blob_close(_blob1);
                    _blob1 = IntPtr.Zero;
                }
                if (_blob2 != IntPtr.Zero)
                {
                    sqlite3_blob_close(_blob2);
                    _blob2 = IntPtr.Zero;
                }
            } // finally
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);

            // Use SupressFinalize in case a subclass
            // of this type implements a finalizer.
            GC.SuppressFinalize(this);     
        }

        #endregion

        #region Virtual Methods
        protected virtual void Dispose(bool disposing)
        {
            // If you need thread safety, use a lock around these 
            // operations, as well as in your methods that use the resource.
            if (!_disposed)
            {
                if (disposing)
                {
                    // There are no managed resources that can be disposed
                }

                // Close BLOB handle if necessary
                if (_blob1 != IntPtr.Zero)
                    sqlite3_blob_close(_blob1);

                // Close connection handle if necessary
                if (_conn1 != IntPtr.Zero)
                    sqlite3_close(_conn1);

                // Close BLOB handle if necessary
                if (_blob2 != IntPtr.Zero)
                    sqlite3_blob_close(_blob2);

                // Close connection handle if necessary
                if (_conn2 != IntPtr.Zero)
                    sqlite3_close(_conn2);

                // Indicate that the instance has been disposed.
                _conn1 = IntPtr.Zero;
                _conn2 = IntPtr.Zero;
                _blob1 = IntPtr.Zero;
                _blob2 = IntPtr.Zero;

                // Mark that the object was disposed
                _disposed = true;
            }
        }
        #endregion

        #region Private Methods
        private byte[] GetFieldBytes(IntPtr conn, string tableName, string columnName, long rowId)
        {
            IntPtr stmt = IntPtr.Zero;
            IntPtr tail = IntPtr.Zero;

            _errorCode = sqlite3_prepare_v2(conn, "SELECT " +
                SQLiteParser.Utils.QuoteIfNeeded(columnName) + " FROM " +
                SQLiteParser.Utils.QuoteIfNeeded(tableName) +
                " WHERE RowID = @rowid", -1, ref stmt, ref tail);
            if (_errorCode > 0)
                throw new SQLiteException(_errorCode, "failed to prepare SELECT statement");

            try
            {
                // Bind the RowID parameter
                _errorCode = sqlite3_bind_int64(stmt, 1, rowId);
                if (_errorCode > 0)
                    throw new SQLiteException(_errorCode, "failed to bind rowid");

                // Execute the prepared statement
                _errorCode = sqlite3_step(stmt);
                if (_errorCode > 0 && _errorCode < 100)
                    throw new SQLiteException(_errorCode, "failed to execute SELECT command");

                byte[] tmp = null;
                IntPtr bres = sqlite3_column_blob(stmt, 0);
                int sz = sqlite3_column_bytes16(stmt, 0);
                tmp = new byte[sz];
                Marshal.Copy(bres, tmp, 0, sz);

                return tmp;
            }
            finally
            {
                // Finalize the prepared statement (we don't need it for more executions).
                sqlite3_finalize(stmt);
            } // catch
        }
        #endregion

        #region Interop Methods
        /* int sqlite3_open(const char *filename, sqlite3 **ppDb); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_open(string filename, ref IntPtr ppDb);

        /* int sqlite3_open_v2(const char *filename, sqlite3 **ppDb,int flags,const char *zVfs); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_open_v2(string filename, ref IntPtr ppb, int flags, string zVfs);

        /* int sqlite3_close(sqlite3 *); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_close(IntPtr ppDb);

        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_blob_open(IntPtr stmt, string zDb, string zTable, string zColumn, long iRow, int flags, ref IntPtr blobHandle);

        /* int sqlite3_blob_bytes(sqlite3_blob *); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_blob_bytes(IntPtr blobHandle);

        /* int sqlite3_blob_close(sqlite3_blob *); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_blob_close(IntPtr blobHandle);

        /* int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_blob_read(IntPtr blobHandle, byte[] data, int len, int offset);

        /* int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_blob_write(IntPtr blobHandle, byte[] data, int len, int offset);

        /* int sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_prepare_v2(IntPtr ppDb, string sql, int maxLen, ref IntPtr stmt, ref IntPtr pzTail);

        /* int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_bind_zeroblob(IntPtr stmt, int prmIndex, int size);

        /* int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_bind_int64(IntPtr stmt, int prmIndex, long value);

        /* int sqlite3_step(sqlite3_stmt*); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_step(IntPtr stmt);

        /* int sqlite3_finalize(sqlite3_stmt *pStmt); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_finalize(IntPtr stmt);

        /* int sqlite3_column_type(sqlite3_stmt*, int iCol); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_column_type(IntPtr stmt, int iCol);

        /* const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern string sqlite3_column_text16(IntPtr stmt, int iCol);

        /* const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);*/
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr sqlite3_column_blob(IntPtr stmt, int iCol);

        /* int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); */
        [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
        public static extern int sqlite3_column_bytes16(IntPtr stmt, int iCol);
        #endregion

        #region Private Constants
        private const int PAGESIZE = 4096;
        private const string SQLITE_DLL = "System.Data.SQLite.DLL";
        private const int SQLITE_OPEN_READONLY = 0x00000001;
        #endregion

        #region Private Variables
        private IntPtr _conn1 = IntPtr.Zero;
        private IntPtr _blob1 = IntPtr.Zero;
        private IntPtr _conn2 = IntPtr.Zero;
        private IntPtr _blob2 = IntPtr.Zero;
        private int _errorCode = 0;
        private bool _disposed = false;
        private byte[] _buffer1 = new byte[PAGESIZE];
        private byte[] _buffer2 = new byte[PAGESIZE];
        #endregion
    }

    /// <summary>
    /// A BLOB reader delegate that will be called during the BLOB reading process on every chunk of 
    /// data that is read from the BLOB field.
    /// </summary>
    /// <param name="buffer">The buffer that was read.</param>
    /// <param name="length">The length into that buffer that was actually read from the BLOB 
    /// (the buffer may be bigger than the amount of data that was actually read).</param>
    /// <param name="bytesRead">The total number of bytes that were read so far</param>
    /// <param name="totalBytes">The total number of bytes that are to read.</param>
    /// <param name="cancel">If the delegate wants to cancel the read process - it can set this parameter to
    /// TRUE. The reading process will stop by throwing a UserCancelledException exception.</param>
    public delegate void BlobReader(byte[] buffer, int length, int bytesRead, int totalBytes, ref bool cancel);

    /// <summary>
    /// A BLOB writer delegate that will be called during the BLOB writing process to fetch chunks of
    /// data that will be written to the BLOB field.
    /// </summary>
    /// <param name="buffer">The buffer that should be filled by the delegate</param>
    /// <param name="max">The maximum amount of bytes to write to the buffer.</param>
    /// <param name="written">The number of bytes that were already written to the BLOB</param>
    /// <param name="total">The total number of bytes that should be written to the BLOB</param>
    /// <param name="cancel">If the delegate will set this field to TRUE - the blob writing process will
    /// stop in the middle and the method will throw a UserCancelledException.</param>
    /// <returns>The total number of bytes that the method wrote to the buffer. Can be max or lower, but should be
    /// greater than 0</returns>
    public delegate int BlobWriter(byte[] buffer, int max, int written, int total, ref bool cancel);

    /// <summary>
    /// A BLOB comparer delegate that will be called during BLOB comparison for purpose of progress
    /// notification and allowing the delegate to cancel the operation.
    /// </summary>
    /// <param name="bytesRead">Tells how many bytes were read by the compare process</param>
    /// <param name="totalBytes">Tells the total number of bytes that need to be compared</param>
    /// <param name="cancel">If the delegate will set this field to TRUE - the blob comparison process
    /// will stop in the middle of the method and will throw a UserCancelledException.</param>
    public delegate void BlobProgressHandler(int bytesRead, int totalBytes, ref bool cancel);
    
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer
Israel Israel
My name is Liron Levi and I'm developing software for fun & profit for 15 years already.

Comments and Discussions