Browse Source

Added DbReload Utility to rebuild database from Log Files

frogsoftware 3 weeks ago
parent
commit
c17dbdc584

+ 16 - 0
DbReload/DbReload.sln

@@ -0,0 +1,16 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbReload", "DbReload\DbReload.csproj", "{23B04B8E-64E5-460C-A292-BBBDAE4F504A}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{23B04B8E-64E5-460C-A292-BBBDAE4F504A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{23B04B8E-64E5-460C-A292-BBBDAE4F504A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{23B04B8E-64E5-460C-A292-BBBDAE4F504A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{23B04B8E-64E5-460C-A292-BBBDAE4F504A}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+EndGlobal

+ 9 - 0
DbReload/DbReload/App.xaml

@@ -0,0 +1,9 @@
+<Application x:Class="DbReload.App"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:local="clr-namespace:DbReload"
+             StartupUri="MainWindow.xaml">
+    <Application.Resources>
+         
+    </Application.Resources>
+</Application>

+ 12 - 0
DbReload/DbReload/App.xaml.cs

@@ -0,0 +1,12 @@
+using System.Configuration;
+using System.Data;
+using System.Windows;
+
+namespace DbReload;
+
+/// <summary>
+/// Interaction logic for App.xaml
+/// </summary>
+public partial class App : Application
+{
+}

+ 10 - 0
DbReload/DbReload/AssemblyInfo.cs

@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+    //(used if a resource is not found in the page,
+    // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+    //(used if a resource is not found in the page,
+    // app, or any theme specific resource dictionaries)
+)]

+ 16 - 0
DbReload/DbReload/DbReload.csproj

@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <OutputType>WinExe</OutputType>
+        <TargetFramework>net8.0-windows</TargetFramework>
+        <Nullable>enable</Nullable>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <UseWPF>true</UseWPF>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+      <PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
+    </ItemGroup>
+
+</Project>

+ 85 - 0
DbReload/DbReload/MainViewModel.cs

@@ -0,0 +1,85 @@
+using System.Data.SQLite;
+using System.IO;
+using System.Windows.Threading;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Microsoft.Win32;
+
+namespace DbReload;
+
+public partial class MainViewModel : ObservableObject
+{
+    [ObservableProperty] private string _sqLiteFile = "";
+    [ObservableProperty] private string[] _logFiles = [];
+    [ObservableProperty] private string _progress = "";
+
+    [RelayCommand]
+    private void CreateSQLiteFile()
+    {
+        OpenFileDialog dlg = new OpenFileDialog();
+        dlg.Filter = "SQLite Files (*.db)|*.db";
+        if (dlg.ShowDialog() == true)
+            SqLiteFile = dlg.FileName;
+    }
+
+    [RelayCommand]
+    private void SelectLogFiles()
+    {
+        OpenFileDialog dlg = new OpenFileDialog();
+        dlg.Filter = "SQL Log Files (*.sql)|*.sql";
+        dlg.Multiselect = true;
+        if (dlg.ShowDialog() == true)
+            LogFiles = dlg.FileNames;
+    }
+
+    private SQLiteConnection _connection;
+    
+    [RelayCommand]
+    private async Task Go()
+    {
+        Progress = "Opening Database File";
+        var sb = new SQLiteConnectionStringBuilder();
+        sb.DataSource = SqLiteFile;
+        sb.Version = 3;
+        sb.DateTimeFormat = SQLiteDateFormats.Ticks;
+        sb.JournalMode = SQLiteJournalModeEnum.Wal;
+        
+        var conn = sb.ToString();
+
+        _connection = new SQLiteConnection(conn);
+        _connection.BusyTimeout = Convert.ToInt32(TimeSpan.FromMinutes(2).TotalMilliseconds);
+
+        _connection.Open();
+        var command = new SQLiteCommand(_connection);
+        var ordered = LogFiles.OrderBy(x => x);
+        foreach (var logfile in ordered)
+        {
+            Progress = $"Opening {Path.GetFileName(logfile)}";
+            var text = await File.ReadAllTextAsync(logfile);
+            //var statements = text.Split(";\r\n");
+            //int i = 0;
+            //foreach (var line in statements)
+            //{
+            //    await Dispatcher.CurrentDispatcher.BeginInvoke(() =>
+            //    {
+            //        Progress = "Processing {Path.GetFileName(logfile)} ({i}/{lines.Length})";
+            //    });
+                
+                command.CommandText = text;
+                try
+                {
+                    command.ExecuteNonQuery();
+                }
+                catch (Exception e)
+                {
+                    Console.WriteLine(e);
+                    //throw;
+                }
+                
+            //    i++;
+            //}    
+        }
+
+    }
+    
+}

+ 68 - 0
DbReload/DbReload/MainWindow.xaml

@@ -0,0 +1,68 @@
+<Window x:Class="DbReload.MainWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:DbReload"
+        mc:Ignorable="d"
+        Title="MainWindow" Height="450" Width="800">
+        <Window.DataContext>
+            <local:MainViewModel />
+        </Window.DataContext>
+                                                   
+        <Grid>
+           <Grid.RowDefinitions>
+               <RowDefinition Height="Auto" />
+               <RowDefinition Height="*" />
+               <RowDefinition Height="Auto" />
+           </Grid.RowDefinitions>
+           <Grid.ColumnDefinitions>
+               <ColumnDefinition Width="Auto"/>
+               <ColumnDefinition Width="*"/>
+               <ColumnDefinition Width="Auto"/>
+           </Grid.ColumnDefinitions>        
+           
+           <Label
+               Grid.Row="0"
+               Grid.Column="0"
+               Content="SQLite File" />
+           <TextBox
+               Grid.Row="0"
+               Grid.Column="1"
+               IsReadOnly="True"
+               Text="{Binding SqLiteFile}" />
+           <Button 
+               Grid.Row="0"
+               Grid.Column="2"
+               Content="Select"
+               Command="{Binding CreateSQLiteFileCommand}"/>
+           
+           <Label
+               Grid.Row="1"
+               Grid.Column="0"
+               Content="Log Folder" />
+           <ListBox
+               Grid.Row="1"
+               Grid.Column="1"
+               ItemsSource="{Binding LogFiles}" />
+           <Button 
+               Grid.Row="1"
+               Grid.Column="2"
+               Content="Select"
+               Command="{Binding SelectLogFilesCommand}"/>
+           
+           <Label
+               Grid.Row="2"
+               Grid.Column="0"
+               Content="Progress" />
+           <TextBox
+               Grid.Row="2"
+               Grid.Column="1"
+               Text="{Binding Progress}" />
+           <Button 
+               Grid.Row="2"
+               Grid.Column="2"
+               Content="Go!"
+               Command="{Binding GoCommand}"/>    
+       </Grid>
+</Window>

+ 23 - 0
DbReload/DbReload/MainWindow.xaml.cs

@@ -0,0 +1,23 @@
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace DbReload;
+
+/// <summary>
+/// Interaction logic for MainWindow.xaml
+/// </summary>
+public partial class MainWindow : Window
+{
+    public MainWindow()
+    {
+        InitializeComponent();
+    }
+}

+ 216 - 0
DbReload/DbReload/SqlTokenizer.cs

@@ -0,0 +1,216 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+public static class SqlTokenizer
+{
+    public static List<string> SplitStatements(string sql, bool keepTerminator = false)
+    {
+        if (sql == null) throw new ArgumentNullException(nameof(sql));
+
+        var results = new List<string>();
+        var sb = new StringBuilder(sql.Length);
+
+        int i = 0;
+        while (i < sql.Length)
+        {
+            char c = sql[i];
+
+            // 1) Statement terminator (only when not in any special mode)
+            if (c == ';')
+            {
+                if (keepTerminator) sb.Append(c);
+
+                var stmt = sb.ToString().Trim();
+                if (stmt.Length > 0) results.Add(stmt);
+
+                sb.Clear();
+                i++;
+                continue;
+            }
+
+            // 2) Single-quoted string: '...'
+            if (c == '\'')
+            {
+                ConsumeSingleQuoted(sql, ref i, sb);
+                continue;
+            }
+
+            // 3) Double-quoted identifier/string: "..."
+            if (c == '"')
+            {
+                ConsumeDoubleQuoted(sql, ref i, sb);
+                continue;
+            }
+
+            // 4) Line comment: -- ... (to end of line)
+            if (c == '-' && i + 1 < sql.Length && sql[i + 1] == '-')
+            {
+                ConsumeLineComment(sql, ref i, sb);
+                continue;
+            }
+
+            // 5) Block comment: /* ... */
+            if (c == '/' && i + 1 < sql.Length && sql[i + 1] == '*')
+            {
+                ConsumeBlockComment(sql, ref i, sb);
+                continue;
+            }
+
+            // 6) PostgreSQL dollar-quoted string: $tag$ ... $tag$ or $$ ... $$
+            if (c == '$')
+            {
+                if (TryConsumeDollarQuoted(sql, ref i, sb))
+                    continue;
+            }
+
+            // Default: copy char
+            sb.Append(c);
+            i++;
+        }
+
+        // Remainder
+        var last = sb.ToString().Trim();
+        if (last.Length > 0) results.Add(last);
+
+        return results;
+    }
+
+    private static void ConsumeSingleQuoted(string s, ref int i, StringBuilder sb)
+    {
+        // We are at opening '
+        sb.Append(s[i]);
+        i++;
+
+        while (i < s.Length)
+        {
+            char c = s[i];
+            sb.Append(c);
+            i++;
+
+            if (c == '\'')
+            {
+                // SQL escape for ' inside string is doubled ''
+                if (i < s.Length && s[i] == '\'')
+                {
+                    sb.Append(s[i]);
+                    i++;
+                    continue;
+                }
+                break; // end of string
+            }
+        }
+    }
+
+    private static void ConsumeDoubleQuoted(string s, ref int i, StringBuilder sb)
+    {
+        // We are at opening "
+        sb.Append(s[i]);
+        i++;
+
+        while (i < s.Length)
+        {
+            char c = s[i];
+            sb.Append(c);
+            i++;
+
+            if (c == '"')
+            {
+                // Escaped " inside identifier/string is doubled ""
+                if (i < s.Length && s[i] == '"')
+                {
+                    sb.Append(s[i]);
+                    i++;
+                    continue;
+                }
+                break; // end
+            }
+        }
+    }
+
+    private static void ConsumeLineComment(string s, ref int i, StringBuilder sb)
+    {
+        // We are at first '-'
+        sb.Append(s[i]);
+        sb.Append(s[i + 1]);
+        i += 2;
+
+        while (i < s.Length)
+        {
+            char c = s[i];
+            sb.Append(c);
+            i++;
+            if (c == '\n') break; // end of line comment
+        }
+    }
+
+    private static void ConsumeBlockComment(string s, ref int i, StringBuilder sb)
+    {
+        // We are at '/'
+        sb.Append(s[i]);
+        sb.Append(s[i + 1]);
+        i += 2;
+
+        while (i < s.Length)
+        {
+            char c = s[i];
+            sb.Append(c);
+            i++;
+
+            if (c == '*' && i < s.Length && s[i] == '/')
+            {
+                sb.Append(s[i]);
+                i++;
+                break;
+            }
+        }
+    }
+
+    private static bool TryConsumeDollarQuoted(string s, ref int i, StringBuilder sb)
+    {
+        // Dollar quote opener: $tag$ where tag is [A-Za-z_][A-Za-z0-9_]* or empty (i.e. $$)
+        // If not a valid opener, return false and let caller handle '$' normally.
+        int start = i;
+        int j = i + 1;
+
+        // Find next '$' to close the opener
+        while (j < s.Length && s[j] != '$')
+        {
+            // tag chars must be letters/digits/_ only
+            char ch = s[j];
+            if (!(char.IsLetterOrDigit(ch) || ch == '_'))
+                return false;
+            j++;
+        }
+
+        if (j >= s.Length || s[j] != '$')
+            return false; // no closing '$' for opener
+
+        // opener is s[start..j] inclusive
+        string tag = s.Substring(start, j - start + 1); // includes both '$'
+        // Examples: "$$" or "$abc$"
+
+        // Consume opener
+        sb.Append(tag);
+        i = j + 1;
+
+        // Now consume until we see the same tag again
+        while (i < s.Length)
+        {
+            // fast check for tag match
+            if (s[i] == '$' && i + tag.Length <= s.Length &&
+                string.CompareOrdinal(s, i, tag, 0, tag.Length) == 0)
+            {
+                sb.Append(tag);
+                i += tag.Length;
+                return true;
+            }
+
+            sb.Append(s[i]);
+            i++;
+        }
+
+        // Unterminated dollar quote: we consumed to end; still treat as consumed
+        return true;
+    }
+}

+ 7 - 0
DbReload/global.json

@@ -0,0 +1,7 @@
+{
+  "sdk": {
+    "version": "8.0.0",
+    "rollForward": "latestMinor",
+    "allowPrerelease": false
+  }
+}