Add SQLite database preview support to Peek#45846
Conversation
This comment has been minimized.
This comment has been minimized.
|
@jiripolasek, |
MuyuanMS
left a comment
There was a problem hiding this comment.
Code Review Suggestions
After thorough review (including iterative Copilot code review on a fork), I've identified several issues ranging from a high-severity SQL injection vulnerability to UX improvements. The suggestions below are ordered by severity and are designed to be self-contained — each can be committed independently via the "Commit suggestion" button.
Summary of Issues
| # | Issue | Severity | File |
|---|---|---|---|
| 1 | SQL identifier injection via bracket-quoting | High | SQLitePreviewer.cs |
| 2 | WinUI binding path fails for column names with special chars | Medium | SQLiteControl.xaml.cs |
| 3 | Misleading row count when table data is truncated | Medium | SQLiteControl.xaml.cs |
| 4 | Key collision when distinct column names sanitize to the same string | Medium | SQLiteColumnInfo.cs |
Recommended approach: Add a new SQLiteHelpers.cs file (suggested in comment 1) that centralizes SQL quoting and binding key management, then update the existing files to use it.
All changes have been verified to compile and function correctly with the Peek module.
|
|
||
| using (var cmd = connection.CreateCommand()) | ||
| { | ||
| cmd.CommandText = $"SELECT COUNT(*) FROM [{tableName}];"; |
There was a problem hiding this comment.
Same bracket-quoting vulnerability as above. Replace with double-quote escaping:
| cmd.CommandText = $"SELECT COUNT(*) FROM [{tableName}];"; | |
| cmd.CommandText = $"SELECT COUNT(*) FROM \"{tableName.Replace("\"", "\"\"")}\";"; |
|
|
||
| using (var cmd = connection.CreateCommand()) | ||
| { | ||
| cmd.CommandText = $"SELECT * FROM [{tableName}] LIMIT 200;"; |
There was a problem hiding this comment.
Same bracket-quoting vulnerability. Replace with double-quote escaping:
| cmd.CommandText = $"SELECT * FROM [{tableName}] LIMIT 200;"; | |
| cmd.CommandText = $"SELECT * FROM \"{tableName.Replace("\"", "\"\"")}\" LIMIT 200;"; |
| { | ||
| TableDataGrid.Columns.Add(new DataGridTextColumn |
There was a problem hiding this comment.
WinUI binding path fails for column names with special characters (Medium Severity)
The binding path $"[{col.Name}]" uses WinUI's indexer syntax to access dictionary keys. If a column name contains characters special to PropertyPath (., [, ], /), the binding silently fails and the DataGrid shows empty cells for that column.
Similarly, the row dictionary (lines 119–124) uses raw column names as keys. If two distinct column names sanitize to the same key (e.g., a.b and a_b both become a_b), one value overwrites the other.
Recommended fix: Add a BindingKey property to SQLiteColumnInfo and a collision-safe key assignment step:
- In
SQLiteColumnInfo.cs, add:
public string BindingKey { get; set; } = string.Empty;- After loading columns, call a helper that sanitizes and deduplicates:
// Replaces .[]/ with _ and appends suffix on collision
SQLiteHelpers.AssignBindingKeys(tableInfo.Columns);- Then here, use
col.BindingKeyinstead ofcol.Name:
| { | |
| TableDataGrid.Columns.Add(new DataGridTextColumn | |
| TableDataGrid.Columns.Add(new DataGridTextColumn | |
| { | |
| Header = col.Name, | |
| Binding = new Binding { Path = new PropertyPath($"[{col.BindingKey}]") }, | |
| IsReadOnly = true, | |
| }); |
- And in
SQLitePreviewer.cs(line 122), usecol.BindingKeyfor the row dictionary key too:
var col = tableInfo.Columns[i];
row[col.BindingKey] = reader.IsDBNull(i) ? null : reader.GetValue(i)?.ToString();There was a problem hiding this comment.
Added a BindingKey property to SQLiteColumnInfo. The new SQLiteHelpers.AssignBindingKeys() method safely sanitizes column names containing special characters (like ., [, ], /) and handles suffix collisions. Both the row dictionary and the DataGrid bindings now safely use BindingKey.
| // so ActualWidth values are valid when we decide whether to stretch the last column. | ||
| TableDataGrid.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, AdjustLastColumnWidth); | ||
|
|
||
| RecordCountText.Text = string.Format( |
There was a problem hiding this comment.
Misleading row count when table data is truncated (Medium Severity)
The record count display shows the total row count from COUNT(*) (e.g., "Records: 5000"), but the DataGrid only shows up to 200 rows due to the LIMIT 200 in the query. Users see "Records: 5000" but can only scroll through 200 rows, with no indication the view is truncated.
Fix: Conditionally format the string to indicate truncation when table.RowCount > table.Rows.Count:
| // so ActualWidth values are valid when we decide whether to stretch the last column. | |
| TableDataGrid.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, AdjustLastColumnWidth); | |
| RecordCountText.Text = string.Format( | |
| RecordCountText.Text = table.RowCount > table.Rows.Count | |
| ? string.Format( | |
| CultureInfo.CurrentCulture, | |
| ResourceLoaderInstance.ResourceLoader.GetString("SQLite_Row_Count_Truncated"), | |
| table.Rows.Count, | |
| table.RowCount) | |
| : string.Format( | |
| CultureInfo.CurrentCulture, | |
| ResourceLoaderInstance.ResourceLoader.GetString("SQLite_Row_Count"), | |
| table.RowCount); |
This requires a new resource string in Resources.resw:
<data name="SQLite_Row_Count_Truncated" xml:space="preserve">
<value>Records: {0} of {1}</value>
<comment>{0} is the number of rows shown (limited), {1} is the total row count</comment>
</data>This way users see "Records: 200 of 5000" making it clear the view is limited.
There was a problem hiding this comment.
Thank you for the suggestion. Added conditional formatting in SQLiteControl.xaml.cs to explicitly show when the table view is truncated. When the total row count exceeds the preview limit, the UI now displays "Records: {shown} of {total}" using the newly added SQLite_Row_Count_Truncated resource string.
|
|
||
| public bool IsPrimaryKey { get; set; } | ||
|
|
||
| public bool IsNotNull { get; set; } |
There was a problem hiding this comment.
Add BindingKey property for safe WinUI data binding
To support the binding path fix (see comment on SQLiteControl.xaml.cs), this model needs a BindingKey property. It stores the sanitized, collision-safe key used by both the row dictionary and the DataGrid binding path. The key is computed after all columns are loaded so that duplicate detection can append numeric suffixes (e.g., a_b → a_b_2).
| public bool IsNotNull { get; set; } | |
| public bool IsNotNull { get; set; } | |
| /// <summary> | |
| /// Sanitized key used for WinUI binding paths and row dictionary keys. | |
| /// Computed after all columns are loaded to handle potential collisions. | |
| /// </summary> | |
| public string BindingKey { get; set; } = string.Empty; |
| var row = new Dictionary<string, string?>(reader.FieldCount, StringComparer.Ordinal); | ||
| for (int i = 0; i < reader.FieldCount; i++) | ||
| { | ||
| row[reader.GetName(i)] = reader.IsDBNull(i) ? null : reader.GetValue(i)?.ToString(); | ||
| } |
There was a problem hiding this comment.
Use column's BindingKey for row dictionary keys
The row dictionary keys must match the PropertyPath used in the DataGrid binding. Currently both use the raw column name, which breaks for special characters. After adding BindingKey to SQLiteColumnInfo and computing it with collision detection (via AssignBindingKeys()), use it here:
| var row = new Dictionary<string, string?>(reader.FieldCount, StringComparer.Ordinal); | |
| for (int i = 0; i < reader.FieldCount; i++) | |
| { | |
| row[reader.GetName(i)] = reader.IsDBNull(i) ? null : reader.GetValue(i)?.ToString(); | |
| } | |
| var row = new Dictionary<string, string?>(reader.FieldCount, StringComparer.Ordinal); | |
| for (int i = 0; i < reader.FieldCount; i++) | |
| { | |
| var col = tableInfo.Columns[i]; | |
| row[col.BindingKey] = reader.IsDBNull(i) ? null : reader.GetValue(i)?.ToString(); | |
| } |
This ensures the DataGrid can always find the correct value regardless of what characters appear in the original column name.
There was a problem hiding this comment.
Thanks, updated the relevant code
- Create SQLiteHelpers for safe quoting and WinUI data binding. - Safely quote identifiers in SQLite queries to mitigate SQL injection. - Add BindingKey property to map valid WinUI binding paths. - Update UI to accurately reflect truncated table rows. - Add SQLite_Row_Count_Truncated resource string. Co-authored-by: thomastv <44024439+thomastv@users.noreply.github.com>
- Update dependency-review-action to v4.3.4 - Update check-spelling to v0.0.22 Co-authored-by: thomastv <44024439+thomastv@users.noreply.github.com>
Update check-spelling and dependency-review actions to fix CI warnings. Co-authored-by: thomastv <44024439+thomastv@users.noreply.github.com>
Remove upload/download-artifact actions because they are using deprecated v3. Co-authored-by: thomastv <44024439+thomastv@users.noreply.github.com>
Co-authored-by: thomastv <44024439+thomastv@users.noreply.github.com>
Co-authored-by: thomastv <44024439+thomastv@users.noreply.github.com>
- Fix upload-artifact and download-artifact v3 deprecation warnings - Fix dependency-review Node.js deprecation warning - Fix check-spelling credential leak advisory failure Co-authored-by: thomastv <44024439+thomastv@users.noreply.github.com>
- Layout cleanup, removed the floating tables and updated the theming - Refactored to follow modern C# syntax
…k-9659624763486596079
Add SQLiteHelpers and update SQLite previewer
### 🎨 UI & UX Improvements
* **Modernized Empty State**: Replaced the plain "Select a table..." text with a proper, centered Windows 11-style empty state that features a large database `FontIcon`.
* **TreeView Icons**: Added a `SQLiteTreeNode` wrapper to support contextual icons within the TreeView. Tables are now visually distinguished from columns via `FontIcon` glyphs.
* **Layout Cleanups**: Removed the floating "1 tables" footer pill and relocated the table count to a clean header at the top of the left pane.
* **Seamless Theming**: Updated the `DataGrid` (including the `ColumnHeaderStyle` and Row Backgrounds) to be completely `Transparent`. This removes the jarring white boxes and allows the tables to seamlessly blend with the application's native Mica/grey background.
### 🛠️ Code Quality & Build Fixes
* **Resolved StyleCop & IDE Warnings**: Cleaned up the codebase to ensure zero active warnings. This included fixing trailing commas (`SA1413`), using discard parameters for unused event arguments (`IDE0060`, `SA1313`), collapsing redundant `if-else` blocks (`IDE0045`), and simplifying indexers (`IDE0056`).
* **Modernized C# Syntax**: Refactored the `ObservableCollection` and `HashSet` initializations in `SQLitePreviewer.cs` to utilize modern C# 12 collection expressions (`[]`) (`IDE0028`).
* **Safe XAML Data Binding**: Ensured `SQLiteTreeNode` was properly structured as a `public sealed class` (`CA1852`, `SA1402`) so the WinUI 3 XAML `{Binding}` engine can reliably read and resolve properties via reflection without silently failing.
* **Fixed SQL Identifier Injection**: Replaced the bracket-quoting `[identifier]` syntax with standard double-quote escaping. I introduced a shared `SQLiteHelpers.QuoteIdentifier()` method and applied it to all SQL queries (`PRAGMA table_info`, `SELECT COUNT(*)`, and `SELECT *`) in `SQLitePreviewer.cs`.
* **Resolved WinUI Binding Path Crashes**: Added a `BindingKey` property to `SQLiteColumnInfo`. The new `SQLiteHelpers.AssignBindingKeys()` method safely sanitizes column names containing special characters (like `.`, `[`, `]`, `/`) and handles suffix collisions. Both the row dictionary and the DataGrid bindings now safely use `BindingKey`.
* **Clarified Truncated Row Counts**: Added conditional formatting in `SQLiteControl.xaml.cs` to explicitly show when the table view is truncated. When the total row count exceeds the preview limit, the UI now displays "Records: {shown} of {total}" using the newly added `SQLite_Row_Count_Truncated` resource string.
|
Can you remind me why you changed .yml workflows in |
Sorry those files were changed when I synced the main branch, I have reverted it back |
Summary of the Pull Request
This PR adds SQLite database file preview support to the Peek utility, resolving issue #45757 . Three new model classes (SQLiteTableInfo, SQLiteColumnInfo, ISQLitePreviewer) and a core SQLitePreviewer class were added to read .db, .sqlite, and .sqlite3 files read-only using Microsoft.Data.Sqlite, fetching table schema via PRAGMA table_info and up to 200 rows per table. A new SQLiteControl XAML control presents the data in a two-pane layout — a tree view listing all tables on the left and a data grid showing columns and rows on the right, with intelligent column sizing that stretches the last column when space allows and falls back to horizontal scrolling for wide tables. The previewer was wired into the existing previewerFactory and FilePreview host, and three localizable strings were added to Resources.resw.
PR Checklist
Detailed Description of the Pull Request / Additional comments
Validation Steps Performed