From 0c44afde7fbe9c06a9e9c2e4575012e8f33d0140 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:05:37 +0000 Subject: [PATCH 1/3] Initial plan From 960d27cc8331e92269c2957b6f0b0cfcd78946e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:10:30 +0000 Subject: [PATCH 2/3] Reinstate EnsureIndexAsync implementation with CREATE INDEX logic Co-authored-by: mrdevrobot <12503462+mrdevrobot@users.noreply.github.com> --- .../SqlitePeerStore.cs | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/EntglDb.Persistence.Sqlite/SqlitePeerStore.cs b/src/EntglDb.Persistence.Sqlite/SqlitePeerStore.cs index 2806d3c..cdc4001 100644 --- a/src/EntglDb.Persistence.Sqlite/SqlitePeerStore.cs +++ b/src/EntglDb.Persistence.Sqlite/SqlitePeerStore.cs @@ -946,10 +946,39 @@ public async Task CountDocumentsAsync(string collection, QueryNode? queryEx return documents.Count(); } - public Task EnsureIndexAsync(string collection, string propertyPath, CancellationToken cancellationToken = default) + public async Task EnsureIndexAsync(string collection, string propertyPath, CancellationToken cancellationToken = default) { - // Not implemented (Placeholder) - return Task.CompletedTask; + using var connection = new SqliteConnection(_connectionString); + await connection.OpenAsync(cancellationToken); + + await EnsureCollectionTablesAsync(connection, collection); + + var tableName = GetDocumentTableName(collection); + var usePerCollection = _options?.UsePerCollectionTables == true; + + // Sanitize names to prevent injection + var safeColl = new string(collection.Where(char.IsLetterOrDigit).ToArray()); + var safeProp = new string(propertyPath.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '.').ToArray()); + var indexName = $"IDX_{safeColl}_{safeProp.Replace(".", "_")}"; + + string sql; + if (usePerCollection) + { + // Per-collection mode: simple index without collection filter + sql = $@"CREATE INDEX IF NOT EXISTS {indexName} + ON {tableName}(json_extract(JsonData, '$.{safeProp}'))"; + } + else + { + // Legacy mode: index with collection filter + sql = $@"CREATE INDEX IF NOT EXISTS {indexName} + ON Documents(json_extract(JsonData, '$.{safeProp}')) + WHERE Collection = '{collection}'"; + } + + await connection.ExecuteAsync(sql); + + _logger.LogInformation("Ensured index {IndexName} on {Collection}.{Property}", indexName, collection, propertyPath); } public async Task> GetCollectionsAsync(CancellationToken cancellationToken = default) @@ -957,7 +986,7 @@ public async Task> GetCollectionsAsync(CancellationToken can using var connection = new SqliteConnection(_connectionString); await connection.OpenAsync(cancellationToken).ConfigureAwait(false); - if (UsePerCollectionTables) + if (_options?.UsePerCollectionTables == true) { // In per-collection mode, rely on known collection tables. return GetKnownCollections(connection); From 167c2f7ac315f4f4f8dd9af7ac121c9e51200b8f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:12:26 +0000 Subject: [PATCH 3/3] Fix SQL injection vulnerability in EnsureIndexAsync WHERE clause Co-authored-by: mrdevrobot <12503462+mrdevrobot@users.noreply.github.com> --- src/EntglDb.Persistence.Sqlite/SqlitePeerStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EntglDb.Persistence.Sqlite/SqlitePeerStore.cs b/src/EntglDb.Persistence.Sqlite/SqlitePeerStore.cs index cdc4001..d132449 100644 --- a/src/EntglDb.Persistence.Sqlite/SqlitePeerStore.cs +++ b/src/EntglDb.Persistence.Sqlite/SqlitePeerStore.cs @@ -973,7 +973,7 @@ public async Task EnsureIndexAsync(string collection, string propertyPath, Cance // Legacy mode: index with collection filter sql = $@"CREATE INDEX IF NOT EXISTS {indexName} ON Documents(json_extract(JsonData, '$.{safeProp}')) - WHERE Collection = '{collection}'"; + WHERE Collection = '{safeColl}'"; } await connection.ExecuteAsync(sql);