From e23eeef63255bb34f62a3d7d1818a06afecf5cad Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Mon, 23 Mar 2026 02:10:24 -0300 Subject: [PATCH 1/3] Add tests for Value column quoting in Single/SingleOrDefault queries. Fix #1234. --- .../Query/ElementaryTests.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/FirebirdSql.EntityFrameworkCore.Firebird.Tests/Query/ElementaryTests.cs b/src/FirebirdSql.EntityFrameworkCore.Firebird.Tests/Query/ElementaryTests.cs index 3c73dd6c5..acfdc3ff3 100644 --- a/src/FirebirdSql.EntityFrameworkCore.Firebird.Tests/Query/ElementaryTests.cs +++ b/src/FirebirdSql.EntityFrameworkCore.Firebird.Tests/Query/ElementaryTests.cs @@ -209,6 +209,53 @@ public async Task SelectWithCollate() StringAssert.Contains(@"CAST(_UTF8'test' AS VARCHAR(4) CHARACTER SET UTF8) COLLATE UNICODE_CI_AI", sql); } } + + [Test] + public async Task SqlQueryScalarSingleQuotesValueColumn() + { + await using (var db = await GetDbContext()) + { + var result = await db.Database.SqlQueryRaw(@"SELECT 1 AS ""Value"" FROM RDB$DATABASE").SingleAsync(); + var sql = db.LastCommandText; + Assert.AreEqual(1, result); + StringAssert.DoesNotContain("Value", sql.Replace(@"""Value""", string.Empty)); + } + } + + [Test] + public async Task SqlQueryScalarSingleOrDefaultQuotesValueColumn() + { + await using (var db = await GetDbContext()) + { + var result = await db.Database.SqlQueryRaw(@"SELECT 1 AS ""Value"" FROM RDB$DATABASE").SingleOrDefaultAsync(); + var sql = db.LastCommandText; + Assert.AreEqual(1, result); + StringAssert.DoesNotContain("Value", sql.Replace(@"""Value""", string.Empty)); + } + } + + [Test] + public async Task SqlQueryScalarComposedWhereQuotesValueColumn() + { + await using (var db = await GetDbContext()) + { + var query = db.Database.SqlQueryRaw(@"SELECT 1 AS ""Value"" FROM RDB$DATABASE").Where(x => x > 0); + Assert.DoesNotThrowAsync(() => query.LoadAsync()); + var sql = db.LastCommandText; + StringAssert.DoesNotContain("Value", sql.Replace(@"""Value""", string.Empty)); + } + } + + [Test] + public async Task EntitySingleOrDefaultQuotesIdentifiers() + { + await using (var db = await GetDbContext()) + { + var result = await db.Set().OrderBy(x => x.AttachmentId).Take(1).SingleOrDefaultAsync(); + var sql = db.LastCommandText; + Assert.IsNotNull(result); + } + } } class SelectContext : FbTestDbContext From 936435c47cc043c34f169784292e075a54fb8491 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Tue, 24 Mar 2026 15:26:00 -0300 Subject: [PATCH 2/3] Address review: use positive assertions and wrap async calls in Assert.DoesNotThrowAsync --- .../Query/ElementaryTests.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/FirebirdSql.EntityFrameworkCore.Firebird.Tests/Query/ElementaryTests.cs b/src/FirebirdSql.EntityFrameworkCore.Firebird.Tests/Query/ElementaryTests.cs index acfdc3ff3..5c756a310 100644 --- a/src/FirebirdSql.EntityFrameworkCore.Firebird.Tests/Query/ElementaryTests.cs +++ b/src/FirebirdSql.EntityFrameworkCore.Firebird.Tests/Query/ElementaryTests.cs @@ -215,10 +215,10 @@ public async Task SqlQueryScalarSingleQuotesValueColumn() { await using (var db = await GetDbContext()) { - var result = await db.Database.SqlQueryRaw(@"SELECT 1 AS ""Value"" FROM RDB$DATABASE").SingleAsync(); + var query = db.Database.SqlQueryRaw(@"SELECT 1 AS ""Value"" FROM RDB$DATABASE"); + Assert.DoesNotThrowAsync(() => query.SingleAsync()); var sql = db.LastCommandText; - Assert.AreEqual(1, result); - StringAssert.DoesNotContain("Value", sql.Replace(@"""Value""", string.Empty)); + StringAssert.Contains(@"""Value""", sql); } } @@ -227,10 +227,10 @@ public async Task SqlQueryScalarSingleOrDefaultQuotesValueColumn() { await using (var db = await GetDbContext()) { - var result = await db.Database.SqlQueryRaw(@"SELECT 1 AS ""Value"" FROM RDB$DATABASE").SingleOrDefaultAsync(); + var query = db.Database.SqlQueryRaw(@"SELECT 1 AS ""Value"" FROM RDB$DATABASE"); + Assert.DoesNotThrowAsync(() => query.SingleOrDefaultAsync()); var sql = db.LastCommandText; - Assert.AreEqual(1, result); - StringAssert.DoesNotContain("Value", sql.Replace(@"""Value""", string.Empty)); + StringAssert.Contains(@"""Value""", sql); } } @@ -242,7 +242,7 @@ public async Task SqlQueryScalarComposedWhereQuotesValueColumn() var query = db.Database.SqlQueryRaw(@"SELECT 1 AS ""Value"" FROM RDB$DATABASE").Where(x => x > 0); Assert.DoesNotThrowAsync(() => query.LoadAsync()); var sql = db.LastCommandText; - StringAssert.DoesNotContain("Value", sql.Replace(@"""Value""", string.Empty)); + StringAssert.Contains(@"""Value""", sql); } } @@ -251,9 +251,8 @@ public async Task EntitySingleOrDefaultQuotesIdentifiers() { await using (var db = await GetDbContext()) { - var result = await db.Set().OrderBy(x => x.AttachmentId).Take(1).SingleOrDefaultAsync(); - var sql = db.LastCommandText; - Assert.IsNotNull(result); + var query = db.Set().OrderBy(x => x.AttachmentId).Take(1); + Assert.DoesNotThrowAsync(() => query.SingleOrDefaultAsync()); } } } From 77bbd2f4fe4fa6194c57116a4b1da3e1075ac762 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Thu, 7 May 2026 05:51:01 -0300 Subject: [PATCH 3/3] Address review: tighten Value-quoting assertion and drop off-topic entity test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Match the alias-qualified `."Value"` pattern instead of bare `"Value"` so the assertion no longer matches the user-supplied raw SQL substring and genuinely verifies EF Core's outer projection quotes the identifier. Remove EntitySingleOrDefaultQuotesIdentifiers — it has no Value column, asserts nothing about SQL content, and the Take(1) before SingleOrDefaultAsync was redundant. --- .../Query/ElementaryTests.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/FirebirdSql.EntityFrameworkCore.Firebird.Tests/Query/ElementaryTests.cs b/src/FirebirdSql.EntityFrameworkCore.Firebird.Tests/Query/ElementaryTests.cs index 5c756a310..18aa34361 100644 --- a/src/FirebirdSql.EntityFrameworkCore.Firebird.Tests/Query/ElementaryTests.cs +++ b/src/FirebirdSql.EntityFrameworkCore.Firebird.Tests/Query/ElementaryTests.cs @@ -218,7 +218,7 @@ public async Task SqlQueryScalarSingleQuotesValueColumn() var query = db.Database.SqlQueryRaw(@"SELECT 1 AS ""Value"" FROM RDB$DATABASE"); Assert.DoesNotThrowAsync(() => query.SingleAsync()); var sql = db.LastCommandText; - StringAssert.Contains(@"""Value""", sql); + StringAssert.Contains(@".""Value""", sql); } } @@ -230,7 +230,7 @@ public async Task SqlQueryScalarSingleOrDefaultQuotesValueColumn() var query = db.Database.SqlQueryRaw(@"SELECT 1 AS ""Value"" FROM RDB$DATABASE"); Assert.DoesNotThrowAsync(() => query.SingleOrDefaultAsync()); var sql = db.LastCommandText; - StringAssert.Contains(@"""Value""", sql); + StringAssert.Contains(@".""Value""", sql); } } @@ -242,17 +242,7 @@ public async Task SqlQueryScalarComposedWhereQuotesValueColumn() var query = db.Database.SqlQueryRaw(@"SELECT 1 AS ""Value"" FROM RDB$DATABASE").Where(x => x > 0); Assert.DoesNotThrowAsync(() => query.LoadAsync()); var sql = db.LastCommandText; - StringAssert.Contains(@"""Value""", sql); - } - } - - [Test] - public async Task EntitySingleOrDefaultQuotesIdentifiers() - { - await using (var db = await GetDbContext()) - { - var query = db.Set().OrderBy(x => x.AttachmentId).Take(1); - Assert.DoesNotThrowAsync(() => query.SingleOrDefaultAsync()); + StringAssert.Contains(@".""Value""", sql); } } }