Click here to Skip to main content
15,895,142 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 285.2K   37.2K   131  
Utility for comparing two SQLite database files for both structure and data
using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using SQLiteParser;
using FastGridApp;
using log4net;

namespace SQLiteTurbo
{
    /// <summary>
    /// This control is responsible to provide a diff view for comparing table rows.
    /// </summary>
    public partial class TableDiffControl : UserControl
    {
        #region Events
        public event EventHandler RowsChanged;
        public event EventHandler StateChanged;
        public event EventHandler SearchRequested;
        #endregion

        #region Constructors
        public TableDiffControl()
        {
            InitializeComponent();

            _italic = new Font(this.Font, FontStyle.Italic);
            _searchDialog = new SearchDataRowsDialog();
        }
        #endregion

        #region Public Methods
        public void SetTableChanges(SchemaComparisonItem item, string leftdb, string rightdb, TableChanges changes, string diff)
        {
            _item = item;
            _leftdb = leftdb;
            _rightdb = rightdb;
            _diff = diff;
            _tableChanges = changes;

            PrepareDataTab();
        }

        /// <summary>
        /// Opens the search data dialog
        /// </summary>
        public void SearchData()
        {
            bool isLeft = grdLeft.Focused;
            SQLiteCreateTableStatement table = 
                isLeft ? (SQLiteCreateTableStatement)_item.LeftDdlStatement : (SQLiteCreateTableStatement)_item.RightDdlStatement;

            FastGrid grid = isLeft ? grdLeft : grdRight;
            FastGridLocation loc = grid.SelectedCellLocation;
            FastGridColumn fcol = grid.Columns[loc.ColumnIndex];
            string cname = (string)fcol.Tag;

            _searchDialog.PrepareDialog(table.Columns, cname, _diff, _tableChanges, loc.RowIndex+1, isLeft);
            DialogResult res = _searchDialog.ShowDialog(this);
            if (res == DialogResult.OK && _searchDialog.MatchedRowIndex != -1)
            {
                FastGridSelection sel = new FastGridSelection();
                sel.AddSelection(_searchDialog.MatchedRowIndex, _searchDialog.MatchedRowIndex);

                FastGridLocation nloc = new FastGridLocation(_searchDialog.MatchedRowIndex, loc.ColumnIndex);
                grdLeft.SelectedCellLocation = nloc;
                grdLeft.Selection = sel;
                grdRight.SelectedCellLocation = nloc;
                grdRight.Selection = (FastGridSelection)sel.Clone();
            }
        }

        #endregion

        #region Event Handlers
        private void grdLeft_ColumnResized(object sender, ColumnResizedEventArgs e)
        {
            if (e.ColumnIndex < grdRight.Columns.Count)
            {
                grdRight.Columns[e.ColumnIndex].Width = e.UpdatedWidth;
                grdRight.RefreshLayout();
                grdRight.Refresh();
            }
        }

        private void grdLeft_LayoutChanged(object sender, EventArgs e)
        {
            HandleGridLayout();
        }

        private void grdLeft_RowNeeded(object sender, RowNeededEventArgs e)
        {
            FillRow(grdLeft, e.RowIndex, e.NeededRow);
        }

        private void grdLeft_Scroll(object sender, EventArgs e)
        {
            scbHorizontal.Value = grdLeft.HorizontalScrollPosition;
            scbRight.Value = (int)grdLeft.FirstDisplayedRowIndex;
            grdRight.FirstDisplayedRowIndex = scbRight.Value;
            grdRight.HorizontalScrollPosition = scbHorizontal.Value;
            selectionBar1.CursorOffset = scbRight.Value;
            Refresh();
        }

        private void grdLeft_SelectionChanged(object sender, EventArgs e)
        {
            grdRight.Selection = grdLeft.Selection;
            selectionBar1.SetData(grdLeft.RowsCount-1, grdLeft.Selection);
            UpdateState();
        }

        private void grdRight_ColumnResized(object sender, ColumnResizedEventArgs e)
        {
            if (e.ColumnIndex < grdLeft.Columns.Count)
            {
                grdLeft.Columns[e.ColumnIndex].Width = e.UpdatedWidth;
                grdLeft.RefreshLayout();
                grdLeft.Refresh();
            }
        }

        private void grdRight_LayoutChanged(object sender, EventArgs e)
        {
            HandleGridLayout();
        }

        private void grdRight_RowNeeded(object sender, RowNeededEventArgs e)
        {
            FillRow(grdRight, e.RowIndex, e.NeededRow);
        }

        private void grdRight_Scroll(object sender, EventArgs e)
        {
            scbHorizontal.Value = grdRight.HorizontalScrollPosition;
            scbRight.Value = (int)grdRight.FirstDisplayedRowIndex;
            grdLeft.FirstDisplayedRowIndex = scbRight.Value;
            grdLeft.HorizontalScrollPosition = scbHorizontal.Value;
            selectionBar1.CursorOffset = scbRight.Value;
            Refresh();
        }

        private void grdRight_SelectionChanged(object sender, EventArgs e)
        {
            grdLeft.Selection = grdRight.Selection;
            selectionBar1.SetData(grdLeft.RowsCount-1, grdLeft.Selection);
            UpdateState();
        }

        private void scbRight_Scroll(object sender, ScrollEventArgs e)
        {
            grdLeft.FirstDisplayedRowIndex = scbRight.Value;
            grdRight.FirstDisplayedRowIndex = scbRight.Value;
            selectionBar1.CursorOffset = scbRight.Value;
            Refresh();
        }

        private void scbHorizontal_Scroll(object sender, ScrollEventArgs e)
        {
            grdLeft.HorizontalScrollPosition = scbHorizontal.Value;
            grdRight.HorizontalScrollPosition = scbHorizontal.Value;
            Refresh();
        }

        private void grdRight_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            HandleCellEdit(true);
        }

        private void grdLeft_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            HandleCellEdit(false);
        }

        private void grdLeft_Enter(object sender, EventArgs e)
        {
            btnEditRow.Image = imageList2.Images[0];
            btnDeleteRows.Image = imageList2.Images[2];
            _lastFocusedGrid = grdLeft;

            if (StateChanged != null)
                StateChanged(this, EventArgs.Empty);
        }

        private void grdRight_Enter(object sender, EventArgs e)
        {
            btnEditRow.Image = imageList2.Images[1];
            btnDeleteRows.Image = imageList2.Images[3];
            _lastFocusedGrid = grdRight;

            if (StateChanged != null)
                StateChanged(this, EventArgs.Empty);
        }

        private void btnCopyLeftToRight_Click(object sender, EventArgs e)
        {
            List<TableChangesRange> rows = new List<TableChangesRange>();
            foreach (FastGridApp.SelectionRange range in grdLeft.Selection.SelectionRanges)
                rows.Add(new TableChangesRange(range.StartRowId, range.EndRowId));

            RowsCopier copier = new RowsCopier(_tableChanges, _diff, rows, true);
            ProgressDialog dlg = new ProgressDialog();
            dlg.Start(this, copier);
            grdRight.RefreshLayout();
            grdLeft.RefreshLayout();

            if (RowsChanged != null)
                RowsChanged(this, EventArgs.Empty);

            if (StateChanged != null)
                StateChanged(this, EventArgs.Empty);
        }

        private void btnCopyRightToLeft_Click(object sender, EventArgs e)
        {
            List<TableChangesRange> rows = new List<TableChangesRange>();
            foreach (FastGridApp.SelectionRange range in grdLeft.Selection.SelectionRanges)
                rows.Add(new TableChangesRange(range.StartRowId, range.EndRowId));
            RowsCopier copier = new RowsCopier(_tableChanges, _diff, rows, false);
            ProgressDialog dlg = new ProgressDialog();
            dlg.Start(this, copier);
            grdRight.RefreshLayout();
            grdLeft.RefreshLayout();

            if (RowsChanged != null)
                RowsChanged(this, EventArgs.Empty);

            if (StateChanged != null)
                StateChanged(this, EventArgs.Empty);
        }

        private void btnEditRow_Click(object sender, EventArgs e)
        {
            HandleEdit();
        }

        private void btnDeleteRows_Click(object sender, EventArgs e)
        {
            if (_lastFocusedGrid == null)
                return;

            if (object.ReferenceEquals(_lastFocusedGrid, grdLeft))
                DeleteRows(false);
            else
                DeleteRows(true);

            _lastFocusedGrid.Focus();

            if (StateChanged != null)
                StateChanged(this, EventArgs.Empty);
        }

        private void grdLeft_EditStarted(object sender, EventArgs e)
        {
            HandleEdit();
        }

        private void grdRight_EditStarted(object sender, EventArgs e)
        {
            HandleEdit();
        }

        private void grdRight_SearchRequested(object sender, EventArgs e)
        {
            if (SearchRequested != null)
                SearchRequested(this, EventArgs.Empty);
        }

        private void grdLeft_SearchRequested(object sender, EventArgs e)
        {
            if (SearchRequested != null)
                SearchRequested(this, EventArgs.Empty);
        }

        #endregion

        #region Private Methods

        private bool CanEditGrid(bool right, out TableChangeItem citem, out FastGridLocation sloc)
        {
            citem = null;
            sloc = FastGridLocation.Empty;
            if (_item == null)
                return false;

            FastGrid grid;
            SQLiteCreateTableStatement table;
            string dbpath;
            if (right)
            {
                grid = grdRight;
                table = (SQLiteCreateTableStatement)_item.RightDdlStatement;
                dbpath = _rightdb;
            }
            else
            {
                grid = grdLeft;
                table = (SQLiteCreateTableStatement)_item.LeftDdlStatement;
                dbpath = _leftdb;
            }

            sloc = grid.SelectedCellLocation;

            string columnName = (string)grid.Columns[sloc.ColumnIndex].Tag;
            SQLiteColumnStatement column = Utils.FindColumn(table.Columns, columnName);
            if (column == null)
                return false;

            long total = _tableChanges.GetTotalChangesCount(new string[] { _diff });
            if (sloc.RowIndex >= total)
                return false;
            citem = _tableChanges.GetChangeItem(_diff, sloc.RowIndex);

            // Can't edit a cell that belongs to a row that doesn't exist
            if (right && citem.Result == ComparisonResult.ExistsInLeftDB)
                return false;
            else if (!right && citem.Result == ComparisonResult.ExistsInRightDB)
                return false;

            return true;
        }

        private void HandleEdit()
        {
            if (_lastFocusedGrid == null)
                return;

            if (object.ReferenceEquals(_lastFocusedGrid, grdLeft))
                HandleCellEdit(false);
            else
                HandleCellEdit(true);

            _lastFocusedGrid.Focus();
        }

        private void UpdateState()
        {
            bool canEdit = true;

            FastGrid grid = null;
            if (grdLeft.Focused)
                grid = grdLeft;
            else if (grdRight.Focused)
                grid = grdRight;

            if (grid == null || grid.RowsCount == 0)
                canEdit = false;

            btnEditRow.Enabled = canEdit;

            bool enabled = grid != null && grid.Selection.SelectionRanges.Count > 0;
            if (btnCopyRightToLeft.Enabled != enabled)
                btnCopyLeftToRight.Enabled = enabled;
            if (btnCopyRightToLeft.Enabled != enabled)
                btnCopyRightToLeft.Enabled = enabled;
            if (btnDeleteRows.Enabled != enabled)
                btnDeleteRows.Enabled = enabled;
        }

        private object GetRowFieldValue(TableChangeItem citem, bool right, string columnName)
        {
            if (right)
            {
                for (int i = 0; i < citem.RightColumnNames.Length; i++)
                {
                    if (citem.RightColumnNames[i].ToLower() == columnName.ToLower())
                        return citem.RightFields[i];
                } // for
            }
            else
            {
                for (int i = 0; i < citem.LeftColumnNames.Length; i++)
                {
                    if (citem.LeftColumnNames[i].ToLower() == columnName.ToLower())
                        return citem.LeftFields[i];
                } // for
            }

            throw new ArgumentException("Illegal row index, column index or column name");
        }

        private DialogResult OpenCellEditDialog(SQLiteCreateTableStatement table, SQLiteColumnStatement column, ref object value)
        {
            DialogResult res;
            CellValueEditorDialog dlg = new CellValueEditorDialog();
            res = dlg.ShowEditor(this, table, column, ref value);
            return res;
        }

        private void DeleteRows(bool right)
        {
            List<TableChangesRange> rows = new List<TableChangesRange>();
            FastGrid grid;

            long totalDeleted = 0;
            if (right)
            {
                grid = grdRight;
                foreach (FastGridApp.SelectionRange range in grdRight.Selection.SelectionRanges)
                {
                    rows.Add(new TableChangesRange(range.StartRowId, range.EndRowId));
                    totalDeleted += range.EndRowId - range.StartRowId + 1;
                }
            }
            else
            {
                grid = grdLeft;
                foreach (FastGridApp.SelectionRange range in grdLeft.Selection.SelectionRanges)
                {
                    rows.Add(new TableChangesRange(range.StartRowId, range.EndRowId));
                    totalDeleted += range.EndRowId - range.StartRowId + 1;
                }
            }

            DialogResult res = MessageBox.Show(this, 
                string.Format("Are you sure you want to delete {0} rows from the {1} database table?", totalDeleted, (right?"right":"left")),
                "Confirm Deletion", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
            if (res == DialogResult.No)
                return;

            RowsDeleter deleter = new RowsDeleter(_tableChanges, _diff, rows, !right);
            ProgressDialog dlg = new ProgressDialog();
            dlg.Start(this, deleter);

            GC.Collect();

            // Notify that row(s) were deleted
            if (RowsChanged != null)
                RowsChanged(this, EventArgs.Empty);

            // Refresh the right/left grids again from the database.
            grdRight.RefreshLayout();
            grdLeft.RefreshLayout();
        }

        private void HandleCellEdit(bool right)
        {
            FastGrid grid;
            SQLiteCreateTableStatement table;
            string dbpath;
            if (right)
            {
                grid = grdRight;
                table = (SQLiteCreateTableStatement)_item.RightDdlStatement;
                dbpath = _rightdb;
            }
            else
            {
                grid = grdLeft;
                table = (SQLiteCreateTableStatement)_item.LeftDdlStatement;
                dbpath = _leftdb;
            }

            FastGridLocation sloc = grid.SelectedCellLocation;

            string columnName = (string)grid.Columns[sloc.ColumnIndex].Tag;
            SQLiteColumnStatement column = Utils.FindColumn(table.Columns, columnName);
            if (column == null)
                return;

            TableChangeItem citem = null;
            try
            {
                citem = _tableChanges.GetChangeItem(_diff, sloc.RowIndex);
            }
            catch (IndexOutOfRangeException iox)
            {
                // Ignore (the user must have pressed on a cell when the table is actually empty)
                return;
            } // catch

            // Can't edit a cell that belongs to a row that doesn't exist
            if (right && citem.Result == ComparisonResult.ExistsInLeftDB)
                return;
            else if (!right && citem.Result == ComparisonResult.ExistsInRightDB)
                return;

            object otherBlob = null;

            // The row id is needed when displaying editor dialog for a BLOB field
            long rowId = -1;
            if (right)
            {
                rowId = citem.RightRowId;
                if (citem.LeftFields != null && TableContainsColumn((SQLiteCreateTableStatement)_item.LeftDdlStatement, column))
                    otherBlob = GetRowFieldValue(citem, false, columnName);
            }
            else
            {
                rowId = citem.LeftRowId;
                if (citem.RightFields != null && TableContainsColumn((SQLiteCreateTableStatement)_item.RightDdlStatement, column))
                    otherBlob = GetRowFieldValue(citem, true, columnName);
            }

            // Extract the current field value from the change item
            object value = GetRowFieldValue(citem, right, columnName);

            // Adjust the BLOB value since it was fetched as the IS NOT NULL expression
            // in order to avoid loading the BLOB field into main memory.
            if (Utils.GetDbType(column.ColumnType) == DbType.Binary)
            {
                long v = (long)value;
                if (v == 0) // means NULL
                    value = DBNull.Value;
            }

            // When the user is clicking on a BLOB field - we need to extract its value
            // to a local file before opening the cell-edit dialog.
            Be.Windows.Forms.DynamicFileByteProvider origProvider = null;
            if (value != DBNull.Value && Utils.GetDbType(column.ColumnType) == DbType.Binary)
            {
                // In case of BLOBs - we have to first load them to the local file-system and
                // only then we can allow the user to edit their contents
                using (BlobLoader loader = new BlobLoader(dbpath,
                    table.ObjectName.ToString(), column.ObjectName.ToString(), rowId, Configuration.TempBlobFilePath))
                {
                    ProgressDialog dlg = new ProgressDialog();
                    dlg.Start(this, loader);

                    if (dlg.Error != null)
                        return;
                } // using

                // Instead of passing a byte[] array to the cell editor dialog - we'll pass
                // a reference to the dynamic file byte provider that is opened on the data file
                // that was written with the BLOB data. This allows us to conserve memory (BLOB
                // fields can be quite large).
                origProvider = new Be.Windows.Forms.DynamicFileByteProvider(Configuration.TempBlobFilePath);
                value = origProvider;
            }

            // Open the cell editor dialog and allow the user to edit the contents of the field.
            DialogResult res = OpenCellEditDialog(table, column, ref value);
            if (res == DialogResult.Cancel)
                return;

            // This part of the IF statement deals with BLOB fields - these require
            // a special (and lengthy) handling.
            if (Utils.GetDbType(column.ColumnType) == DbType.Binary || value is Be.Windows.Forms.DynamicFileByteProvider)
            {
                if (otherBlob != null && (citem.Result == ComparisonResult.Same || citem.Result == ComparisonResult.DifferentData))
                {
                    if (otherBlob is long)
                    {
                        // Another adjustment for the other BLOB field value
                        long ob = (long)otherBlob;
                        if (ob == 0) // means NULL blob field
                            otherBlob = DBNull.Value;
                    }
                }

                if (value != DBNull.Value)
                {
                    string fpath = null;
                    long blobLength = 0;
                    if (value is Be.Windows.Forms.DynamicFileByteProvider)
                    {
                        Be.Windows.Forms.DynamicFileByteProvider dp = (Be.Windows.Forms.DynamicFileByteProvider)value;
                        if (dp.HasChanges())
                            dp.ApplyChanges();
                        dp.Dispose();
                            
                        fpath = dp.FilePath;
                        FileInfo fi = new FileInfo(fpath);
                        blobLength = fi.Length;
                    }
                    else
                        throw new InvalidOperationException("cell editor returned unexpected value");

                    BlobSaver saver = null;
                    try
                    {
                        // In this case we need to store the updated file/buffer back to the BLOB field
                        // and run BLOB comparison in order to check if the BLOB is different then
                        // the one in the other database.

                        // Save the file specified in the call as a BLOB
                        saver = new BlobSaver(dbpath, table.ObjectName.ToString(), column.ObjectName.ToString(), rowId, fpath);

                        ProgressDialog dlg = new ProgressDialog();
                        dlg.Start(this, saver);
                        if (dlg.Error != null)
                        {
                            // Notify that a row has changed
                            if (RowsChanged != null)
                                RowsChanged(this, EventArgs.Empty);

                            return;
                        }
                    }
                    finally
                    {
                        saver.Dispose();
                    } // finally

                    // Update the table change item with an indication that the BLOB field that was just saved
                    // is not null.
                    citem.SetField(column.ObjectName.ToString(), !right, (long)1);

                    if (otherBlob != null && (citem.Result == ComparisonResult.DifferentData || citem.Result == ComparisonResult.Same))
                    {
                        // At this point we need to compare the BLOB that was saved with the BLOB field in the other 
                        // database in order to update the ChangedBlobsColumnNames field of the table change item so
                        // that the user can know if the BLOB is equal to the other BLOB or if the BLOB is different.
                        if (otherBlob != DBNull.Value)
                        {
                            // Run BLOB comparison
                            bool equalBlobs = false;
                            using (BlobCompareWorker bcw = new BlobCompareWorker(_leftdb, _rightdb,
                                SQLiteParser.Utils.Chop(table.ObjectName.ToString()),
                                SQLiteParser.Utils.Chop(column.ObjectName.ToString()),
                                citem.LeftRowId, citem.RightRowId))
                            {
                                ProgressDialog pdlg = new ProgressDialog();
                                pdlg.Start(this, bcw);
                                if (pdlg.Error != null)
                                {
                                    // Notify that a row has changed
                                    if (RowsChanged != null)
                                        RowsChanged(this, EventArgs.Empty);

                                    return;
                                }
                                equalBlobs = bcw.IsBlobsEqual;
                            } // using

                            if (equalBlobs)
                            {
                                // The two BLOBs are equal so remove any difference mark                           
                                if (citem.ChangedBlobsColumnNames != null && citem.ChangedBlobsColumnNames.Contains(column.ObjectName.ToString()))
                                    citem.ChangedBlobsColumnNames.Remove(column.ObjectName.ToString());
                            }
                            else
                            {
                                // The two BLOBs are different so add a difference mark if necessary
                                if (citem.ChangedBlobsColumnNames == null)
                                    citem.ChangedBlobsColumnNames = new List<string>();
                                if (!citem.ChangedBlobsColumnNames.Contains(column.ObjectName.ToString()))
                                    citem.ChangedBlobsColumnNames.Add(column.ObjectName.ToString());
                                citem.Result = ComparisonResult.DifferentData;
                            } // else
                        }
                        else
                        {
                            // If the other BLOB field is NULL - it means that the two BLOBs are different
                            // because one is NULL and the other is not.
                            if (citem.ChangedBlobsColumnNames == null)
                                citem.ChangedBlobsColumnNames = new List<string>();
                            if (!citem.ChangedBlobsColumnNames.Contains(column.ObjectName.ToString()))
                                citem.ChangedBlobsColumnNames.Add(column.ObjectName.ToString());
                            citem.Result = ComparisonResult.DifferentData;
                        } // else
                    } // if
                }
                else
                {
                    // Ask the table changes object to set this field to null
                    _tableChanges.SetColumnField(_diff, sloc.RowIndex, column.ObjectName.ToString(), right, DBNull.Value);
                    citem.SetField(column.ObjectName.ToString(), !right, (long)0);

                    if (otherBlob != null && (citem.Result == ComparisonResult.Same || citem.Result == ComparisonResult.DifferentData))
                    {
                        // A BLOB field was set to NULL - compare it again to the other BLOB field and decide
                        // if the ChangedBlobsColumnNames field should be updated to reflect this.
                        if (otherBlob == DBNull.Value)
                        {
                            // The other BLOB field is NULL so the two fields are equal
                            if (citem.ChangedBlobsColumnNames != null && citem.ChangedBlobsColumnNames.Contains(column.ObjectName.ToString()))
                                citem.ChangedBlobsColumnNames.Remove(column.ObjectName.ToString());
                        }
                        else
                        {
                            if (citem.ChangedBlobsColumnNames == null)
                                citem.ChangedBlobsColumnNames = new List<string>();

                            // The other BLOB field is not NULL so the two fields are not equal
                            if (!citem.ChangedBlobsColumnNames.Contains(column.ObjectName.ToString()))
                                citem.ChangedBlobsColumnNames.Add(column.ObjectName.ToString());
                            citem.Result = ComparisonResult.DifferentData;
                        } // else
                    } // if
                } // else
            }
            else
            {
                // The field that was edited is not a BLOB so we can deal with it normally
                try
                {                               
                    _tableChanges.SetColumnField(_diff, sloc.RowIndex, column.ObjectName.ToString(), right, value);
                }
                catch (Exception ex)
                {
                    _log.Error("Failed to set column field", ex);
                    MessageBox.Show(this, ex.Message, "Operation Failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
                } // catch
            } // else

            GC.Collect();

            // Mark that the table changes object does not contain precise results
            _tableChanges.HasPreciseResults = false;

            // Notify that a row has changed
            if (RowsChanged != null)
                RowsChanged(this, EventArgs.Empty);

            // Refresh the right/left grids again from the database.
            grdRight.RefreshLayout();
            grdLeft.RefreshLayout();
        }

        /// <summary>
        /// Checks if the specified table contains a column with the specified name.
        /// </summary>
        /// <param name="table">The table to check</param>
        /// <param name="column">The column to chcek</param>
        /// <returns>TRUE if the table contains a column with the specified name, FALSE otherwise.</returns>
        private bool TableContainsColumn(SQLiteCreateTableStatement table, SQLiteColumnStatement column)
        {
            foreach (SQLiteColumnStatement tc in table.Columns)
            {
                if (tc.ObjectName.Equals(column.ObjectName))
                    return true;
            } // foreach
            return false;
        }

        /// <summary>
        /// Update the horizontal and vertical scrollbars according to changes
        /// in the left/right grids
        /// </summary>
        private void HandleGridLayout()
        {
            int maxhscroll = grdLeft.MaxHorizontalScroll;
            if (maxhscroll < grdRight.MaxHorizontalScroll)
                maxhscroll = grdRight.MaxHorizontalScroll;

            int lchange = grdLeft.HorizontalScrollPageSize;
            if (lchange > grdRight.HorizontalScrollPageSize)
                lchange = grdRight.HorizontalScrollPageSize;

            if (scbHorizontal.Maximum != maxhscroll ||
                scbHorizontal.LargeChange != lchange)
            {
                scbHorizontal.Maximum = maxhscroll;
                scbHorizontal.LargeChange = lchange;               
                scbHorizontal.SmallChange = (int)Math.Ceiling(lchange / 10F);
            }

            if (scbRight.Maximum != grdLeft.RowsCount - 1 ||
                scbRight.LargeChange != grdLeft.VerticalPageSize)
            {
                if (grdLeft.RowsCount - 1 >= 0)
                {
                    scbRight.Maximum = (int)(grdLeft.RowsCount - 1);
                    scbRight.LargeChange = grdLeft.VerticalPageSize;
                }
                else
                {
                    scbRight.Maximum = 100;
                    scbRight.LargeChange = 101;
                }
            }
        }

        /// <summary>
        /// This purpose of this method is to fill the needed row with data
        /// taken from the table-changes object. This is part of a virtual mode
        /// implementation of the FastGrid control designed to minimize memory
        /// requirements only to the bare essentials.
        /// </summary>
        /// <param name="grid">The grid that issued to the RowNeeded request</param>
        /// <param name="rowIndex">The index of the row to fill</param>
        /// <param name="row">The row to fill</param>
        private void FillRow(FastGrid grid, long rowIndex, FastGridRow row)
        {
            // Fetch the table change item from the cache/database
            TableChangeItem item = _tableChanges.GetChangeItem(_diff, rowIndex);

            bool empty;
            bool isLeft;
            SQLiteCreateTableStatement table = null;
            if (grid == grdLeft)
            {
                isLeft = true;
                empty = item.LeftFields == null;
                table = _tableChanges.LeftTable;
            }
            else
            {
                isLeft = false;
                empty = item.RightFields == null;
                table = _tableChanges.RightTable;
            } // else

            if (empty)
            {
                for (int i = 0; i < row.Cells.Length; i++)
                    row.Cells[i].Style.BackColor = EMPTY_ROW_BACK_COLOR;
            }
            else
            {
                for (int i = 0; i < row.Cells.Length; i++)
                {
                    string cname = (string)grid.Columns[i].Tag;
                    SQLiteColumnStatement col = Utils.GetColumnByName(table, cname);
                    object fval = item.GetField(cname, isLeft);
                    if (fval == DBNull.Value || fval == null)
                    {
                        row.Cells[i].Value = "NULL";
                        row.Cells[i].Style.Font = _italic;
                    }
                    else if (Utils.GetDbType(col.ColumnType) == DbType.Binary)
                    {
                        long v = (long)fval;
                        if (v == 1)
                            row.Cells[i].Value = "BLOB";
                        else
                            row.Cells[i].Value = "NULL";
                        row.Cells[i].Style.Font = _italic;
                    }
                    else
                    {
                        if (fval is byte[])
                        {
                            string tmp = Encoding.ASCII.GetString((byte[])fval);
                            row.Cells[i].Value = tmp;
                        }
                        else
                        {
                            row.Cells[i].Value = fval;
                        }
                    } // else

                    // Mark different cells with special background color
                    if (item.Result == ComparisonResult.DifferentData)
                    {
                        if (i < item.LeftFields.Length && i < item.RightFields.Length)
                        {
                            if (item.ChangedBlobsColumnNames != null && item.ChangedBlobsColumnNames.Contains(col.ObjectName.ToString()))
                                row.Cells[i].Style.BackColor = DIFFERENT_CELL_BACK_COLOR;
                            else 
                            {
                                // Check only if the field appears in both tables and has different values
                                if (item.HasField(cname,true) && item.HasField(cname,false) && !item.GetField(cname, true).Equals(item.GetField(cname, false)))
                                {
                                    object tmp = item.GetField(cname, isLeft);
                                    if (tmp is long && ((long)tmp) == 0 && Utils.GetDbType(col.ColumnType) == DbType.Binary &&
                                        item.GetField(cname, !isLeft) == DBNull.Value)
                                    {
                                        // Ignore the case when the values are not equal when one of the fields is a BLOB and the
                                        // other is not, but both values indicate NULL content.
                                    }
                                    else
                                    {
                                        tmp = item.GetField(cname, !isLeft);
                                        SQLiteCreateTableStatement tbl;
                                        if (isLeft)
                                            tbl = _tableChanges.RightTable;
                                        else
                                            tbl = _tableChanges.LeftTable;
                                        object tmp2 = item.GetField(cname, isLeft);
                                        SQLiteColumnStatement ocol = Utils.GetColumnByName(tbl.Columns, cname);
                                        if (tmp2 == DBNull.Value && tmp != null && tmp is long && ((long)tmp) == 0)
                                        {
                                            // Ignore the reverse case when both fields are actually NULL, but one of them is BLOB
                                            // and ther other is not.
                                        }
                                        else
                                            row.Cells[i].Style.BackColor = DIFFERENT_CELL_BACK_COLOR;
                                    } // else
                                } // if
                            } // else
                        } // if
                    } // if
                } // for
            } // else
        }

        private void PrepareDataTab()
        {
            lblLeftPath.Text = _leftdb;
            lblRightPath.Text = _rightdb;

            _total = _tableChanges.GetTotalChangesCount(new string[] { _diff });

            grdLeft.RowsCount = 0;
            grdRight.RowsCount = 0;

            // Add columns to both grids
            FillColumns();

            // Set the total number of rows to display
            grdLeft.RowsCount = _total;
            grdRight.RowsCount = _total;

            // Start with first line
            if (_total > 0)
            {
                grdLeft.FirstDisplayedRowIndex = 0;
                grdRight.FirstDisplayedRowIndex = 0;
            }

            // Update the selection bar control
            selectionBar1.SetData(_total, grdLeft.Selection);
            selectionBar1.CursorOffset = 0;

            UpdateState();
        }

        private void FillColumns()
        {
            grdLeft.Columns.Clear();
            grdRight.Columns.Clear();

            // Add columns to both grids
            List<SQLiteColumnStatement> common = Utils.GetCommonColumns(
                (SQLiteCreateTableStatement)_item.LeftDdlStatement,
                (SQLiteCreateTableStatement)_item.RightDdlStatement);

            FillGridColumns(grdLeft, common, (SQLiteCreateTableStatement)_item.LeftDdlStatement);
            FillGridColumns(grdRight, common, (SQLiteCreateTableStatement)_item.RightDdlStatement);
        }

        private void FillGridColumns(FastGrid grid, List<SQLiteColumnStatement> common, SQLiteCreateTableStatement table)
        {
            // Construct a list of columns that starts with all the primary key columns
            // and followed by all other columns that are common to both tables.
            List<SQLiteColumnStatement> pkeys = Utils.GetPrimaryColumns(table);
            List<SQLiteColumnStatement> cols = new List<SQLiteColumnStatement>();
            foreach (SQLiteColumnStatement cs in common)
            {
                bool found = false;
                for (int i = 0; i < pkeys.Count; i++)
                {
                    if (pkeys[i].ObjectName.Equals(cs.ObjectName))
                    {
                        found = true;
                        break;
                    }
                } // for
                if (!found)
                    cols.Add(cs);
            } // foreach

            // Construct a list of columns that are unique to this table (not common with another table)
            List<SQLiteColumnStatement> mycols = new List<SQLiteColumnStatement>();
            foreach (SQLiteColumnStatement c in table.Columns)
            {
                bool found = false;
                foreach (SQLiteColumnStatement cm in common)
                {
                    if (c.ObjectName.Equals(cm.ObjectName))
                    {
                        found = true;
                        break;
                    }
                }
                if (!found)
                    mycols.Add(c);
            } // foreach

            grid.Columns.Clear();

            // Add primary key columns
            for (int i = 0; i < pkeys.Count; i++)
                grid.Columns.Add(MakeGridColumn(true, GetColumnFromTable(pkeys[i].ObjectName, table.Columns)));

            // Add common columns
            for (int i = 0; i < cols.Count; i++)
                grid.Columns.Add(MakeGridColumn(false, GetColumnFromTable(cols[i].ObjectName, table.Columns)));

            // Add non common columns
            for (int i = 0; i < mycols.Count; i++)
                grid.Columns.Add(MakeGridColumn(false, GetColumnFromTable(mycols[i].ObjectName, table.Columns)));

            grid.RefreshLayout();
        }

        private SQLiteColumnStatement GetColumnFromTable(SQLiteObjectName name, List<SQLiteColumnStatement> clist)
        {
            foreach (SQLiteColumnStatement col in clist)
            {
                if (col.ObjectName.Equals(name))
                    return col;
            } // foreach
            return null;
        }

        private FastGridColumn MakeGridColumn(bool pkey, SQLiteColumnStatement col)
        {
            string cname = SQLiteParser.Utils.Chop(col.ObjectName.ToString());
            FastGridColumn res = new FastGridColumn(cname);
            if (Utils.GetDbType(col.ColumnType) == DbType.Boolean)
                res.ColumnType = FastGridColumnType.CheckBox;
            if (pkey)
                res.Image = imageList1.Images[0];
            res.Tag = cname;
            return res;
        }
        #endregion

        #region Constants
        private Color EMPTY_ROW_BACK_COLOR = Color.LightGray;
        private Color DIFFERENT_CELL_BACK_COLOR = Color.Khaki;
        #endregion

        #region Private Variables
        private long _total;
        private string _diff;
        private string _leftdb;
        private string _rightdb;
        private Font _italic;
        private SchemaComparisonItem _item;
        private TableChanges _tableChanges;
        private FastGrid _lastFocusedGrid;
        private SearchDataRowsDialog _searchDialog;
        private ILog _log = LogManager.GetLogger(typeof(TableDiffControl));
        #endregion
    }
}

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