Skip to content

Commit 58c600c

Browse files
author
MPCoreDeveloper
committed
working basic UI
1 parent c437dcc commit 58c600c

20 files changed

+1463
-146
lines changed

SharpCoreDB.Data.Provider/SharpCoreDBCommand.cs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Data;
22
using System.Data.Common;
33
using System.Diagnostics.CodeAnalysis;
4+
using SharpCoreDB.Interfaces;
45

56
namespace SharpCoreDB.Data.Provider;
67

@@ -139,6 +140,16 @@ public override int ExecuteNonQuery()
139140
else
140141
db.ExecuteSQL(CommandText!);
141142

143+
// ? CRITICAL FIX: Flush data after non-query commands to ensure persistence!
144+
// Without this, INSERT/UPDATE/DELETE data only lives in memory until connection close.
145+
var commandUpper = CommandText!.Trim().ToUpperInvariant();
146+
if (commandUpper.StartsWith("INSERT") ||
147+
commandUpper.StartsWith("UPDATE") ||
148+
commandUpper.StartsWith("DELETE"))
149+
{
150+
db.Flush();
151+
}
152+
142153
// SharpCoreDB doesn't return rows affected, return -1 as per ADO.NET convention
143154
return -1;
144155
}
@@ -160,6 +171,13 @@ protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
160171
var db = Connection!.DbInstance!;
161172
var parameters = BuildParameterDictionary();
162173

174+
// ? FIX: Intercept SQLite system table queries and redirect to IMetadataProvider
175+
var commandTextUpper = CommandText!.ToUpperInvariant().Trim();
176+
if (commandTextUpper.Contains("SQLITE_MASTER") || commandTextUpper.Contains("SQLITE_SCHEMA"))
177+
{
178+
return ExecuteSystemTableQuery(commandTextUpper, behavior);
179+
}
180+
163181
List<Dictionary<string, object>> results;
164182
if (parameters.Count > 0)
165183
results = db.ExecuteQuery(CommandText!, parameters);
@@ -174,6 +192,148 @@ protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
174192
}
175193
}
176194

195+
/// <summary>
196+
/// Executes queries against SQLite system tables (sqlite_master) by redirecting to IMetadataProvider.
197+
/// </summary>
198+
private DbDataReader ExecuteSystemTableQuery(string commandTextUpper, CommandBehavior behavior)
199+
{
200+
#if DEBUG
201+
System.Diagnostics.Debug.WriteLine($"[ExecuteSystemTableQuery] Query: {CommandText}");
202+
System.Diagnostics.Debug.WriteLine($"[ExecuteSystemTableQuery] Command (upper): {commandTextUpper}");
203+
#endif
204+
205+
try
206+
{
207+
var db = Connection!.DbInstance!;
208+
209+
// Check if this is a table list query
210+
if (commandTextUpper.Contains("SELECT") && commandTextUpper.Contains("NAME"))
211+
{
212+
#if DEBUG
213+
System.Diagnostics.Debug.WriteLine($"[ExecuteSystemTableQuery] Detected table list query");
214+
System.Diagnostics.Debug.WriteLine($"[ExecuteSystemTableQuery] Database type: {db.GetType().Name}");
215+
System.Diagnostics.Debug.WriteLine($"[ExecuteSystemTableQuery] IMetadataProvider: {db is IMetadataProvider}");
216+
#endif
217+
218+
// Redirect to IMetadataProvider.GetTables()
219+
if (db is IMetadataProvider metadata)
220+
{
221+
var tables = metadata.GetTables();
222+
223+
#if DEBUG
224+
System.Diagnostics.Debug.WriteLine($"[ExecuteSystemTableQuery] GetTables() returned {tables?.Count ?? 0} tables");
225+
#endif
226+
227+
// Convert to the format expected by SQLite queries
228+
var results = new List<Dictionary<string, object>>();
229+
230+
if (tables != null && tables.Count > 0)
231+
{
232+
foreach (var table in tables)
233+
{
234+
#if DEBUG
235+
System.Diagnostics.Debug.WriteLine($"[ExecuteSystemTableQuery] Table: {table.Name}, Type: {table.Type}");
236+
#endif
237+
results.Add(new Dictionary<string, object>
238+
{
239+
["name"] = table.Name ?? string.Empty,
240+
["type"] = table.Type ?? "TABLE"
241+
});
242+
}
243+
}
244+
245+
#if DEBUG
246+
System.Diagnostics.Debug.WriteLine($"[ExecuteSystemTableQuery] Returning {results.Count} rows");
247+
#endif
248+
249+
// Return empty result set if no tables (this is valid - new database)
250+
return new SharpCoreDBDataReader(results, behavior);
251+
}
252+
else
253+
{
254+
#if DEBUG
255+
System.Diagnostics.Debug.WriteLine($"[ExecuteSystemTableQuery] ERROR: Database does not implement IMetadataProvider!");
256+
#endif
257+
}
258+
}
259+
260+
// Check if this is a column list query (pragma_table_info or similar)
261+
if (commandTextUpper.Contains("PRAGMA") || commandTextUpper.Contains("TABLE_INFO"))
262+
{
263+
// Extract table name from query (simplified parsing)
264+
var tableName = ExtractTableNameFromPragma(CommandText!);
265+
266+
if (!string.IsNullOrEmpty(tableName) && db is IMetadataProvider metadata)
267+
{
268+
var columns = metadata.GetColumns(tableName);
269+
270+
var results = new List<Dictionary<string, object>>();
271+
foreach (var column in columns)
272+
{
273+
results.Add(new Dictionary<string, object>
274+
{
275+
["cid"] = column.Ordinal,
276+
["name"] = column.Name,
277+
["type"] = column.DataType,
278+
["notnull"] = !column.IsNullable ? 1 : 0,
279+
["dflt_value"] = DBNull.Value,
280+
["pk"] = 0
281+
});
282+
}
283+
284+
return new SharpCoreDBDataReader(results, behavior);
285+
}
286+
}
287+
288+
// Fallback: Return empty result set for unsupported system queries
289+
// This is valid - don't throw an exception
290+
return new SharpCoreDBDataReader([], behavior);
291+
}
292+
catch (Exception ex)
293+
{
294+
// Log the error but return empty result set instead of crashing
295+
System.Diagnostics.Debug.WriteLine($"[SharpCoreDB] System table query failed: {ex.Message}");
296+
return new SharpCoreDBDataReader([], behavior);
297+
}
298+
}
299+
300+
/// <summary>
301+
/// Extracts table name from PRAGMA table_info() or similar queries.
302+
/// </summary>
303+
private static string? ExtractTableNameFromPragma(string sql)
304+
{
305+
try
306+
{
307+
// Simple regex to extract table name from PRAGMA table_info('tablename')
308+
var match = System.Text.RegularExpressions.Regex.Match(
309+
sql,
310+
@"table_info\s*\(\s*['""]?(\w+)['""]?\s*\)",
311+
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
312+
313+
if (match.Success && match.Groups.Count > 1)
314+
{
315+
return match.Groups[1].Value;
316+
}
317+
318+
// Try to extract from SELECT * FROM pragma_table_info('tablename')
319+
match = System.Text.RegularExpressions.Regex.Match(
320+
sql,
321+
@"pragma_table_info\s*\(\s*['""]?(\w+)['""]?\s*\)",
322+
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
323+
324+
if (match.Success && match.Groups.Count > 1)
325+
{
326+
return match.Groups[1].Value;
327+
}
328+
}
329+
catch
330+
{
331+
// Ignore parsing errors
332+
}
333+
334+
return null;
335+
}
336+
177337
/// <summary>
178338
/// Executes a SQL statement and returns the first column of the first row.
179339
/// </summary>
@@ -224,6 +384,17 @@ public override async Task<int> ExecuteNonQueryAsync(CancellationToken cancellat
224384
else
225385
await db.ExecuteSQLAsync(CommandText!, cancellationToken);
226386

387+
// ? CRITICAL FIX: Flush data after non-query commands to ensure persistence!
388+
// Without this, INSERT/UPDATE/DELETE data only lives in memory until connection close.
389+
// This is essential for data durability - we want data on disk IMMEDIATELY.
390+
var commandUpper = CommandText!.Trim().ToUpperInvariant();
391+
if (commandUpper.StartsWith("INSERT") ||
392+
commandUpper.StartsWith("UPDATE") ||
393+
commandUpper.StartsWith("DELETE"))
394+
{
395+
db.Flush();
396+
}
397+
227398
return -1;
228399
}
229400
catch (Exception ex)
@@ -280,6 +451,13 @@ protected override async Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBeha
280451
var db = Connection!.DbInstance!;
281452
var parameters = BuildParameterDictionary();
282453

454+
// ? FIX: Intercept SQLite system table queries and redirect to IMetadataProvider
455+
var commandTextUpper = CommandText!.ToUpperInvariant().Trim();
456+
if (commandTextUpper.Contains("SQLITE_MASTER") || commandTextUpper.Contains("SQLITE_SCHEMA"))
457+
{
458+
return ExecuteSystemTableQuery(commandTextUpper, behavior);
459+
}
460+
283461
// ExecuteQuery doesn't have async version, so use Task.Run
284462
var results = await Task.Run(() =>
285463
{

SharpCoreDB.Data.Provider/SharpCoreDBConnection.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ public override void Open()
117117
catch (Exception ex)
118118
{
119119
_state = ConnectionState.Broken;
120+
121+
// ? FIX: Provide clear error message if sqlite_master is mentioned
122+
if (ex.Message.Contains("sqlite_master", StringComparison.OrdinalIgnoreCase))
123+
{
124+
throw new SharpCoreDBException(
125+
"SharpCoreDB is not SQLite. The error 'Table sqlite_master does not exist' indicates that " +
126+
"some tooling or code is trying to query SQLite system tables. " +
127+
"SharpCoreDB uses its own metadata system via IMetadataProvider. " +
128+
$"Original error: {ex.Message}", ex);
129+
}
130+
120131
throw new SharpCoreDBException("Failed to open connection to SharpCoreDB.", ex);
121132
}
122133
}
@@ -131,6 +142,23 @@ public override void Close()
131142

132143
try
133144
{
145+
// ? FIX: FORCE save metadata on close to ensure persistence
146+
if (_database != null)
147+
{
148+
try
149+
{
150+
_database.ForceSave();
151+
}
152+
catch (Exception ex)
153+
{
154+
#if DEBUG
155+
System.Diagnostics.Debug.WriteLine($"[SharpCoreDB] Failed to save during close: {ex.Message}");
156+
#endif
157+
// Suppress exception to allow connection to close gracefully
158+
_ = ex; // Avoid unused variable warning
159+
}
160+
}
161+
134162
_database = null;
135163

136164
if (_serviceProvider is IDisposable disposable)

SharpCoreDB.Viewer/App.axaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
<Application.Styles>
2222
<FluentTheme />
23+
<!-- Include DataGrid styles so it renders headers/rows -->
24+
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
2325

2426
<!-- Custom Visual Studio Dark Theme -->
2527
<StyleInclude Source="/Styles/VSTheme.axaml"/>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using Avalonia.Data.Converters;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Globalization;
5+
6+
namespace SharpCoreDB.Viewer.Converters;
7+
8+
/// <summary>
9+
/// Converter to extract values from Dictionary<string, object> for DataGrid binding.
10+
/// </summary>
11+
public class DictionaryValueConverter : IValueConverter
12+
{
13+
public string? ColumnName { get; set; }
14+
15+
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
16+
{
17+
#if DEBUG
18+
System.Diagnostics.Debug.WriteLine($"[DictionaryValueConverter] Convert called - value type: {value?.GetType().Name ?? "NULL"}, parameter: {parameter}");
19+
#endif
20+
21+
if (value is Dictionary<string, object> dict)
22+
{
23+
var key = parameter as string ?? ColumnName;
24+
25+
#if DEBUG
26+
System.Diagnostics.Debug.WriteLine($"[DictionaryValueConverter] Dictionary has {dict.Count} keys, looking for key: '{key}'");
27+
#endif
28+
29+
if (key != null && dict.TryGetValue(key, out var val))
30+
{
31+
#if DEBUG
32+
System.Diagnostics.Debug.WriteLine($"[DictionaryValueConverter] Found value: {val} (type: {val?.GetType().Name ?? "NULL"})");
33+
#endif
34+
35+
// Convert DBNull to empty string
36+
if (val == DBNull.Value || val == null)
37+
return string.Empty;
38+
39+
return val.ToString() ?? string.Empty;
40+
}
41+
else
42+
{
43+
#if DEBUG
44+
System.Diagnostics.Debug.WriteLine($"[DictionaryValueConverter] Key '{key}' NOT FOUND in dictionary. Available keys: {string.Join(", ", dict.Keys)}");
45+
#endif
46+
}
47+
}
48+
else
49+
{
50+
#if DEBUG
51+
System.Diagnostics.Debug.WriteLine($"[DictionaryValueConverter] Value is NOT a Dictionary!");
52+
#endif
53+
}
54+
55+
return string.Empty;
56+
}
57+
58+
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
59+
{
60+
throw new NotImplementedException();
61+
}
62+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using Avalonia.Data.Converters;
2+
using System;
3+
using System.Globalization;
4+
5+
namespace SharpCoreDB.Viewer.Converters;
6+
7+
/// <summary>
8+
/// Converter to safely convert any object to string for display in DataGrid.
9+
/// Handles DBNull, null, DateTime, bool, and other types.
10+
/// </summary>
11+
public class ObjectToStringConverter : IValueConverter
12+
{
13+
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
14+
{
15+
#if DEBUG
16+
System.Diagnostics.Debug.WriteLine($"[ObjectToStringConverter] Convert called - value type: {value?.GetType().Name ?? "NULL"}, value: {value}");
17+
#endif
18+
19+
if (value == null || value == DBNull.Value)
20+
return string.Empty;
21+
22+
if (value is DateTime dt)
23+
return dt.ToString("yyyy-MM-dd HH:mm:ss");
24+
25+
if (value is bool b)
26+
return b ? "1" : "0";
27+
28+
if (value is decimal dec)
29+
return dec.ToString("N2", culture);
30+
31+
if (value is double dbl)
32+
return dbl.ToString("N2", culture);
33+
34+
if (value is float flt)
35+
return flt.ToString("N2", culture);
36+
37+
var result = value.ToString() ?? string.Empty;
38+
39+
#if DEBUG
40+
System.Diagnostics.Debug.WriteLine($"[ObjectToStringConverter] Returning: '{result}'");
41+
#endif
42+
43+
return result;
44+
}
45+
46+
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
47+
{
48+
throw new NotImplementedException();
49+
}
50+
}

SharpCoreDB.Viewer/Converters/PasswordVisibilityConverters.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ public static class BoolConverters
1414
private static readonly LocalizationService _localization = LocalizationService.Instance;
1515

1616
/// <summary>
17-
/// Converts boolean to PasswordChar: empty string (visible) or bullet (hidden)
17+
/// Converts boolean to PasswordChar: empty string (visible) or asterisk (hidden)
1818
/// </summary>
1919
public static readonly IValueConverter PasswordCharConverter =
20-
new FuncValueConverter<bool, char>(isVisible => isVisible ? '\0' : '?');
20+
new FuncValueConverter<bool, char>(isVisible => isVisible ? '\0' : '*');
2121

2222
/// <summary>
2323
/// Converts boolean to Eye icon: EyeIcon (visible) or EyeOffIcon (hidden)

0 commit comments

Comments
 (0)