diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..97f37dc
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,13 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "csharpier": {
+ "version": "1.2.6",
+ "commands": [
+ "csharpier"
+ ],
+ "rollForward": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/.csharpierignore b/.csharpierignore
new file mode 100644
index 0000000..48bafbc
--- /dev/null
+++ b/.csharpierignore
@@ -0,0 +1 @@
+worktrees/
diff --git a/Directory.Build.props b/Directory.Build.props
index b8bc638..326abfd 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,5 +1,4 @@
-
enable
2.0.1
@@ -19,5 +18,4 @@
-
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/AdvancedQueryTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/AdvancedQueryTests.cs
index 7341c20..81a5d16 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/AdvancedQueryTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/AdvancedQueryTests.cs
@@ -3,13 +3,15 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class AdvancedQueryTests(ParadeDbFixture fixture) {
+public class AdvancedQueryTests(ParadeDbFixture fixture)
+{
[Fact]
- public async Task Parse_TantivySyntax_FiltersByField() {
+ public async Task Parse_TantivySyntax_FiltersByField()
+ {
await using var ctx = fixture.CreateDbContext();
- var results = await ctx.Articles
- .Where(a => EF.Functions.Parse(a.Id, "title:transformer"))
+ var results = await ctx
+ .Articles.Where(a => EF.Functions.Parse(a.Id, "title:transformer"))
.Select(a => a.Title)
.ToListAsync();
@@ -18,13 +20,16 @@ public async Task Parse_TantivySyntax_FiltersByField() {
}
[Fact]
- public async Task MoreLikeThis_ExecutesAndReturnsRelatedArticle() {
+ public async Task MoreLikeThis_ExecutesAndReturnsRelatedArticle()
+ {
await using var ctx = fixture.CreateDbContext();
- var seed = await ctx.Articles.SingleAsync(a => a.Title == "Introduction to neural networks");
+ var seed = await ctx.Articles.SingleAsync(a =>
+ a.Title == "Introduction to neural networks"
+ );
- var related = await ctx.Articles
- .Where(a => EF.Functions.MoreLikeThis(a.Id, seed.Id))
+ var related = await ctx
+ .Articles.Where(a => EF.Functions.MoreLikeThis(a.Id, seed.Id))
.Select(a => a.Title)
.ToListAsync();
@@ -35,15 +40,19 @@ public async Task MoreLikeThis_ExecutesAndReturnsRelatedArticle() {
}
[Fact]
- public async Task JsonSearch_BooleanQuery_CombinesParseAndTerm() {
+ public async Task JsonSearch_BooleanQuery_CombinesParseAndTerm()
+ {
await using var ctx = fixture.CreateDbContext();
- var query = ParadeDbJsonQuery.Boolean(b => b.Must(
- ParadeDbJsonQuery.Parse("neural"),
- ParadeDbJsonQuery.Term("category", "machine-learning")));
+ var query = ParadeDbJsonQuery.Boolean(b =>
+ b.Must(
+ ParadeDbJsonQuery.Parse("neural"),
+ ParadeDbJsonQuery.Term("category", "machine-learning")
+ )
+ );
- var results = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var results = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/BasicSearchTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/BasicSearchTests.cs
index b7930fd..19482ff 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/BasicSearchTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/BasicSearchTests.cs
@@ -3,13 +3,15 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class BasicSearchTests(ParadeDbFixture fixture) {
+public class BasicSearchTests(ParadeDbFixture fixture)
+{
[Fact]
- public async Task Matches_OrOperator_ReturnsMatchingDocuments() {
+ public async Task Matches_OrOperator_ReturnsMatchingDocuments()
+ {
await using var ctx = fixture.CreateDbContext();
- var results = await ctx.Articles
- .Where(a => EF.Functions.Matches(a.Content, "neural networks"))
+ var results = await ctx
+ .Articles.Where(a => EF.Functions.Matches(a.Content, "neural networks"))
.Select(a => a.Title)
.ToListAsync();
@@ -18,11 +20,12 @@ public async Task Matches_OrOperator_ReturnsMatchingDocuments() {
}
[Fact]
- public async Task MatchesAll_AndOperator_RequiresAllTerms() {
+ public async Task MatchesAll_AndOperator_RequiresAllTerms()
+ {
await using var ctx = fixture.CreateDbContext();
- var results = await ctx.Articles
- .Where(a => EF.Functions.MatchesAll(a.Content, "attention transformers"))
+ var results = await ctx
+ .Articles.Where(a => EF.Functions.MatchesAll(a.Content, "attention transformers"))
.Select(a => a.Title)
.ToListAsync();
@@ -31,14 +34,15 @@ public async Task MatchesAll_AndOperator_RequiresAllTerms() {
}
[Fact]
- public async Task MatchesPhrase_RequiresExactOrder() {
+ public async Task MatchesPhrase_RequiresExactOrder()
+ {
await using var ctx = fixture.CreateDbContext();
- var withExactPhrase = await ctx.Articles
- .Where(a => EF.Functions.MatchesPhrase(a.Content, "neural networks"))
+ var withExactPhrase = await ctx
+ .Articles.Where(a => EF.Functions.MatchesPhrase(a.Content, "neural networks"))
.CountAsync();
- var withReversedPhrase = await ctx.Articles
- .Where(a => EF.Functions.MatchesPhrase(a.Content, "networks neural"))
+ var withReversedPhrase = await ctx
+ .Articles.Where(a => EF.Functions.MatchesPhrase(a.Content, "networks neural"))
.CountAsync();
Assert.Equal(1, withExactPhrase);
@@ -46,11 +50,12 @@ public async Task MatchesPhrase_RequiresExactOrder() {
}
[Fact]
- public async Task MatchesFuzzy_ToleratesTypos() {
+ public async Task MatchesFuzzy_ToleratesTypos()
+ {
await using var ctx = fixture.CreateDbContext();
- var results = await ctx.Articles
- .Where(a => EF.Functions.MatchesFuzzy(a.Content, "nueral", 2))
+ var results = await ctx
+ .Articles.Where(a => EF.Functions.MatchesFuzzy(a.Content, "nueral", 2))
.Select(a => a.Title)
.ToListAsync();
@@ -58,11 +63,12 @@ public async Task MatchesFuzzy_ToleratesTypos() {
}
[Fact]
- public async Task MatchesTerm_RequiresExactToken() {
+ public async Task MatchesTerm_RequiresExactToken()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.Articles
- .Where(a => EF.Functions.MatchesTerm(a.Content, "gpus"))
+ var hits = await ctx
+ .Articles.Where(a => EF.Functions.MatchesTerm(a.Content, "gpus"))
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests.csproj b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests.csproj
index 467c082..f93b024 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests.csproj
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests.csproj
@@ -1,5 +1,4 @@
-
net10.0
enable
@@ -24,5 +23,4 @@
-
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/FieldTypeTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/FieldTypeTests.cs
index f366bd7..fa41609 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/FieldTypeTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/FieldTypeTests.cs
@@ -3,18 +3,20 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class FieldTypeTests(ParadeDbFixture fixture) {
+public class FieldTypeTests(ParadeDbFixture fixture)
+{
[Fact]
- public async Task Bm25Boolean_IndexedFastColumn_FiltersByValue() {
+ public async Task Bm25Boolean_IndexedFastColumn_FiltersByValue()
+ {
await using var ctx = fixture.CreateDbContext();
- var inStockNames = await ctx.Products
- .Where(p => EF.Functions.Matches(p.Name, "keyboard"))
+ var inStockNames = await ctx
+ .Products.Where(p => EF.Functions.Matches(p.Name, "keyboard"))
.Where(p => p.InStock)
.Select(p => p.Name)
.ToListAsync();
- var outOfStockNames = await ctx.Products
- .Where(p => EF.Functions.Matches(p.Name, "keyboard"))
+ var outOfStockNames = await ctx
+ .Products.Where(p => EF.Functions.Matches(p.Name, "keyboard"))
.Where(p => !p.InStock)
.Select(p => p.Name)
.ToListAsync();
@@ -24,12 +26,13 @@ public async Task Bm25Boolean_IndexedFastColumn_FiltersByValue() {
}
[Fact]
- public async Task Bm25DateTime_FastColumn_SupportsRangeFilter() {
+ public async Task Bm25DateTime_FastColumn_SupportsRangeFilter()
+ {
await using var ctx = fixture.CreateDbContext();
var cutoff = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);
- var recent = await ctx.Products
- .Where(p => EF.Functions.Matches(p.Name, "laptop OR mouse OR keyboard"))
+ var recent = await ctx
+ .Products.Where(p => EF.Functions.Matches(p.Name, "laptop OR mouse OR keyboard"))
.Where(p => p.ReleasedAt >= cutoff)
.Select(p => p.Name)
.ToListAsync();
@@ -40,11 +43,12 @@ public async Task Bm25DateTime_FastColumn_SupportsRangeFilter() {
}
[Fact]
- public async Task Bm25Json_WithExpandDots_IndexesJsonField() {
+ public async Task Bm25Json_WithExpandDots_IndexesJsonField()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.Products
- .Where(p => EF.Functions.Matches(p.Name, "laptop OR mouse OR keyboard"))
+ var hits = await ctx
+ .Products.Where(p => EF.Functions.Matches(p.Name, "laptop OR mouse OR keyboard"))
.Select(p => p.Name)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/IndexConfigurationTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/IndexConfigurationTests.cs
index f0929b6..3abc374 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/IndexConfigurationTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/IndexConfigurationTests.cs
@@ -4,9 +4,11 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class IndexConfigurationTests(ParadeDbFixture fixture) {
+public class IndexConfigurationTests(ParadeDbFixture fixture)
+{
[Fact]
- public async Task PgSearchExtension_IsInstalled() {
+ public async Task PgSearchExtension_IsInstalled()
+ {
await using var conn = new NpgsqlConnection(fixture.ConnectionString);
await conn.OpenAsync();
await using var cmd = conn.CreateCommand();
@@ -18,7 +20,8 @@ public async Task PgSearchExtension_IsInstalled() {
}
[Fact]
- public async Task Bm25Index_IsCreatedWithStorageParameters() {
+ public async Task Bm25Index_IsCreatedWithStorageParameters()
+ {
await using var conn = new NpgsqlConnection(fixture.ConnectionString);
await conn.OpenAsync();
await using var cmd = conn.CreateCommand();
@@ -37,11 +40,12 @@ SELECT indexdef FROM pg_indexes
}
[Fact]
- public async Task EnglishStemmer_MatchesWordVariants() {
+ public async Task EnglishStemmer_MatchesWordVariants()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.Articles
- .Where(a => EF.Functions.Matches(a.Content, "run"))
+ var hits = await ctx
+ .Articles.Where(a => EF.Functions.Matches(a.Content, "run"))
.Select(a => a.Title)
.ToListAsync();
@@ -50,14 +54,15 @@ public async Task EnglishStemmer_MatchesWordVariants() {
}
[Fact]
- public async Task RawTokenizer_KeepsExactCategoryValue() {
+ public async Task RawTokenizer_KeepsExactCategoryValue()
+ {
await using var ctx = fixture.CreateDbContext();
- var exactHits = await ctx.Articles
- .Where(a => EF.Functions.MatchesTerm(a.Category, "machine-learning"))
+ var exactHits = await ctx
+ .Articles.Where(a => EF.Functions.MatchesTerm(a.Category, "machine-learning"))
.CountAsync();
- var tokenizedAttempt = await ctx.Articles
- .Where(a => EF.Functions.MatchesTerm(a.Category, "machine"))
+ var tokenizedAttempt = await ctx
+ .Articles.Where(a => EF.Functions.MatchesTerm(a.Category, "machine"))
.CountAsync();
Assert.Equal(2, exactHits);
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/IntegrationDbContext.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/IntegrationDbContext.cs
index 61f9c8f..8e20644 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/IntegrationDbContext.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/IntegrationDbContext.cs
@@ -3,7 +3,9 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
-public sealed class IntegrationDbContext(DbContextOptions options) : DbContext(options) {
+public sealed class IntegrationDbContext(DbContextOptions options)
+ : DbContext(options)
+{
public DbSet Articles => Set();
public DbSet Products => Set();
public DbSet KeywordRecords => Set();
@@ -16,7 +18,8 @@ public sealed class IntegrationDbContext(DbContextOptions
[Table("articles")]
[Bm25Index(nameof(Id), nameof(Title), nameof(Content), nameof(Category), nameof(Rating))]
-public sealed class Article {
+public sealed class Article
+{
[Column("id")]
public int Id { get; set; }
@@ -39,7 +42,8 @@ public sealed class Article {
[Table("products")]
[Bm25Index(nameof(Id), nameof(Name), nameof(InStock), nameof(ReleasedAt), nameof(Specs))]
-public sealed class Product {
+public sealed class Product
+{
[Column("id")]
public int Id { get; set; }
@@ -62,7 +66,8 @@ public sealed class Product {
[Table("keyword_records")]
[Bm25Index(nameof(Id), nameof(Code))]
-public sealed class KeywordRecord {
+public sealed class KeywordRecord
+{
[Column("id")]
public int Id { get; set; }
@@ -73,7 +78,8 @@ public sealed class KeywordRecord {
[Table("ngram_records")]
[Bm25Index(nameof(Id), nameof(Body))]
-public sealed class NgramRecord {
+public sealed class NgramRecord
+{
[Column("id")]
public int Id { get; set; }
@@ -84,7 +90,8 @@ public sealed class NgramRecord {
[Table("icu_records")]
[Bm25Index(nameof(Id), nameof(Body))]
-public sealed class IcuRecord {
+public sealed class IcuRecord
+{
[Column("id")]
public int Id { get; set; }
@@ -95,7 +102,8 @@ public sealed class IcuRecord {
[Table("source_code_records")]
[Bm25Index(nameof(Id), nameof(Snippet))]
-public sealed class SourceCodeRecord {
+public sealed class SourceCodeRecord
+{
[Column("id")]
public int Id { get; set; }
@@ -106,7 +114,8 @@ public sealed class SourceCodeRecord {
[Table("regex_records")]
[Bm25Index(nameof(Id), nameof(Body))]
-public sealed class RegexRecord {
+public sealed class RegexRecord
+{
[Column("id")]
public int Id { get; set; }
@@ -117,7 +126,8 @@ public sealed class RegexRecord {
[Table("german_articles")]
[Bm25Index(nameof(Id), nameof(Content))]
-public sealed class GermanArticle {
+public sealed class GermanArticle
+{
[Column("id")]
public int Id { get; set; }
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonBoostTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonBoostTests.cs
index be35464..dbd8bfd 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonBoostTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonBoostTests.cs
@@ -3,39 +3,49 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonBoostTests(ParadeDbFixture fixture) {
+public class JsonBoostTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.Boost wraps an inner query into {"boost":{"query":...,"factor":N}}
// and that the factor actually amplifies BM25 scores via the JSON @@@ operator path.
// A regression in CloneNode (e.g., shared mutable JsonNode reuse) or JSON shape would
// either fail to serialize, fail to match, or return un-amplified scores.
[Fact]
- public async Task Boost_WrapsInnerQuery_AmplifiesScoreVsUnwrappedQuery() {
+ public async Task Boost_WrapsInnerQuery_AmplifiesScoreVsUnwrappedQuery()
+ {
await using var ctx = fixture.CreateDbContext();
const double boostFactor = 5.0;
var inner = ParadeDbJsonQuery.Parse("neural");
var boosted = ParadeDbJsonQuery.Boost(inner, boostFactor);
- var boostedResults = await ctx.Articles
- .JsonSearch(a => a.Id, boosted)
+ var boostedResults = await ctx
+ .Articles.JsonSearch(a => a.Id, boosted)
.Select(a => new { a.Title, Score = EF.Functions.Score(a.Id) })
.ToListAsync();
- var unboostedResults = await ctx.Articles
- .JsonSearch(a => a.Id, inner)
+ var unboostedResults = await ctx
+ .Articles.JsonSearch(a => a.Id, inner)
.Select(a => new { a.Title, Score = EF.Functions.Score(a.Id) })
.ToListAsync();
// Boost only changes scores, not the matching set.
- Assert.Equal(unboostedResults.Select(x => x.Title).OrderBy(t => t),
- boostedResults.Select(x => x.Title).OrderBy(t => t));
+ Assert.Equal(
+ unboostedResults.Select(x => x.Title).OrderBy(t => t),
+ boostedResults.Select(x => x.Title).OrderBy(t => t)
+ );
Assert.Contains(boostedResults, x => x.Title == "Introduction to neural networks");
var boostedHit = boostedResults.Single(x => x.Title == "Introduction to neural networks");
- var unboostedHit = unboostedResults.Single(x => x.Title == "Introduction to neural networks");
- Assert.True(unboostedHit.Score > 0,
- $"Sanity: unboosted Parse score should be positive; was {unboostedHit.Score}.");
- Assert.True(boostedHit.Score > unboostedHit.Score * 2.0,
- $"Boost factor {boostFactor} should substantially amplify the score; " +
- $"boosted={boostedHit.Score}, unboosted={unboostedHit.Score}.");
+ var unboostedHit = unboostedResults.Single(x =>
+ x.Title == "Introduction to neural networks"
+ );
+ Assert.True(
+ unboostedHit.Score > 0,
+ $"Sanity: unboosted Parse score should be positive; was {unboostedHit.Score}."
+ );
+ Assert.True(
+ boostedHit.Score > unboostedHit.Score * 2.0,
+ $"Boost factor {boostFactor} should substantially amplify the score; "
+ + $"boosted={boostedHit.Score}, unboosted={unboostedHit.Score}."
+ );
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonConstScoreTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonConstScoreTests.cs
index aa0dfe6..1d4fb7a 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonConstScoreTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonConstScoreTests.cs
@@ -3,22 +3,24 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonConstScoreTests(ParadeDbFixture fixture) {
+public class JsonConstScoreTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.ConstScore produces {"const_score":{"query":...,"score":N}}
// and that pg_search overrides the BM25 score with N for every matching document. This
// differs from Boost (which multiplies the existing score). A regression that swapped
// "score" → "factor", or mis-wired CloneNode, would either fail to apply the override
// or change the result set.
[Fact]
- public async Task ConstScore_WrapsInnerQuery_FixesScoreToConstantForAllMatches() {
+ public async Task ConstScore_WrapsInnerQuery_FixesScoreToConstantForAllMatches()
+ {
await using var ctx = fixture.CreateDbContext();
const double constantScore = 3.14;
var inner = ParadeDbJsonQuery.Term("category", "machine-learning");
var query = ParadeDbJsonQuery.ConstScore(inner, constantScore);
- var results = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var results = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => new { a.Title, Score = EF.Functions.Score(a.Id) })
.ToListAsync();
@@ -26,9 +28,12 @@ public async Task ConstScore_WrapsInnerQuery_FixesScoreToConstantForAllMatches()
Assert.Equal(2, results.Count);
Assert.Contains(results, x => x.Title == "Introduction to neural networks");
Assert.Contains(results, x => x.Title == "Transformer architectures");
- foreach (var hit in results) {
- Assert.True(Math.Abs(hit.Score - constantScore) < 0.001,
- $"ConstScore should fix score to {constantScore}; got {hit.Score} for '{hit.Title}'.");
+ foreach (var hit in results)
+ {
+ Assert.True(
+ Math.Abs(hit.Score - constantScore) < 0.001,
+ $"ConstScore should fix score to {constantScore}; got {hit.Score} for '{hit.Title}'."
+ );
}
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonDateTimeRangeTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonDateTimeRangeTests.cs
index aad260e..0670ecb 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonDateTimeRangeTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonDateTimeRangeTests.cs
@@ -3,23 +3,31 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonDateTimeRangeTests(ParadeDbFixture fixture) {
+public class JsonDateTimeRangeTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.Range with isDatetime: true serializes UTC bounds as
// ISO-8601 ("yyyy-MM-ddTHH:mm:ssZ") and adds "is_datetime": true so pg_search parses
// them as timestamps, not strings. A regression in CreateJsonValue's DateTime branch
// (wrong format, missing 'Z', non-UTC handling) or a missing is_datetime flag would
// either fail to filter or return zero matches against a Bm25DateTime-indexed column.
[Fact]
- public async Task DateTimeRange_FiltersByDateBoundary_IncludesOnlyProductsInRange() {
+ public async Task DateTimeRange_FiltersByDateBoundary_IncludesOnlyProductsInRange()
+ {
await using var ctx = fixture.CreateDbContext();
var lower = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var upper = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
- var query = ParadeDbJsonQuery.Range("released_at", lower, upper,
- lowerInclusive: true, upperInclusive: false, isDatetime: true);
+ var query = ParadeDbJsonQuery.Range(
+ "released_at",
+ lower,
+ upper,
+ lowerInclusive: true,
+ upperInclusive: false,
+ isDatetime: true
+ );
- var hits = await ctx.Products
- .JsonSearch(p => p.Id, query)
+ var hits = await ctx
+ .Products.JsonSearch(p => p.Id, query)
.Select(p => p.Name)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonDisjunctionMaxTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonDisjunctionMaxTests.cs
index 8229ab5..16fb34e 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonDisjunctionMaxTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonDisjunctionMaxTests.cs
@@ -3,24 +3,27 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonDisjunctionMaxTests(ParadeDbFixture fixture) {
+public class JsonDisjunctionMaxTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.DisjunctionMax produces {"disjunction_max":{"disjuncts":[...]}}
// with OR matching semantics — a document matching ANY disjunct appears once, even when
// it matches multiple. Distinct from Boolean.Should (which sums scores). A regression in
// CloneNode usage across the disjuncts array, or the disjuncts being misnamed, would
// either fail to deserialize or change the matching set.
[Fact]
- public async Task DisjunctionMax_OverlappingDisjuncts_ReturnsDistinctUnionOfMatches() {
+ public async Task DisjunctionMax_OverlappingDisjuncts_ReturnsDistinctUnionOfMatches()
+ {
await using var ctx = fixture.CreateDbContext();
// "neural" matches Article 1 only; "machine-learning" category matches Articles 1 and 2.
// Disjunction → union (Articles 1, 2). Article 1 is in both disjuncts but must appear once.
var query = ParadeDbJsonQuery.DisjunctionMax(
ParadeDbJsonQuery.Parse("neural"),
- ParadeDbJsonQuery.Term("category", "machine-learning"));
+ ParadeDbJsonQuery.Term("category", "machine-learning")
+ );
- var hits = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var hits = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonExistsTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonExistsTests.cs
index 6f0d12e..0ad0564 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonExistsTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonExistsTests.cs
@@ -3,18 +3,20 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonExistsTests(ParadeDbFixture fixture) {
+public class JsonExistsTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.Exists produces {"exists":{"field":"..."}} and pg_search
// returns every row that has the named field indexed. The two-key shape is small, so
// the test serves mainly as a regression guard against typos in "exists"/"field".
[Fact]
- public async Task Exists_OnPopulatedField_MatchesAllIndexedRows() {
+ public async Task Exists_OnPopulatedField_MatchesAllIndexedRows()
+ {
await using var ctx = fixture.CreateDbContext();
var query = ParadeDbJsonQuery.Exists("category");
- var hits = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var hits = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonFuzzyTermOptionsTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonFuzzyTermOptionsTests.cs
index 1adea3d..4ddae1c 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonFuzzyTermOptionsTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonFuzzyTermOptionsTests.cs
@@ -3,7 +3,8 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonFuzzyTermOptionsTests(ParadeDbFixture fixture) {
+public class JsonFuzzyTermOptionsTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.FuzzyTerm(field, value, distance, prefix,
// transpositionCostOne) conditionally emits the JSON keys "prefix" and
// "transposition_cost_one" on the {"fuzzy_term":{...}} node. Distinct from
@@ -11,21 +12,32 @@ public class JsonFuzzyTermOptionsTests(ParadeDbFixture fixture) {
// A regression renaming "transposition_cost_one" (or hard-coding it) would
// silently make distance=1 reject the adjacent-swap that the test relies on.
[Fact]
- public async Task FuzzyTerm_WithTranspositionCostOne_MatchesAdjacentSwapAtDistance1() {
+ public async Task FuzzyTerm_WithTranspositionCostOne_MatchesAdjacentSwapAtDistance1()
+ {
await using var ctx = fixture.CreateDbContext();
// "nueral" ↔ "neural" is an adjacent transposition (transp distance 1, Levenshtein 2).
- var withTransposition = ParadeDbJsonQuery.FuzzyTerm("content", "nueral", 1,
- prefix: false, transpositionCostOne: true);
- var withoutTransposition = ParadeDbJsonQuery.FuzzyTerm("content", "nueral", 1,
- prefix: false, transpositionCostOne: false);
+ var withTransposition = ParadeDbJsonQuery.FuzzyTerm(
+ "content",
+ "nueral",
+ 1,
+ prefix: false,
+ transpositionCostOne: true
+ );
+ var withoutTransposition = ParadeDbJsonQuery.FuzzyTerm(
+ "content",
+ "nueral",
+ 1,
+ prefix: false,
+ transpositionCostOne: false
+ );
- var hitsWith = await ctx.Articles
- .JsonSearch(a => a.Id, withTransposition)
+ var hitsWith = await ctx
+ .Articles.JsonSearch(a => a.Id, withTransposition)
.Select(a => a.Title)
.ToListAsync();
- var hitsWithout = await ctx.Articles
- .JsonSearch(a => a.Id, withoutTransposition)
+ var hitsWithout = await ctx
+ .Articles.JsonSearch(a => a.Id, withoutTransposition)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonFuzzyTermTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonFuzzyTermTests.cs
index 3d45642..aebef05 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonFuzzyTermTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonFuzzyTermTests.cs
@@ -3,20 +3,22 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonFuzzyTermTests(ParadeDbFixture fixture) {
+public class JsonFuzzyTermTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.FuzzyTerm builds {"fuzzy_term":{"field":"...","value":"...","distance":N}}
// and pg_search accepts it via the @@@ jsonb path. Distinct from the CLR MatchesTermFuzzy
// translation (which goes through pdb.fuzzy(...) casts). A regression in the JSON keys
// ("fuzzy_term"/"field"/"value"/"distance") would either fail to deserialize or zero matches.
[Fact]
- public async Task FuzzyTerm_WithDistance2_MatchesTypoedToken() {
+ public async Task FuzzyTerm_WithDistance2_MatchesTypoedToken()
+ {
await using var ctx = fixture.CreateDbContext();
// "nueral" → "neural" is a Levenshtein distance of 2; distance: 2 should match.
var query = ParadeDbJsonQuery.FuzzyTerm("content", "nueral", 2);
- var hits = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var hits = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMatchConjunctionModeTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMatchConjunctionModeTests.cs
index 564f8ed..3587100 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMatchConjunctionModeTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMatchConjunctionModeTests.cs
@@ -3,7 +3,8 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonMatchConjunctionModeTests(ParadeDbFixture fixture) {
+public class JsonMatchConjunctionModeTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.Match 4-arg actually toggles AND/OR semantics
// via the conditional "conjunction_mode" key. The existing JsonMatchTests
// uses "nueral netwroks" — both fuzzy-terms only co-occur in Article 1 — so
@@ -12,20 +13,31 @@ public class JsonMatchConjunctionModeTests(ParadeDbFixture fixture) {
// where the two tokens live in disjoint articles, so flipping the flag MUST
// change the hit set.
[Fact]
- public async Task Match_WithConjunctionModeFalse_AppliesOrSemantics() {
+ public async Task Match_WithConjunctionModeFalse_AppliesOrSemantics()
+ {
await using var ctx = fixture.CreateDbContext();
// "neural" appears in Article 1's content; "pasta" in Article 4's content;
// no single article's content has both — so OR and AND must differ.
- var orQuery = ParadeDbJsonQuery.Match("neural pasta", "content", distance: 0, conjunctionMode: false);
- var andQuery = ParadeDbJsonQuery.Match("neural pasta", "content", distance: 0, conjunctionMode: true);
+ var orQuery = ParadeDbJsonQuery.Match(
+ "neural pasta",
+ "content",
+ distance: 0,
+ conjunctionMode: false
+ );
+ var andQuery = ParadeDbJsonQuery.Match(
+ "neural pasta",
+ "content",
+ distance: 0,
+ conjunctionMode: true
+ );
- var orHits = await ctx.Articles
- .JsonSearch(a => a.Id, orQuery)
+ var orHits = await ctx
+ .Articles.JsonSearch(a => a.Id, orQuery)
.Select(a => a.Title)
.ToListAsync();
- var andHits = await ctx.Articles
- .JsonSearch(a => a.Id, andQuery)
+ var andHits = await ctx
+ .Articles.JsonSearch(a => a.Id, andQuery)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMatchFieldTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMatchFieldTests.cs
index bbd3902..a99e0a1 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMatchFieldTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMatchFieldTests.cs
@@ -3,14 +3,16 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonMatchFieldTests(ParadeDbFixture fixture) {
+public class JsonMatchFieldTests(ParadeDbFixture fixture)
+{
// Verifies the 2-arg ParadeDbJsonQuery.Match(value, field) overload restricts
// the match to the named field. The C# parameter order (value, field) is the
// opposite of the resulting JSON key order ({"field":..., "value":...}); a
// refactor that "aligns" param order with the JSON would silently invert
// every caller. The 4-arg overload is the only currently-tested Match shape.
[Fact]
- public async Task Match_WithFieldAndValue_RestrictsToNamedField() {
+ public async Task Match_WithFieldAndValue_RestrictsToNamedField()
+ {
await using var ctx = fixture.CreateDbContext();
// "models" appears in Article 1 and Article 2 content (stems to "model"),
@@ -19,12 +21,12 @@ public async Task Match_WithFieldAndValue_RestrictsToNamedField() {
var titleQuery = ParadeDbJsonQuery.Match("models", "title");
var contentQuery = ParadeDbJsonQuery.Match("models", "content");
- var titleHits = await ctx.Articles
- .JsonSearch(a => a.Id, titleQuery)
+ var titleHits = await ctx
+ .Articles.JsonSearch(a => a.Id, titleQuery)
.Select(a => a.Title)
.ToListAsync();
- var contentHits = await ctx.Articles
- .JsonSearch(a => a.Id, contentQuery)
+ var contentHits = await ctx
+ .Articles.JsonSearch(a => a.Id, contentQuery)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMatchTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMatchTests.cs
index 433c780..cfae856 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMatchTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMatchTests.cs
@@ -3,22 +3,24 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonMatchTests(ParadeDbFixture fixture) {
+public class JsonMatchTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.Match 4-arg overload emits the full options shape:
// {"match":{"field":"...","value":"...","distance":N,"conjunction_mode":true}}
// — combines fuzzy matching with AND semantics in one JSON node. A regression in
// any of the four keys, or the conditional emission of conjunction_mode, would
// either fail to deserialize or change the match set.
[Fact]
- public async Task Match_WithDistanceAndConjunctionMode_MatchesAllFuzzyTerms() {
+ public async Task Match_WithDistanceAndConjunctionMode_MatchesAllFuzzyTerms()
+ {
await using var ctx = fixture.CreateDbContext();
// "nueral" and "netwroks" both typo'd; distance=2 + conjunction=true means
// every term must fuzzy-match. Article 1's content has both "neural" and "networks".
var query = ParadeDbJsonQuery.Match("nueral netwroks", "content", 2, conjunctionMode: true);
- var hits = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var hits = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMoreLikeThisTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMoreLikeThisTests.cs
index 3073832..587692a 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMoreLikeThisTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonMoreLikeThisTests.cs
@@ -3,20 +3,24 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonMoreLikeThisTests(ParadeDbFixture fixture) {
+public class JsonMoreLikeThisTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.MoreLikeThis builds {"more_like_this":{"key_value":N}}
// and pg_search accepts it via the @@@ jsonb path. Distinct from the CLR MoreLikeThis
// (which translates via pdb.more_like_this(documentId)) — a regression in the JSON
// key names would still return zero matches without surfacing the underlying mistake.
[Fact]
- public async Task MoreLikeThis_FromSeedArticle_ExecutesAndReturnsSimilarArticle() {
+ public async Task MoreLikeThis_FromSeedArticle_ExecutesAndReturnsSimilarArticle()
+ {
await using var ctx = fixture.CreateDbContext();
- var seed = await ctx.Articles.SingleAsync(a => a.Title == "Introduction to neural networks");
+ var seed = await ctx.Articles.SingleAsync(a =>
+ a.Title == "Introduction to neural networks"
+ );
var query = ParadeDbJsonQuery.MoreLikeThis(seed.Id);
- var related = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var related = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonParseLenientTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonParseLenientTests.cs
index 3e9e016..647bc77 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonParseLenientTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonParseLenientTests.cs
@@ -4,7 +4,8 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonParseLenientTests(ParadeDbFixture fixture) {
+public class JsonParseLenientTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.Parse's "lenient" JSON-key emission actually
// takes effect — the existing JsonParseOptionsTests pins lenient: true in
// both halves to isolate conjunction_mode, so the lenient branch is never
@@ -12,27 +13,32 @@ public class JsonParseLenientTests(ParadeDbFixture fixture) {
// that pg_search rejects in strict mode and tolerates in lenient mode —
// a regression dropping the "lenient" key would let the strict case parse.
[Fact]
- public async Task Parse_WithLenientFalse_RejectsMalformedQueryThatLenientTrueAccepts() {
+ public async Task Parse_WithLenientFalse_RejectsMalformedQueryThatLenientTrueAccepts()
+ {
await using var ctx = fixture.CreateDbContext();
// Trailing AND with no right-hand operand — syntactically invalid.
- var lenientQuery = ParadeDbJsonQuery.Parse("neural AND",
- lenient: true, conjunctionMode: false);
- var strictQuery = ParadeDbJsonQuery.Parse("neural AND",
- lenient: false, conjunctionMode: false);
+ var lenientQuery = ParadeDbJsonQuery.Parse(
+ "neural AND",
+ lenient: true,
+ conjunctionMode: false
+ );
+ var strictQuery = ParadeDbJsonQuery.Parse(
+ "neural AND",
+ lenient: false,
+ conjunctionMode: false
+ );
// Lenient: parser ignores the trailing operator and matches "neural" → Article 1.
- var lenientHits = await ctx.Articles
- .JsonSearch(a => a.Id, lenientQuery)
+ var lenientHits = await ctx
+ .Articles.JsonSearch(a => a.Id, lenientQuery)
.Select(a => a.Title)
.ToListAsync();
Assert.Contains(lenientHits, t => t == "Introduction to neural networks");
// Strict: pg_search rejects the malformed query at execution time.
await Assert.ThrowsAsync(async () =>
- await ctx.Articles
- .JsonSearch(a => a.Id, strictQuery)
- .Select(a => a.Title)
- .ToListAsync());
+ await ctx.Articles.JsonSearch(a => a.Id, strictQuery).Select(a => a.Title).ToListAsync()
+ );
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonParseOptionsTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonParseOptionsTests.cs
index 3a28a50..36922a4 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonParseOptionsTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonParseOptionsTests.cs
@@ -3,7 +3,8 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonParseOptionsTests(ParadeDbFixture fixture) {
+public class JsonParseOptionsTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.Parse(string, bool, bool) emits the JSON keys
// "lenient" and "conjunction_mode" — distinct from the 1-arg overload that
// emits only "query_string". The CLR sibling EF.Functions.Parse(...) is
@@ -11,19 +12,28 @@ public class JsonParseOptionsTests(ParadeDbFixture fixture) {
// regression renaming either key (e.g. "lenient" → "strict") would silently
// flip the AND/OR meaning of every unquoted multi-term parse query.
[Fact]
- public async Task Parse_WithConjunctionMode_RequiresAllTermsInsteadOfAny() {
+ public async Task Parse_WithConjunctionMode_RequiresAllTermsInsteadOfAny()
+ {
await using var ctx = fixture.CreateDbContext();
// "neural" appears only in Article 1; "quantum" only in Article 3 — no article has both.
- var orQuery = ParadeDbJsonQuery.Parse("neural quantum", lenient: true, conjunctionMode: false);
- var andQuery = ParadeDbJsonQuery.Parse("neural quantum", lenient: true, conjunctionMode: true);
+ var orQuery = ParadeDbJsonQuery.Parse(
+ "neural quantum",
+ lenient: true,
+ conjunctionMode: false
+ );
+ var andQuery = ParadeDbJsonQuery.Parse(
+ "neural quantum",
+ lenient: true,
+ conjunctionMode: true
+ );
- var orHits = await ctx.Articles
- .JsonSearch(a => a.Id, orQuery)
+ var orHits = await ctx
+ .Articles.JsonSearch(a => a.Id, orQuery)
.Select(a => a.Title)
.ToListAsync();
- var andHits = await ctx.Articles
- .JsonSearch(a => a.Id, andQuery)
+ var andHits = await ctx
+ .Articles.JsonSearch(a => a.Id, andQuery)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonPhrasePrefixTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonPhrasePrefixTests.cs
index 797a193..f064274 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonPhrasePrefixTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonPhrasePrefixTests.cs
@@ -3,20 +3,22 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonPhrasePrefixTests(ParadeDbFixture fixture) {
+public class JsonPhrasePrefixTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.PhrasePrefix builds {"phrase_prefix":{"field":"...","phrases":[...]}}
// — the last phrase element is treated as a prefix (useful for autocomplete). The CLR
// PhrasePrefix translates via pdb.phrase_prefix(ARRAY[...]); this JSON form goes through
// the @@@ jsonb path. A regression in the phrases array or field key would either fail
// to match or change the match set entirely.
[Fact]
- public async Task PhrasePrefix_LastTermAsPrefix_MatchesContainingDocument() {
+ public async Task PhrasePrefix_LastTermAsPrefix_MatchesContainingDocument()
+ {
await using var ctx = fixture.CreateDbContext();
var query = ParadeDbJsonQuery.PhrasePrefix("content", "neural", "net");
- var hits = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var hits = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonPhraseSlopTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonPhraseSlopTests.cs
index 6db0998..44e4a7b 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonPhraseSlopTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonPhraseSlopTests.cs
@@ -3,21 +3,26 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonPhraseSlopTests(ParadeDbFixture fixture) {
+public class JsonPhraseSlopTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.Phrase with slop produces {"phrase":{"field":"...","phrases":[...],"slop":N}}
// and pg_search allows N words between phrase terms. Article 1's content has "Deep learning models" —
// strict "deep models" doesn't match (1 word in between), but slop>=1 does.
// A regression that dropped the "slop" key would fail the test by returning zero matches.
[Fact]
- public async Task Phrase_WithSlop_AllowsWordsBetweenTerms() {
+ public async Task Phrase_WithSlop_AllowsWordsBetweenTerms()
+ {
await using var ctx = fixture.CreateDbContext();
- var strict = await ctx.Articles
- .JsonSearch(a => a.Id, ParadeDbJsonQuery.Phrase("content", "deep", "models"))
+ var strict = await ctx
+ .Articles.JsonSearch(a => a.Id, ParadeDbJsonQuery.Phrase("content", "deep", "models"))
.Select(a => a.Title)
.ToListAsync();
- var withSlop = await ctx.Articles
- .JsonSearch(a => a.Id, ParadeDbJsonQuery.Phrase("content", 2, "deep", "models"))
+ var withSlop = await ctx
+ .Articles.JsonSearch(
+ a => a.Id,
+ ParadeDbJsonQuery.Phrase("content", 2, "deep", "models")
+ )
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonQueryTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonQueryTests.cs
index feaded8..df972ed 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonQueryTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonQueryTests.cs
@@ -3,16 +3,23 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonQueryTests(ParadeDbFixture fixture) {
+public class JsonQueryTests(ParadeDbFixture fixture)
+{
[Fact]
- public async Task JsonSearch_NumericRange_FiltersByRating() {
+ public async Task JsonSearch_NumericRange_FiltersByRating()
+ {
await using var ctx = fixture.CreateDbContext();
- var query = ParadeDbJsonQuery.Range("rating", lowerBound: 4, upperBound: 5,
- lowerInclusive: true, upperInclusive: true);
+ var query = ParadeDbJsonQuery.Range(
+ "rating",
+ lowerBound: 4,
+ upperBound: 5,
+ lowerInclusive: true,
+ upperInclusive: true
+ );
- var hits = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var hits = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => a.Title)
.ToListAsync();
@@ -21,15 +28,19 @@ public async Task JsonSearch_NumericRange_FiltersByRating() {
}
[Fact]
- public async Task JsonSearch_Should_OrSemantics() {
+ public async Task JsonSearch_Should_OrSemantics()
+ {
await using var ctx = fixture.CreateDbContext();
- var query = ParadeDbJsonQuery.Boolean(b => b.Should(
- ParadeDbJsonQuery.Term("category", "cooking"),
- ParadeDbJsonQuery.Term("category", "physics")));
+ var query = ParadeDbJsonQuery.Boolean(b =>
+ b.Should(
+ ParadeDbJsonQuery.Term("category", "cooking"),
+ ParadeDbJsonQuery.Term("category", "physics")
+ )
+ );
- var hits = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var hits = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => a.Title)
.ToListAsync();
@@ -39,15 +50,16 @@ public async Task JsonSearch_Should_OrSemantics() {
}
[Fact]
- public async Task JsonSearch_MustNot_ExcludesMatchingDocs() {
+ public async Task JsonSearch_MustNot_ExcludesMatchingDocs()
+ {
await using var ctx = fixture.CreateDbContext();
- var query = ParadeDbJsonQuery.Boolean(b => b
- .Must(ParadeDbJsonQuery.All())
- .MustNot(ParadeDbJsonQuery.Term("category", "cooking")));
+ var query = ParadeDbJsonQuery.Boolean(b =>
+ b.Must(ParadeDbJsonQuery.All()).MustNot(ParadeDbJsonQuery.Term("category", "cooking"))
+ );
- var hits = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var hits = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => a.Title)
.ToListAsync();
@@ -56,14 +68,19 @@ public async Task JsonSearch_MustNot_ExcludesMatchingDocs() {
}
[Fact]
- public async Task JsonSearch_InlineBuilder_OverloadWorks() {
+ public async Task JsonSearch_InlineBuilder_OverloadWorks()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.Articles
- .JsonSearch(a => a.Id, b => b
- .Must(
- ParadeDbJsonQuery.Parse("neural"),
- ParadeDbJsonQuery.Term("category", "machine-learning")))
+ var hits = await ctx
+ .Articles.JsonSearch(
+ a => a.Id,
+ b =>
+ b.Must(
+ ParadeDbJsonQuery.Parse("neural"),
+ ParadeDbJsonQuery.Term("category", "machine-learning")
+ )
+ )
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonRangeExclusiveBoundTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonRangeExclusiveBoundTests.cs
index b6ec774..f8c751e 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonRangeExclusiveBoundTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonRangeExclusiveBoundTests.cs
@@ -3,28 +3,40 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonRangeExclusiveBoundTests(ParadeDbFixture fixture) {
+public class JsonRangeExclusiveBoundTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.Range emits "excluded" (not "included") when
// lowerInclusive is false. The existing JsonQueryTests numeric-range test
// only uses both bounds inclusive, so the "excluded" JSON-key branch is
// never executed. A regression that swaps "included"/"excluded" or flips
// the boolean condition would silently change the boundary semantics.
[Fact]
- public async Task Range_WithExclusiveLowerBound_ExcludesBoundaryRating() {
+ public async Task Range_WithExclusiveLowerBound_ExcludesBoundaryRating()
+ {
await using var ctx = fixture.CreateDbContext();
// Article 2 has rating=4 (the boundary value); Articles 1+4 have rating=5.
- var inclusive = ParadeDbJsonQuery.Range("rating", lowerBound: 4, upperBound: 5,
- lowerInclusive: true, upperInclusive: true);
- var exclusive = ParadeDbJsonQuery.Range("rating", lowerBound: 4, upperBound: 5,
- lowerInclusive: false, upperInclusive: true);
+ var inclusive = ParadeDbJsonQuery.Range(
+ "rating",
+ lowerBound: 4,
+ upperBound: 5,
+ lowerInclusive: true,
+ upperInclusive: true
+ );
+ var exclusive = ParadeDbJsonQuery.Range(
+ "rating",
+ lowerBound: 4,
+ upperBound: 5,
+ lowerInclusive: false,
+ upperInclusive: true
+ );
- var inclusiveHits = await ctx.Articles
- .JsonSearch(a => a.Id, inclusive)
+ var inclusiveHits = await ctx
+ .Articles.JsonSearch(a => a.Id, inclusive)
.Select(a => a.Title)
.ToListAsync();
- var exclusiveHits = await ctx.Articles
- .JsonSearch(a => a.Id, exclusive)
+ var exclusiveHits = await ctx
+ .Articles.JsonSearch(a => a.Id, exclusive)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonRangeOpenEndedTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonRangeOpenEndedTests.cs
index e4c458d..f9d6be5 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonRangeOpenEndedTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonRangeOpenEndedTests.cs
@@ -3,23 +3,29 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonRangeOpenEndedTests(ParadeDbFixture fixture) {
+public class JsonRangeOpenEndedTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.Range omits the "upper_bound" key entirely
// when upperBound is null — the `if (upperBound != null)` branch. The
// existing JsonQueryTests/JsonDateTimeRangeTests always pass both bounds,
// so a regression that always emits "upper_bound" (or crashes on null)
// would only be caught here.
[Fact]
- public async Task Range_WithNullUpperBound_MatchesAllValuesAtOrAboveLowerBound() {
+ public async Task Range_WithNullUpperBound_MatchesAllValuesAtOrAboveLowerBound()
+ {
await using var ctx = fixture.CreateDbContext();
// Ratings in the seed: Article 1=5, 2=4, 3=3, 4=5. lower=4 inclusive,
// no upper → expect Articles 1, 2, 4 (rating >= 4); Article 3 excluded.
- var query = ParadeDbJsonQuery.Range("rating",
- lowerBound: 4, upperBound: null!, lowerInclusive: true);
+ var query = ParadeDbJsonQuery.Range(
+ "rating",
+ lowerBound: 4,
+ upperBound: null!,
+ lowerInclusive: true
+ );
- var hits = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var hits = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonRegexTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonRegexTests.cs
index ec9fde1..3fe7527 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonRegexTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonRegexTests.cs
@@ -3,19 +3,21 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonRegexTests(ParadeDbFixture fixture) {
+public class JsonRegexTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.Regex builds {"regex":{"field":"...","pattern":"..."}} and
// pg_search accepts it via the @@@ jsonb path. Distinct from the CLR Regex translation
// (which uses pdb.regex(...)). A regression in the "regex"/"field"/"pattern" key names
// would either fail to deserialize or return zero matches.
[Fact]
- public async Task Regex_Pattern_MatchesIndexedTokensMatchingExpression() {
+ public async Task Regex_Pattern_MatchesIndexedTokensMatchingExpression()
+ {
await using var ctx = fixture.CreateDbContext();
var query = ParadeDbJsonQuery.Regex("content", "neur.*");
- var hits = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var hits = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonTermSetTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonTermSetTests.cs
index abc5f19..f3992e0 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonTermSetTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/JsonTermSetTests.cs
@@ -3,19 +3,21 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class JsonTermSetTests(ParadeDbFixture fixture) {
+public class JsonTermSetTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbJsonQuery.TermSet produces {"term_set":{"field":"...","terms":[...]}}
// — matches any of the listed exact terms on the Raw-tokenized category column.
// Distinct from CLR MatchesTermSet (which translates to `=== ARRAY[...]`); this
// exercises the JSON @@@ jsonb path and the params object[] → JsonArray conversion.
[Fact]
- public async Task TermSet_MultipleCategories_MatchesAnyListedTerm() {
+ public async Task TermSet_MultipleCategories_MatchesAnyListedTerm()
+ {
await using var ctx = fixture.CreateDbContext();
var query = ParadeDbJsonQuery.TermSet("category", "machine-learning", "cooking");
- var hits = await ctx.Articles
- .JsonSearch(a => a.Id, query)
+ var hits = await ctx
+ .Articles.JsonSearch(a => a.Id, query)
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesAllBoostedTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesAllBoostedTests.cs
index 5f36668..46c91f1 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesAllBoostedTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesAllBoostedTests.cs
@@ -3,33 +3,41 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class MatchesAllBoostedTests(ParadeDbFixture fixture) {
+public class MatchesAllBoostedTests(ParadeDbFixture fixture)
+{
// Verifies MatchesAllBoosted translates to: column &&& 'query'::pdb.boost(factor)
// with AND semantics — distinct from MatchesBoosted (||| / OR). A regression in
// the method-call translator that drops the boost cast, or swaps the operator,
// would either return matches with unboosted scores or change the result set.
[Fact]
- public async Task MatchesAllBoosted_AndOperator_AmplifiesScoreVsUnboostedMatchesAll() {
+ public async Task MatchesAllBoosted_AndOperator_AmplifiesScoreVsUnboostedMatchesAll()
+ {
await using var ctx = fixture.CreateDbContext();
const double boostFactor = 5.0;
- var boosted = await ctx.Articles
- .Where(a => EF.Functions.MatchesAllBoosted(a.Content, "neural networks", boostFactor))
+ var boosted = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesAllBoosted(a.Content, "neural networks", boostFactor)
+ )
.Select(a => new { a.Title, Score = EF.Functions.Score(a.Id) })
.ToListAsync();
- var unboosted = await ctx.Articles
- .Where(a => EF.Functions.MatchesAll(a.Content, "neural networks"))
+ var unboosted = await ctx
+ .Articles.Where(a => EF.Functions.MatchesAll(a.Content, "neural networks"))
.Select(a => new { a.Title, Score = EF.Functions.Score(a.Id) })
.ToListAsync();
// Same AND semantics → same matching rows; only the score should differ.
- Assert.Equal(unboosted.Select(x => x.Title).OrderBy(t => t),
- boosted.Select(x => x.Title).OrderBy(t => t));
+ Assert.Equal(
+ unboosted.Select(x => x.Title).OrderBy(t => t),
+ boosted.Select(x => x.Title).OrderBy(t => t)
+ );
Assert.Contains(boosted, x => x.Title == "Introduction to neural networks");
var boostedHit = boosted.Single(x => x.Title == "Introduction to neural networks");
var unboostedHit = unboosted.Single(x => x.Title == "Introduction to neural networks");
- Assert.True(boostedHit.Score > unboostedHit.Score * 2.0,
- $"Boost factor {boostFactor} should amplify score substantially; " +
- $"boosted={boostedHit.Score}, unboosted={unboostedHit.Score}.");
+ Assert.True(
+ boostedHit.Score > unboostedHit.Score * 2.0,
+ $"Boost factor {boostFactor} should amplify score substantially; "
+ + $"boosted={boostedHit.Score}, unboosted={unboostedHit.Score}."
+ );
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesAllFuzzyBoostedTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesAllFuzzyBoostedTests.cs
index 2d30771..0cff96b 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesAllFuzzyBoostedTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesAllFuzzyBoostedTests.cs
@@ -3,35 +3,50 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class MatchesAllFuzzyBoostedTests(ParadeDbFixture fixture) {
+public class MatchesAllFuzzyBoostedTests(ParadeDbFixture fixture)
+{
// Verifies MatchesAllFuzzyBoosted translates to: column &&& 'query'::pdb.fuzzy(d)::pdb.boost(f).
// Chains TWO cast operators on AND (&&&) — the most complex translation path in the
// function set. A regression that drops either cast (fuzzy or boost), inverts cast
// order, or swaps &&& for ||| would either change the result set (typos no longer
// tolerated / OR semantics applied) or leave the score un-amplified.
[Fact]
- public async Task MatchesAllFuzzyBoosted_TypoTolerantAndOperator_AmplifiesScoreVsPlainFuzzyAll() {
+ public async Task MatchesAllFuzzyBoosted_TypoTolerantAndOperator_AmplifiesScoreVsPlainFuzzyAll()
+ {
await using var ctx = fixture.CreateDbContext();
const int distance = 2;
const double boostFactor = 5.0;
- var boosted = await ctx.Articles
- .Where(a => EF.Functions.MatchesAllFuzzyBoosted(a.Content, "nueral netwroks", distance, boostFactor))
+ var boosted = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesAllFuzzyBoosted(
+ a.Content,
+ "nueral netwroks",
+ distance,
+ boostFactor
+ )
+ )
.Select(a => new { a.Title, Score = EF.Functions.Score(a.Id) })
.ToListAsync();
- var unboosted = await ctx.Articles
- .Where(a => EF.Functions.MatchesAllFuzzy(a.Content, "nueral netwroks", distance))
+ var unboosted = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesAllFuzzy(a.Content, "nueral netwroks", distance)
+ )
.Select(a => new { a.Title, Score = EF.Functions.Score(a.Id) })
.ToListAsync();
// Same AND+fuzzy semantics → identical matches; only the score should differ.
- Assert.Equal(unboosted.Select(x => x.Title).OrderBy(t => t),
- boosted.Select(x => x.Title).OrderBy(t => t));
+ Assert.Equal(
+ unboosted.Select(x => x.Title).OrderBy(t => t),
+ boosted.Select(x => x.Title).OrderBy(t => t)
+ );
Assert.Contains(boosted, x => x.Title == "Introduction to neural networks");
var boostedHit = boosted.Single(x => x.Title == "Introduction to neural networks");
var unboostedHit = unboosted.Single(x => x.Title == "Introduction to neural networks");
- Assert.True(boostedHit.Score > unboostedHit.Score * 2.0,
- $"Boost factor {boostFactor} should amplify the fuzzy AND score; " +
- $"boosted={boostedHit.Score}, unboosted={unboostedHit.Score}.");
+ Assert.True(
+ boostedHit.Score > unboostedHit.Score * 2.0,
+ $"Boost factor {boostFactor} should amplify the fuzzy AND score; "
+ + $"boosted={boostedHit.Score}, unboosted={unboostedHit.Score}."
+ );
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesAllFuzzyOptionsTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesAllFuzzyOptionsTests.cs
index c944b40..e61e25d 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesAllFuzzyOptionsTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesAllFuzzyOptionsTests.cs
@@ -3,7 +3,8 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class MatchesAllFuzzyOptionsTests(ParadeDbFixture fixture) {
+public class MatchesAllFuzzyOptionsTests(ParadeDbFixture fixture)
+{
// Verifies the 5-arg MatchesAllFuzzy overload emits conjunction_mode => true on the
// shared pdb.match(...) translation path (see BuildFuzzyMatchFunc in the translator).
// The OR-mode sibling — MatchesFuzzy 5-arg — goes through the same helper without that
@@ -11,21 +12,36 @@ public class MatchesAllFuzzyOptionsTests(ParadeDbFixture fixture) {
// OR branch (or forgets the conjunction_mode arg) would let docs match on a single
// fuzzy term, making AND-fuzzy silently behave like OR-fuzzy.
[Fact]
- public async Task MatchesAllFuzzy_WithFullOptions_RequiresEveryTermToFuzzyMatch() {
+ public async Task MatchesAllFuzzy_WithFullOptions_RequiresEveryTermToFuzzyMatch()
+ {
await using var ctx = fixture.CreateDbContext();
// "nueral" ↔ "neural" is an adjacent-character swap (transposition distance 1).
// "xyzzzzz" has no edit-distance-1 neighbor anywhere in the seeded content.
// OR-fuzzy must still match Article 1 on the "nueral" hit alone.
- var orHits = await ctx.Articles
- .Where(a => EF.Functions.MatchesFuzzy(a.Content, "nueral xyzzzzz", 1,
- prefix: false, transpositionCostOne: true))
+ var orHits = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesFuzzy(
+ a.Content,
+ "nueral xyzzzzz",
+ 1,
+ prefix: false,
+ transpositionCostOne: true
+ )
+ )
.Select(a => a.Title)
.ToListAsync();
// AND-fuzzy must require every term — "xyzzzzz" fuzzy-matches nothing → no hits.
- var andHits = await ctx.Articles
- .Where(a => EF.Functions.MatchesAllFuzzy(a.Content, "nueral xyzzzzz", 1,
- prefix: false, transpositionCostOne: true))
+ var andHits = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesAllFuzzy(
+ a.Content,
+ "nueral xyzzzzz",
+ 1,
+ prefix: false,
+ transpositionCostOne: true
+ )
+ )
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesFuzzyBoostedTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesFuzzyBoostedTests.cs
index 083d6ed..444be56 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesFuzzyBoostedTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesFuzzyBoostedTests.cs
@@ -3,34 +3,42 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class MatchesFuzzyBoostedTests(ParadeDbFixture fixture) {
+public class MatchesFuzzyBoostedTests(ParadeDbFixture fixture)
+{
// Verifies MatchesFuzzyBoosted translates to: column ||| 'query'::pdb.fuzzy(d)::pdb.boost(f).
// OR variant of the already-tested MatchesAllFuzzyBoosted — same chained-cast path,
// different boolean operator (||| vs &&&). A regression that swapped operators or
// dropped a cast would either change the match set or leave the score un-amplified.
[Fact]
- public async Task MatchesFuzzyBoosted_OrOperator_AmplifiesScoreVsUnboostedFuzzy() {
+ public async Task MatchesFuzzyBoosted_OrOperator_AmplifiesScoreVsUnboostedFuzzy()
+ {
await using var ctx = fixture.CreateDbContext();
const int distance = 2;
const double boostFactor = 5.0;
- var boosted = await ctx.Articles
- .Where(a => EF.Functions.MatchesFuzzyBoosted(a.Content, "nueral", distance, boostFactor))
+ var boosted = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesFuzzyBoosted(a.Content, "nueral", distance, boostFactor)
+ )
.Select(a => new { a.Title, Score = EF.Functions.Score(a.Id) })
.ToListAsync();
- var unboosted = await ctx.Articles
- .Where(a => EF.Functions.MatchesFuzzy(a.Content, "nueral", distance))
+ var unboosted = await ctx
+ .Articles.Where(a => EF.Functions.MatchesFuzzy(a.Content, "nueral", distance))
.Select(a => new { a.Title, Score = EF.Functions.Score(a.Id) })
.ToListAsync();
// Same OR+fuzzy semantics → identical matches; only the score should differ.
- Assert.Equal(unboosted.Select(x => x.Title).OrderBy(t => t),
- boosted.Select(x => x.Title).OrderBy(t => t));
+ Assert.Equal(
+ unboosted.Select(x => x.Title).OrderBy(t => t),
+ boosted.Select(x => x.Title).OrderBy(t => t)
+ );
Assert.Contains(boosted, x => x.Title == "Introduction to neural networks");
var boostedHit = boosted.Single(x => x.Title == "Introduction to neural networks");
var unboostedHit = unboosted.Single(x => x.Title == "Introduction to neural networks");
- Assert.True(boostedHit.Score > unboostedHit.Score * 2.0,
- $"Boost factor {boostFactor} should amplify the fuzzy OR score; " +
- $"boosted={boostedHit.Score}, unboosted={unboostedHit.Score}.");
+ Assert.True(
+ boostedHit.Score > unboostedHit.Score * 2.0,
+ $"Boost factor {boostFactor} should amplify the fuzzy OR score; "
+ + $"boosted={boostedHit.Score}, unboosted={unboostedHit.Score}."
+ );
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesFuzzyOptionsTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesFuzzyOptionsTests.cs
index 8051d3e..9ba472a 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesFuzzyOptionsTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesFuzzyOptionsTests.cs
@@ -3,7 +3,8 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class MatchesFuzzyOptionsTests(ParadeDbFixture fixture) {
+public class MatchesFuzzyOptionsTests(ParadeDbFixture fixture)
+{
// Verifies the 5-arg MatchesFuzzy overload translates the transpositionCostOne flag
// into pdb.fuzzy(distance, prefix, transposition_cost_one) so an adjacent-character
// swap counts as 1 edit (default: 2). "nueral" vs "neural" has Levenshtein distance 2
@@ -11,17 +12,32 @@ public class MatchesFuzzyOptionsTests(ParadeDbFixture fixture) {
// that makes the match succeed. A regression that drops the extra args (or swaps them)
// would flip both assertions.
[Fact]
- public async Task MatchesFuzzy_WithTranspositionCostOne_MatchesAdjacentSwapAtDistance1() {
+ public async Task MatchesFuzzy_WithTranspositionCostOne_MatchesAdjacentSwapAtDistance1()
+ {
await using var ctx = fixture.CreateDbContext();
- var withTransposition = await ctx.Articles
- .Where(a => EF.Functions.MatchesFuzzy(a.Content, "nueral", 1,
- prefix: false, transpositionCostOne: true))
+ var withTransposition = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesFuzzy(
+ a.Content,
+ "nueral",
+ 1,
+ prefix: false,
+ transpositionCostOne: true
+ )
+ )
.Select(a => a.Title)
.ToListAsync();
- var withoutTransposition = await ctx.Articles
- .Where(a => EF.Functions.MatchesFuzzy(a.Content, "nueral", 1,
- prefix: false, transpositionCostOne: false))
+ var withoutTransposition = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesFuzzy(
+ a.Content,
+ "nueral",
+ 1,
+ prefix: false,
+ transpositionCostOne: false
+ )
+ )
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesFuzzyPrefixTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesFuzzyPrefixTests.cs
index 5bd7d7d..4aa053c 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesFuzzyPrefixTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesFuzzyPrefixTests.cs
@@ -3,7 +3,8 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class MatchesFuzzyPrefixTests(ParadeDbFixture fixture) {
+public class MatchesFuzzyPrefixTests(ParadeDbFixture fixture)
+{
// Verifies the 5-arg MatchesFuzzy overload's "prefix" flag — exempts the
// initial substring from edit distance, equivalent to prefix matching.
// MatchesFuzzyOptionsTests and MatchesAllFuzzyOptionsTests both only
@@ -12,7 +13,8 @@ public class MatchesFuzzyPrefixTests(ParadeDbFixture fixture) {
// untested. A regression that swaps the prefix and transpositionCostOne
// args (both bool, same position-class) would flip this test.
[Fact]
- public async Task MatchesFuzzy_WithPrefixTrue_MatchesTokensStartingWithQuery() {
+ public async Task MatchesFuzzy_WithPrefixTrue_MatchesTokensStartingWithQuery()
+ {
await using var ctx = fixture.CreateDbContext();
// "neurla" is Levenshtein distance 2 from "neural". With distance: 1
@@ -20,14 +22,28 @@ public async Task MatchesFuzzy_WithPrefixTrue_MatchesTokensStartingWithQuery() {
// With distance: 1 and prefix: true → the prefix flag exempts the
// initial substring from edit distance, so distance: 1 is enough
// → matches Article 1.
- var prefixOff = await ctx.Articles
- .Where(a => EF.Functions.MatchesFuzzy(a.Content, "neurla", 1,
- prefix: false, transpositionCostOne: false))
+ var prefixOff = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesFuzzy(
+ a.Content,
+ "neurla",
+ 1,
+ prefix: false,
+ transpositionCostOne: false
+ )
+ )
.Select(a => a.Title)
.ToListAsync();
- var prefixOn = await ctx.Articles
- .Where(a => EF.Functions.MatchesFuzzy(a.Content, "neurla", 1,
- prefix: true, transpositionCostOne: false))
+ var prefixOn = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesFuzzy(
+ a.Content,
+ "neurla",
+ 1,
+ prefix: true,
+ transpositionCostOne: false
+ )
+ )
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesTermFuzzyOptionsTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesTermFuzzyOptionsTests.cs
index 170a8a3..8984dc4 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesTermFuzzyOptionsTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesTermFuzzyOptionsTests.cs
@@ -3,7 +3,8 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class MatchesTermFuzzyOptionsTests(ParadeDbFixture fixture) {
+public class MatchesTermFuzzyOptionsTests(ParadeDbFixture fixture)
+{
// Verifies the 5-arg MatchesTermFuzzy overload translates to
// pdb.fuzzy_term(value, distance, transposition_cost_one, prefix) — a POSITIONAL
// function call (BuildFuzzyTermFunc), distinct from the named-arg pdb.match(...)
@@ -11,18 +12,33 @@ public class MatchesTermFuzzyOptionsTests(ParadeDbFixture fixture) {
// the positional order (e.g. emits prefix before transposition_cost_one) would
// flip the meaning of the two booleans and silently invert both assertions.
[Fact]
- public async Task MatchesTermFuzzy_WithTranspositionCostOne_MatchesAdjacentSwapAtDistance1() {
+ public async Task MatchesTermFuzzy_WithTranspositionCostOne_MatchesAdjacentSwapAtDistance1()
+ {
await using var ctx = fixture.CreateDbContext();
// "nueral" ↔ "neural" is an adjacent swap (transposition distance 1, Levenshtein 2).
- var withTransposition = await ctx.Articles
- .Where(a => EF.Functions.MatchesTermFuzzy(a.Content, "nueral", 1,
- prefix: false, transpositionCostOne: true))
+ var withTransposition = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesTermFuzzy(
+ a.Content,
+ "nueral",
+ 1,
+ prefix: false,
+ transpositionCostOne: true
+ )
+ )
.Select(a => a.Title)
.ToListAsync();
- var withoutTransposition = await ctx.Articles
- .Where(a => EF.Functions.MatchesTermFuzzy(a.Content, "nueral", 1,
- prefix: false, transpositionCostOne: false))
+ var withoutTransposition = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesTermFuzzy(
+ a.Content,
+ "nueral",
+ 1,
+ prefix: false,
+ transpositionCostOne: false
+ )
+ )
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesTermFuzzyPrefixTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesTermFuzzyPrefixTests.cs
index cd334ea..996deb7 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesTermFuzzyPrefixTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MatchesTermFuzzyPrefixTests.cs
@@ -3,7 +3,8 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class MatchesTermFuzzyPrefixTests(ParadeDbFixture fixture) {
+public class MatchesTermFuzzyPrefixTests(ParadeDbFixture fixture)
+{
// Verifies the 5-arg MatchesTermFuzzy overload's "prefix" flag — routes
// through BuildFuzzyTermFunc which emits pdb.fuzzy_term(value, distance,
// transposition_cost_one, prefix) as a POSITIONAL call. The existing
@@ -11,20 +12,35 @@ public class MatchesTermFuzzyPrefixTests(ParadeDbFixture fixture) {
// prefix at false), so a regression that swaps positional args 3 and 4
// would slip through it but be caught here.
[Fact]
- public async Task MatchesTermFuzzy_WithPrefixTrue_ExtendsMatchPastEditDistance() {
+ public async Task MatchesTermFuzzy_WithPrefixTrue_ExtendsMatchPastEditDistance()
+ {
await using var ctx = fixture.CreateDbContext();
// "neurla" → "neural" is Levenshtein distance 2. At distance: 1 with
// prefix: false, no match (distance too small). With prefix: true the
// initial substring is exempt from edit distance, allowing the match.
- var prefixOff = await ctx.Articles
- .Where(a => EF.Functions.MatchesTermFuzzy(a.Content, "neurla", 1,
- prefix: false, transpositionCostOne: false))
+ var prefixOff = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesTermFuzzy(
+ a.Content,
+ "neurla",
+ 1,
+ prefix: false,
+ transpositionCostOne: false
+ )
+ )
.Select(a => a.Title)
.ToListAsync();
- var prefixOn = await ctx.Articles
- .Where(a => EF.Functions.MatchesTermFuzzy(a.Content, "neurla", 1,
- prefix: true, transpositionCostOne: false))
+ var prefixOn = await ctx
+ .Articles.Where(a =>
+ EF.Functions.MatchesTermFuzzy(
+ a.Content,
+ "neurla",
+ 1,
+ prefix: true,
+ transpositionCostOne: false
+ )
+ )
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MoreLikeThisFieldsTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MoreLikeThisFieldsTests.cs
index 55421df..d764d69 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MoreLikeThisFieldsTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/MoreLikeThisFieldsTests.cs
@@ -3,18 +3,22 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class MoreLikeThisFieldsTests(ParadeDbFixture fixture) {
+public class MoreLikeThisFieldsTests(ParadeDbFixture fixture)
+{
// Verifies MoreLikeThis(keyField, documentId, params string[] fields) translates to
// pdb.more_like_this(documentId, ARRAY['field1', ...]) — restricting the similarity
// computation to the named fields. The 2-arg form is already covered; this exercises
// the array-of-strings parameter, distinct from GH-15/GH-17 translation paths.
[Fact]
- public async Task MoreLikeThis_WithFieldsRestriction_ExecutesAndReturnsSimilarArticle() {
+ public async Task MoreLikeThis_WithFieldsRestriction_ExecutesAndReturnsSimilarArticle()
+ {
await using var ctx = fixture.CreateDbContext();
- var seed = await ctx.Articles.SingleAsync(a => a.Title == "Introduction to neural networks");
+ var seed = await ctx.Articles.SingleAsync(a =>
+ a.Title == "Introduction to neural networks"
+ );
- var related = await ctx.Articles
- .Where(a => EF.Functions.MoreLikeThis(a.Id, seed.Id, "content"))
+ var related = await ctx
+ .Articles.Where(a => EF.Functions.MoreLikeThis(a.Id, seed.Id, "content"))
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/OrderByScoreAscendingTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/OrderByScoreAscendingTests.cs
index 48b05dc..5305a8d 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/OrderByScoreAscendingTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/OrderByScoreAscendingTests.cs
@@ -3,29 +3,33 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class OrderByScoreAscendingTests(ParadeDbFixture fixture) {
+public class OrderByScoreAscendingTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbSearchExtensions.OrderByScore composes an ascending Score()
// ORDER BY equivalent to .OrderBy(a => EF.Functions.Score(a.Id)) by hand. Pairs
// with the already-covered OrderByScoreDescending — both branches of the shared
// reflection-built ApplyScoreOrdering helper need a regression guard.
[Fact]
- public async Task OrderByScore_RanksMatchingArticles_SameOrderAsExplicitScoreSelector() {
+ public async Task OrderByScore_RanksMatchingArticles_SameOrderAsExplicitScoreSelector()
+ {
await using var ctx = fixture.CreateDbContext();
- var viaExtension = await ctx.Articles
- .Where(a => EF.Functions.Matches(a.Content, "models machine learning"))
+ var viaExtension = await ctx
+ .Articles.Where(a => EF.Functions.Matches(a.Content, "models machine learning"))
.OrderByScore(a => a.Id)
.Select(a => a.Title)
.ToListAsync();
- var viaExplicit = await ctx.Articles
- .Where(a => EF.Functions.Matches(a.Content, "models machine learning"))
+ var viaExplicit = await ctx
+ .Articles.Where(a => EF.Functions.Matches(a.Content, "models machine learning"))
.OrderBy(a => EF.Functions.Score(a.Id))
.Select(a => a.Title)
.ToListAsync();
- Assert.True(viaExtension.Count >= 2,
- $"Need at least 2 matches for ordering to be observable; got {viaExtension.Count}.");
+ Assert.True(
+ viaExtension.Count >= 2,
+ $"Need at least 2 matches for ordering to be observable; got {viaExtension.Count}."
+ );
Assert.Equal(viaExplicit, viaExtension);
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/OrderByScoreTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/OrderByScoreTests.cs
index af90ba5..8b14427 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/OrderByScoreTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/OrderByScoreTests.cs
@@ -3,30 +3,34 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class OrderByScoreTests(ParadeDbFixture fixture) {
+public class OrderByScoreTests(ParadeDbFixture fixture)
+{
// Verifies ParadeDbSearchExtensions.OrderByScoreDescending composes a Score() ORDER BY
// equivalent to writing .OrderByDescending(a => EF.Functions.Score(a.Id)) by hand.
// A regression in the reflection-built expression (wrong Queryable overload, missing
// Convert for a value-type key, broken StripConvert) would either throw at translation
// or produce a different ordering than the explicit form.
[Fact]
- public async Task OrderByScoreDescending_RanksMatchingArticles_SameOrderAsExplicitScoreSelector() {
+ public async Task OrderByScoreDescending_RanksMatchingArticles_SameOrderAsExplicitScoreSelector()
+ {
await using var ctx = fixture.CreateDbContext();
- var viaExtension = await ctx.Articles
- .Where(a => EF.Functions.Matches(a.Content, "models machine learning"))
+ var viaExtension = await ctx
+ .Articles.Where(a => EF.Functions.Matches(a.Content, "models machine learning"))
.OrderByScoreDescending(a => a.Id)
.Select(a => a.Title)
.ToListAsync();
- var viaExplicit = await ctx.Articles
- .Where(a => EF.Functions.Matches(a.Content, "models machine learning"))
+ var viaExplicit = await ctx
+ .Articles.Where(a => EF.Functions.Matches(a.Content, "models machine learning"))
.OrderByDescending(a => EF.Functions.Score(a.Id))
.Select(a => a.Title)
.ToListAsync();
- Assert.True(viaExtension.Count >= 2,
- $"Need at least 2 matches for ordering to be observable; got {viaExtension.Count}.");
+ Assert.True(
+ viaExtension.Count >= 2,
+ $"Need at least 2 matches for ordering to be observable; got {viaExtension.Count}."
+ );
Assert.Equal(viaExplicit, viaExtension);
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/ParadeDbFixture.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/ParadeDbFixture.cs
index c116319..7faaba4 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/ParadeDbFixture.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/ParadeDbFixture.cs
@@ -3,7 +3,8 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
-public sealed class ParadeDbFixture : IAsyncLifetime {
+public sealed class ParadeDbFixture : IAsyncLifetime
+{
private readonly PostgreSqlContainer _container = new PostgreSqlBuilder()
.WithImage("paradedb/paradedb:latest")
.WithDatabase("paradedb_test")
@@ -13,7 +14,8 @@ public sealed class ParadeDbFixture : IAsyncLifetime {
public string ConnectionString { get; private set; } = default!;
- public async Task InitializeAsync() {
+ public async Task InitializeAsync()
+ {
await _container.StartAsync();
ConnectionString = _container.GetConnectionString();
@@ -22,85 +24,108 @@ public async Task InitializeAsync() {
await SeedAsync(ctx);
}
- public async Task DisposeAsync() {
+ public async Task DisposeAsync()
+ {
await _container.DisposeAsync();
}
- public IntegrationDbContext CreateDbContext() => new(
- new DbContextOptionsBuilder()
- .UseNpgsql(ConnectionString, n => n.UseParadeDb())
- .Options);
+ public IntegrationDbContext CreateDbContext() =>
+ new(
+ new DbContextOptionsBuilder()
+ .UseNpgsql(ConnectionString, n => n.UseParadeDb())
+ .Options
+ );
- private static async Task SeedAsync(IntegrationDbContext ctx) {
+ private static async Task SeedAsync(IntegrationDbContext ctx)
+ {
ctx.Articles.AddRange(
- new Article {
+ new Article
+ {
Title = "Introduction to neural networks",
- Content = "Deep learning models running on GPUs revolutionize machine learning. Neural networks are layered.",
+ Content =
+ "Deep learning models running on GPUs revolutionize machine learning. Neural networks are layered.",
Category = "machine-learning",
Rating = 5,
},
- new Article {
+ new Article
+ {
Title = "Transformer architectures",
- Content = "The attention mechanism powers modern language models. Transformers run efficiently on TPUs.",
+ Content =
+ "The attention mechanism powers modern language models. Transformers run efficiently on TPUs.",
Category = "machine-learning",
Rating = 4,
},
- new Article {
+ new Article
+ {
Title = "Quantum computing fundamentals",
- Content = "Qubits and entanglement enable parallel computation beyond classical bits.",
+ Content =
+ "Qubits and entanglement enable parallel computation beyond classical bits.",
Category = "physics",
Rating = 3,
},
- new Article {
+ new Article
+ {
Title = "Cooking pasta perfectly",
- Content = "Salt the water generously and cook the pasta until al dente. Taste it as you go.",
+ Content =
+ "Salt the water generously and cook the pasta until al dente. Taste it as you go.",
Category = "cooking",
Rating = 5,
- });
+ }
+ );
ctx.Products.AddRange(
- new Product {
+ new Product
+ {
Name = "Ultra book laptop",
InStock = true,
ReleasedAt = new DateTime(2024, 6, 1, 0, 0, 0, DateTimeKind.Utc),
Specs = """{"weight": 1200, "color": "silver"}""",
},
- new Product {
+ new Product
+ {
Name = "Mechanical keyboard",
InStock = false,
ReleasedAt = new DateTime(2023, 1, 15, 0, 0, 0, DateTimeKind.Utc),
Specs = """{"weight": 900, "color": "black"}""",
},
- new Product {
+ new Product
+ {
Name = "Wireless mouse",
InStock = true,
ReleasedAt = new DateTime(2024, 11, 20, 0, 0, 0, DateTimeKind.Utc),
Specs = """{"weight": 80, "color": "white"}""",
- });
+ }
+ );
ctx.KeywordRecords.AddRange(
new KeywordRecord { Code = "ABC-123" },
- new KeywordRecord { Code = "XYZ-789" });
+ new KeywordRecord { Code = "XYZ-789" }
+ );
ctx.NgramRecords.AddRange(
new NgramRecord { Body = "supercalifragilistic" },
- new NgramRecord { Body = "ordinary text" });
+ new NgramRecord { Body = "ordinary text" }
+ );
ctx.IcuRecords.AddRange(
new IcuRecord { Body = "Café résumé naïve" },
- new IcuRecord { Body = "Plain ASCII text" });
+ new IcuRecord { Body = "Plain ASCII text" }
+ );
ctx.SourceCodeRecords.AddRange(
new SourceCodeRecord { Snippet = "GetUserById" },
- new SourceCodeRecord { Snippet = "SaveChangesAsync" });
+ new SourceCodeRecord { Snippet = "SaveChangesAsync" }
+ );
ctx.RegexRecords.AddRange(
new RegexRecord { Body = "alpha beta gamma" },
- new RegexRecord { Body = "delta epsilon" });
+ new RegexRecord { Body = "delta epsilon" }
+ );
ctx.GermanArticles.AddRange(
new GermanArticle { Content = "Die Häuser sind groß und schön." },
- new GermanArticle { Content = "Der Mann läuft schnell." });
+ new GermanArticle { Content = "Der Mann läuft schnell." }
+ );
await ctx.SaveChangesAsync();
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/PhrasePrefixMaxExpansionsTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/PhrasePrefixMaxExpansionsTests.cs
index db14bd4..b13db3f 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/PhrasePrefixMaxExpansionsTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/PhrasePrefixMaxExpansionsTests.cs
@@ -3,16 +3,18 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class PhrasePrefixMaxExpansionsTests(ParadeDbFixture fixture) {
+public class PhrasePrefixMaxExpansionsTests(ParadeDbFixture fixture)
+{
// Verifies the 3-arg PhrasePrefix overload (with maxExpansions) translates to
// pdb.phrase_prefix(ARRAY[...], max_expansion => N) and runs. Guards against drift
// back to the plural "max_expansions" — pg_search's named arg is singular.
[Fact]
- public async Task PhrasePrefix_WithMaxExpansions_MatchesContainingDocument() {
+ public async Task PhrasePrefix_WithMaxExpansions_MatchesContainingDocument()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.Articles
- .Where(a => EF.Functions.PhrasePrefix(a.Content, 5, "neural", "net"))
+ var hits = await ctx
+ .Articles.Where(a => EF.Functions.PhrasePrefix(a.Content, 5, "neural", "net"))
.Select(a => a.Title)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/ScoringAndSnippetTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/ScoringAndSnippetTests.cs
index ba1baf1..9ae95c4 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/ScoringAndSnippetTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/ScoringAndSnippetTests.cs
@@ -3,30 +3,37 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class ScoringAndSnippetTests(ParadeDbFixture fixture) {
+public class ScoringAndSnippetTests(ParadeDbFixture fixture)
+{
[Fact]
- public async Task Score_OrdersByRelevance() {
+ public async Task Score_OrdersByRelevance()
+ {
await using var ctx = fixture.CreateDbContext();
- var ranked = await ctx.Articles
- .Where(a => EF.Functions.Matches(a.Content, "neural networks"))
+ var ranked = await ctx
+ .Articles.Where(a => EF.Functions.Matches(a.Content, "neural networks"))
.Select(a => new { a.Title, Score = EF.Functions.Score(a.Id) })
.OrderByDescending(x => x.Score)
.ToListAsync();
Assert.NotEmpty(ranked);
Assert.True(ranked[0].Score > 0);
- if (ranked.Count > 1) {
- Assert.True(ranked[0].Score >= ranked[1].Score, "Results should be ordered by descending score.");
+ if (ranked.Count > 1)
+ {
+ Assert.True(
+ ranked[0].Score >= ranked[1].Score,
+ "Results should be ordered by descending score."
+ );
}
}
[Fact]
- public async Task Snippet_HighlightsMatchedTerms() {
+ public async Task Snippet_HighlightsMatchedTerms()
+ {
await using var ctx = fixture.CreateDbContext();
- var snippets = await ctx.Articles
- .Where(a => EF.Functions.Matches(a.Content, "neural networks"))
+ var snippets = await ctx
+ .Articles.Where(a => EF.Functions.Matches(a.Content, "neural networks"))
.Select(a => EF.Functions.Snippet(a.Content, "", "", 100))
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/SearchApiTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/SearchApiTests.cs
index c8873ca..5472d73 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/SearchApiTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/SearchApiTests.cs
@@ -3,28 +3,33 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class SearchApiTests(ParadeDbFixture fixture) {
+public class SearchApiTests(ParadeDbFixture fixture)
+{
[Fact]
- public async Task MatchesBoosted_RaisesScoreOfBoostedTerm() {
+ public async Task MatchesBoosted_RaisesScoreOfBoostedTerm()
+ {
await using var ctx = fixture.CreateDbContext();
- var withBoost = await ctx.Articles
- .Where(a => EF.Functions.MatchesBoosted(a.Content, "neural", 5.0))
+ var withBoost = await ctx
+ .Articles.Where(a => EF.Functions.MatchesBoosted(a.Content, "neural", 5.0))
.Select(a => new { a.Title, Score = EF.Functions.Score(a.Id) })
.OrderByDescending(a => a.Score)
.ToListAsync();
Assert.NotEmpty(withBoost);
- Assert.True(withBoost[0].Score > 1.0,
- $"Boosted score should be amplified above default 1.0; was {withBoost[0].Score}.");
+ Assert.True(
+ withBoost[0].Score > 1.0,
+ $"Boosted score should be amplified above default 1.0; was {withBoost[0].Score}."
+ );
}
[Fact]
- public async Task MatchesTermSet_MatchesAnyToken() {
+ public async Task MatchesTermSet_MatchesAnyToken()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.Articles
- .Where(a => EF.Functions.MatchesTermSet(a.Content, "gpus", "tpus"))
+ var hits = await ctx
+ .Articles.Where(a => EF.Functions.MatchesTermSet(a.Content, "gpus", "tpus"))
.Select(a => a.Title)
.ToListAsync();
@@ -33,14 +38,15 @@ public async Task MatchesTermSet_MatchesAnyToken() {
}
[Fact]
- public async Task MatchesPhraseWithSlop_AllowsWordsBetweenTerms() {
+ public async Task MatchesPhraseWithSlop_AllowsWordsBetweenTerms()
+ {
await using var ctx = fixture.CreateDbContext();
- var exact = await ctx.Articles
- .Where(a => EF.Functions.MatchesPhrase(a.Content, "deep models"))
+ var exact = await ctx
+ .Articles.Where(a => EF.Functions.MatchesPhrase(a.Content, "deep models"))
.CountAsync();
- var withSlop = await ctx.Articles
- .Where(a => EF.Functions.MatchesPhrase(a.Content, "deep models", 2))
+ var withSlop = await ctx
+ .Articles.Where(a => EF.Functions.MatchesPhrase(a.Content, "deep models", 2))
.CountAsync();
Assert.Equal(0, exact);
@@ -48,11 +54,12 @@ public async Task MatchesPhraseWithSlop_AllowsWordsBetweenTerms() {
}
[Fact]
- public async Task MatchesAllFuzzy_AndOperatorToleratesTypos() {
+ public async Task MatchesAllFuzzy_AndOperatorToleratesTypos()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.Articles
- .Where(a => EF.Functions.MatchesAllFuzzy(a.Content, "nueral netwroks", 2))
+ var hits = await ctx
+ .Articles.Where(a => EF.Functions.MatchesAllFuzzy(a.Content, "nueral netwroks", 2))
.Select(a => a.Title)
.ToListAsync();
@@ -60,11 +67,12 @@ public async Task MatchesAllFuzzy_AndOperatorToleratesTypos() {
}
[Fact]
- public async Task MatchesTermFuzzy_SingleTermToleratesTypos() {
+ public async Task MatchesTermFuzzy_SingleTermToleratesTypos()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.Articles
- .Where(a => EF.Functions.MatchesTermFuzzy(a.Content, "neurla", 2))
+ var hits = await ctx
+ .Articles.Where(a => EF.Functions.MatchesTermFuzzy(a.Content, "neurla", 2))
.Select(a => a.Title)
.ToListAsync();
@@ -72,11 +80,12 @@ public async Task MatchesTermFuzzy_SingleTermToleratesTypos() {
}
[Fact]
- public async Task Snippets_ReturnsHighlightedExcerptArray() {
+ public async Task Snippets_ReturnsHighlightedExcerptArray()
+ {
await using var ctx = fixture.CreateDbContext();
- var snippets = await ctx.Articles
- .Where(a => EF.Functions.Matches(a.Content, "neural"))
+ var snippets = await ctx
+ .Articles.Where(a => EF.Functions.Matches(a.Content, "neural"))
.Select(a => EF.Functions.Snippets(a.Content, 80, 3, 0))
.ToListAsync();
@@ -85,11 +94,12 @@ public async Task Snippets_ReturnsHighlightedExcerptArray() {
}
[Fact]
- public async Task Regex_FindsTokensMatchingPattern() {
+ public async Task Regex_FindsTokensMatchingPattern()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.Articles
- .Where(a => EF.Functions.Regex(a.Content, "neur.*"))
+ var hits = await ctx
+ .Articles.Where(a => EF.Functions.Regex(a.Content, "neur.*"))
.Select(a => a.Title)
.ToListAsync();
@@ -97,11 +107,12 @@ public async Task Regex_FindsTokensMatchingPattern() {
}
[Fact]
- public async Task PhrasePrefix_MatchesPartialLastWord() {
+ public async Task PhrasePrefix_MatchesPartialLastWord()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.Articles
- .Where(a => EF.Functions.PhrasePrefix(a.Content, "neural", "net"))
+ var hits = await ctx
+ .Articles.Where(a => EF.Functions.PhrasePrefix(a.Content, "neural", "net"))
.Select(a => a.Title)
.ToListAsync();
@@ -109,12 +120,15 @@ public async Task PhrasePrefix_MatchesPartialLastWord() {
}
[Fact]
- public async Task Parse_LenientMode_IgnoresMalformedSyntax() {
+ public async Task Parse_LenientMode_IgnoresMalformedSyntax()
+ {
await using var ctx = fixture.CreateDbContext();
// Trailing operator would normally throw; lenient + conjunctionMode lets it parse.
- var hits = await ctx.Articles
- .Where(a => EF.Functions.Parse(a.Id, "neural networks", lenient: true, conjunctionMode: true))
+ var hits = await ctx
+ .Articles.Where(a =>
+ EF.Functions.Parse(a.Id, "neural networks", lenient: true, conjunctionMode: true)
+ )
.Select(a => a.Title)
.ToListAsync();
@@ -122,12 +136,16 @@ public async Task Parse_LenientMode_IgnoresMalformedSyntax() {
}
[Fact]
- public async Task ComposedLinq_SearchPlusWhereAndProjection_Works() {
+ public async Task ComposedLinq_SearchPlusWhereAndProjection_Works()
+ {
await using var ctx = fixture.CreateDbContext();
- var top = await ctx.Articles
- .Where(a => EF.Functions.Matches(a.Content, "neural networks") && a.Rating >= 4)
- .Select(a => new {
+ var top = await ctx
+ .Articles.Where(a =>
+ EF.Functions.Matches(a.Content, "neural networks") && a.Rating >= 4
+ )
+ .Select(a => new
+ {
a.Title,
Snippet = EF.Functions.Snippet(a.Content, "", "", 60),
Score = EF.Functions.Score(a.Id),
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/SnippetDefaultTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/SnippetDefaultTests.cs
index fe4707b..4fea727 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/SnippetDefaultTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/SnippetDefaultTests.cs
@@ -3,17 +3,19 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class SnippetDefaultTests(ParadeDbFixture fixture) {
+public class SnippetDefaultTests(ParadeDbFixture fixture)
+{
// Verifies the no-arg Snippet overload translates to bare pdb.snippet(column) — distinct
// from the parameterised pdb.snippet(column, start_tag => ..., end_tag => ..., max_num_chars => ...)
// form that's already tested. Default ParadeDB tags are .... A regression that
// routed through the named-arg path would either fail or change the highlight markers.
[Fact]
- public async Task Snippet_NoArgs_HighlightsWithDefaultBoldTags() {
+ public async Task Snippet_NoArgs_HighlightsWithDefaultBoldTags()
+ {
await using var ctx = fixture.CreateDbContext();
- var snippets = await ctx.Articles
- .Where(a => EF.Functions.Matches(a.Content, "neural networks"))
+ var snippets = await ctx
+ .Articles.Where(a => EF.Functions.Matches(a.Content, "neural networks"))
.Select(a => EF.Functions.Snippet(a.Content))
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/TokenizerTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/TokenizerTests.cs
index 1194f85..fba590a 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/TokenizerTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests/TokenizerTests.cs
@@ -3,13 +3,15 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.IntegrationTests;
[Collection(nameof(ParadeDbCollection))]
-public class TokenizerTests(ParadeDbFixture fixture) {
+public class TokenizerTests(ParadeDbFixture fixture)
+{
[Fact]
- public async Task KeywordTokenizer_TreatsCodeAsSingleToken() {
+ public async Task KeywordTokenizer_TreatsCodeAsSingleToken()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.KeywordRecords
- .Where(k => EF.Functions.MatchesTerm(k.Code, "ABC-123"))
+ var hits = await ctx
+ .KeywordRecords.Where(k => EF.Functions.MatchesTerm(k.Code, "ABC-123"))
.Select(k => k.Code)
.ToListAsync();
@@ -18,11 +20,12 @@ public async Task KeywordTokenizer_TreatsCodeAsSingleToken() {
}
[Fact]
- public async Task NgramTokenizer_MatchesSubstring() {
+ public async Task NgramTokenizer_MatchesSubstring()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.NgramRecords
- .Where(n => EF.Functions.MatchesTerm(n.Body, "frag"))
+ var hits = await ctx
+ .NgramRecords.Where(n => EF.Functions.MatchesTerm(n.Body, "frag"))
.Select(n => n.Body)
.ToListAsync();
@@ -30,11 +33,12 @@ public async Task NgramTokenizer_MatchesSubstring() {
}
[Fact]
- public async Task IcuTokenizer_HandlesUnicodeText() {
+ public async Task IcuTokenizer_HandlesUnicodeText()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.IcuRecords
- .Where(i => EF.Functions.Matches(i.Body, "café"))
+ var hits = await ctx
+ .IcuRecords.Where(i => EF.Functions.Matches(i.Body, "café"))
.Select(i => i.Body)
.ToListAsync();
@@ -42,11 +46,12 @@ public async Task IcuTokenizer_HandlesUnicodeText() {
}
[Fact]
- public async Task SourceCodeTokenizer_SplitsCamelCase() {
+ public async Task SourceCodeTokenizer_SplitsCamelCase()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.SourceCodeRecords
- .Where(s => EF.Functions.Matches(s.Snippet, "user"))
+ var hits = await ctx
+ .SourceCodeRecords.Where(s => EF.Functions.Matches(s.Snippet, "user"))
.Select(s => s.Snippet)
.ToListAsync();
@@ -54,11 +59,12 @@ public async Task SourceCodeTokenizer_SplitsCamelCase() {
}
[Fact]
- public async Task RegexTokenizer_TokenizesByPattern() {
+ public async Task RegexTokenizer_TokenizesByPattern()
+ {
await using var ctx = fixture.CreateDbContext();
- var hits = await ctx.RegexRecords
- .Where(r => EF.Functions.Matches(r.Body, "alpha"))
+ var hits = await ctx
+ .RegexRecords.Where(r => EF.Functions.Matches(r.Body, "alpha"))
.Select(r => r.Body)
.ToListAsync();
@@ -66,12 +72,13 @@ public async Task RegexTokenizer_TokenizesByPattern() {
}
[Fact]
- public async Task GermanStemmer_MatchesGermanWordVariants() {
+ public async Task GermanStemmer_MatchesGermanWordVariants()
+ {
await using var ctx = fixture.CreateDbContext();
// 'Häuser' stems to a German root; 'Haus' should match via the same stem.
- var hits = await ctx.GermanArticles
- .Where(g => EF.Functions.Matches(g.Content, "Haus"))
+ var hits = await ctx
+ .GermanArticles.Where(g => EF.Functions.Matches(g.Content, "Haus"))
.Select(g => g.Content)
.ToListAsync();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.Tests/Bm25EnumExtensionsTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.Tests/Bm25EnumExtensionsTests.cs
index fa04547..8ae70d6 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.Tests/Bm25EnumExtensionsTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.Tests/Bm25EnumExtensionsTests.cs
@@ -6,7 +6,8 @@ namespace Equibles.ParadeDB.EntityFrameworkCore.Tests;
/// Tests for — every enum value must map to its pg_search
/// string. Unspecified throws because the indexer expects an explicit mapping.
///
-public class Bm25EnumExtensionsTests {
+public class Bm25EnumExtensionsTests
+{
// ── Bm25Tokenizer ─────────────────────────────────────────────────
[Theory]
@@ -23,17 +24,23 @@ public class Bm25EnumExtensionsTests {
[InlineData(Bm25Tokenizer.JapaneseLindera, "japanese_lindera")]
[InlineData(Bm25Tokenizer.KoreanLindera, "korean_lindera")]
[InlineData(Bm25Tokenizer.Jieba, "jieba")]
- public void Tokenizer_maps_to_expected_pg_search_string(Bm25Tokenizer tokenizer, string expected) {
+ public void Tokenizer_maps_to_expected_pg_search_string(
+ Bm25Tokenizer tokenizer,
+ string expected
+ )
+ {
Assert.Equal(expected, tokenizer.ToParadeDbString());
}
[Fact]
- public void Tokenizer_Unspecified_throws() {
+ public void Tokenizer_Unspecified_throws()
+ {
Assert.Throws(() => Bm25Tokenizer.Unspecified.ToParadeDbString());
}
[Fact]
- public void Tokenizer_out_of_range_throws() {
+ public void Tokenizer_out_of_range_throws()
+ {
var bogus = (Bm25Tokenizer)999;
Assert.Throws(() => bogus.ToParadeDbString());
}
@@ -61,17 +68,20 @@ public void Tokenizer_out_of_range_throws() {
[InlineData(Bm25Language.Swedish, "Swedish")]
[InlineData(Bm25Language.Tamil, "Tamil")]
[InlineData(Bm25Language.Turkish, "Turkish")]
- public void Language_maps_to_expected_pg_search_string(Bm25Language language, string expected) {
+ public void Language_maps_to_expected_pg_search_string(Bm25Language language, string expected)
+ {
Assert.Equal(expected, language.ToParadeDbString());
}
[Fact]
- public void Language_Unspecified_throws() {
+ public void Language_Unspecified_throws()
+ {
Assert.Throws(() => Bm25Language.Unspecified.ToParadeDbString());
}
[Fact]
- public void Language_out_of_range_throws() {
+ public void Language_out_of_range_throws()
+ {
var bogus = (Bm25Language)999;
Assert.Throws(() => bogus.ToParadeDbString());
}
@@ -82,17 +92,20 @@ public void Language_out_of_range_throws() {
[InlineData(Bm25Record.Basic, "basic")]
[InlineData(Bm25Record.Freq, "freq")]
[InlineData(Bm25Record.Position, "position")]
- public void Record_maps_to_expected_pg_search_string(Bm25Record record, string expected) {
+ public void Record_maps_to_expected_pg_search_string(Bm25Record record, string expected)
+ {
Assert.Equal(expected, record.ToParadeDbString());
}
[Fact]
- public void Record_Unspecified_throws() {
+ public void Record_Unspecified_throws()
+ {
Assert.Throws(() => Bm25Record.Unspecified.ToParadeDbString());
}
[Fact]
- public void Record_out_of_range_throws() {
+ public void Record_out_of_range_throws()
+ {
var bogus = (Bm25Record)999;
Assert.Throws(() => bogus.ToParadeDbString());
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.Tests/Bm25IndexConfigurationTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.Tests/Bm25IndexConfigurationTests.cs
index 94c8379..adace31 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.Tests/Bm25IndexConfigurationTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.Tests/Bm25IndexConfigurationTests.cs
@@ -2,14 +2,18 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.Tests;
-public class Bm25IndexConfigurationTests {
- private static string GetCreateScript() where TEntity : class {
+public class Bm25IndexConfigurationTests
+{
+ private static string GetCreateScript()
+ where TEntity : class
+ {
using var ctx = new ConfigTestContext();
return ctx.Database.GenerateCreateScript();
}
[Fact]
- public void EntityWithoutPerColumnAttributes_EmitsOnlyKeyField() {
+ public void EntityWithoutPerColumnAttributes_EmitsOnlyKeyField()
+ {
var sql = GetCreateScript();
Assert.Contains("key_field='Id'", sql);
Assert.DoesNotContain("text_fields=", sql);
@@ -20,93 +24,114 @@ public void EntityWithoutPerColumnAttributes_EmitsOnlyKeyField() {
}
[Fact]
- public void Bm25Text_WithEnglishStemmer_EmitsStemmerInTokenizer() {
+ public void Bm25Text_WithEnglishStemmer_EmitsStemmerInTokenizer()
+ {
var sql = GetCreateScript();
- Assert.Contains("""text_fields='{"Content":{"tokenizer":{"type":"default","stemmer":"English"}}}'""", sql);
+ Assert.Contains(
+ """text_fields='{"Content":{"tokenizer":{"type":"default","stemmer":"English"}}}'""",
+ sql
+ );
}
[Fact]
- public void Bm25Text_WithRawTokenizer_EmitsRawType() {
+ public void Bm25Text_WithRawTokenizer_EmitsRawType()
+ {
var sql = GetCreateScript();
Assert.Contains("""text_fields='{"Slug":{"tokenizer":{"type":"raw"}}}'""", sql);
}
[Fact]
- public void Bm25Text_WithStopwordsLanguage_EmitsStopwordsKey() {
+ public void Bm25Text_WithStopwordsLanguage_EmitsStopwordsKey()
+ {
var sql = GetCreateScript();
Assert.Contains("\"stopwords_language\":\"French\"", sql);
}
[Fact]
- public void Bm25Text_WithNgramTokenizer_EmitsMinMaxGramAndPrefixOnly() {
+ public void Bm25Text_WithNgramTokenizer_EmitsMinMaxGramAndPrefixOnly()
+ {
var sql = GetCreateScript();
- Assert.Contains("""text_fields='{"Content":{"tokenizer":{"type":"ngram","min_gram":2,"max_gram":4,"prefix_only":false}}}'""", sql);
+ Assert.Contains(
+ """text_fields='{"Content":{"tokenizer":{"type":"ngram","min_gram":2,"max_gram":4,"prefix_only":false}}}'""",
+ sql
+ );
}
[Fact]
- public void Bm25Text_WithNgramAndPrefixOnly_EmitsPrefixOnly() {
+ public void Bm25Text_WithNgramAndPrefixOnly_EmitsPrefixOnly()
+ {
var sql = GetCreateScript();
Assert.Contains("\"prefix_only\":true", sql);
}
[Fact]
- public void Bm25Text_WithRegexTokenizer_EmitsPattern() {
+ public void Bm25Text_WithRegexTokenizer_EmitsPattern()
+ {
var sql = GetCreateScript();
Assert.Contains("\"type\":\"regex\"", sql);
Assert.Contains("\"pattern\":", sql);
}
[Fact]
- public void Bm25Text_WithFastTrue_EmitsFastKey() {
+ public void Bm25Text_WithFastTrue_EmitsFastKey()
+ {
var sql = GetCreateScript();
Assert.Contains("\"fast\":true", sql);
}
[Fact]
- public void Bm25Text_WithRecordPosition_EmitsRecordKey() {
+ public void Bm25Text_WithRecordPosition_EmitsRecordKey()
+ {
var sql = GetCreateScript();
Assert.Contains("\"record\":\"position\"", sql);
}
[Fact]
- public void Bm25Text_WithIndexedFalse_EmitsIndexedFalse() {
+ public void Bm25Text_WithIndexedFalse_EmitsIndexedFalse()
+ {
var sql = GetCreateScript();
Assert.Contains("\"indexed\":false", sql);
}
[Fact]
- public void Bm25Text_WithFieldnormsFalse_EmitsFieldnormsFalse() {
+ public void Bm25Text_WithFieldnormsFalse_EmitsFieldnormsFalse()
+ {
var sql = GetCreateScript();
Assert.Contains("\"fieldnorms\":false", sql);
}
[Fact]
- public void Bm25Numeric_WithFastTrue_EmitsNumericFields() {
+ public void Bm25Numeric_WithFastTrue_EmitsNumericFields()
+ {
var sql = GetCreateScript();
Assert.Contains("""numeric_fields='{"Rating":{"fast":true}}'""", sql);
}
[Fact]
- public void Bm25Boolean_WithFastTrue_EmitsBooleanFields() {
+ public void Bm25Boolean_WithFastTrue_EmitsBooleanFields()
+ {
var sql = GetCreateScript();
Assert.Contains("""boolean_fields='{"InStock":{"fast":true}}'""", sql);
}
[Fact]
- public void Bm25DateTime_WithFastTrue_EmitsDatetimeFields() {
+ public void Bm25DateTime_WithFastTrue_EmitsDatetimeFields()
+ {
var sql = GetCreateScript();
Assert.Contains("""datetime_fields='{"PublishedAt":{"fast":true}}'""", sql);
}
[Fact]
- public void Bm25Json_WithExpandDots_EmitsExpandDots() {
+ public void Bm25Json_WithExpandDots_EmitsExpandDots()
+ {
var sql = GetCreateScript();
Assert.Contains("json_fields=", sql);
Assert.Contains("\"expand_dots\":true", sql);
}
[Fact]
- public void MixedFieldTypes_EmitSeparateStorageParameters() {
+ public void MixedFieldTypes_EmitSeparateStorageParameters()
+ {
var sql = GetCreateScript();
Assert.Contains("text_fields=", sql);
Assert.Contains("numeric_fields=", sql);
@@ -114,249 +139,328 @@ public void MixedFieldTypes_EmitSeparateStorageParameters() {
}
[Fact]
- public void NgramParamWithoutNgramTokenizer_Throws() {
- var ex = Assert.Throws(() => GetCreateScript());
- Assert.Contains("MinGram/MaxGram/PrefixOnly require Tokenizer = Bm25Tokenizer.Ngram", ex.Message);
+ public void NgramParamWithoutNgramTokenizer_Throws()
+ {
+ var ex = Assert.Throws(() =>
+ GetCreateScript()
+ );
+ Assert.Contains(
+ "MinGram/MaxGram/PrefixOnly require Tokenizer = Bm25Tokenizer.Ngram",
+ ex.Message
+ );
}
[Fact]
- public void NgramTokenizerWithoutMinMax_Throws() {
- var ex = Assert.Throws(() => GetCreateScript());
+ public void NgramTokenizerWithoutMinMax_Throws()
+ {
+ var ex = Assert.Throws(() =>
+ GetCreateScript()
+ );
Assert.Contains("Bm25Tokenizer.Ngram requires both MinGram and MaxGram", ex.Message);
}
[Fact]
- public void RegexTokenizerWithoutPattern_Throws() {
- var ex = Assert.Throws(() => GetCreateScript());
+ public void RegexTokenizerWithoutPattern_Throws()
+ {
+ var ex = Assert.Throws(() =>
+ GetCreateScript()
+ );
Assert.Contains("Bm25Tokenizer.Regex requires a RegexPattern", ex.Message);
}
[Fact]
- public void RegexParamWithoutRegexTokenizer_Throws() {
- var ex = Assert.Throws(() => GetCreateScript());
+ public void RegexParamWithoutRegexTokenizer_Throws()
+ {
+ var ex = Assert.Throws(() =>
+ GetCreateScript()
+ );
Assert.Contains("RegexPattern requires Tokenizer = Bm25Tokenizer.Regex", ex.Message);
}
[Fact]
- public void Bm25Numeric_WithIndexedFalse_EmitsIndexedFalse() {
+ public void Bm25Numeric_WithIndexedFalse_EmitsIndexedFalse()
+ {
var sql = GetCreateScript();
Assert.Contains("numeric_fields=", sql);
Assert.Contains("\"indexed\":false", sql);
}
[Fact]
- public void Bm25Boolean_WithIndexedFalse_EmitsIndexedFalse() {
+ public void Bm25Boolean_WithIndexedFalse_EmitsIndexedFalse()
+ {
var sql = GetCreateScript();
Assert.Contains("boolean_fields=", sql);
Assert.Contains("\"indexed\":false", sql);
}
[Fact]
- public void Bm25DateTime_WithIndexedFalse_EmitsIndexedFalse() {
+ public void Bm25DateTime_WithIndexedFalse_EmitsIndexedFalse()
+ {
var sql = GetCreateScript();
Assert.Contains("datetime_fields=", sql);
Assert.Contains("\"indexed\":false", sql);
}
[Fact]
- public void OrphanFieldAttributeWithoutBm25Index_Throws() {
- var ex = Assert.Throws(() => GetCreateScript());
+ public void OrphanFieldAttributeWithoutBm25Index_Throws()
+ {
+ var ex = Assert.Throws(() =>
+ GetCreateScript()
+ );
Assert.Contains("OrphanText", ex.Message);
Assert.Contains("no [Bm25Index]", ex.Message);
}
[Fact]
- public void FieldAttributeOnPropertyNotInIndexColumns_Throws() {
- var ex = Assert.Throws(() => GetCreateScript());
+ public void FieldAttributeOnPropertyNotInIndexColumns_Throws()
+ {
+ var ex = Assert.Throws(() =>
+ GetCreateScript()
+ );
Assert.Contains("Untracked", ex.Message);
Assert.Contains("not listed in the [Bm25Index] columns", ex.Message);
}
}
-internal sealed class ConfigTestContext : DbContext where TEntity : class {
- protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
+internal sealed class ConfigTestContext : DbContext
+ where TEntity : class
+{
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
optionsBuilder.UseNpgsql("Host=localhost;Database=test", npgsql => npgsql.UseParadeDb());
}
- protected override void OnModelCreating(ModelBuilder modelBuilder) {
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
modelBuilder.Entity();
}
}
[Bm25Index(nameof(Id), nameof(Content))]
-internal class PlainEntity {
+internal class PlainEntity
+{
public int Id { get; set; }
public string Content { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Content))]
-internal class EnglishStemmerEntity {
+internal class EnglishStemmerEntity
+{
public int Id { get; set; }
+
[Bm25Text(Stemmer = Bm25Language.English)]
public string Content { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Slug))]
-internal class RawTokenizerEntity {
+internal class RawTokenizerEntity
+{
public int Id { get; set; }
+
[Bm25Text(Tokenizer = Bm25Tokenizer.Raw)]
public string Slug { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Content))]
-internal class StopwordsEntity {
+internal class StopwordsEntity
+{
public int Id { get; set; }
+
[Bm25Text(StopwordsLanguage = Bm25Language.French)]
public string Content { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Content))]
-internal class NgramEntity {
+internal class NgramEntity
+{
public int Id { get; set; }
+
[Bm25Text(Tokenizer = Bm25Tokenizer.Ngram, MinGram = 2, MaxGram = 4)]
public string Content { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Content))]
-internal class NgramPrefixEntity {
+internal class NgramPrefixEntity
+{
public int Id { get; set; }
+
[Bm25Text(Tokenizer = Bm25Tokenizer.Ngram, MinGram = 2, MaxGram = 4, PrefixOnly = true)]
public string Content { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Content))]
-internal class RegexEntity {
+internal class RegexEntity
+{
public int Id { get; set; }
+
[Bm25Text(Tokenizer = Bm25Tokenizer.Regex, RegexPattern = "neuro.*")]
public string Content { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Title))]
-internal class FastTextEntity {
+internal class FastTextEntity
+{
public int Id { get; set; }
+
[Bm25Text(Fast = true)]
public string Title { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Content))]
-internal class RecordPositionEntity {
+internal class RecordPositionEntity
+{
public int Id { get; set; }
+
[Bm25Text(Record = Bm25Record.Position)]
public string Content { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Content))]
-internal class NotIndexedTextEntity {
+internal class NotIndexedTextEntity
+{
public int Id { get; set; }
+
[Bm25Text(Indexed = false)]
public string Content { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Content))]
-internal class NoFieldnormsEntity {
+internal class NoFieldnormsEntity
+{
public int Id { get; set; }
+
[Bm25Text(Fieldnorms = false)]
public string Content { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Rating))]
-internal class NumericEntity {
+internal class NumericEntity
+{
public int Id { get; set; }
+
[Bm25Numeric(Fast = true)]
public int Rating { get; set; }
}
[Bm25Index(nameof(Id), nameof(InStock))]
-internal class BooleanEntity {
+internal class BooleanEntity
+{
public int Id { get; set; }
+
[Bm25Boolean(Fast = true)]
public bool InStock { get; set; }
}
[Bm25Index(nameof(Id), nameof(PublishedAt))]
-internal class DateTimeEntity {
+internal class DateTimeEntity
+{
public int Id { get; set; }
+
[Bm25DateTime(Fast = true)]
public DateTime PublishedAt { get; set; }
}
[Bm25Index(nameof(Id), nameof(Metadata))]
-internal class JsonEntity {
+internal class JsonEntity
+{
public int Id { get; set; }
+
[Bm25Json(ExpandDots = true)]
public string Metadata { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Title), nameof(Rating), nameof(InStock))]
-internal class MixedEntity {
+internal class MixedEntity
+{
public int Id { get; set; }
+
[Bm25Text(Fast = true)]
public string Title { get; set; } = null!;
+
[Bm25Numeric(Fast = true)]
public int Rating { get; set; }
+
[Bm25Boolean(Fast = true)]
public bool InStock { get; set; }
}
[Bm25Index(nameof(Id), nameof(Content))]
-internal class InvalidNgramParamEntity {
+internal class InvalidNgramParamEntity
+{
public int Id { get; set; }
+
[Bm25Text(MinGram = 2)]
public string Content { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Content))]
-internal class NgramMissingMinMaxEntity {
+internal class NgramMissingMinMaxEntity
+{
public int Id { get; set; }
+
[Bm25Text(Tokenizer = Bm25Tokenizer.Ngram)]
public string Content { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Content))]
-internal class RegexMissingPatternEntity {
+internal class RegexMissingPatternEntity
+{
public int Id { get; set; }
+
[Bm25Text(Tokenizer = Bm25Tokenizer.Regex)]
public string Content { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Content))]
-internal class RegexParamOnNonRegexTokenizerEntity {
+internal class RegexParamOnNonRegexTokenizerEntity
+{
public int Id { get; set; }
+
[Bm25Text(Tokenizer = Bm25Tokenizer.Default, RegexPattern = "neuro.*")]
public string Content { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Rating))]
-internal class NotIndexedNumericEntity {
+internal class NotIndexedNumericEntity
+{
public int Id { get; set; }
+
[Bm25Numeric(Indexed = false)]
public int Rating { get; set; }
}
[Bm25Index(nameof(Id), nameof(InStock))]
-internal class NotIndexedBooleanEntity {
+internal class NotIndexedBooleanEntity
+{
public int Id { get; set; }
+
[Bm25Boolean(Indexed = false)]
public bool InStock { get; set; }
}
[Bm25Index(nameof(Id), nameof(PublishedAt))]
-internal class NotIndexedDateTimeEntity {
+internal class NotIndexedDateTimeEntity
+{
public int Id { get; set; }
+
[Bm25DateTime(Indexed = false)]
public DateTime PublishedAt { get; set; }
}
-internal class OrphanNoIndexEntity {
+internal class OrphanNoIndexEntity
+{
public int Id { get; set; }
+
[Bm25Text]
public string OrphanText { get; set; } = null!;
}
[Bm25Index(nameof(Id), nameof(Indexed))]
-internal class FieldAttrNotInIndexEntity {
+internal class FieldAttrNotInIndexEntity
+{
public int Id { get; set; }
public string Indexed { get; set; } = null!;
+
[Bm25Text]
public string Untracked { get; set; } = null!;
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.Tests/Equibles.ParadeDB.EntityFrameworkCore.Tests.csproj b/Equibles.ParadeDB.EntityFrameworkCore.Tests/Equibles.ParadeDB.EntityFrameworkCore.Tests.csproj
index cd0eea6..ff3569b 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.Tests/Equibles.ParadeDB.EntityFrameworkCore.Tests.csproj
+++ b/Equibles.ParadeDB.EntityFrameworkCore.Tests/Equibles.ParadeDB.EntityFrameworkCore.Tests.csproj
@@ -1,5 +1,4 @@
-
net8.0;net9.0;net10.0
enable
@@ -34,5 +33,4 @@
-
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.Tests/ExpressionTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.Tests/ExpressionTests.cs
index fef4992..95675b0 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.Tests/ExpressionTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.Tests/ExpressionTests.cs
@@ -4,60 +4,113 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.Tests;
-public class ExpressionTests {
+public class ExpressionTests
+{
private static SqlExpression Stub(string token) => new StubSqlExpression(token);
- private sealed class StubSqlExpression : SqlExpression {
+ private sealed class StubSqlExpression : SqlExpression
+ {
private readonly string _token;
- public StubSqlExpression(string token) : base(typeof(string), new FakeTypeMapping()) {
+
+ public StubSqlExpression(string token)
+ : base(typeof(string), new FakeTypeMapping())
+ {
_token = token;
}
+
protected override System.Linq.Expressions.Expression VisitChildren(
- System.Linq.Expressions.ExpressionVisitor visitor) => this;
- protected override void Print(ExpressionPrinter expressionPrinter) => expressionPrinter.Append(_token);
- public override bool Equals(object? obj) => obj is StubSqlExpression other && other._token == _token;
+ System.Linq.Expressions.ExpressionVisitor visitor
+ ) => this;
+
+ protected override void Print(ExpressionPrinter expressionPrinter) =>
+ expressionPrinter.Append(_token);
+
+ public override bool Equals(object? obj) =>
+ obj is StubSqlExpression other && other._token == _token;
+
public override int GetHashCode() => _token.GetHashCode();
+
#if NET9_0_OR_GREATER
- public override System.Linq.Expressions.Expression Quote() => throw new NotSupportedException();
+ public override System.Linq.Expressions.Expression Quote() =>
+ throw new NotSupportedException();
#endif
}
- private sealed class FakeTypeMapping : RelationalTypeMapping {
- public FakeTypeMapping() : base("text", typeof(string)) { }
- protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) => this;
+ private sealed class FakeTypeMapping : RelationalTypeMapping
+ {
+ public FakeTypeMapping()
+ : base("text", typeof(string)) { }
+
+ protected override RelationalTypeMapping Clone(
+ RelationalTypeMappingParameters parameters
+ ) => this;
}
[Fact]
- public void ModifiedQueryExpression_Equals_ReturnsTrueForSameInnerAndSuffix() {
+ public void ModifiedQueryExpression_Equals_ReturnsTrueForSameInnerAndSuffix()
+ {
var inner = Stub("hello");
- var a = new ParadeDbModifiedQueryExpression(inner, "::pdb.fuzzy(2)", typeof(string), inner.TypeMapping);
- var b = new ParadeDbModifiedQueryExpression(inner, "::pdb.fuzzy(2)", typeof(string), inner.TypeMapping);
+ var a = new ParadeDbModifiedQueryExpression(
+ inner,
+ "::pdb.fuzzy(2)",
+ typeof(string),
+ inner.TypeMapping
+ );
+ var b = new ParadeDbModifiedQueryExpression(
+ inner,
+ "::pdb.fuzzy(2)",
+ typeof(string),
+ inner.TypeMapping
+ );
Assert.True(a.Equals(b));
Assert.Equal(a.GetHashCode(), b.GetHashCode());
}
[Fact]
- public void ModifiedQueryExpression_Equals_ReturnsFalseForDifferentSuffix() {
+ public void ModifiedQueryExpression_Equals_ReturnsFalseForDifferentSuffix()
+ {
var inner = Stub("hello");
- var a = new ParadeDbModifiedQueryExpression(inner, "::pdb.fuzzy(2)", typeof(string), inner.TypeMapping);
- var b = new ParadeDbModifiedQueryExpression(inner, "::pdb.boost(2)", typeof(string), inner.TypeMapping);
+ var a = new ParadeDbModifiedQueryExpression(
+ inner,
+ "::pdb.fuzzy(2)",
+ typeof(string),
+ inner.TypeMapping
+ );
+ var b = new ParadeDbModifiedQueryExpression(
+ inner,
+ "::pdb.boost(2)",
+ typeof(string),
+ inner.TypeMapping
+ );
Assert.False(a.Equals(b));
}
[Fact]
- public void ModifiedQueryExpression_Equals_ReturnsFalseForUnrelatedType() {
+ public void ModifiedQueryExpression_Equals_ReturnsFalseForUnrelatedType()
+ {
var inner = Stub("hello");
- var a = new ParadeDbModifiedQueryExpression(inner, "::pdb.fuzzy(2)", typeof(string), inner.TypeMapping);
+ var a = new ParadeDbModifiedQueryExpression(
+ inner,
+ "::pdb.fuzzy(2)",
+ typeof(string),
+ inner.TypeMapping
+ );
Assert.False(a.Equals("not an expression"));
}
[Fact]
- public void ModifiedQueryExpression_Print_EmitsInnerThenSuffix() {
+ public void ModifiedQueryExpression_Print_EmitsInnerThenSuffix()
+ {
var inner = Stub("hello");
- var expr = new ParadeDbModifiedQueryExpression(inner, "::pdb.fuzzy(2)", typeof(string), inner.TypeMapping);
+ var expr = new ParadeDbModifiedQueryExpression(
+ inner,
+ "::pdb.fuzzy(2)",
+ typeof(string),
+ inner.TypeMapping
+ );
var printer = new ExpressionPrinter();
printer.Visit(expr);
@@ -66,46 +119,87 @@ public void ModifiedQueryExpression_Print_EmitsInnerThenSuffix() {
}
[Fact]
- public void NamedArgFunctionExpression_Equals_ReturnsTrueForSameStructure() {
+ public void NamedArgFunctionExpression_Equals_ReturnsTrueForSameStructure()
+ {
var arg = Stub("c");
var named = Stub("");
- var a = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [arg], [("start_tag", named)], typeof(string), arg.TypeMapping);
- var b = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [arg], [("start_tag", named)], typeof(string), arg.TypeMapping);
+ var a = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg],
+ [("start_tag", named)],
+ typeof(string),
+ arg.TypeMapping
+ );
+ var b = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg],
+ [("start_tag", named)],
+ typeof(string),
+ arg.TypeMapping
+ );
Assert.True(a.Equals(b));
Assert.Equal(a.GetHashCode(), b.GetHashCode());
}
[Fact]
- public void NamedArgFunctionExpression_Equals_ReturnsFalseForDifferentFunctionName() {
+ public void NamedArgFunctionExpression_Equals_ReturnsFalseForDifferentFunctionName()
+ {
var arg = Stub("c");
- var a = new ParadeDbNamedArgFunctionExpression("pdb.snippet", [arg], [], typeof(string), arg.TypeMapping);
- var b = new ParadeDbNamedArgFunctionExpression("pdb.snippets", [arg], [], typeof(string), arg.TypeMapping);
+ var a = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg],
+ [],
+ typeof(string),
+ arg.TypeMapping
+ );
+ var b = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippets",
+ [arg],
+ [],
+ typeof(string),
+ arg.TypeMapping
+ );
Assert.False(a.Equals(b));
}
[Fact]
- public void NamedArgFunctionExpression_Equals_ReturnsFalseForDifferentNamedArgName() {
+ public void NamedArgFunctionExpression_Equals_ReturnsFalseForDifferentNamedArgName()
+ {
var arg = Stub("c");
var v1 = Stub("");
- var a = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [arg], [("start_tag", v1)], typeof(string), arg.TypeMapping);
- var b = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [arg], [("end_tag", v1)], typeof(string), arg.TypeMapping);
+ var a = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg],
+ [("start_tag", v1)],
+ typeof(string),
+ arg.TypeMapping
+ );
+ var b = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg],
+ [("end_tag", v1)],
+ typeof(string),
+ arg.TypeMapping
+ );
Assert.False(a.Equals(b));
}
[Fact]
- public void NamedArgFunctionExpression_Print_EmitsPositionalAndNamedArgs() {
+ public void NamedArgFunctionExpression_Print_EmitsPositionalAndNamedArgs()
+ {
var positional = Stub("c");
var named = Stub("");
- var expr = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [positional], [("start_tag", named)], typeof(string), positional.TypeMapping);
+ var expr = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [positional],
+ [("start_tag", named)],
+ typeof(string),
+ positional.TypeMapping
+ );
var printer = new ExpressionPrinter();
printer.Visit(expr);
@@ -118,17 +212,30 @@ public void NamedArgFunctionExpression_Print_EmitsPositionalAndNamedArgs() {
#if NET9_0_OR_GREATER
[Fact]
- public void ModifiedQueryExpression_Quote_Throws() {
+ public void ModifiedQueryExpression_Quote_Throws()
+ {
var inner = Stub("hello");
- var expr = new ParadeDbModifiedQueryExpression(inner, "::pdb.fuzzy(2)", typeof(string), inner.TypeMapping);
+ var expr = new ParadeDbModifiedQueryExpression(
+ inner,
+ "::pdb.fuzzy(2)",
+ typeof(string),
+ inner.TypeMapping
+ );
Assert.Throws(() => expr.Quote());
}
[Fact]
- public void NamedArgFunctionExpression_Quote_Throws() {
+ public void NamedArgFunctionExpression_Quote_Throws()
+ {
var arg = Stub("c");
- var expr = new ParadeDbNamedArgFunctionExpression("pdb.snippet", [arg], [], typeof(string), arg.TypeMapping);
+ var expr = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg],
+ [],
+ typeof(string),
+ arg.TypeMapping
+ );
Assert.Throws(() => expr.Quote());
}
@@ -141,29 +248,43 @@ public void NamedArgFunctionExpression_Quote_Throws() {
/// expression-tree visitation. Lets us assert that VisitChildren returns a NEW
/// expression when a child changes (the "something changed" branch).
///
- private sealed class ReplaceVisitor : System.Linq.Expressions.ExpressionVisitor {
+ private sealed class ReplaceVisitor : System.Linq.Expressions.ExpressionVisitor
+ {
public System.Linq.Expressions.Expression Target { get; }
public System.Linq.Expressions.Expression Replacement { get; }
- public ReplaceVisitor(System.Linq.Expressions.Expression target,
- System.Linq.Expressions.Expression replacement) {
+ public ReplaceVisitor(
+ System.Linq.Expressions.Expression target,
+ System.Linq.Expressions.Expression replacement
+ )
+ {
Target = target;
Replacement = replacement;
}
- public override System.Linq.Expressions.Expression Visit(System.Linq.Expressions.Expression? node) {
- if (node is not null && ReferenceEquals(node, Target)) return Replacement;
+ public override System.Linq.Expressions.Expression Visit(
+ System.Linq.Expressions.Expression? node
+ )
+ {
+ if (node is not null && ReferenceEquals(node, Target))
+ return Replacement;
return base.Visit(node)!;
}
}
[Fact]
- public void NamedArgFunctionExpression_VisitChildren_returns_new_when_positional_changes() {
+ public void NamedArgFunctionExpression_VisitChildren_returns_new_when_positional_changes()
+ {
var original = Stub("c");
var replacement = Stub("c2");
var named = Stub("");
- var expr = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [original], [("start_tag", named)], typeof(string), original.TypeMapping);
+ var expr = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [original],
+ [("start_tag", named)],
+ typeof(string),
+ original.TypeMapping
+ );
var visitor = new ReplaceVisitor(original, replacement);
var result = visitor.Visit(expr);
@@ -175,12 +296,18 @@ public void NamedArgFunctionExpression_VisitChildren_returns_new_when_positional
}
[Fact]
- public void NamedArgFunctionExpression_VisitChildren_returns_new_when_named_changes() {
+ public void NamedArgFunctionExpression_VisitChildren_returns_new_when_named_changes()
+ {
var positional = Stub("c");
var original = Stub("");
var replacement = Stub("");
- var expr = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [positional], [("start_tag", original)], typeof(string), positional.TypeMapping);
+ var expr = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [positional],
+ [("start_tag", original)],
+ typeof(string),
+ positional.TypeMapping
+ );
var visitor = new ReplaceVisitor(original, replacement);
var result = visitor.Visit(expr);
@@ -192,11 +319,17 @@ public void NamedArgFunctionExpression_VisitChildren_returns_new_when_named_chan
}
[Fact]
- public void NamedArgFunctionExpression_VisitChildren_returns_same_when_no_changes() {
+ public void NamedArgFunctionExpression_VisitChildren_returns_same_when_no_changes()
+ {
var arg = Stub("c");
var named = Stub("");
- var expr = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [arg], [("start_tag", named)], typeof(string), arg.TypeMapping);
+ var expr = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg],
+ [("start_tag", named)],
+ typeof(string),
+ arg.TypeMapping
+ );
var visitor = new ReplaceVisitor(Stub("nothing-matches-this"), Stub("unused"));
var result = visitor.Visit(expr);
@@ -205,9 +338,15 @@ public void NamedArgFunctionExpression_VisitChildren_returns_same_when_no_change
}
[Fact]
- public void ModifiedQueryExpression_Print_DelegatesToInner() {
+ public void ModifiedQueryExpression_Print_DelegatesToInner()
+ {
var inner = Stub("hello");
- var expr = new ParadeDbModifiedQueryExpression(inner, "::pdb.fuzzy(2)", typeof(string), inner.TypeMapping);
+ var expr = new ParadeDbModifiedQueryExpression(
+ inner,
+ "::pdb.fuzzy(2)",
+ typeof(string),
+ inner.TypeMapping
+ );
var printer = new ExpressionPrinter();
printer.Visit(expr);
@@ -218,10 +357,16 @@ public void ModifiedQueryExpression_Print_DelegatesToInner() {
}
[Fact]
- public void ModifiedQueryExpression_VisitChildren_returns_new_when_inner_changes() {
+ public void ModifiedQueryExpression_VisitChildren_returns_new_when_inner_changes()
+ {
var original = Stub("hello");
var replacement = Stub("world");
- var expr = new ParadeDbModifiedQueryExpression(original, "::pdb.boost(2)", typeof(string), original.TypeMapping);
+ var expr = new ParadeDbModifiedQueryExpression(
+ original,
+ "::pdb.boost(2)",
+ typeof(string),
+ original.TypeMapping
+ );
var visitor = new ReplaceVisitor(original, replacement);
var result = visitor.Visit(expr);
@@ -233,9 +378,15 @@ public void ModifiedQueryExpression_VisitChildren_returns_new_when_inner_changes
}
[Fact]
- public void ModifiedQueryExpression_VisitChildren_returns_same_when_inner_unchanged() {
+ public void ModifiedQueryExpression_VisitChildren_returns_same_when_inner_unchanged()
+ {
var inner = Stub("hello");
- var expr = new ParadeDbModifiedQueryExpression(inner, "::pdb.boost(2)", typeof(string), inner.TypeMapping);
+ var expr = new ParadeDbModifiedQueryExpression(
+ inner,
+ "::pdb.boost(2)",
+ typeof(string),
+ inner.TypeMapping
+ );
var visitor = new ReplaceVisitor(Stub("nothing-matches-this"), Stub("unused"));
var result = visitor.Visit(expr);
@@ -246,64 +397,125 @@ public void ModifiedQueryExpression_VisitChildren_returns_same_when_inner_unchan
// ── NamedArgFunctionExpression.Equals — remaining branch arms ──────
[Fact]
- public void NamedArgFunctionExpression_Equals_ReturnsFalseForUnrelatedType() {
+ public void NamedArgFunctionExpression_Equals_ReturnsFalseForUnrelatedType()
+ {
var arg = Stub("c");
- var a = new ParadeDbNamedArgFunctionExpression("pdb.snippet", [arg], [], typeof(string), arg.TypeMapping);
+ var a = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg],
+ [],
+ typeof(string),
+ arg.TypeMapping
+ );
Assert.False(a.Equals("not an expression"));
}
[Fact]
- public void NamedArgFunctionExpression_Equals_ReturnsFalseForDifferentPositionalCount() {
+ public void NamedArgFunctionExpression_Equals_ReturnsFalseForDifferentPositionalCount()
+ {
var arg1 = Stub("c");
var arg2 = Stub("d");
- var a = new ParadeDbNamedArgFunctionExpression("pdb.snippet", [arg1], [], typeof(string), arg1.TypeMapping);
- var b = new ParadeDbNamedArgFunctionExpression("pdb.snippet", [arg1, arg2], [], typeof(string), arg1.TypeMapping);
+ var a = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg1],
+ [],
+ typeof(string),
+ arg1.TypeMapping
+ );
+ var b = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg1, arg2],
+ [],
+ typeof(string),
+ arg1.TypeMapping
+ );
Assert.False(a.Equals(b));
}
[Fact]
- public void NamedArgFunctionExpression_Equals_ReturnsFalseForDifferentNamedCount() {
+ public void NamedArgFunctionExpression_Equals_ReturnsFalseForDifferentNamedCount()
+ {
var arg = Stub("c");
var v = Stub("");
- var a = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [arg], [("start_tag", v)], typeof(string), arg.TypeMapping);
- var b = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [arg], [("start_tag", v), ("end_tag", v)], typeof(string), arg.TypeMapping);
+ var a = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg],
+ [("start_tag", v)],
+ typeof(string),
+ arg.TypeMapping
+ );
+ var b = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg],
+ [("start_tag", v), ("end_tag", v)],
+ typeof(string),
+ arg.TypeMapping
+ );
Assert.False(a.Equals(b));
}
[Fact]
- public void NamedArgFunctionExpression_Equals_ReturnsFalseForDifferentPositionalValue() {
+ public void NamedArgFunctionExpression_Equals_ReturnsFalseForDifferentPositionalValue()
+ {
var arg1 = Stub("c");
var arg2 = Stub("d");
- var a = new ParadeDbNamedArgFunctionExpression("pdb.snippet", [arg1], [], typeof(string), arg1.TypeMapping);
- var b = new ParadeDbNamedArgFunctionExpression("pdb.snippet", [arg2], [], typeof(string), arg1.TypeMapping);
+ var a = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg1],
+ [],
+ typeof(string),
+ arg1.TypeMapping
+ );
+ var b = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg2],
+ [],
+ typeof(string),
+ arg1.TypeMapping
+ );
Assert.False(a.Equals(b));
}
[Fact]
- public void NamedArgFunctionExpression_Equals_ReturnsFalseForDifferentNamedValue() {
+ public void NamedArgFunctionExpression_Equals_ReturnsFalseForDifferentNamedValue()
+ {
var arg = Stub("c");
var v1 = Stub("");
var v2 = Stub("");
- var a = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [arg], [("start_tag", v1)], typeof(string), arg.TypeMapping);
- var b = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [arg], [("start_tag", v2)], typeof(string), arg.TypeMapping);
+ var a = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg],
+ [("start_tag", v1)],
+ typeof(string),
+ arg.TypeMapping
+ );
+ var b = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [arg],
+ [("start_tag", v2)],
+ typeof(string),
+ arg.TypeMapping
+ );
Assert.False(a.Equals(b));
}
[Fact]
- public void NamedArgFunctionExpression_Print_emits_separator_between_multiple_positional_args() {
+ public void NamedArgFunctionExpression_Print_emits_separator_between_multiple_positional_args()
+ {
var p1 = Stub("c");
var p2 = Stub("d");
- var expr = new ParadeDbNamedArgFunctionExpression("pdb.fn",
- [p1, p2], [], typeof(string), p1.TypeMapping);
+ var expr = new ParadeDbNamedArgFunctionExpression(
+ "pdb.fn",
+ [p1, p2],
+ [],
+ typeof(string),
+ p1.TypeMapping
+ );
var printer = new ExpressionPrinter();
printer.Visit(expr);
@@ -314,11 +526,17 @@ public void NamedArgFunctionExpression_Print_emits_separator_between_multiple_po
}
[Fact]
- public void NamedArgFunctionExpression_Print_handles_named_args_without_positional() {
+ public void NamedArgFunctionExpression_Print_handles_named_args_without_positional()
+ {
var v1 = Stub("");
var v2 = Stub("");
- var expr = new ParadeDbNamedArgFunctionExpression("pdb.fn",
- [], [("start_tag", v1), ("end_tag", v2)], typeof(string), v1.TypeMapping);
+ var expr = new ParadeDbNamedArgFunctionExpression(
+ "pdb.fn",
+ [],
+ [("start_tag", v1), ("end_tag", v2)],
+ typeof(string),
+ v1.TypeMapping
+ );
var printer = new ExpressionPrinter();
printer.Visit(expr);
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.Tests/InternalsCoverageTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.Tests/InternalsCoverageTests.cs
index da2e551..00b22ba 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.Tests/InternalsCoverageTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.Tests/InternalsCoverageTests.cs
@@ -15,8 +15,10 @@ namespace Equibles.ParadeDB.EntityFrameworkCore.Tests;
/// - rebuild path
/// (children change during processing).
///
-public class InternalsCoverageTests {
- private static IServiceProvider Services() {
+public class InternalsCoverageTests
+{
+ private static IServiceProvider Services()
+ {
var ctx = new TestDbContext();
return ((IInfrastructure)ctx).Instance;
}
@@ -24,11 +26,15 @@ private static IServiceProvider Services() {
// ── Translator fall-through ───────────────────────────────────────
[Fact]
- public void Translate_returns_null_for_unknown_method() {
+ public void Translate_returns_null_for_unknown_method()
+ {
var services = Services();
var plugins = services.GetService>()!;
- var translator = plugins.OfType().Single()
- .Translators.OfType().Single();
+ var translator = plugins
+ .OfType()
+ .Single()
+ .Translators.OfType()
+ .Single();
var unknown = typeof(string).GetMethod(nameof(string.StartsWith), [typeof(string)])!;
var sql = services.GetService()!;
@@ -45,12 +51,15 @@ public void Translate_returns_null_for_unknown_method() {
// ── Nullability processor: child-pass-through path ────────────────
- private static ParadeDbSqlNullabilityProcessor CreateProcessor() {
+ private static ParadeDbSqlNullabilityProcessor CreateProcessor()
+ {
var services = Services();
var factory = (ParadeDbParameterBasedSqlProcessorFactory)
services.GetService()!;
- var depsField = typeof(ParadeDbParameterBasedSqlProcessorFactory)
- .GetField("_dependencies", BindingFlags.NonPublic | BindingFlags.Instance)!;
+ var depsField = typeof(ParadeDbParameterBasedSqlProcessorFactory).GetField(
+ "_dependencies",
+ BindingFlags.NonPublic | BindingFlags.Instance
+ )!;
var deps = (RelationalParameterBasedSqlProcessorDependencies)depsField.GetValue(factory)!;
#if NET8_0
@@ -58,31 +67,46 @@ private static ParadeDbSqlNullabilityProcessor CreateProcessor() {
#elif NET9_0
// EF Core 9 ctor: (bool useRelationalNulls, IReadOnlySet parametersToConstantize)
var parameters = new RelationalParameterBasedSqlProcessorParameters(
- false, new HashSet());
+ false,
+ new HashSet()
+ );
return new ParadeDbSqlNullabilityProcessor(deps, parameters);
#else
// EF Core 10 ctor: (bool useRelationalNulls, ParameterTranslationMode)
var parameters = new RelationalParameterBasedSqlProcessorParameters(
- false, ParameterTranslationMode.Constant);
+ false,
+ ParameterTranslationMode.Constant
+ );
return new ParadeDbSqlNullabilityProcessor(deps, parameters);
#endif
}
- private static SqlExpression InvokeVisitCustom(ParadeDbSqlNullabilityProcessor processor,
- SqlExpression expr, bool allowOptimizedExpansion = false) {
+ private static SqlExpression InvokeVisitCustom(
+ ParadeDbSqlNullabilityProcessor processor,
+ SqlExpression expr,
+ bool allowOptimizedExpansion = false
+ )
+ {
var method = typeof(ParadeDbSqlNullabilityProcessor).GetMethod(
"VisitCustomSqlExpression",
- BindingFlags.NonPublic | BindingFlags.Instance)!;
+ BindingFlags.NonPublic | BindingFlags.Instance
+ )!;
var args = new object?[] { expr, allowOptimizedExpansion, null };
return (SqlExpression)method.Invoke(processor, args)!;
}
[Fact]
- public void NullabilityProcessor_ModifiedQueryExpression_keeps_instance_when_inner_unchanged() {
+ public void NullabilityProcessor_ModifiedQueryExpression_keeps_instance_when_inner_unchanged()
+ {
var processor = CreateProcessor();
var sql = Services().GetService()!;
var inner = sql.Constant("hello");
- var expr = new ParadeDbModifiedQueryExpression(inner, "::pdb.boost(2)", inner.Type, inner.TypeMapping);
+ var expr = new ParadeDbModifiedQueryExpression(
+ inner,
+ "::pdb.boost(2)",
+ inner.Type,
+ inner.TypeMapping
+ );
var result = InvokeVisitCustom(processor, expr);
@@ -91,13 +115,19 @@ public void NullabilityProcessor_ModifiedQueryExpression_keeps_instance_when_inn
}
[Fact]
- public void NullabilityProcessor_NamedArgFunctionExpression_keeps_instance_when_unchanged() {
+ public void NullabilityProcessor_NamedArgFunctionExpression_keeps_instance_when_unchanged()
+ {
var processor = CreateProcessor();
var sql = Services().GetService()!;
var positional = sql.Constant("col");
var namedValue = sql.Constant("");
- var expr = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [positional], [("start_tag", namedValue)], typeof(string), positional.TypeMapping);
+ var expr = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [positional],
+ [("start_tag", namedValue)],
+ typeof(string),
+ positional.TypeMapping
+ );
var result = InvokeVisitCustom(processor, expr);
@@ -110,13 +140,19 @@ public void NullabilityProcessor_NamedArgFunctionExpression_keeps_instance_when_
/// "positional changed → rebuild" branch on .
///
[Fact]
- public void NullabilityProcessor_NamedArgFunctionExpression_rebuilds_when_positional_simplified() {
+ public void NullabilityProcessor_NamedArgFunctionExpression_rebuilds_when_positional_simplified()
+ {
var processor = CreateProcessor();
var sql = Services().GetService()!;
var positional = sql.IsNotNull(sql.Constant("hi"));
var namedValue = sql.Constant("");
- var expr = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [positional], [("start_tag", namedValue)], typeof(string), namedValue.TypeMapping);
+ var expr = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [positional],
+ [("start_tag", namedValue)],
+ typeof(string),
+ namedValue.TypeMapping
+ );
var result = InvokeVisitCustom(processor, expr);
@@ -126,13 +162,19 @@ public void NullabilityProcessor_NamedArgFunctionExpression_rebuilds_when_positi
}
[Fact]
- public void NullabilityProcessor_NamedArgFunctionExpression_rebuilds_when_named_simplified() {
+ public void NullabilityProcessor_NamedArgFunctionExpression_rebuilds_when_named_simplified()
+ {
var processor = CreateProcessor();
var sql = Services().GetService()!;
var positional = sql.Constant("col");
var namedValue = sql.IsNotNull(sql.Constant("hi"));
- var expr = new ParadeDbNamedArgFunctionExpression("pdb.snippet",
- [positional], [("flag", namedValue)], typeof(string), positional.TypeMapping);
+ var expr = new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
+ [positional],
+ [("flag", namedValue)],
+ typeof(string),
+ positional.TypeMapping
+ );
var result = InvokeVisitCustom(processor, expr);
@@ -146,11 +188,17 @@ public void NullabilityProcessor_NamedArgFunctionExpression_rebuilds_when_named_
/// around the new instance — otherwise downstream visitors keep seeing the stale child.
///
[Fact]
- public void NullabilityProcessor_ModifiedQueryExpression_rebuilds_when_inner_simplified() {
+ public void NullabilityProcessor_ModifiedQueryExpression_rebuilds_when_inner_simplified()
+ {
var processor = CreateProcessor();
var sql = Services().GetService()!;
var inner = sql.IsNotNull(sql.Constant("hi"));
- var expr = new ParadeDbModifiedQueryExpression(inner, "::pdb.boost(2)", inner.Type, inner.TypeMapping);
+ var expr = new ParadeDbModifiedQueryExpression(
+ inner,
+ "::pdb.boost(2)",
+ inner.Type,
+ inner.TypeMapping
+ );
var result = InvokeVisitCustom(processor, expr);
@@ -159,4 +207,3 @@ public void NullabilityProcessor_ModifiedQueryExpression_rebuilds_when_inner_sim
Assert.Equal("::pdb.boost(2)", rebuilt.ModifierSuffix);
}
}
-
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.Tests/ParadeDbDbContextOptionsExtensionTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.Tests/ParadeDbDbContextOptionsExtensionTests.cs
index 122e8f4..fdfd215 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.Tests/ParadeDbDbContextOptionsExtensionTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.Tests/ParadeDbDbContextOptionsExtensionTests.cs
@@ -8,8 +8,10 @@ namespace Equibles.ParadeDB.EntityFrameworkCore.Tests;
/// invoked by EF Core when describing the active provider/plugins; the LINQ tests don't
/// touch all of them so we exercise them explicitly here.
///
-public class ParadeDbDbContextOptionsExtensionTests {
- private static DbContextOptionsExtensionInfo Info() {
+public class ParadeDbDbContextOptionsExtensionTests
+{
+ private static DbContextOptionsExtensionInfo Info()
+ {
using var ctx = new TestDbContext();
var ext = ctx.GetService()
.FindExtension()!;
@@ -17,29 +19,34 @@ private static DbContextOptionsExtensionInfo Info() {
}
[Fact]
- public void IsDatabaseProvider_is_false() {
+ public void IsDatabaseProvider_is_false()
+ {
Assert.False(Info().IsDatabaseProvider);
}
[Fact]
- public void LogFragment_mentions_paradedb() {
+ public void LogFragment_mentions_paradedb()
+ {
Assert.Contains("ParadeDB", Info().LogFragment, StringComparison.OrdinalIgnoreCase);
}
[Fact]
- public void GetServiceProviderHashCode_is_stable() {
+ public void GetServiceProviderHashCode_is_stable()
+ {
var info = Info();
Assert.Equal(info.GetServiceProviderHashCode(), info.GetServiceProviderHashCode());
}
[Fact]
- public void ShouldUseSameServiceProvider_is_true_for_same_extension_info_type() {
+ public void ShouldUseSameServiceProvider_is_true_for_same_extension_info_type()
+ {
var info = Info();
Assert.True(info.ShouldUseSameServiceProvider(Info()));
}
[Fact]
- public void PopulateDebugInfo_writes_paradedb_marker() {
+ public void PopulateDebugInfo_writes_paradedb_marker()
+ {
var debug = new Dictionary();
Info().PopulateDebugInfo(debug);
Assert.Equal("1", debug["ParadeDB:BM25"]);
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.Tests/ParadeDbFunctionsTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.Tests/ParadeDbFunctionsTests.cs
index cc4f6c8..8e7aeaf 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.Tests/ParadeDbFunctionsTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.Tests/ParadeDbFunctionsTests.cs
@@ -8,146 +8,183 @@ namespace Equibles.ParadeDB.EntityFrameworkCore.Tests;
/// that the translator never touches (LINQ provider replaces the call before
/// the body runs).
///
-public class ParadeDbFunctionsTests {
+public class ParadeDbFunctionsTests
+{
private readonly DbFunctions _ef = EF.Functions;
[Fact]
- public void Matches_throws_when_called_directly() {
+ public void Matches_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.Matches("col", "q"));
}
[Fact]
- public void MatchesAll_throws_when_called_directly() {
+ public void MatchesAll_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.MatchesAll("col", "q"));
}
[Fact]
- public void MatchesPhrase_throws_when_called_directly() {
+ public void MatchesPhrase_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.MatchesPhrase("col", "q"));
}
[Fact]
- public void MatchesPhrase_with_slop_throws_when_called_directly() {
+ public void MatchesPhrase_with_slop_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.MatchesPhrase("col", "q", 2));
}
[Fact]
- public void MatchesTerm_throws_when_called_directly() {
+ public void MatchesTerm_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.MatchesTerm("col", "q"));
}
[Fact]
- public void MatchesTermSet_throws_when_called_directly() {
+ public void MatchesTermSet_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.MatchesTermSet("col", "a", "b"));
}
[Fact]
- public void MatchesFuzzy_throws_when_called_directly() {
+ public void MatchesFuzzy_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.MatchesFuzzy("col", "q", 2));
}
[Fact]
- public void MatchesFuzzy_full_throws_when_called_directly() {
- Assert.Throws(() => _ef.MatchesFuzzy("col", "q", 2, true, false));
+ public void MatchesFuzzy_full_throws_when_called_directly()
+ {
+ Assert.Throws(() =>
+ _ef.MatchesFuzzy("col", "q", 2, true, false)
+ );
}
[Fact]
- public void MatchesAllFuzzy_throws_when_called_directly() {
+ public void MatchesAllFuzzy_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.MatchesAllFuzzy("col", "q", 2));
}
[Fact]
- public void MatchesAllFuzzy_full_throws_when_called_directly() {
- Assert.Throws(() => _ef.MatchesAllFuzzy("col", "q", 1, false, true));
+ public void MatchesAllFuzzy_full_throws_when_called_directly()
+ {
+ Assert.Throws(() =>
+ _ef.MatchesAllFuzzy("col", "q", 1, false, true)
+ );
}
[Fact]
- public void MatchesTermFuzzy_throws_when_called_directly() {
+ public void MatchesTermFuzzy_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.MatchesTermFuzzy("col", "q", 1));
}
[Fact]
- public void MatchesTermFuzzy_full_throws_when_called_directly() {
- Assert.Throws(() => _ef.MatchesTermFuzzy("col", "q", 2, true, true));
+ public void MatchesTermFuzzy_full_throws_when_called_directly()
+ {
+ Assert.Throws(() =>
+ _ef.MatchesTermFuzzy("col", "q", 2, true, true)
+ );
}
[Fact]
- public void MatchesBoosted_throws_when_called_directly() {
+ public void MatchesBoosted_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.MatchesBoosted("col", "q", 2.0));
}
[Fact]
- public void MatchesAllBoosted_throws_when_called_directly() {
+ public void MatchesAllBoosted_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.MatchesAllBoosted("col", "q", 1.5));
}
[Fact]
- public void MatchesFuzzyBoosted_throws_when_called_directly() {
+ public void MatchesFuzzyBoosted_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.MatchesFuzzyBoosted("col", "q", 2, 2.0));
}
[Fact]
- public void MatchesAllFuzzyBoosted_throws_when_called_directly() {
- Assert.Throws(() => _ef.MatchesAllFuzzyBoosted("col", "q", 1, 3.0));
+ public void MatchesAllFuzzyBoosted_throws_when_called_directly()
+ {
+ Assert.Throws(() =>
+ _ef.MatchesAllFuzzyBoosted("col", "q", 1, 3.0)
+ );
}
[Fact]
- public void Score_throws_when_called_directly() {
+ public void Score_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.Score(new object()));
}
[Fact]
- public void Snippet_throws_when_called_directly() {
+ public void Snippet_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.Snippet("col"));
}
[Fact]
- public void Snippet_params_throws_when_called_directly() {
+ public void Snippet_params_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.Snippet("col", "", "", 100));
}
[Fact]
- public void Snippets_throws_when_called_directly() {
+ public void Snippets_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.Snippets("col", 15, 5, 0));
}
[Fact]
- public void Parse_throws_when_called_directly() {
+ public void Parse_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.Parse(new object(), "q"));
}
[Fact]
- public void Parse_with_options_throws_when_called_directly() {
+ public void Parse_with_options_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.Parse(new object(), "q", true, true));
}
[Fact]
- public void Regex_throws_when_called_directly() {
+ public void Regex_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.Regex("col", "neuro.*"));
}
[Fact]
- public void PhrasePrefix_throws_when_called_directly() {
+ public void PhrasePrefix_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.PhrasePrefix("col", "a", "b"));
}
[Fact]
- public void PhrasePrefix_max_expansions_throws_when_called_directly() {
+ public void PhrasePrefix_max_expansions_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.PhrasePrefix("col", 10, "a", "b"));
}
[Fact]
- public void MoreLikeThis_throws_when_called_directly() {
+ public void MoreLikeThis_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.MoreLikeThis(new object(), 1));
}
[Fact]
- public void MoreLikeThis_with_fields_throws_when_called_directly() {
+ public void MoreLikeThis_with_fields_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.MoreLikeThis(new object(), 1, "f"));
}
[Fact]
- public void JsonSearch_throws_when_called_directly() {
+ public void JsonSearch_throws_when_called_directly()
+ {
Assert.Throws(() => _ef.JsonSearch(new object(), "{}"));
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.Tests/ParadeDbJsonQueryTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.Tests/ParadeDbJsonQueryTests.cs
index 58f2735..691fb67 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.Tests/ParadeDbJsonQueryTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.Tests/ParadeDbJsonQueryTests.cs
@@ -5,20 +5,26 @@ namespace Equibles.ParadeDB.EntityFrameworkCore.Tests;
///
/// Pure CLR tests validating JSON output from factory methods.
///
-public class ParadeDbJsonQueryTests {
+public class ParadeDbJsonQueryTests
+{
private static JsonElement Parse(string json) => JsonDocument.Parse(json).RootElement;
// ── Parse ────────────────────────────────────────────────────────
[Fact]
- public void Parse_creates_correct_json() {
+ public void Parse_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.Parse("revenue growth").ToJson();
var doc = Parse(json);
- Assert.Equal("revenue growth", doc.GetProperty("parse").GetProperty("query_string").GetString());
+ Assert.Equal(
+ "revenue growth",
+ doc.GetProperty("parse").GetProperty("query_string").GetString()
+ );
}
[Fact]
- public void Parse_with_options_creates_correct_json() {
+ public void Parse_with_options_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.Parse("revenue growth", true, true).ToJson();
var doc = Parse(json);
var parse = doc.GetProperty("parse");
@@ -30,7 +36,8 @@ public void Parse_with_options_creates_correct_json() {
// ── Term ─────────────────────────────────────────────────────────
[Fact]
- public void Term_with_string_value_creates_correct_json() {
+ public void Term_with_string_value_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.Term("DocumentId", "abc-123").ToJson();
var doc = Parse(json);
var term = doc.GetProperty("term");
@@ -39,7 +46,8 @@ public void Term_with_string_value_creates_correct_json() {
}
[Fact]
- public void Term_with_int_value_creates_correct_json() {
+ public void Term_with_int_value_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.Term("DocumentType", 10).ToJson();
var doc = Parse(json);
var term = doc.GetProperty("term");
@@ -48,7 +56,8 @@ public void Term_with_int_value_creates_correct_json() {
}
[Fact]
- public void Term_with_guid_value_creates_correct_json() {
+ public void Term_with_guid_value_creates_correct_json()
+ {
var guid = Guid.Parse("1d56ce60-1234-5678-9abc-def012345678");
var json = ParadeDbJsonQuery.Term("DocumentId", guid).ToJson();
var doc = Parse(json);
@@ -58,7 +67,8 @@ public void Term_with_guid_value_creates_correct_json() {
// ── Term Set ─────────────────────────────────────────────────────
[Fact]
- public void TermSet_creates_correct_json() {
+ public void TermSet_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.TermSet("Tags", "gpu", "tpu").ToJson();
var doc = Parse(json);
var termSet = doc.GetProperty("term_set");
@@ -72,7 +82,8 @@ public void TermSet_creates_correct_json() {
// ── Match ────────────────────────────────────────────────────────
[Fact]
- public void Match_with_field_and_options_creates_correct_json() {
+ public void Match_with_field_and_options_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.Match("shoes", "Content", 2, true).ToJson();
var doc = Parse(json);
var match = doc.GetProperty("match");
@@ -85,7 +96,8 @@ public void Match_with_field_and_options_creates_correct_json() {
// ── Fuzzy Term ───────────────────────────────────────────────────
[Fact]
- public void FuzzyTerm_creates_correct_json() {
+ public void FuzzyTerm_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.FuzzyTerm("Content", "machin", 2).ToJson();
var doc = Parse(json);
var ft = doc.GetProperty("fuzzy_term");
@@ -95,7 +107,8 @@ public void FuzzyTerm_creates_correct_json() {
}
[Fact]
- public void FuzzyTerm_with_options_creates_correct_json() {
+ public void FuzzyTerm_with_options_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.FuzzyTerm("Content", "machin", 2, true, true).ToJson();
var doc = Parse(json);
var ft = doc.GetProperty("fuzzy_term");
@@ -106,7 +119,8 @@ public void FuzzyTerm_with_options_creates_correct_json() {
// ── Phrase ────────────────────────────────────────────────────────
[Fact]
- public void Phrase_creates_correct_json() {
+ public void Phrase_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.Phrase("Content", "neural", "networks").ToJson();
var doc = Parse(json);
var phrase = doc.GetProperty("phrase");
@@ -117,7 +131,8 @@ public void Phrase_creates_correct_json() {
}
[Fact]
- public void Phrase_with_slop_creates_correct_json() {
+ public void Phrase_with_slop_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.Phrase("Content", 2, "neural", "networks").ToJson();
var doc = Parse(json);
var phrase = doc.GetProperty("phrase");
@@ -127,7 +142,8 @@ public void Phrase_with_slop_creates_correct_json() {
// ── Phrase Prefix ────────────────────────────────────────────────
[Fact]
- public void PhrasePrefix_creates_correct_json() {
+ public void PhrasePrefix_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.PhrasePrefix("Content", "running", "sh").ToJson();
var doc = Parse(json);
var pp = doc.GetProperty("phrase_prefix");
@@ -138,7 +154,8 @@ public void PhrasePrefix_creates_correct_json() {
// ── Regex ────────────────────────────────────────────────────────
[Fact]
- public void Regex_creates_correct_json() {
+ public void Regex_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.Regex("Content", "neuro.*").ToJson();
var doc = Parse(json);
var regex = doc.GetProperty("regex");
@@ -149,8 +166,11 @@ public void Regex_creates_correct_json() {
// ── Range ────────────────────────────────────────────────────────
[Fact]
- public void Range_with_both_bounds_creates_correct_json() {
- var json = ParadeDbJsonQuery.Range("Price", 10, 100, lowerInclusive: true, upperInclusive: false).ToJson();
+ public void Range_with_both_bounds_creates_correct_json()
+ {
+ var json = ParadeDbJsonQuery
+ .Range("Price", 10, 100, lowerInclusive: true, upperInclusive: false)
+ .ToJson();
var doc = Parse(json);
var range = doc.GetProperty("range");
Assert.Equal("Price", range.GetProperty("field").GetString());
@@ -159,7 +179,8 @@ public void Range_with_both_bounds_creates_correct_json() {
}
[Fact]
- public void Range_with_lower_bound_only_creates_correct_json() {
+ public void Range_with_lower_bound_only_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.Range("Price", 10, null).ToJson();
var doc = Parse(json);
var range = doc.GetProperty("range");
@@ -168,15 +189,19 @@ public void Range_with_lower_bound_only_creates_correct_json() {
}
[Fact]
- public void Range_with_lower_exclusive_uses_excluded_key() {
- var json = ParadeDbJsonQuery.Range("Price", 10, 100, lowerInclusive: false, upperInclusive: true).ToJson();
+ public void Range_with_lower_exclusive_uses_excluded_key()
+ {
+ var json = ParadeDbJsonQuery
+ .Range("Price", 10, 100, lowerInclusive: false, upperInclusive: true)
+ .ToJson();
var range = Parse(json).GetProperty("range");
Assert.Equal(10, range.GetProperty("lower_bound").GetProperty("excluded").GetInt32());
Assert.Equal(100, range.GetProperty("upper_bound").GetProperty("included").GetInt32());
}
[Fact]
- public void Range_with_upper_bound_only_creates_correct_json() {
+ public void Range_with_upper_bound_only_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.Range("Price", null, 100).ToJson();
var range = Parse(json).GetProperty("range");
Assert.Equal(JsonValueKind.Null, range.GetProperty("lower_bound").ValueKind);
@@ -184,19 +209,24 @@ public void Range_with_upper_bound_only_creates_correct_json() {
}
[Fact]
- public void Range_with_is_datetime_creates_correct_json() {
+ public void Range_with_is_datetime_creates_correct_json()
+ {
var dt = new DateTime(2025, 1, 15, 0, 0, 0, DateTimeKind.Utc);
var json = ParadeDbJsonQuery.Range("ReportingDate", dt, null, isDatetime: true).ToJson();
var doc = Parse(json);
var range = doc.GetProperty("range");
Assert.True(range.GetProperty("is_datetime").GetBoolean());
- Assert.Equal("2025-01-15T00:00:00Z", range.GetProperty("lower_bound").GetProperty("included").GetString());
+ Assert.Equal(
+ "2025-01-15T00:00:00Z",
+ range.GetProperty("lower_bound").GetProperty("included").GetString()
+ );
}
// ── Boost ────────────────────────────────────────────────────────
[Fact]
- public void Boost_wraps_inner_query_correctly() {
+ public void Boost_wraps_inner_query_correctly()
+ {
var inner = ParadeDbJsonQuery.Parse("shoes");
var json = ParadeDbJsonQuery.Boost(inner, 2.5).ToJson();
var doc = Parse(json);
@@ -208,7 +238,8 @@ public void Boost_wraps_inner_query_correctly() {
// ── Const Score ──────────────────────────────────────────────────
[Fact]
- public void ConstScore_wraps_inner_query_correctly() {
+ public void ConstScore_wraps_inner_query_correctly()
+ {
var inner = ParadeDbJsonQuery.Parse("shoes");
var json = ParadeDbJsonQuery.ConstScore(inner, 1.0).ToJson();
var doc = Parse(json);
@@ -220,7 +251,8 @@ public void ConstScore_wraps_inner_query_correctly() {
// ── Exists ───────────────────────────────────────────────────────
[Fact]
- public void Exists_creates_correct_json() {
+ public void Exists_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.Exists("Content").ToJson();
var doc = Parse(json);
Assert.Equal("Content", doc.GetProperty("exists").GetProperty("field").GetString());
@@ -229,7 +261,8 @@ public void Exists_creates_correct_json() {
// ── All ──────────────────────────────────────────────────────────
[Fact]
- public void All_creates_correct_json() {
+ public void All_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.All().ToJson();
var doc = Parse(json);
Assert.Equal(JsonValueKind.Null, doc.GetProperty("all").ValueKind);
@@ -238,10 +271,14 @@ public void All_creates_correct_json() {
// ── Disjunction Max ──────────────────────────────────────────────
[Fact]
- public void DisjunctionMax_creates_correct_json() {
- var json = ParadeDbJsonQuery.DisjunctionMax(
- ParadeDbJsonQuery.Parse("shoes"),
- ParadeDbJsonQuery.Match("boots", "Content")).ToJson();
+ public void DisjunctionMax_creates_correct_json()
+ {
+ var json = ParadeDbJsonQuery
+ .DisjunctionMax(
+ ParadeDbJsonQuery.Parse("shoes"),
+ ParadeDbJsonQuery.Match("boots", "Content")
+ )
+ .ToJson();
var doc = Parse(json);
var dm = doc.GetProperty("disjunction_max");
Assert.Equal(2, dm.GetProperty("disjuncts").GetArrayLength());
@@ -250,7 +287,8 @@ public void DisjunctionMax_creates_correct_json() {
// ── More Like This ───────────────────────────────────────────────
[Fact]
- public void MoreLikeThis_creates_correct_json() {
+ public void MoreLikeThis_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.MoreLikeThis(42).ToJson();
var doc = Parse(json);
Assert.Equal(42, doc.GetProperty("more_like_this").GetProperty("key_value").GetInt32());
@@ -259,9 +297,11 @@ public void MoreLikeThis_creates_correct_json() {
// ── Boolean ──────────────────────────────────────────────────────
[Fact]
- public void Boolean_must_creates_correct_json() {
- var json = ParadeDbJsonQuery.Boolean(b => b
- .Must(ParadeDbJsonQuery.Parse("shoes"))).ToJson();
+ public void Boolean_must_creates_correct_json()
+ {
+ var json = ParadeDbJsonQuery
+ .Boolean(b => b.Must(ParadeDbJsonQuery.Parse("shoes")))
+ .ToJson();
var doc = Parse(json);
var boolean = doc.GetProperty("boolean");
Assert.Equal(1, boolean.GetProperty("must").GetArrayLength());
@@ -270,27 +310,37 @@ public void Boolean_must_creates_correct_json() {
}
[Fact]
- public void Boolean_should_creates_correct_json() {
- var json = ParadeDbJsonQuery.Boolean(b => b
- .Should(ParadeDbJsonQuery.Parse("shoes"), ParadeDbJsonQuery.Parse("boots"))).ToJson();
+ public void Boolean_should_creates_correct_json()
+ {
+ var json = ParadeDbJsonQuery
+ .Boolean(b =>
+ b.Should(ParadeDbJsonQuery.Parse("shoes"), ParadeDbJsonQuery.Parse("boots"))
+ )
+ .ToJson();
var doc = Parse(json);
Assert.Equal(2, doc.GetProperty("boolean").GetProperty("should").GetArrayLength());
}
[Fact]
- public void Boolean_must_not_creates_correct_json() {
- var json = ParadeDbJsonQuery.Boolean(b => b
- .MustNot(ParadeDbJsonQuery.Term("Status", "archived"))).ToJson();
+ public void Boolean_must_not_creates_correct_json()
+ {
+ var json = ParadeDbJsonQuery
+ .Boolean(b => b.MustNot(ParadeDbJsonQuery.Term("Status", "archived")))
+ .ToJson();
var doc = Parse(json);
Assert.Equal(1, doc.GetProperty("boolean").GetProperty("must_not").GetArrayLength());
}
[Fact]
- public void Boolean_combined_creates_correct_json() {
- var json = ParadeDbJsonQuery.Boolean(b => b
- .Must(ParadeDbJsonQuery.Parse("revenue growth"))
- .Should(ParadeDbJsonQuery.Term("DocumentType", 10))
- .MustNot(ParadeDbJsonQuery.Term("Status", "archived"))).ToJson();
+ public void Boolean_combined_creates_correct_json()
+ {
+ var json = ParadeDbJsonQuery
+ .Boolean(b =>
+ b.Must(ParadeDbJsonQuery.Parse("revenue growth"))
+ .Should(ParadeDbJsonQuery.Term("DocumentType", 10))
+ .MustNot(ParadeDbJsonQuery.Term("Status", "archived"))
+ )
+ .ToJson();
var doc = Parse(json);
var boolean = doc.GetProperty("boolean");
Assert.Equal(1, boolean.GetProperty("must").GetArrayLength());
@@ -299,14 +349,21 @@ public void Boolean_combined_creates_correct_json() {
}
[Fact]
- public void Nested_boolean_creates_correct_json() {
- var json = ParadeDbJsonQuery.Boolean(b => b
- .Must(
- ParadeDbJsonQuery.Parse("revenue growth"),
- ParadeDbJsonQuery.Boolean(inner => inner
- .Should(
- ParadeDbJsonQuery.Term("DocumentType", 10),
- ParadeDbJsonQuery.Term("DocumentType", 20))))).ToJson();
+ public void Nested_boolean_creates_correct_json()
+ {
+ var json = ParadeDbJsonQuery
+ .Boolean(b =>
+ b.Must(
+ ParadeDbJsonQuery.Parse("revenue growth"),
+ ParadeDbJsonQuery.Boolean(inner =>
+ inner.Should(
+ ParadeDbJsonQuery.Term("DocumentType", 10),
+ ParadeDbJsonQuery.Term("DocumentType", 20)
+ )
+ )
+ )
+ )
+ .ToJson();
var doc = Parse(json);
var must = doc.GetProperty("boolean").GetProperty("must");
Assert.Equal(2, must.GetArrayLength());
@@ -317,7 +374,8 @@ public void Nested_boolean_creates_correct_json() {
// ── ToString / ToJson parity ─────────────────────────────────────
[Fact]
- public void ToString_returns_same_string_as_ToJson() {
+ public void ToString_returns_same_string_as_ToJson()
+ {
var query = ParadeDbJsonQuery.Parse("shoes");
Assert.Equal(query.ToJson(), query.ToString());
}
@@ -325,7 +383,8 @@ public void ToString_returns_same_string_as_ToJson() {
// ── Match (field, value) — overload without options ──────────────
[Fact]
- public void Match_with_field_only_creates_correct_json() {
+ public void Match_with_field_only_creates_correct_json()
+ {
var json = ParadeDbJsonQuery.Match("shoes", "Content").ToJson();
var doc = Parse(json);
var match = doc.GetProperty("match");
@@ -338,7 +397,8 @@ public void Match_with_field_only_creates_correct_json() {
// ── CreateJsonValue — remaining primitive switch arms ────────────
[Fact]
- public void Term_with_long_value_serializes_as_number() {
+ public void Term_with_long_value_serializes_as_number()
+ {
var json = ParadeDbJsonQuery.Term("Big", 9_000_000_000L).ToJson();
var value = Parse(json).GetProperty("term").GetProperty("value");
Assert.Equal(JsonValueKind.Number, value.ValueKind);
@@ -346,7 +406,8 @@ public void Term_with_long_value_serializes_as_number() {
}
[Fact]
- public void Term_with_double_value_serializes_as_number() {
+ public void Term_with_double_value_serializes_as_number()
+ {
var json = ParadeDbJsonQuery.Term("Score", 2.5d).ToJson();
var value = Parse(json).GetProperty("term").GetProperty("value");
Assert.Equal(JsonValueKind.Number, value.ValueKind);
@@ -354,7 +415,8 @@ public void Term_with_double_value_serializes_as_number() {
}
[Fact]
- public void Term_with_float_value_serializes_as_number() {
+ public void Term_with_float_value_serializes_as_number()
+ {
var json = ParadeDbJsonQuery.Term("Score", 1.25f).ToJson();
var value = Parse(json).GetProperty("term").GetProperty("value");
Assert.Equal(JsonValueKind.Number, value.ValueKind);
@@ -362,14 +424,16 @@ public void Term_with_float_value_serializes_as_number() {
}
[Fact]
- public void Term_with_bool_value_serializes_as_boolean() {
+ public void Term_with_bool_value_serializes_as_boolean()
+ {
var json = ParadeDbJsonQuery.Term("InStock", true).ToJson();
var value = Parse(json).GetProperty("term").GetProperty("value");
Assert.Equal(JsonValueKind.True, value.ValueKind);
}
[Fact]
- public void Term_with_enum_value_serializes_as_underlying_int() {
+ public void Term_with_enum_value_serializes_as_underlying_int()
+ {
var json = ParadeDbJsonQuery.Term("Record", Bm25Record.Position).ToJson();
var value = Parse(json).GetProperty("term").GetProperty("value");
Assert.Equal(JsonValueKind.Number, value.ValueKind);
@@ -377,7 +441,8 @@ public void Term_with_enum_value_serializes_as_underlying_int() {
}
[Fact]
- public void Term_with_unsupported_type_falls_back_to_ToString() {
+ public void Term_with_unsupported_type_falls_back_to_ToString()
+ {
// TimeSpan has a culture-invariant ToString — keeps the test stable across locales.
var span = new TimeSpan(1, 2, 3);
var json = ParadeDbJsonQuery.Term("Duration", span).ToJson();
@@ -387,7 +452,8 @@ public void Term_with_unsupported_type_falls_back_to_ToString() {
}
[Fact]
- public void Term_with_non_utc_datetime_uses_round_trip_format() {
+ public void Term_with_non_utc_datetime_uses_round_trip_format()
+ {
var local = new DateTime(2025, 1, 15, 12, 30, 0, DateTimeKind.Unspecified);
var json = ParadeDbJsonQuery.Term("PublishedAt", local).ToJson();
var value = Parse(json).GetProperty("term").GetProperty("value");
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.Tests/QueryTranslationTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.Tests/QueryTranslationTests.cs
index d652573..521b618 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.Tests/QueryTranslationTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.Tests/QueryTranslationTests.cs
@@ -2,7 +2,8 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.Tests;
-public class QueryTranslationTests : IDisposable {
+public class QueryTranslationTests : IDisposable
+{
private readonly TestDbContext _db = new();
public void Dispose() => _db.Dispose();
@@ -12,13 +13,15 @@ public class QueryTranslationTests : IDisposable {
// ── Basic Search ──────────────────────────────────────────────────
[Fact]
- public void Matches_generates_or_operator() {
+ public void Matches_generates_or_operator()
+ {
var sql = Sql(_db.Articles.Where(a => EF.Functions.Matches(a.Content, "shoes")));
Assert.Contains("|||", sql);
}
[Fact]
- public void MatchesAll_generates_and_operator() {
+ public void MatchesAll_generates_and_operator()
+ {
var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesAll(a.Content, "shoes")));
Assert.Contains("&&&", sql);
}
@@ -26,14 +29,20 @@ public void MatchesAll_generates_and_operator() {
// ── Phrase Search ─────────────────────────────────────────────────
[Fact]
- public void MatchesPhrase_generates_phrase_operator() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesPhrase(a.Content, "neural networks")));
+ public void MatchesPhrase_generates_phrase_operator()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.MatchesPhrase(a.Content, "neural networks"))
+ );
Assert.Contains("###", sql);
}
[Fact]
- public void MatchesPhrase_with_slop_generates_slop_modifier() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesPhrase(a.Content, "neural networks", 2)));
+ public void MatchesPhrase_with_slop_generates_slop_modifier()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.MatchesPhrase(a.Content, "neural networks", 2))
+ );
Assert.Contains("###", sql);
Assert.Contains("::pdb.slop(2)", sql);
}
@@ -41,14 +50,18 @@ public void MatchesPhrase_with_slop_generates_slop_modifier() {
// ── Term Search ───────────────────────────────────────────────────
[Fact]
- public void MatchesTerm_generates_term_operator() {
+ public void MatchesTerm_generates_term_operator()
+ {
var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesTerm(a.Content, "gpu")));
Assert.Contains("===", sql);
}
[Fact]
- public void MatchesTermSet_generates_term_operator_with_array() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesTermSet(a.Content, "gpu", "tpu")));
+ public void MatchesTermSet_generates_term_operator_with_array()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.MatchesTermSet(a.Content, "gpu", "tpu"))
+ );
Assert.Contains("===", sql);
Assert.Contains("ARRAY", sql);
}
@@ -56,15 +69,19 @@ public void MatchesTermSet_generates_term_operator_with_array() {
// ── Fuzzy Search ──────────────────────────────────────────────────
[Fact]
- public void MatchesFuzzy_generates_fuzzy_modifier() {
+ public void MatchesFuzzy_generates_fuzzy_modifier()
+ {
var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesFuzzy(a.Content, "machin", 2)));
Assert.Contains("|||", sql);
Assert.Contains("::pdb.fuzzy(2)", sql);
}
[Fact]
- public void MatchesFuzzy_full_generates_pdb_match_with_options() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesFuzzy(a.Content, "machin", 2, true, false)));
+ public void MatchesFuzzy_full_generates_pdb_match_with_options()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.MatchesFuzzy(a.Content, "machin", 2, true, false))
+ );
Assert.Contains("@@@", sql);
Assert.Contains("pdb.match(", sql);
Assert.Contains("distance =>", sql);
@@ -74,30 +91,46 @@ public void MatchesFuzzy_full_generates_pdb_match_with_options() {
}
[Fact]
- public void MatchesAllFuzzy_generates_and_with_fuzzy() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesAllFuzzy(a.Content, "machin", 2)));
+ public void MatchesAllFuzzy_generates_and_with_fuzzy()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.MatchesAllFuzzy(a.Content, "machin", 2))
+ );
Assert.Contains("&&&", sql);
Assert.Contains("::pdb.fuzzy(2)", sql);
}
[Fact]
- public void MatchesAllFuzzy_full_generates_pdb_match_with_conjunction() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesAllFuzzy(a.Content, "machin", 1, false, true)));
+ public void MatchesAllFuzzy_full_generates_pdb_match_with_conjunction()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a =>
+ EF.Functions.MatchesAllFuzzy(a.Content, "machin", 1, false, true)
+ )
+ );
Assert.Contains("@@@", sql);
Assert.Contains("pdb.match(", sql);
Assert.Contains("conjunction_mode =>", sql);
}
[Fact]
- public void MatchesTermFuzzy_generates_term_with_fuzzy() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesTermFuzzy(a.Content, "machin", 1)));
+ public void MatchesTermFuzzy_generates_term_with_fuzzy()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.MatchesTermFuzzy(a.Content, "machin", 1))
+ );
Assert.Contains("===", sql);
Assert.Contains("::pdb.fuzzy(1)", sql);
}
[Fact]
- public void MatchesTermFuzzy_full_generates_pdb_fuzzy_term_function() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesTermFuzzy(a.Content, "machin", 2, true, true)));
+ public void MatchesTermFuzzy_full_generates_pdb_fuzzy_term_function()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a =>
+ EF.Functions.MatchesTermFuzzy(a.Content, "machin", 2, true, true)
+ )
+ );
Assert.Contains("@@@", sql);
Assert.Contains("pdb.fuzzy_term(", sql);
}
@@ -105,15 +138,21 @@ public void MatchesTermFuzzy_full_generates_pdb_fuzzy_term_function() {
// ── Boost ─────────────────────────────────────────────────────────
[Fact]
- public void MatchesBoosted_generates_boost_modifier() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesBoosted(a.Content, "shoes", 2.0)));
+ public void MatchesBoosted_generates_boost_modifier()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.MatchesBoosted(a.Content, "shoes", 2.0))
+ );
Assert.Contains("|||", sql);
Assert.Contains("::pdb.boost(2)", sql);
}
[Fact]
- public void MatchesAllBoosted_generates_and_with_boost() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesAllBoosted(a.Content, "shoes", 1.5)));
+ public void MatchesAllBoosted_generates_and_with_boost()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.MatchesAllBoosted(a.Content, "shoes", 1.5))
+ );
Assert.Contains("&&&", sql);
Assert.Contains("::pdb.boost(1.5)", sql);
}
@@ -121,15 +160,21 @@ public void MatchesAllBoosted_generates_and_with_boost() {
// ── Fuzzy + Boost Combined ────────────────────────────────────────
[Fact]
- public void MatchesFuzzyBoosted_generates_fuzzy_and_boost() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesFuzzyBoosted(a.Content, "shoes", 2, 2.0)));
+ public void MatchesFuzzyBoosted_generates_fuzzy_and_boost()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.MatchesFuzzyBoosted(a.Content, "shoes", 2, 2.0))
+ );
Assert.Contains("|||", sql);
Assert.Contains("::pdb.fuzzy(2)::pdb.boost(2)", sql);
}
[Fact]
- public void MatchesAllFuzzyBoosted_generates_and_fuzzy_and_boost() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.MatchesAllFuzzyBoosted(a.Content, "shoes", 1, 3.0)));
+ public void MatchesAllFuzzyBoosted_generates_and_fuzzy_and_boost()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.MatchesAllFuzzyBoosted(a.Content, "shoes", 1, 3.0))
+ );
Assert.Contains("&&&", sql);
Assert.Contains("::pdb.fuzzy(1)::pdb.boost(3)", sql);
}
@@ -137,28 +182,34 @@ public void MatchesAllFuzzyBoosted_generates_and_fuzzy_and_boost() {
// ── BM25 Scoring ──────────────────────────────────────────────────
[Fact]
- public void Score_generates_pdb_score_function() {
- var sql = Sql(_db.Articles
- .Where(a => EF.Functions.Matches(a.Content, "test"))
- .Select(a => new { Score = EF.Functions.Score(a.Id) }));
+ public void Score_generates_pdb_score_function()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.Matches(a.Content, "test"))
+ .Select(a => new { Score = EF.Functions.Score(a.Id) })
+ );
Assert.Contains("pdb.score(", sql);
}
// ── Snippets ──────────────────────────────────────────────────────
[Fact]
- public void Snippet_generates_pdb_snippet_function() {
- var sql = Sql(_db.Articles
- .Where(a => EF.Functions.Matches(a.Content, "test"))
- .Select(a => new { Snip = EF.Functions.Snippet(a.Content) }));
+ public void Snippet_generates_pdb_snippet_function()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.Matches(a.Content, "test"))
+ .Select(a => new { Snip = EF.Functions.Snippet(a.Content) })
+ );
Assert.Contains("pdb.snippet(", sql);
}
[Fact]
- public void Snippet_with_params_generates_named_args() {
- var sql = Sql(_db.Articles
- .Where(a => EF.Functions.Matches(a.Content, "test"))
- .Select(a => new { Snip = EF.Functions.Snippet(a.Content, "", "", 100) }));
+ public void Snippet_with_params_generates_named_args()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.Matches(a.Content, "test"))
+ .Select(a => new { Snip = EF.Functions.Snippet(a.Content, "", "", 100) })
+ );
Assert.Contains("pdb.snippet(", sql);
Assert.Contains("start_tag =>", sql);
Assert.Contains("end_tag =>", sql);
@@ -166,10 +217,12 @@ public void Snippet_with_params_generates_named_args() {
}
[Fact]
- public void Snippets_generates_named_args_with_quoted_limit_offset() {
- var sql = Sql(_db.Articles
- .Where(a => EF.Functions.Matches(a.Content, "test"))
- .Select(a => new { Snips = EF.Functions.Snippets(a.Content, 15, 5, 0) }));
+ public void Snippets_generates_named_args_with_quoted_limit_offset()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.Matches(a.Content, "test"))
+ .Select(a => new { Snips = EF.Functions.Snippets(a.Content, 15, 5, 0) })
+ );
Assert.Contains("pdb.snippets(", sql);
Assert.Contains("max_num_chars =>", sql);
Assert.Contains("\"limit\" =>", sql);
@@ -179,14 +232,16 @@ public void Snippets_generates_named_args_with_quoted_limit_offset() {
// ── Parse Query ───────────────────────────────────────────────────
[Fact]
- public void Parse_generates_parse_with_at_operator() {
+ public void Parse_generates_parse_with_at_operator()
+ {
var sql = Sql(_db.Articles.Where(a => EF.Functions.Parse(a.Id, "title:shoes")));
Assert.Contains("@@@", sql);
Assert.Contains("pdb.parse(", sql);
}
[Fact]
- public void Parse_with_options_generates_named_args() {
+ public void Parse_with_options_generates_named_args()
+ {
var sql = Sql(_db.Articles.Where(a => EF.Functions.Parse(a.Id, "shoes", true, true)));
Assert.Contains("@@@", sql);
Assert.Contains("pdb.parse(", sql);
@@ -197,7 +252,8 @@ public void Parse_with_options_generates_named_args() {
// ── Regex Search ──────────────────────────────────────────────────
[Fact]
- public void Regex_generates_regex_with_at_operator() {
+ public void Regex_generates_regex_with_at_operator()
+ {
var sql = Sql(_db.Articles.Where(a => EF.Functions.Regex(a.Content, "neuro.*")));
Assert.Contains("@@@", sql);
Assert.Contains("pdb.regex(", sql);
@@ -206,15 +262,21 @@ public void Regex_generates_regex_with_at_operator() {
// ── Phrase Prefix ─────────────────────────────────────────────────
[Fact]
- public void PhrasePrefix_generates_phrase_prefix_with_array() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.PhrasePrefix(a.Content, "running", "sh")));
+ public void PhrasePrefix_generates_phrase_prefix_with_array()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.PhrasePrefix(a.Content, "running", "sh"))
+ );
Assert.Contains("@@@", sql);
Assert.Contains("pdb.phrase_prefix(", sql);
}
[Fact]
- public void PhrasePrefix_with_max_expansions_generates_named_arg() {
- var sql = Sql(_db.Articles.Where(a => EF.Functions.PhrasePrefix(a.Content, 10, "running", "sh")));
+ public void PhrasePrefix_with_max_expansions_generates_named_arg()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.PhrasePrefix(a.Content, 10, "running", "sh"))
+ );
Assert.Contains("@@@", sql);
Assert.Contains("pdb.phrase_prefix(", sql);
Assert.Contains("max_expansion =>", sql);
@@ -223,14 +285,16 @@ public void PhrasePrefix_with_max_expansions_generates_named_arg() {
// ── More Like This ────────────────────────────────────────────────
[Fact]
- public void MoreLikeThis_generates_more_like_this_function() {
+ public void MoreLikeThis_generates_more_like_this_function()
+ {
var sql = Sql(_db.Articles.Where(a => EF.Functions.MoreLikeThis(a.Id, 3)));
Assert.Contains("@@@", sql);
Assert.Contains("pdb.more_like_this(", sql);
}
[Fact]
- public void MoreLikeThis_with_fields_generates_array_arg() {
+ public void MoreLikeThis_with_fields_generates_array_arg()
+ {
var sql = Sql(_db.Articles.Where(a => EF.Functions.MoreLikeThis(a.Id, 3, "description")));
Assert.Contains("@@@", sql);
Assert.Contains("pdb.more_like_this(", sql);
@@ -239,7 +303,8 @@ public void MoreLikeThis_with_fields_generates_array_arg() {
// ── JSON Query Search ──────────────────────────────────────────────
[Fact]
- public void JsonSearch_generates_at_operator_with_pdb_query_cast() {
+ public void JsonSearch_generates_at_operator_with_pdb_query_cast()
+ {
var json = ParadeDbJsonQuery.Parse("revenue growth").ToJson();
var sql = Sql(_db.Chunks.Where(c => EF.Functions.JsonSearch(c.Id, json)));
Assert.Contains("@@@", sql);
@@ -247,22 +312,27 @@ public void JsonSearch_generates_at_operator_with_pdb_query_cast() {
}
[Fact]
- public void JsonSearch_boolean_query_generates_json_with_cast() {
- var query = ParadeDbJsonQuery.Boolean(b => b
- .Must(
+ public void JsonSearch_boolean_query_generates_json_with_cast()
+ {
+ var query = ParadeDbJsonQuery.Boolean(b =>
+ b.Must(
ParadeDbJsonQuery.Parse("revenue growth"),
- ParadeDbJsonQuery.Term("DocumentType", 10)));
+ ParadeDbJsonQuery.Term("DocumentType", 10)
+ )
+ );
var sql = Sql(_db.Chunks.Where(c => EF.Functions.JsonSearch(c.Id, query.ToJson())));
Assert.Contains("@@@", sql);
Assert.Contains("::jsonb", sql);
}
[Fact]
- public void JsonSearch_composes_with_order_by_score() {
+ public void JsonSearch_composes_with_order_by_score()
+ {
var json = ParadeDbJsonQuery.Parse("test").ToJson();
- var sql = Sql(_db.Chunks
- .Where(c => EF.Functions.JsonSearch(c.Id, json))
- .OrderByDescending(c => EF.Functions.Score(c.Id)));
+ var sql = Sql(
+ _db.Chunks.Where(c => EF.Functions.JsonSearch(c.Id, json))
+ .OrderByDescending(c => EF.Functions.Score(c.Id))
+ );
Assert.Contains("@@@", sql);
Assert.Contains("::jsonb", sql);
Assert.Contains("pdb.score(", sql);
@@ -270,27 +340,29 @@ public void JsonSearch_composes_with_order_by_score() {
}
[Fact]
- public void JsonSearch_composes_with_take() {
+ public void JsonSearch_composes_with_take()
+ {
var json = ParadeDbJsonQuery.Parse("test").ToJson();
- var sql = Sql(_db.Chunks
- .Where(c => EF.Functions.JsonSearch(c.Id, json))
- .Take(5));
+ var sql = Sql(_db.Chunks.Where(c => EF.Functions.JsonSearch(c.Id, json)).Take(5));
Assert.Contains("@@@", sql);
Assert.Contains("LIMIT", sql);
}
[Fact]
- public void JsonSearch_composes_with_standard_linq_where() {
+ public void JsonSearch_composes_with_standard_linq_where()
+ {
var json = ParadeDbJsonQuery.Parse("test").ToJson();
- var sql = Sql(_db.Chunks
- .Where(c => EF.Functions.JsonSearch(c.Id, json) && c.DocumentType > 5));
+ var sql = Sql(
+ _db.Chunks.Where(c => EF.Functions.JsonSearch(c.Id, json) && c.DocumentType > 5)
+ );
Assert.Contains("@@@", sql);
Assert.Contains("::jsonb", sql);
Assert.Contains(">", sql);
}
[Fact]
- public void JsonSearch_extension_generates_same_as_ef_functions() {
+ public void JsonSearch_extension_generates_same_as_ef_functions()
+ {
var query = ParadeDbJsonQuery.Parse("test");
var sqlExt = Sql(_db.Chunks.JsonSearch(c => c.Id, query));
var sqlDirect = Sql(_db.Chunks.Where(c => EF.Functions.JsonSearch(c.Id, query.ToJson())));
@@ -300,11 +372,18 @@ public void JsonSearch_extension_generates_same_as_ef_functions() {
}
[Fact]
- public void JsonSearch_inline_boolean_generates_correct_sql() {
- var sql = Sql(_db.Chunks.JsonSearch(c => c.Id, b => b
- .Must(
- ParadeDbJsonQuery.Parse("revenue growth"),
- ParadeDbJsonQuery.Term("DocumentType", 10))));
+ public void JsonSearch_inline_boolean_generates_correct_sql()
+ {
+ var sql = Sql(
+ _db.Chunks.JsonSearch(
+ c => c.Id,
+ b =>
+ b.Must(
+ ParadeDbJsonQuery.Parse("revenue growth"),
+ ParadeDbJsonQuery.Term("DocumentType", 10)
+ )
+ )
+ );
Assert.Contains("@@@", sql);
Assert.Contains("::jsonb", sql);
}
@@ -312,18 +391,20 @@ public void JsonSearch_inline_boolean_generates_correct_sql() {
// ── Combining with LINQ ───────────────────────────────────────────
[Fact]
- public void Search_composes_with_standard_linq_where() {
- var sql = Sql(_db.Articles
- .Where(a => EF.Functions.Matches(a.Content, "test") && a.Id > 5));
+ public void Search_composes_with_standard_linq_where()
+ {
+ var sql = Sql(_db.Articles.Where(a => EF.Functions.Matches(a.Content, "test") && a.Id > 5));
Assert.Contains("|||", sql);
Assert.Contains(">", sql);
}
[Fact]
- public void Search_composes_with_order_by_score() {
- var sql = Sql(_db.Articles
- .Where(a => EF.Functions.Matches(a.Content, "test"))
- .OrderByDescending(a => EF.Functions.Score(a.Id)));
+ public void Search_composes_with_order_by_score()
+ {
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.Matches(a.Content, "test"))
+ .OrderByDescending(a => EF.Functions.Score(a.Id))
+ );
Assert.Contains("|||", sql);
Assert.Contains("pdb.score(", sql);
Assert.Contains("ORDER BY", sql);
@@ -332,20 +413,24 @@ public void Search_composes_with_order_by_score() {
// ── Score Ordering Extensions ─────────────────────────────────────
[Fact]
- public void OrderByScoreDescending_extension_emits_pdb_score_with_desc() {
- var sql = Sql(_db.Chunks
- .JsonSearch(c => c.Id, ParadeDbJsonQuery.Parse("test"))
- .OrderByScoreDescending(c => c.Id));
+ public void OrderByScoreDescending_extension_emits_pdb_score_with_desc()
+ {
+ var sql = Sql(
+ _db.Chunks.JsonSearch(c => c.Id, ParadeDbJsonQuery.Parse("test"))
+ .OrderByScoreDescending(c => c.Id)
+ );
Assert.Contains("pdb.score(", sql);
Assert.Contains("ORDER BY", sql);
Assert.Contains("DESC", sql);
}
[Fact]
- public void OrderByScore_extension_emits_pdb_score_without_desc() {
- var sql = Sql(_db.Chunks
- .JsonSearch(c => c.Id, ParadeDbJsonQuery.Parse("test"))
- .OrderByScore(c => c.Id));
+ public void OrderByScore_extension_emits_pdb_score_without_desc()
+ {
+ var sql = Sql(
+ _db.Chunks.JsonSearch(c => c.Id, ParadeDbJsonQuery.Parse("test"))
+ .OrderByScore(c => c.Id)
+ );
Assert.Contains("pdb.score(", sql);
Assert.Contains("ORDER BY", sql);
Assert.DoesNotContain("DESC", sql);
@@ -359,7 +444,8 @@ public void OrderByScore_extension_emits_pdb_score_without_desc() {
/// plugin, including ours — ours should return null so Npgsql's translator handles it.
///
[Fact]
- public void NonParadeDbMethod_is_translated_by_other_plugins() {
+ public void NonParadeDbMethod_is_translated_by_other_plugins()
+ {
var sql = Sql(_db.Articles.Where(a => a.Title.StartsWith("foo")));
Assert.DoesNotContain("@@@", sql);
Assert.DoesNotContain("|||", sql);
@@ -377,13 +463,18 @@ public void NonParadeDbMethod_is_translated_by_other_plugins() {
/// instances — exercising the "args changed → build new expression" branch.
///
[Fact]
- public void Snippet_with_captured_parameters_still_emits_named_args() {
+ public void Snippet_with_captured_parameters_still_emits_named_args()
+ {
var startTag = "";
var endTag = "";
var maxChars = 100;
- var sql = Sql(_db.Articles
- .Where(a => EF.Functions.Matches(a.Content, "test"))
- .Select(a => new { Snip = EF.Functions.Snippet(a.Content, startTag, endTag, maxChars) }));
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.Matches(a.Content, "test"))
+ .Select(a => new
+ {
+ Snip = EF.Functions.Snippet(a.Content, startTag, endTag, maxChars),
+ })
+ );
Assert.Contains("pdb.snippet(", sql);
Assert.Contains("start_tag =>", sql);
Assert.Contains("end_tag =>", sql);
@@ -391,13 +482,18 @@ public void Snippet_with_captured_parameters_still_emits_named_args() {
}
[Fact]
- public void Snippets_with_captured_parameters_still_emits_named_args() {
+ public void Snippets_with_captured_parameters_still_emits_named_args()
+ {
var maxChars = 15;
var limit = 5;
var offset = 0;
- var sql = Sql(_db.Articles
- .Where(a => EF.Functions.Matches(a.Content, "test"))
- .Select(a => new { Snips = EF.Functions.Snippets(a.Content, maxChars, limit, offset) }));
+ var sql = Sql(
+ _db.Articles.Where(a => EF.Functions.Matches(a.Content, "test"))
+ .Select(a => new
+ {
+ Snips = EF.Functions.Snippets(a.Content, maxChars, limit, offset),
+ })
+ );
Assert.Contains("pdb.snippets(", sql);
Assert.Contains("max_num_chars =>", sql);
Assert.Contains("\"limit\" =>", sql);
@@ -412,18 +508,22 @@ public void Snippets_with_captured_parameters_still_emits_named_args() {
/// suffix (e.g. ::pdb.fuzzy(2)). Captured int → SqlParameterExpression → throws.
///
[Fact]
- public void MatchesFuzzy_with_captured_distance_throws_at_translation() {
+ public void MatchesFuzzy_with_captured_distance_throws_at_translation()
+ {
var distance = 2;
- var ex = Assert.Throws(
- () => Sql(_db.Articles.Where(a => EF.Functions.MatchesFuzzy(a.Content, "x", distance))));
+ var ex = Assert.Throws(() =>
+ Sql(_db.Articles.Where(a => EF.Functions.MatchesFuzzy(a.Content, "x", distance)))
+ );
Assert.Contains("compile-time constants", ex.Message);
}
[Fact]
- public void MatchesBoosted_with_captured_boost_throws_at_translation() {
+ public void MatchesBoosted_with_captured_boost_throws_at_translation()
+ {
var boost = 2.0;
- var ex = Assert.Throws(
- () => Sql(_db.Articles.Where(a => EF.Functions.MatchesBoosted(a.Content, "x", boost))));
+ var ex = Assert.Throws(() =>
+ Sql(_db.Articles.Where(a => EF.Functions.MatchesBoosted(a.Content, "x", boost)))
+ );
Assert.Contains("compile-time constants", ex.Message);
}
@@ -435,7 +535,8 @@ public void MatchesBoosted_with_captured_boost_throws_at_translation() {
/// take their pass-through branches. The int-keyed tests don't exercise either path.
///
[Fact]
- public void JsonSearch_with_reference_type_key_emits_at_operator() {
+ public void JsonSearch_with_reference_type_key_emits_at_operator()
+ {
var sql = Sql(_db.Chunks.JsonSearch(c => c.Content, ParadeDbJsonQuery.Parse("revenue")));
Assert.Contains("@@@", sql);
Assert.Contains("::jsonb", sql);
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.Tests/SqlOutputTests.cs b/Equibles.ParadeDB.EntityFrameworkCore.Tests/SqlOutputTests.cs
index 760ef07..e1f1a3d 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.Tests/SqlOutputTests.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.Tests/SqlOutputTests.cs
@@ -5,64 +5,185 @@ namespace Equibles.ParadeDB.EntityFrameworkCore.Tests;
///
/// Prints generated SQL for visual inspection. Not assertions — just output.
///
-public class SqlOutputTests : IDisposable {
+public class SqlOutputTests : IDisposable
+{
private readonly TestDbContext _db = new();
public void Dispose() => _db.Dispose();
private string Sql(IQueryable query) => query.ToQueryString();
- private IQueryable JsonSearchQuery() {
+ private IQueryable JsonSearchQuery()
+ {
var json = ParadeDbJsonQuery.Parse("revenue growth").ToJson();
return _db.Chunks.Where(c => EF.Functions.JsonSearch(c.Id, json));
}
- private IQueryable JsonSearchBooleanQuery() {
- var json = ParadeDbJsonQuery.Boolean(b => b
- .Must(
- ParadeDbJsonQuery.Parse("revenue growth"),
- ParadeDbJsonQuery.Term("DocumentType", 10))).ToJson();
+ private IQueryable JsonSearchBooleanQuery()
+ {
+ var json = ParadeDbJsonQuery
+ .Boolean(b =>
+ b.Must(
+ ParadeDbJsonQuery.Parse("revenue growth"),
+ ParadeDbJsonQuery.Term("DocumentType", 10)
+ )
+ )
+ .ToJson();
return _db.Chunks.Where(c => EF.Functions.JsonSearch(c.Id, json));
}
[Fact]
- public void Print_all_query_translations() {
- var queries = new (string Label, string Sql)[] {
+ public void Print_all_query_translations()
+ {
+ var queries = new (string Label, string Sql)[]
+ {
("Matches", Sql(_db.Articles.Where(a => EF.Functions.Matches(a.Content, "shoes")))),
- ("MatchesAll", Sql(_db.Articles.Where(a => EF.Functions.MatchesAll(a.Content, "shoes")))),
- ("MatchesPhrase", Sql(_db.Articles.Where(a => EF.Functions.MatchesPhrase(a.Content, "neural networks")))),
- ("MatchesPhrase+slop", Sql(_db.Articles.Where(a => EF.Functions.MatchesPhrase(a.Content, "neural networks", 2)))),
- ("MatchesTerm", Sql(_db.Articles.Where(a => EF.Functions.MatchesTerm(a.Content, "gpu")))),
- ("MatchesTermSet", Sql(_db.Articles.Where(a => EF.Functions.MatchesTermSet(a.Content, "gpu", "tpu")))),
- ("MatchesFuzzy", Sql(_db.Articles.Where(a => EF.Functions.MatchesFuzzy(a.Content, "machin", 2)))),
- ("MatchesFuzzy+opts", Sql(_db.Articles.Where(a => EF.Functions.MatchesFuzzy(a.Content, "machin", 2, true, false)))),
- ("MatchesAllFuzzy", Sql(_db.Articles.Where(a => EF.Functions.MatchesAllFuzzy(a.Content, "machin", 2)))),
- ("MatchesTermFuzzy", Sql(_db.Articles.Where(a => EF.Functions.MatchesTermFuzzy(a.Content, "machin", 1)))),
- ("MatchesBoosted", Sql(_db.Articles.Where(a => EF.Functions.MatchesBoosted(a.Content, "shoes", 2.0)))),
- ("MatchesAllBoosted", Sql(_db.Articles.Where(a => EF.Functions.MatchesAllBoosted(a.Content, "shoes", 1.5)))),
- ("MatchesFuzzyBoosted", Sql(_db.Articles.Where(a => EF.Functions.MatchesFuzzyBoosted(a.Content, "shoes", 2, 2.0)))),
- ("MatchesAllFuzzyBoosted", Sql(_db.Articles.Where(a => EF.Functions.MatchesAllFuzzyBoosted(a.Content, "shoes", 1, 3.0)))),
- ("Score", Sql(_db.Articles.Where(a => EF.Functions.Matches(a.Content, "test")).Select(a => new { Score = EF.Functions.Score(a.Id) }))),
- ("Snippet", Sql(_db.Articles.Where(a => EF.Functions.Matches(a.Content, "test")).Select(a => new { Snip = EF.Functions.Snippet(a.Content) }))),
- ("Snippet+params", Sql(_db.Articles.Where(a => EF.Functions.Matches(a.Content, "test")).Select(a => new { Snip = EF.Functions.Snippet(a.Content, "", "", 100) }))),
- ("Snippets", Sql(_db.Articles.Where(a => EF.Functions.Matches(a.Content, "test")).Select(a => new { Snips = EF.Functions.Snippets(a.Content, 15, 5, 0) }))),
+ (
+ "MatchesAll",
+ Sql(_db.Articles.Where(a => EF.Functions.MatchesAll(a.Content, "shoes")))
+ ),
+ (
+ "MatchesPhrase",
+ Sql(
+ _db.Articles.Where(a =>
+ EF.Functions.MatchesPhrase(a.Content, "neural networks")
+ )
+ )
+ ),
+ (
+ "MatchesPhrase+slop",
+ Sql(
+ _db.Articles.Where(a =>
+ EF.Functions.MatchesPhrase(a.Content, "neural networks", 2)
+ )
+ )
+ ),
+ (
+ "MatchesTerm",
+ Sql(_db.Articles.Where(a => EF.Functions.MatchesTerm(a.Content, "gpu")))
+ ),
+ (
+ "MatchesTermSet",
+ Sql(_db.Articles.Where(a => EF.Functions.MatchesTermSet(a.Content, "gpu", "tpu")))
+ ),
+ (
+ "MatchesFuzzy",
+ Sql(_db.Articles.Where(a => EF.Functions.MatchesFuzzy(a.Content, "machin", 2)))
+ ),
+ (
+ "MatchesFuzzy+opts",
+ Sql(
+ _db.Articles.Where(a =>
+ EF.Functions.MatchesFuzzy(a.Content, "machin", 2, true, false)
+ )
+ )
+ ),
+ (
+ "MatchesAllFuzzy",
+ Sql(_db.Articles.Where(a => EF.Functions.MatchesAllFuzzy(a.Content, "machin", 2)))
+ ),
+ (
+ "MatchesTermFuzzy",
+ Sql(_db.Articles.Where(a => EF.Functions.MatchesTermFuzzy(a.Content, "machin", 1)))
+ ),
+ (
+ "MatchesBoosted",
+ Sql(_db.Articles.Where(a => EF.Functions.MatchesBoosted(a.Content, "shoes", 2.0)))
+ ),
+ (
+ "MatchesAllBoosted",
+ Sql(
+ _db.Articles.Where(a => EF.Functions.MatchesAllBoosted(a.Content, "shoes", 1.5))
+ )
+ ),
+ (
+ "MatchesFuzzyBoosted",
+ Sql(
+ _db.Articles.Where(a =>
+ EF.Functions.MatchesFuzzyBoosted(a.Content, "shoes", 2, 2.0)
+ )
+ )
+ ),
+ (
+ "MatchesAllFuzzyBoosted",
+ Sql(
+ _db.Articles.Where(a =>
+ EF.Functions.MatchesAllFuzzyBoosted(a.Content, "shoes", 1, 3.0)
+ )
+ )
+ ),
+ (
+ "Score",
+ Sql(
+ _db.Articles.Where(a => EF.Functions.Matches(a.Content, "test"))
+ .Select(a => new { Score = EF.Functions.Score(a.Id) })
+ )
+ ),
+ (
+ "Snippet",
+ Sql(
+ _db.Articles.Where(a => EF.Functions.Matches(a.Content, "test"))
+ .Select(a => new { Snip = EF.Functions.Snippet(a.Content) })
+ )
+ ),
+ (
+ "Snippet+params",
+ Sql(
+ _db.Articles.Where(a => EF.Functions.Matches(a.Content, "test"))
+ .Select(a => new
+ {
+ Snip = EF.Functions.Snippet(a.Content, "", "", 100),
+ })
+ )
+ ),
+ (
+ "Snippets",
+ Sql(
+ _db.Articles.Where(a => EF.Functions.Matches(a.Content, "test"))
+ .Select(a => new { Snips = EF.Functions.Snippets(a.Content, 15, 5, 0) })
+ )
+ ),
("Parse", Sql(_db.Articles.Where(a => EF.Functions.Parse(a.Id, "title:shoes")))),
- ("Parse+opts", Sql(_db.Articles.Where(a => EF.Functions.Parse(a.Id, "shoes", true, true)))),
+ (
+ "Parse+opts",
+ Sql(_db.Articles.Where(a => EF.Functions.Parse(a.Id, "shoes", true, true)))
+ ),
("Regex", Sql(_db.Articles.Where(a => EF.Functions.Regex(a.Content, "neuro.*")))),
- ("PhrasePrefix", Sql(_db.Articles.Where(a => EF.Functions.PhrasePrefix(a.Content, "running", "sh")))),
- ("PhrasePrefix+max", Sql(_db.Articles.Where(a => EF.Functions.PhrasePrefix(a.Content, 10, "running", "sh")))),
+ (
+ "PhrasePrefix",
+ Sql(_db.Articles.Where(a => EF.Functions.PhrasePrefix(a.Content, "running", "sh")))
+ ),
+ (
+ "PhrasePrefix+max",
+ Sql(
+ _db.Articles.Where(a =>
+ EF.Functions.PhrasePrefix(a.Content, 10, "running", "sh")
+ )
+ )
+ ),
("MoreLikeThis", Sql(_db.Articles.Where(a => EF.Functions.MoreLikeThis(a.Id, 3)))),
- ("MoreLikeThis+fields", Sql(_db.Articles.Where(a => EF.Functions.MoreLikeThis(a.Id, 3, "description")))),
+ (
+ "MoreLikeThis+fields",
+ Sql(_db.Articles.Where(a => EF.Functions.MoreLikeThis(a.Id, 3, "description")))
+ ),
("JsonSearch", Sql(JsonSearchQuery())),
("JsonSearch+boolean", Sql(JsonSearchBooleanQuery())),
- ("JsonSearch+extension", Sql(_db.Chunks.JsonSearch(c => c.Id, ParadeDbJsonQuery.Parse("test")))),
- ("JsonSearch+score+limit", Sql(_db.Chunks
- .JsonSearch(c => c.Id, ParadeDbJsonQuery.Parse("test"))
- .OrderByScoreDescending(c => c.Id)
- .Take(5))),
+ (
+ "JsonSearch+extension",
+ Sql(_db.Chunks.JsonSearch(c => c.Id, ParadeDbJsonQuery.Parse("test")))
+ ),
+ (
+ "JsonSearch+score+limit",
+ Sql(
+ _db.Chunks.JsonSearch(c => c.Id, ParadeDbJsonQuery.Parse("test"))
+ .OrderByScoreDescending(c => c.Id)
+ .Take(5)
+ )
+ ),
};
- foreach (var (label, sql) in queries) {
+ foreach (var (label, sql) in queries)
+ {
Console.WriteLine($"── {label} ──");
Console.WriteLine(sql);
Console.WriteLine();
diff --git a/Equibles.ParadeDB.EntityFrameworkCore.Tests/TestDbContext.cs b/Equibles.ParadeDB.EntityFrameworkCore.Tests/TestDbContext.cs
index f0e3661..67ff217 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore.Tests/TestDbContext.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore.Tests/TestDbContext.cs
@@ -3,7 +3,8 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.Tests;
[Bm25Index(nameof(Id), nameof(Title), nameof(Content))]
-public class Article {
+public class Article
+{
public int Id { get; set; }
public string Title { get; set; } = null!;
public string Content { get; set; } = null!;
@@ -11,18 +12,21 @@ public class Article {
}
[Bm25Index(nameof(Id), nameof(Content), nameof(DocumentId), nameof(DocumentType))]
-public class Chunk {
+public class Chunk
+{
public int Id { get; set; }
public string Content { get; set; } = null!;
public Guid DocumentId { get; set; }
public int DocumentType { get; set; }
}
-public class TestDbContext : DbContext {
+public class TestDbContext : DbContext
+{
public DbSet Articles { get; set; } = null!;
public DbSet Chunks { get; set; } = null!;
- protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
optionsBuilder.UseNpgsql("Host=localhost;Database=test", npgsql => npgsql.UseParadeDb());
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/Bm25BooleanAttribute.cs b/Equibles.ParadeDB.EntityFrameworkCore/Bm25BooleanAttribute.cs
index 6224466..a9e81cf 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/Bm25BooleanAttribute.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/Bm25BooleanAttribute.cs
@@ -4,7 +4,8 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
/// Configures a boolean column inside a BM25 index.
///
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
-public sealed class Bm25BooleanAttribute : Attribute {
+public sealed class Bm25BooleanAttribute : Attribute
+{
public bool Fast { get; set; }
public bool Indexed { get; set; } = true;
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/Bm25DateTimeAttribute.cs b/Equibles.ParadeDB.EntityFrameworkCore/Bm25DateTimeAttribute.cs
index 4798442..790260b 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/Bm25DateTimeAttribute.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/Bm25DateTimeAttribute.cs
@@ -4,7 +4,8 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
/// Configures a datetime column inside a BM25 index.
///
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
-public sealed class Bm25DateTimeAttribute : Attribute {
+public sealed class Bm25DateTimeAttribute : Attribute
+{
public bool Fast { get; set; }
public bool Indexed { get; set; } = true;
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/Bm25IndexAttribute.cs b/Equibles.ParadeDB.EntityFrameworkCore/Bm25IndexAttribute.cs
index af6b637..b9c1bb9 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/Bm25IndexAttribute.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/Bm25IndexAttribute.cs
@@ -1,12 +1,14 @@
namespace Equibles.ParadeDB.EntityFrameworkCore;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
-public sealed class Bm25IndexAttribute : Attribute {
+public sealed class Bm25IndexAttribute : Attribute
+{
public string KeyField { get; }
public string[] Columns { get; }
- public Bm25IndexAttribute(string keyField, params string[] columns) {
+ public Bm25IndexAttribute(string keyField, params string[] columns)
+ {
KeyField = keyField;
- Columns = [keyField, ..columns];
+ Columns = [keyField, .. columns];
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/Bm25JsonAttribute.cs b/Equibles.ParadeDB.EntityFrameworkCore/Bm25JsonAttribute.cs
index 75fecf2..d4e429a 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/Bm25JsonAttribute.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/Bm25JsonAttribute.cs
@@ -5,7 +5,8 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
/// settings as plus .
///
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
-public sealed class Bm25JsonAttribute : Attribute {
+public sealed class Bm25JsonAttribute : Attribute
+{
public Bm25Tokenizer Tokenizer { get; set; }
/// Required when is .
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/Bm25Language.cs b/Equibles.ParadeDB.EntityFrameworkCore/Bm25Language.cs
index a302443..f07b8dc 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/Bm25Language.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/Bm25Language.cs
@@ -4,7 +4,8 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
/// Languages supported by pg_search's stemmer and stopwords token filters.
/// means the property carries no language setting; the filter is not applied.
///
-public enum Bm25Language {
+public enum Bm25Language
+{
Unspecified = 0,
Arabic,
Czech,
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/Bm25NumericAttribute.cs b/Equibles.ParadeDB.EntityFrameworkCore/Bm25NumericAttribute.cs
index e1b0eb4..a1bc528 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/Bm25NumericAttribute.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/Bm25NumericAttribute.cs
@@ -4,7 +4,8 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
/// Configures a numeric column inside a BM25 index.
///
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
-public sealed class Bm25NumericAttribute : Attribute {
+public sealed class Bm25NumericAttribute : Attribute
+{
public bool Fast { get; set; }
public bool Indexed { get; set; } = true;
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/Bm25Record.cs b/Equibles.ParadeDB.EntityFrameworkCore/Bm25Record.cs
index 01750d3..333958f 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/Bm25Record.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/Bm25Record.cs
@@ -5,7 +5,8 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
/// Position is required for phrase queries; Basic and Freq use less disk.
/// means the property carries no record setting; pg_search uses its default.
///
-public enum Bm25Record {
+public enum Bm25Record
+{
Unspecified = 0,
Basic,
Freq,
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/Bm25TextAttribute.cs b/Equibles.ParadeDB.EntityFrameworkCore/Bm25TextAttribute.cs
index 7309c5d..9dc527f 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/Bm25TextAttribute.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/Bm25TextAttribute.cs
@@ -5,7 +5,8 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
/// in the entity's column set.
///
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
-public sealed class Bm25TextAttribute : Attribute {
+public sealed class Bm25TextAttribute : Attribute
+{
public Bm25Tokenizer Tokenizer { get; set; }
/// Required when is .
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/Bm25Tokenizer.cs b/Equibles.ParadeDB.EntityFrameworkCore/Bm25Tokenizer.cs
index 9329910..b866333 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/Bm25Tokenizer.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/Bm25Tokenizer.cs
@@ -5,7 +5,8 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
/// Names map to pg_search tokenizer type strings.
/// means the property carries no tokenizer setting; pg_search uses its default.
///
-public enum Bm25Tokenizer {
+public enum Bm25Tokenizer
+{
Unspecified = 0,
Default,
Whitespace,
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/Equibles.ParadeDB.EntityFrameworkCore.csproj b/Equibles.ParadeDB.EntityFrameworkCore/Equibles.ParadeDB.EntityFrameworkCore.csproj
index 7a2e7f9..ba75b06 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/Equibles.ParadeDB.EntityFrameworkCore.csproj
+++ b/Equibles.ParadeDB.EntityFrameworkCore/Equibles.ParadeDB.EntityFrameworkCore.csproj
@@ -1,27 +1,25 @@
+
+ net8.0;net9.0;net10.0
+ EF Core integration for ParadeDB pg_search BM25 full-text search indexes on PostgreSQL. Provides a [Bm25Index] attribute, UseParadeDb() extension, and LINQ query methods for BM25 search, scoring, and snippets.
+ Equibles.ParadeDB.EntityFrameworkCore
+
+ EF1001
+
-
- net8.0;net9.0;net10.0
- EF Core integration for ParadeDB pg_search BM25 full-text search indexes on PostgreSQL. Provides a [Bm25Index] attribute, UseParadeDb() extension, and LINQ query methods for BM25 search, scoring, and snippets.
- Equibles.ParadeDB.EntityFrameworkCore
-
- EF1001
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/Internal/Bm25EnumExtensions.cs b/Equibles.ParadeDB.EntityFrameworkCore/Internal/Bm25EnumExtensions.cs
index 30c0607..dfdc26e 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/Internal/Bm25EnumExtensions.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/Internal/Bm25EnumExtensions.cs
@@ -1,54 +1,70 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.Internal;
-internal static class Bm25EnumExtensions {
- public static string ToParadeDbString(this Bm25Tokenizer tokenizer) => tokenizer switch {
- Bm25Tokenizer.Unspecified => throw new ArgumentException("Bm25Tokenizer.Unspecified has no pg_search representation.", nameof(tokenizer)),
- Bm25Tokenizer.Default => "default",
- Bm25Tokenizer.Whitespace => "whitespace",
- Bm25Tokenizer.Raw => "raw",
- Bm25Tokenizer.Keyword => "keyword",
- Bm25Tokenizer.SourceCode => "source_code",
- Bm25Tokenizer.Icu => "icu",
- Bm25Tokenizer.Ngram => "ngram",
- Bm25Tokenizer.Regex => "regex",
- Bm25Tokenizer.ChineseCompatible => "chinese_compatible",
- Bm25Tokenizer.ChineseLindera => "chinese_lindera",
- Bm25Tokenizer.JapaneseLindera => "japanese_lindera",
- Bm25Tokenizer.KoreanLindera => "korean_lindera",
- Bm25Tokenizer.Jieba => "jieba",
- _ => throw new ArgumentOutOfRangeException(nameof(tokenizer), tokenizer, null),
- };
+internal static class Bm25EnumExtensions
+{
+ public static string ToParadeDbString(this Bm25Tokenizer tokenizer) =>
+ tokenizer switch
+ {
+ Bm25Tokenizer.Unspecified => throw new ArgumentException(
+ "Bm25Tokenizer.Unspecified has no pg_search representation.",
+ nameof(tokenizer)
+ ),
+ Bm25Tokenizer.Default => "default",
+ Bm25Tokenizer.Whitespace => "whitespace",
+ Bm25Tokenizer.Raw => "raw",
+ Bm25Tokenizer.Keyword => "keyword",
+ Bm25Tokenizer.SourceCode => "source_code",
+ Bm25Tokenizer.Icu => "icu",
+ Bm25Tokenizer.Ngram => "ngram",
+ Bm25Tokenizer.Regex => "regex",
+ Bm25Tokenizer.ChineseCompatible => "chinese_compatible",
+ Bm25Tokenizer.ChineseLindera => "chinese_lindera",
+ Bm25Tokenizer.JapaneseLindera => "japanese_lindera",
+ Bm25Tokenizer.KoreanLindera => "korean_lindera",
+ Bm25Tokenizer.Jieba => "jieba",
+ _ => throw new ArgumentOutOfRangeException(nameof(tokenizer), tokenizer, null),
+ };
- public static string ToParadeDbString(this Bm25Language language) => language switch {
- Bm25Language.Unspecified => throw new ArgumentException("Bm25Language.Unspecified has no pg_search representation.", nameof(language)),
- Bm25Language.Arabic => "Arabic",
- Bm25Language.Czech => "Czech",
- Bm25Language.Danish => "Danish",
- Bm25Language.Dutch => "Dutch",
- Bm25Language.English => "English",
- Bm25Language.Finnish => "Finnish",
- Bm25Language.French => "French",
- Bm25Language.German => "German",
- Bm25Language.Greek => "Greek",
- Bm25Language.Hungarian => "Hungarian",
- Bm25Language.Italian => "Italian",
- Bm25Language.Norwegian => "Norwegian",
- Bm25Language.Polish => "Polish",
- Bm25Language.Portuguese => "Portuguese",
- Bm25Language.Romanian => "Romanian",
- Bm25Language.Russian => "Russian",
- Bm25Language.Spanish => "Spanish",
- Bm25Language.Swedish => "Swedish",
- Bm25Language.Tamil => "Tamil",
- Bm25Language.Turkish => "Turkish",
- _ => throw new ArgumentOutOfRangeException(nameof(language), language, null),
- };
+ public static string ToParadeDbString(this Bm25Language language) =>
+ language switch
+ {
+ Bm25Language.Unspecified => throw new ArgumentException(
+ "Bm25Language.Unspecified has no pg_search representation.",
+ nameof(language)
+ ),
+ Bm25Language.Arabic => "Arabic",
+ Bm25Language.Czech => "Czech",
+ Bm25Language.Danish => "Danish",
+ Bm25Language.Dutch => "Dutch",
+ Bm25Language.English => "English",
+ Bm25Language.Finnish => "Finnish",
+ Bm25Language.French => "French",
+ Bm25Language.German => "German",
+ Bm25Language.Greek => "Greek",
+ Bm25Language.Hungarian => "Hungarian",
+ Bm25Language.Italian => "Italian",
+ Bm25Language.Norwegian => "Norwegian",
+ Bm25Language.Polish => "Polish",
+ Bm25Language.Portuguese => "Portuguese",
+ Bm25Language.Romanian => "Romanian",
+ Bm25Language.Russian => "Russian",
+ Bm25Language.Spanish => "Spanish",
+ Bm25Language.Swedish => "Swedish",
+ Bm25Language.Tamil => "Tamil",
+ Bm25Language.Turkish => "Turkish",
+ _ => throw new ArgumentOutOfRangeException(nameof(language), language, null),
+ };
- public static string ToParadeDbString(this Bm25Record record) => record switch {
- Bm25Record.Unspecified => throw new ArgumentException("Bm25Record.Unspecified has no pg_search representation.", nameof(record)),
- Bm25Record.Basic => "basic",
- Bm25Record.Freq => "freq",
- Bm25Record.Position => "position",
- _ => throw new ArgumentOutOfRangeException(nameof(record), record, null),
- };
+ public static string ToParadeDbString(this Bm25Record record) =>
+ record switch
+ {
+ Bm25Record.Unspecified => throw new ArgumentException(
+ "Bm25Record.Unspecified has no pg_search representation.",
+ nameof(record)
+ ),
+ Bm25Record.Basic => "basic",
+ Bm25Record.Freq => "freq",
+ Bm25Record.Position => "position",
+ _ => throw new ArgumentOutOfRangeException(nameof(record), record, null),
+ };
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/Internal/Bm25StorageParameterBuilder.cs b/Equibles.ParadeDB.EntityFrameworkCore/Internal/Bm25StorageParameterBuilder.cs
index 248451d..4376a63 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/Internal/Bm25StorageParameterBuilder.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/Internal/Bm25StorageParameterBuilder.cs
@@ -2,117 +2,189 @@
namespace Equibles.ParadeDB.EntityFrameworkCore.Internal;
-internal static class Bm25StorageParameterBuilder {
+internal static class Bm25StorageParameterBuilder
+{
public static string Serialize(JsonObject root) =>
root.ToJsonString(new System.Text.Json.JsonSerializerOptions { WriteIndented = false });
- public static JsonObject BuildTextField(Bm25TextAttribute attr, string propertyName) {
+ public static JsonObject BuildTextField(Bm25TextAttribute attr, string propertyName)
+ {
ValidateTokenizerParameters(
propertyName,
attr.Tokenizer,
- attr.MinGram, attr.MaxGram, attr.PrefixOnly,
- attr.RegexPattern);
+ attr.MinGram,
+ attr.MaxGram,
+ attr.PrefixOnly,
+ attr.RegexPattern
+ );
var node = new JsonObject();
- AddTokenizerKey(node, attr.Tokenizer, attr.MinGram, attr.MaxGram, attr.PrefixOnly, attr.RegexPattern,
- attr.Stemmer, attr.StopwordsLanguage);
+ AddTokenizerKey(
+ node,
+ attr.Tokenizer,
+ attr.MinGram,
+ attr.MaxGram,
+ attr.PrefixOnly,
+ attr.RegexPattern,
+ attr.Stemmer,
+ attr.StopwordsLanguage
+ );
AddSharedKeys(node, attr.Fast, attr.Record, attr.Indexed, attr.Fieldnorms);
return node;
}
- public static JsonObject BuildNumericField(Bm25NumericAttribute attr) {
+ public static JsonObject BuildNumericField(Bm25NumericAttribute attr)
+ {
var node = new JsonObject();
- if (attr.Fast) node["fast"] = true;
- if (!attr.Indexed) node["indexed"] = false;
+ if (attr.Fast)
+ node["fast"] = true;
+ if (!attr.Indexed)
+ node["indexed"] = false;
return node;
}
- public static JsonObject BuildBooleanField(Bm25BooleanAttribute attr) {
+ public static JsonObject BuildBooleanField(Bm25BooleanAttribute attr)
+ {
var node = new JsonObject();
- if (attr.Fast) node["fast"] = true;
- if (!attr.Indexed) node["indexed"] = false;
+ if (attr.Fast)
+ node["fast"] = true;
+ if (!attr.Indexed)
+ node["indexed"] = false;
return node;
}
- public static JsonObject BuildDateTimeField(Bm25DateTimeAttribute attr) {
+ public static JsonObject BuildDateTimeField(Bm25DateTimeAttribute attr)
+ {
var node = new JsonObject();
- if (attr.Fast) node["fast"] = true;
- if (!attr.Indexed) node["indexed"] = false;
+ if (attr.Fast)
+ node["fast"] = true;
+ if (!attr.Indexed)
+ node["indexed"] = false;
return node;
}
- public static JsonObject BuildJsonField(Bm25JsonAttribute attr, string propertyName) {
+ public static JsonObject BuildJsonField(Bm25JsonAttribute attr, string propertyName)
+ {
ValidateTokenizerParameters(
propertyName,
attr.Tokenizer,
- attr.MinGram, attr.MaxGram, attr.PrefixOnly,
- attr.RegexPattern);
+ attr.MinGram,
+ attr.MaxGram,
+ attr.PrefixOnly,
+ attr.RegexPattern
+ );
var node = new JsonObject();
- AddTokenizerKey(node, attr.Tokenizer, attr.MinGram, attr.MaxGram, attr.PrefixOnly, attr.RegexPattern,
- attr.Stemmer, attr.StopwordsLanguage);
+ AddTokenizerKey(
+ node,
+ attr.Tokenizer,
+ attr.MinGram,
+ attr.MaxGram,
+ attr.PrefixOnly,
+ attr.RegexPattern,
+ attr.Stemmer,
+ attr.StopwordsLanguage
+ );
AddSharedKeys(node, attr.Fast, attr.Record, attr.Indexed, attr.Fieldnorms);
- if (attr.ExpandDots) node["expand_dots"] = true;
+ if (attr.ExpandDots)
+ node["expand_dots"] = true;
return node;
}
- private static void AddTokenizerKey(JsonObject node, Bm25Tokenizer tokenizer,
- int minGram, int maxGram, bool prefixOnly, string regexPattern,
- Bm25Language stemmer, Bm25Language stopwordsLanguage) {
- var hasAnySetting = tokenizer != Bm25Tokenizer.Unspecified
+ private static void AddTokenizerKey(
+ JsonObject node,
+ Bm25Tokenizer tokenizer,
+ int minGram,
+ int maxGram,
+ bool prefixOnly,
+ string regexPattern,
+ Bm25Language stemmer,
+ Bm25Language stopwordsLanguage
+ )
+ {
+ var hasAnySetting =
+ tokenizer != Bm25Tokenizer.Unspecified
|| stemmer != Bm25Language.Unspecified
|| stopwordsLanguage != Bm25Language.Unspecified;
- if (!hasAnySetting) return;
+ if (!hasAnySetting)
+ return;
- var effectiveTokenizer = tokenizer == Bm25Tokenizer.Unspecified
- ? Bm25Tokenizer.Default
- : tokenizer;
+ var effectiveTokenizer =
+ tokenizer == Bm25Tokenizer.Unspecified ? Bm25Tokenizer.Default : tokenizer;
- var tok = new JsonObject {
- ["type"] = effectiveTokenizer.ToParadeDbString(),
- };
+ var tok = new JsonObject { ["type"] = effectiveTokenizer.ToParadeDbString() };
- if (effectiveTokenizer == Bm25Tokenizer.Ngram) {
+ if (effectiveTokenizer == Bm25Tokenizer.Ngram)
+ {
tok["min_gram"] = minGram;
tok["max_gram"] = maxGram;
tok["prefix_only"] = prefixOnly;
}
- if (effectiveTokenizer == Bm25Tokenizer.Regex) {
+ if (effectiveTokenizer == Bm25Tokenizer.Regex)
+ {
tok["pattern"] = regexPattern;
}
- if (stemmer != Bm25Language.Unspecified) tok["stemmer"] = stemmer.ToParadeDbString();
- if (stopwordsLanguage != Bm25Language.Unspecified) tok["stopwords_language"] = stopwordsLanguage.ToParadeDbString();
+ if (stemmer != Bm25Language.Unspecified)
+ tok["stemmer"] = stemmer.ToParadeDbString();
+ if (stopwordsLanguage != Bm25Language.Unspecified)
+ tok["stopwords_language"] = stopwordsLanguage.ToParadeDbString();
node["tokenizer"] = tok;
}
- private static void AddSharedKeys(JsonObject node, bool fast, Bm25Record record, bool indexed, bool fieldnorms) {
- if (fast) node["fast"] = true;
- if (record != Bm25Record.Unspecified) node["record"] = record.ToParadeDbString();
- if (!indexed) node["indexed"] = false;
- if (!fieldnorms) node["fieldnorms"] = false;
+ private static void AddSharedKeys(
+ JsonObject node,
+ bool fast,
+ Bm25Record record,
+ bool indexed,
+ bool fieldnorms
+ )
+ {
+ if (fast)
+ node["fast"] = true;
+ if (record != Bm25Record.Unspecified)
+ node["record"] = record.ToParadeDbString();
+ if (!indexed)
+ node["indexed"] = false;
+ if (!fieldnorms)
+ node["fieldnorms"] = false;
}
- private static void ValidateTokenizerParameters(string propertyName, Bm25Tokenizer tokenizer,
- int minGram, int maxGram, bool prefixOnly, string regexPattern) {
+ private static void ValidateTokenizerParameters(
+ string propertyName,
+ Bm25Tokenizer tokenizer,
+ int minGram,
+ int maxGram,
+ bool prefixOnly,
+ string regexPattern
+ )
+ {
var hasNgramParam = minGram != 0 || maxGram != 0 || prefixOnly;
var hasRegexParam = regexPattern is not null;
- if (hasNgramParam && tokenizer != Bm25Tokenizer.Ngram) {
+ if (hasNgramParam && tokenizer != Bm25Tokenizer.Ngram)
+ {
throw new InvalidOperationException(
- $"Property '{propertyName}': MinGram/MaxGram/PrefixOnly require Tokenizer = Bm25Tokenizer.Ngram.");
+ $"Property '{propertyName}': MinGram/MaxGram/PrefixOnly require Tokenizer = Bm25Tokenizer.Ngram."
+ );
}
- if (tokenizer == Bm25Tokenizer.Ngram && (minGram == 0 || maxGram == 0)) {
+ if (tokenizer == Bm25Tokenizer.Ngram && (minGram == 0 || maxGram == 0))
+ {
throw new InvalidOperationException(
- $"Property '{propertyName}': Tokenizer = Bm25Tokenizer.Ngram requires both MinGram and MaxGram (> 0).");
+ $"Property '{propertyName}': Tokenizer = Bm25Tokenizer.Ngram requires both MinGram and MaxGram (> 0)."
+ );
}
- if (hasRegexParam && tokenizer != Bm25Tokenizer.Regex) {
+ if (hasRegexParam && tokenizer != Bm25Tokenizer.Regex)
+ {
throw new InvalidOperationException(
- $"Property '{propertyName}': RegexPattern requires Tokenizer = Bm25Tokenizer.Regex.");
+ $"Property '{propertyName}': RegexPattern requires Tokenizer = Bm25Tokenizer.Regex."
+ );
}
- if (tokenizer == Bm25Tokenizer.Regex && !hasRegexParam) {
+ if (tokenizer == Bm25Tokenizer.Regex && !hasRegexParam)
+ {
throw new InvalidOperationException(
- $"Property '{propertyName}': Tokenizer = Bm25Tokenizer.Regex requires a RegexPattern.");
+ $"Property '{propertyName}': Tokenizer = Bm25Tokenizer.Regex requires a RegexPattern."
+ );
}
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbBooleanQuery.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbBooleanQuery.cs
index 05aa9db..4bbfb53 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbBooleanQuery.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbBooleanQuery.cs
@@ -5,40 +5,50 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
///
/// Mutable builder for boolean query clauses used within .
///
-public sealed class ParadeDbBooleanQuery {
+public sealed class ParadeDbBooleanQuery
+{
internal List MustClauses { get; } = [];
internal List ShouldClauses { get; } = [];
internal List MustNotClauses { get; } = [];
/// Adds must (AND) clauses.
- public ParadeDbBooleanQuery Must(params ParadeDbJsonQuery[] queries) {
+ public ParadeDbBooleanQuery Must(params ParadeDbJsonQuery[] queries)
+ {
MustClauses.AddRange(queries);
return this;
}
/// Adds should (OR) clauses.
- public ParadeDbBooleanQuery Should(params ParadeDbJsonQuery[] queries) {
+ public ParadeDbBooleanQuery Should(params ParadeDbJsonQuery[] queries)
+ {
ShouldClauses.AddRange(queries);
return this;
}
/// Adds must_not (NOT) clauses.
- public ParadeDbBooleanQuery MustNot(params ParadeDbJsonQuery[] queries) {
+ public ParadeDbBooleanQuery MustNot(params ParadeDbJsonQuery[] queries)
+ {
MustNotClauses.AddRange(queries);
return this;
}
- internal JsonNode ToJsonNode() {
+ internal JsonNode ToJsonNode()
+ {
var inner = new JsonObject();
- if (MustClauses.Count > 0) inner["must"] = ToArray(MustClauses);
- if (ShouldClauses.Count > 0) inner["should"] = ToArray(ShouldClauses);
- if (MustNotClauses.Count > 0) inner["must_not"] = ToArray(MustNotClauses);
+ if (MustClauses.Count > 0)
+ inner["must"] = ToArray(MustClauses);
+ if (ShouldClauses.Count > 0)
+ inner["should"] = ToArray(ShouldClauses);
+ if (MustNotClauses.Count > 0)
+ inner["must_not"] = ToArray(MustNotClauses);
return new JsonObject { ["boolean"] = inner };
}
- private static JsonArray ToArray(List queries) {
+ private static JsonArray ToArray(List queries)
+ {
var arr = new JsonArray();
- foreach (var q in queries) arr.Add(q.CloneNode());
+ foreach (var q in queries)
+ arr.Add(q.CloneNode());
return arr;
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbConventionSetPlugin.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbConventionSetPlugin.cs
index 4bb885e..db32c50 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbConventionSetPlugin.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbConventionSetPlugin.cs
@@ -3,8 +3,10 @@
namespace Equibles.ParadeDB.EntityFrameworkCore;
-public sealed class ParadeDbConventionSetPlugin : IConventionSetPlugin {
- public ConventionSet ModifyConventions(ConventionSet conventionSet) {
+public sealed class ParadeDbConventionSetPlugin : IConventionSetPlugin
+{
+ public ConventionSet ModifyConventions(ConventionSet conventionSet)
+ {
conventionSet.ModelFinalizingConventions.Add(new ParadeDbModelFinalizingConvention());
return conventionSet;
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbDbContextOptionsBuilderExtensions.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbDbContextOptionsBuilderExtensions.cs
index 29639c4..e30cedf 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbDbContextOptionsBuilderExtensions.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbDbContextOptionsBuilderExtensions.cs
@@ -5,17 +5,27 @@
namespace Equibles.ParadeDB.EntityFrameworkCore;
-public static class ParadeDbDbContextOptionsBuilderExtensions {
- public static NpgsqlDbContextOptionsBuilder UseParadeDb(this NpgsqlDbContextOptionsBuilder npgsqlBuilder) {
- var builder = ((IRelationalDbContextOptionsBuilderInfrastructure)npgsqlBuilder).OptionsBuilder;
+public static class ParadeDbDbContextOptionsBuilderExtensions
+{
+ public static NpgsqlDbContextOptionsBuilder UseParadeDb(
+ this NpgsqlDbContextOptionsBuilder npgsqlBuilder
+ )
+ {
+ var builder = (
+ (IRelationalDbContextOptionsBuilderInfrastructure)npgsqlBuilder
+ ).OptionsBuilder;
- var extension = builder.Options.FindExtension()
- ?? new ParadeDbDbContextOptionsExtension();
+ var extension =
+ builder.Options.FindExtension()
+ ?? new ParadeDbDbContextOptionsExtension();
((IDbContextOptionsBuilderInfrastructure)builder).AddOrUpdateExtension(extension);
builder.ReplaceService();
- builder.ReplaceService();
+ builder.ReplaceService<
+ IRelationalParameterBasedSqlProcessorFactory,
+ ParadeDbParameterBasedSqlProcessorFactory
+ >();
return npgsqlBuilder;
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbDbContextOptionsExtension.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbDbContextOptionsExtension.cs
index 3ed6e65..89b784e 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbDbContextOptionsExtension.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbDbContextOptionsExtension.cs
@@ -5,10 +5,12 @@
namespace Equibles.ParadeDB.EntityFrameworkCore;
-public sealed class ParadeDbDbContextOptionsExtension : IDbContextOptionsExtension {
+public sealed class ParadeDbDbContextOptionsExtension : IDbContextOptionsExtension
+{
public DbContextOptionsExtensionInfo Info => new ParadeDbExtensionInfo(this);
- public void ApplyServices(IServiceCollection services) {
+ public void ApplyServices(IServiceCollection services)
+ {
new EntityFrameworkRelationalServicesBuilder(services)
.TryAdd()
.TryAdd();
@@ -16,13 +18,20 @@ public void ApplyServices(IServiceCollection services) {
public void Validate(IDbContextOptions options) { }
- private sealed class ParadeDbExtensionInfo : DbContextOptionsExtensionInfo {
- public ParadeDbExtensionInfo(IDbContextOptionsExtension extension) : base(extension) { }
+ private sealed class ParadeDbExtensionInfo : DbContextOptionsExtensionInfo
+ {
+ public ParadeDbExtensionInfo(IDbContextOptionsExtension extension)
+ : base(extension) { }
public override bool IsDatabaseProvider => false;
public override string LogFragment => "using ParadeDB ";
+
public override int GetServiceProviderHashCode() => 0;
- public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) => other is ParadeDbExtensionInfo;
- public override void PopulateDebugInfo(IDictionary debugInfo) => debugInfo["ParadeDB:BM25"] = "1";
+
+ public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) =>
+ other is ParadeDbExtensionInfo;
+
+ public override void PopulateDebugInfo(IDictionary debugInfo) =>
+ debugInfo["ParadeDB:BM25"] = "1";
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbFunctions.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbFunctions.cs
index 4bf1acb..243f19c 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbFunctions.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbFunctions.cs
@@ -6,7 +6,8 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
/// Provides CLR methods that translate to ParadeDB pg_search SQL operators and functions.
/// These methods can only be used in EF Core LINQ queries — they have no in-memory implementation.
///
-public static class ParadeDbFunctions {
+public static class ParadeDbFunctions
+{
private const string OnlyInLinq = "This method can only be used in EF Core LINQ queries.";
// ── Basic Search ──────────────────────────────────────────────────
@@ -15,15 +16,15 @@ public static class ParadeDbFunctions {
/// BM25 disjunction match (OR). Translates to: column ||| 'query'.
/// Matches documents containing any of the query terms.
///
- public static bool Matches(this DbFunctions _, string column, string query)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool Matches(this DbFunctions _, string column, string query) =>
+ throw new InvalidOperationException(OnlyInLinq);
///
/// BM25 conjunction match (AND). Translates to: column &&& 'query'.
/// Matches documents containing all of the query terms.
///
- public static bool MatchesAll(this DbFunctions _, string column, string query)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesAll(this DbFunctions _, string column, string query) =>
+ throw new InvalidOperationException(OnlyInLinq);
// ── Phrase Search ─────────────────────────────────────────────────
@@ -31,15 +32,15 @@ public static bool MatchesAll(this DbFunctions _, string column, string query)
/// Phrase match. Translates to: column ### 'query'.
/// Matches terms in exact order.
///
- public static bool MatchesPhrase(this DbFunctions _, string column, string query)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesPhrase(this DbFunctions _, string column, string query) =>
+ throw new InvalidOperationException(OnlyInLinq);
///
/// Phrase match with slop. Translates to: column ### 'query'::pdb.slop(N).
/// Allows N words between terms or transposition of adjacent terms.
///
- public static bool MatchesPhrase(this DbFunctions _, string column, string query, int slop)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesPhrase(this DbFunctions _, string column, string query, int slop) =>
+ throw new InvalidOperationException(OnlyInLinq);
// ── Term Search ───────────────────────────────────────────────────
@@ -47,15 +48,15 @@ public static bool MatchesPhrase(this DbFunctions _, string column, string query
/// Exact term match. Translates to: column === 'query'.
/// The query is NOT tokenized — no stemming or lowercasing is applied.
///
- public static bool MatchesTerm(this DbFunctions _, string column, string query)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesTerm(this DbFunctions _, string column, string query) =>
+ throw new InvalidOperationException(OnlyInLinq);
///
/// Multi-term match. Translates to: column === ARRAY['a', 'b'].
/// Matches any of the exact terms.
///
- public static bool MatchesTermSet(this DbFunctions _, string column, params string[] terms)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesTermSet(this DbFunctions _, string column, params string[] terms) =>
+ throw new InvalidOperationException(OnlyInLinq);
// ── Fuzzy Search (Levenshtein Distance) ───────────────────────────
@@ -63,43 +64,70 @@ public static bool MatchesTermSet(this DbFunctions _, string column, params stri
/// Fuzzy OR match. Translates to: column ||| 'query'::pdb.fuzzy(distance).
/// Tolerates typos by allowing up to N single-character edits. Max distance is 2.
///
- public static bool MatchesFuzzy(this DbFunctions _, string column, string query, int distance)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesFuzzy(
+ this DbFunctions _,
+ string column,
+ string query,
+ int distance
+ ) => throw new InvalidOperationException(OnlyInLinq);
///
/// Fuzzy OR match with options. Translates to: column @@@ pdb.match('query', distance => D, transposition_cost_one => T, prefix => P).
/// exempts the initial substring from edit distance.
/// counts swapping two adjacent characters as one edit.
///
- public static bool MatchesFuzzy(this DbFunctions _, string column, string query, int distance,
- bool prefix, bool transpositionCostOne)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesFuzzy(
+ this DbFunctions _,
+ string column,
+ string query,
+ int distance,
+ bool prefix,
+ bool transpositionCostOne
+ ) => throw new InvalidOperationException(OnlyInLinq);
///
/// Fuzzy AND match. Translates to: column &&& 'query'::pdb.fuzzy(distance).
///
- public static bool MatchesAllFuzzy(this DbFunctions _, string column, string query, int distance)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesAllFuzzy(
+ this DbFunctions _,
+ string column,
+ string query,
+ int distance
+ ) => throw new InvalidOperationException(OnlyInLinq);
///
/// Fuzzy AND match with options. Translates to: column @@@ pdb.match('query', distance => D, transposition_cost_one => T, prefix => P, conjunction_mode => true).
///
- public static bool MatchesAllFuzzy(this DbFunctions _, string column, string query, int distance,
- bool prefix, bool transpositionCostOne)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesAllFuzzy(
+ this DbFunctions _,
+ string column,
+ string query,
+ int distance,
+ bool prefix,
+ bool transpositionCostOne
+ ) => throw new InvalidOperationException(OnlyInLinq);
///
/// Fuzzy term match. Translates to: column === 'query'::pdb.fuzzy(distance).
///
- public static bool MatchesTermFuzzy(this DbFunctions _, string column, string query, int distance)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesTermFuzzy(
+ this DbFunctions _,
+ string column,
+ string query,
+ int distance
+ ) => throw new InvalidOperationException(OnlyInLinq);
///
/// Fuzzy term match with options. Translates to: column @@@ pdb.fuzzy_term('query', distance, transposition_cost_one, prefix).
///
- public static bool MatchesTermFuzzy(this DbFunctions _, string column, string query, int distance,
- bool prefix, bool transpositionCostOne)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesTermFuzzy(
+ this DbFunctions _,
+ string column,
+ string query,
+ int distance,
+ bool prefix,
+ bool transpositionCostOne
+ ) => throw new InvalidOperationException(OnlyInLinq);
// ── Boost ─────────────────────────────────────────────────────────
@@ -107,28 +135,46 @@ public static bool MatchesTermFuzzy(this DbFunctions _, string column, string qu
/// Boosted OR match. Translates to: column ||| 'query'::pdb.boost(factor).
/// Increases the BM25 relevance weight. Factor range: -2048 to 2048.
///
- public static bool MatchesBoosted(this DbFunctions _, string column, string query, double boost)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesBoosted(
+ this DbFunctions _,
+ string column,
+ string query,
+ double boost
+ ) => throw new InvalidOperationException(OnlyInLinq);
///
/// Boosted AND match. Translates to: column &&& 'query'::pdb.boost(factor).
///
- public static bool MatchesAllBoosted(this DbFunctions _, string column, string query, double boost)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesAllBoosted(
+ this DbFunctions _,
+ string column,
+ string query,
+ double boost
+ ) => throw new InvalidOperationException(OnlyInLinq);
// ── Fuzzy + Boost Combined ────────────────────────────────────────
///
/// Fuzzy OR match with boost. Translates to: column ||| 'query'::pdb.fuzzy(distance)::pdb.boost(factor).
///
- public static bool MatchesFuzzyBoosted(this DbFunctions _, string column, string query, int distance, double boost)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesFuzzyBoosted(
+ this DbFunctions _,
+ string column,
+ string query,
+ int distance,
+ double boost
+ ) => throw new InvalidOperationException(OnlyInLinq);
///
/// Fuzzy AND match with boost. Translates to: column &&& 'query'::pdb.fuzzy(distance)::pdb.boost(factor).
///
- public static bool MatchesAllFuzzyBoosted(this DbFunctions _, string column, string query, int distance, double boost)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MatchesAllFuzzyBoosted(
+ this DbFunctions _,
+ string column,
+ string query,
+ int distance,
+ double boost
+ ) => throw new InvalidOperationException(OnlyInLinq);
// ── BM25 Scoring ──────────────────────────────────────────────────
@@ -136,8 +182,8 @@ public static bool MatchesAllFuzzyBoosted(this DbFunctions _, string column, str
/// BM25 relevance score. Translates to: pdb.score(key_field).
/// Returns the BM25 relevance score for the matched document.
///
- public static double Score(this DbFunctions _, object keyField)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static double Score(this DbFunctions _, object keyField) =>
+ throw new InvalidOperationException(OnlyInLinq);
// ── Snippets ──────────────────────────────────────────────────────
@@ -145,21 +191,31 @@ public static double Score(this DbFunctions _, object keyField)
/// Basic snippet. Translates to: pdb.snippet(column).
/// Returns a text excerpt with matched terms highlighted.
///
- public static string Snippet(this DbFunctions _, string column)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static string Snippet(this DbFunctions _, string column) =>
+ throw new InvalidOperationException(OnlyInLinq);
///
/// Parameterized snippet. Translates to: pdb.snippet(column, start_tag => '...', end_tag => '...', max_num_chars => N).
///
- public static string Snippet(this DbFunctions _, string column, string startTag, string endTag, int maxNumChars)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static string Snippet(
+ this DbFunctions _,
+ string column,
+ string startTag,
+ string endTag,
+ int maxNumChars
+ ) => throw new InvalidOperationException(OnlyInLinq);
///
/// Multiple snippets. Translates to: pdb.snippets(column, max_num_chars => N, "limit" => L, "offset" => O).
/// Returns a text[] of highlighted excerpts.
///
- public static string[] Snippets(this DbFunctions _, string column, int maxNumChars, int limit, int offset)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static string[] Snippets(
+ this DbFunctions _,
+ string column,
+ int maxNumChars,
+ int limit,
+ int offset
+ ) => throw new InvalidOperationException(OnlyInLinq);
// ── Parse Query (Tantivy Syntax) ──────────────────────────────────
@@ -167,15 +223,20 @@ public static string[] Snippets(this DbFunctions _, string column, int maxNumCha
/// Parse query. Translates to: key @@@ pdb.parse('query').
/// Full query parser supporting field:value, boolean operators, ranges, and wildcards.
///
- public static bool Parse(this DbFunctions _, object keyField, string query)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool Parse(this DbFunctions _, object keyField, string query) =>
+ throw new InvalidOperationException(OnlyInLinq);
///
/// Parse query with options. Translates to: key @@@ pdb.parse('query', lenient => true, conjunction_mode => true).
/// : ignores syntax errors. : defaults terms to AND.
///
- public static bool Parse(this DbFunctions _, object keyField, string query, bool lenient, bool conjunctionMode)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool Parse(
+ this DbFunctions _,
+ object keyField,
+ string query,
+ bool lenient,
+ bool conjunctionMode
+ ) => throw new InvalidOperationException(OnlyInLinq);
// ── Regex Search ──────────────────────────────────────────────────
@@ -183,8 +244,8 @@ public static bool Parse(this DbFunctions _, object keyField, string query, bool
/// Regex match. Translates to: column @@@ pdb.regex('pattern').
/// Matches indexed tokens against a regular expression (Rust regex syntax).
///
- public static bool Regex(this DbFunctions _, string column, string pattern)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool Regex(this DbFunctions _, string column, string pattern) =>
+ throw new InvalidOperationException(OnlyInLinq);
// ── Phrase Prefix ─────────────────────────────────────────────────
@@ -192,14 +253,18 @@ public static bool Regex(this DbFunctions _, string column, string pattern)
/// Phrase prefix match. Translates to: column @@@ pdb.phrase_prefix(ARRAY['term1', 'term2']).
/// The last term is treated as a prefix — useful for autocomplete/type-ahead.
///
- public static bool PhrasePrefix(this DbFunctions _, string column, params string[] terms)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool PhrasePrefix(this DbFunctions _, string column, params string[] terms) =>
+ throw new InvalidOperationException(OnlyInLinq);
///
/// Phrase prefix match with max expansions. Translates to: column @@@ pdb.phrase_prefix(ARRAY[...], max_expansion => N).
///
- public static bool PhrasePrefix(this DbFunctions _, string column, int maxExpansions, params string[] terms)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool PhrasePrefix(
+ this DbFunctions _,
+ string column,
+ int maxExpansions,
+ params string[] terms
+ ) => throw new InvalidOperationException(OnlyInLinq);
// ── More Like This ────────────────────────────────────────────────
@@ -207,14 +272,18 @@ public static bool PhrasePrefix(this DbFunctions _, string column, int maxExpans
/// More-like-this search. Translates to: key @@@ pdb.more_like_this(documentId).
/// Finds documents similar to a given document.
///
- public static bool MoreLikeThis(this DbFunctions _, object keyField, int documentId)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MoreLikeThis(this DbFunctions _, object keyField, int documentId) =>
+ throw new InvalidOperationException(OnlyInLinq);
///
/// More-like-this with field restriction. Translates to: key @@@ pdb.more_like_this(documentId, ARRAY['field1', ...]).
///
- public static bool MoreLikeThis(this DbFunctions _, object keyField, int documentId, params string[] fields)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool MoreLikeThis(
+ this DbFunctions _,
+ object keyField,
+ int documentId,
+ params string[] fields
+ ) => throw new InvalidOperationException(OnlyInLinq);
// ── JSON Query Search ────────────────────────────────────────────
@@ -223,6 +292,6 @@ public static bool MoreLikeThis(this DbFunctions _, object keyField, int documen
/// Executes a structured query using ParadeDB's JSON query syntax.
/// Build the query string using .
///
- public static bool JsonSearch(this DbFunctions _, object keyField, string jsonQuery)
- => throw new InvalidOperationException(OnlyInLinq);
+ public static bool JsonSearch(this DbFunctions _, object keyField, string jsonQuery) =>
+ throw new InvalidOperationException(OnlyInLinq);
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbJsonQuery.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbJsonQuery.cs
index 0904add..ef18e67 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbJsonQuery.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbJsonQuery.cs
@@ -6,17 +6,18 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
/// Builds a ParadeDB JSON query for use with the @@@ operator.
/// Use static factory methods to create query nodes, then call to serialize.
///
-public sealed class ParadeDbJsonQuery {
+public sealed class ParadeDbJsonQuery
+{
private readonly JsonNode _node;
- internal ParadeDbJsonQuery(JsonNode node) {
+ internal ParadeDbJsonQuery(JsonNode node)
+ {
_node = node;
}
/// Serializes the query to a compact JSON string.
- public string ToJson() => _node.ToJsonString(new System.Text.Json.JsonSerializerOptions {
- WriteIndented = false
- });
+ public string ToJson() =>
+ _node.ToJsonString(new System.Text.Json.JsonSerializerOptions { WriteIndented = false });
///
public override string ToString() => ToJson();
@@ -35,11 +36,13 @@ public static ParadeDbJsonQuery Parse(string queryString) =>
///
/// Parse query with options. Produces: {"parse":{"query_string":"...","lenient":true,"conjunction_mode":true}}.
///
- public static ParadeDbJsonQuery Parse(string queryString, bool lenient, bool conjunctionMode) {
- var inner = new JsonObject {
+ public static ParadeDbJsonQuery Parse(string queryString, bool lenient, bool conjunctionMode)
+ {
+ var inner = new JsonObject
+ {
["query_string"] = queryString,
["lenient"] = lenient,
- ["conjunction_mode"] = conjunctionMode
+ ["conjunction_mode"] = conjunctionMode,
};
return new(new JsonObject { ["parse"] = inner });
}
@@ -50,17 +53,29 @@ public static ParadeDbJsonQuery Parse(string queryString, bool lenient, bool con
/// Exact term match. Produces: {"term":{"field":"...","value":...}}.
///
public static ParadeDbJsonQuery Term(string field, object value) =>
- new(new JsonObject { ["term"] = new JsonObject { ["field"] = field, ["value"] = CreateJsonValue(value) } });
+ new(
+ new JsonObject
+ {
+ ["term"] = new JsonObject { ["field"] = field, ["value"] = CreateJsonValue(value) },
+ }
+ );
// ── Term Set ─────────────────────────────────────────────────────
///
/// Multi-term match. Produces: {"term_set":{"field":"...","terms":[...]}}.
///
- public static ParadeDbJsonQuery TermSet(string field, params object[] terms) {
+ public static ParadeDbJsonQuery TermSet(string field, params object[] terms)
+ {
var arr = new JsonArray();
- foreach (var t in terms) arr.Add(CreateJsonValue(t));
- return new(new JsonObject { ["term_set"] = new JsonObject { ["field"] = field, ["terms"] = arr } });
+ foreach (var t in terms)
+ arr.Add(CreateJsonValue(t));
+ return new(
+ new JsonObject
+ {
+ ["term_set"] = new JsonObject { ["field"] = field, ["terms"] = arr },
+ }
+ );
}
// ── Match ────────────────────────────────────────────────────────
@@ -69,17 +84,29 @@ public static ParadeDbJsonQuery TermSet(string field, params object[] terms) {
/// Match query with field. Produces: {"match":{"field":"...","value":"..."}}.
///
public static ParadeDbJsonQuery Match(string value, string field) =>
- new(new JsonObject { ["match"] = new JsonObject { ["field"] = field, ["value"] = value } });
+ new(
+ new JsonObject
+ {
+ ["match"] = new JsonObject { ["field"] = field, ["value"] = value },
+ }
+ );
///
/// Match query with field and options.
///
- public static ParadeDbJsonQuery Match(string value, string field, int distance, bool conjunctionMode) {
- var inner = new JsonObject {
+ public static ParadeDbJsonQuery Match(
+ string value,
+ string field,
+ int distance,
+ bool conjunctionMode
+ )
+ {
+ var inner = new JsonObject
+ {
["field"] = field,
["value"] = value,
["distance"] = distance,
- ["conjunction_mode"] = conjunctionMode
+ ["conjunction_mode"] = conjunctionMode,
};
return new(new JsonObject { ["match"] = inner });
}
@@ -90,18 +117,36 @@ public static ParadeDbJsonQuery Match(string value, string field, int distance,
/// Fuzzy term match. Produces: {"fuzzy_term":{"field":"...","value":"...","distance":N}}.
///
public static ParadeDbJsonQuery FuzzyTerm(string field, string value, int distance) =>
- new(new JsonObject { ["fuzzy_term"] = new JsonObject { ["field"] = field, ["value"] = value, ["distance"] = distance } });
+ new(
+ new JsonObject
+ {
+ ["fuzzy_term"] = new JsonObject
+ {
+ ["field"] = field,
+ ["value"] = value,
+ ["distance"] = distance,
+ },
+ }
+ );
///
/// Fuzzy term match with full options.
///
- public static ParadeDbJsonQuery FuzzyTerm(string field, string value, int distance, bool prefix, bool transpositionCostOne) {
- var inner = new JsonObject {
+ public static ParadeDbJsonQuery FuzzyTerm(
+ string field,
+ string value,
+ int distance,
+ bool prefix,
+ bool transpositionCostOne
+ )
+ {
+ var inner = new JsonObject
+ {
["field"] = field,
["value"] = value,
["distance"] = distance,
["prefix"] = prefix,
- ["transposition_cost_one"] = transpositionCostOne
+ ["transposition_cost_one"] = transpositionCostOne,
};
return new(new JsonObject { ["fuzzy_term"] = inner });
}
@@ -111,19 +156,38 @@ public static ParadeDbJsonQuery FuzzyTerm(string field, string value, int distan
///
/// Phrase match. Produces: {"phrase":{"field":"...","phrases":[...]}}.
///
- public static ParadeDbJsonQuery Phrase(string field, params string[] phrases) {
+ public static ParadeDbJsonQuery Phrase(string field, params string[] phrases)
+ {
var arr = new JsonArray();
- foreach (var p in phrases) arr.Add(JsonValue.Create(p));
- return new(new JsonObject { ["phrase"] = new JsonObject { ["field"] = field, ["phrases"] = arr } });
+ foreach (var p in phrases)
+ arr.Add(JsonValue.Create(p));
+ return new(
+ new JsonObject
+ {
+ ["phrase"] = new JsonObject { ["field"] = field, ["phrases"] = arr },
+ }
+ );
}
///
/// Phrase match with slop.
///
- public static ParadeDbJsonQuery Phrase(string field, int slop, params string[] phrases) {
+ public static ParadeDbJsonQuery Phrase(string field, int slop, params string[] phrases)
+ {
var arr = new JsonArray();
- foreach (var p in phrases) arr.Add(JsonValue.Create(p));
- return new(new JsonObject { ["phrase"] = new JsonObject { ["field"] = field, ["phrases"] = arr, ["slop"] = slop } });
+ foreach (var p in phrases)
+ arr.Add(JsonValue.Create(p));
+ return new(
+ new JsonObject
+ {
+ ["phrase"] = new JsonObject
+ {
+ ["field"] = field,
+ ["phrases"] = arr,
+ ["slop"] = slop,
+ },
+ }
+ );
}
// ── Phrase Prefix ────────────────────────────────────────────────
@@ -131,10 +195,17 @@ public static ParadeDbJsonQuery Phrase(string field, int slop, params string[] p
///
/// Phrase prefix match. Produces: {"phrase_prefix":{"field":"...","phrases":[...]}}.
///
- public static ParadeDbJsonQuery PhrasePrefix(string field, params string[] phrases) {
+ public static ParadeDbJsonQuery PhrasePrefix(string field, params string[] phrases)
+ {
var arr = new JsonArray();
- foreach (var p in phrases) arr.Add(JsonValue.Create(p));
- return new(new JsonObject { ["phrase_prefix"] = new JsonObject { ["field"] = field, ["phrases"] = arr } });
+ foreach (var p in phrases)
+ arr.Add(JsonValue.Create(p));
+ return new(
+ new JsonObject
+ {
+ ["phrase_prefix"] = new JsonObject { ["field"] = field, ["phrases"] = arr },
+ }
+ );
}
// ── Regex ────────────────────────────────────────────────────────
@@ -143,7 +214,12 @@ public static ParadeDbJsonQuery PhrasePrefix(string field, params string[] phras
/// Regex match. Produces: {"regex":{"field":"...","pattern":"..."}}.
///
public static ParadeDbJsonQuery Regex(string field, string pattern) =>
- new(new JsonObject { ["regex"] = new JsonObject { ["field"] = field, ["pattern"] = pattern } });
+ new(
+ new JsonObject
+ {
+ ["regex"] = new JsonObject { ["field"] = field, ["pattern"] = pattern },
+ }
+ );
// ── Range ────────────────────────────────────────────────────────
@@ -153,18 +229,35 @@ public static ParadeDbJsonQuery Regex(string field, string pattern) =>
/// which pg_search requires — omitting the key throws a deserialization panic).
/// Set to true for DateTime range queries (adds "is_datetime":true).
///
- public static ParadeDbJsonQuery Range(string field, object lowerBound, object upperBound,
- bool lowerInclusive = true, bool upperInclusive = false, bool isDatetime = false) {
- var inner = new JsonObject {
+ public static ParadeDbJsonQuery Range(
+ string field,
+ object lowerBound,
+ object upperBound,
+ bool lowerInclusive = true,
+ bool upperInclusive = false,
+ bool isDatetime = false
+ )
+ {
+ var inner = new JsonObject
+ {
["field"] = field,
- ["lower_bound"] = lowerBound != null
- ? new JsonObject { [lowerInclusive ? "included" : "excluded"] = CreateJsonValue(lowerBound) }
- : null,
- ["upper_bound"] = upperBound != null
- ? new JsonObject { [upperInclusive ? "included" : "excluded"] = CreateJsonValue(upperBound) }
- : null
+ ["lower_bound"] =
+ lowerBound != null
+ ? new JsonObject
+ {
+ [lowerInclusive ? "included" : "excluded"] = CreateJsonValue(lowerBound),
+ }
+ : null,
+ ["upper_bound"] =
+ upperBound != null
+ ? new JsonObject
+ {
+ [upperInclusive ? "included" : "excluded"] = CreateJsonValue(upperBound),
+ }
+ : null,
};
- if (isDatetime) inner["is_datetime"] = true;
+ if (isDatetime)
+ inner["is_datetime"] = true;
return new(new JsonObject { ["range"] = inner });
}
@@ -174,7 +267,12 @@ public static ParadeDbJsonQuery Range(string field, object lowerBound, object up
/// Wraps a query with a boost factor. Produces: {"boost":{"query":{...},"factor":N}}.
///
public static ParadeDbJsonQuery Boost(ParadeDbJsonQuery query, double factor) =>
- new(new JsonObject { ["boost"] = new JsonObject { ["query"] = query.CloneNode(), ["factor"] = factor } });
+ new(
+ new JsonObject
+ {
+ ["boost"] = new JsonObject { ["query"] = query.CloneNode(), ["factor"] = factor },
+ }
+ );
// ── Const Score ──────────────────────────────────────────────────
@@ -182,7 +280,16 @@ public static ParadeDbJsonQuery Boost(ParadeDbJsonQuery query, double factor) =>
/// Wraps a query with a constant score. Produces: {"const_score":{"query":{...},"score":N}}.
///
public static ParadeDbJsonQuery ConstScore(ParadeDbJsonQuery query, double score) =>
- new(new JsonObject { ["const_score"] = new JsonObject { ["query"] = query.CloneNode(), ["score"] = score } });
+ new(
+ new JsonObject
+ {
+ ["const_score"] = new JsonObject
+ {
+ ["query"] = query.CloneNode(),
+ ["score"] = score,
+ },
+ }
+ );
// ── Exists ───────────────────────────────────────────────────────
@@ -197,17 +304,18 @@ public static ParadeDbJsonQuery Exists(string field) =>
///
/// Match all documents. Produces: {"all":null}.
///
- public static ParadeDbJsonQuery All() =>
- new(new JsonObject { ["all"] = null });
+ public static ParadeDbJsonQuery All() => new(new JsonObject { ["all"] = null });
// ── Disjunction Max ──────────────────────────────────────────────
///
/// Disjunction max query. Produces: {"disjunction_max":{"disjuncts":[...]}}.
///
- public static ParadeDbJsonQuery DisjunctionMax(params ParadeDbJsonQuery[] queries) {
+ public static ParadeDbJsonQuery DisjunctionMax(params ParadeDbJsonQuery[] queries)
+ {
var arr = new JsonArray();
- foreach (var q in queries) arr.Add(q.CloneNode());
+ foreach (var q in queries)
+ arr.Add(q.CloneNode());
return new(new JsonObject { ["disjunction_max"] = new JsonObject { ["disjuncts"] = arr } });
}
@@ -224,7 +332,8 @@ public static ParadeDbJsonQuery MoreLikeThis(int documentId) =>
///
/// Boolean query combining must/should/must_not clauses.
///
- public static ParadeDbJsonQuery Boolean(Action configure) {
+ public static ParadeDbJsonQuery Boolean(Action configure)
+ {
var builder = new ParadeDbBooleanQuery();
configure(builder);
return new(builder.ToJsonNode());
@@ -232,18 +341,20 @@ public static ParadeDbJsonQuery Boolean(Action configure)
// ── Helpers ──────────────────────────────────────────────────────
- internal static JsonNode CreateJsonValue(object value) => value switch {
- string s => JsonValue.Create(s),
- int i => JsonValue.Create(i),
- long l => JsonValue.Create(l),
- double d => JsonValue.Create(d),
- float f => JsonValue.Create(f),
- bool b => JsonValue.Create(b),
- Guid g => JsonValue.Create(g.ToString()),
- DateTime dt => JsonValue.Create(dt.Kind == DateTimeKind.Utc
- ? dt.ToString("yyyy-MM-ddTHH:mm:ssZ")
- : dt.ToString("O")),
- Enum e => JsonValue.Create(Convert.ToInt32(e)),
- _ => JsonValue.Create(value.ToString())
- };
+ internal static JsonNode CreateJsonValue(object value) =>
+ value switch
+ {
+ string s => JsonValue.Create(s),
+ int i => JsonValue.Create(i),
+ long l => JsonValue.Create(l),
+ double d => JsonValue.Create(d),
+ float f => JsonValue.Create(f),
+ bool b => JsonValue.Create(b),
+ Guid g => JsonValue.Create(g.ToString()),
+ DateTime dt => JsonValue.Create(
+ dt.Kind == DateTimeKind.Utc ? dt.ToString("yyyy-MM-ddTHH:mm:ssZ") : dt.ToString("O")
+ ),
+ Enum e => JsonValue.Create(Convert.ToInt32(e)),
+ _ => JsonValue.Create(value.ToString()),
+ };
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbMethodCallTranslator.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbMethodCallTranslator.cs
index 9f8591c..9987ac5 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbMethodCallTranslator.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbMethodCallTranslator.cs
@@ -8,114 +8,208 @@
namespace Equibles.ParadeDB.EntityFrameworkCore;
-public sealed class ParadeDbMethodCallTranslator : IMethodCallTranslator {
+public sealed class ParadeDbMethodCallTranslator : IMethodCallTranslator
+{
private readonly ISqlExpressionFactory _sql;
private readonly IRelationalTypeMappingSource _typeMappingSource;
// ── Basic Search ──────────────────────────────────────────────────
- private static readonly MethodInfo MatchesMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.Matches), [typeof(DbFunctions), typeof(string), typeof(string)])!;
+ private static readonly MethodInfo MatchesMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.Matches),
+ [typeof(DbFunctions), typeof(string), typeof(string)]
+ )!;
- private static readonly MethodInfo MatchesAllMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesAll), [typeof(DbFunctions), typeof(string), typeof(string)])!;
+ private static readonly MethodInfo MatchesAllMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesAll),
+ [typeof(DbFunctions), typeof(string), typeof(string)]
+ )!;
// ── Phrase Search ─────────────────────────────────────────────────
- private static readonly MethodInfo MatchesPhraseMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesPhrase), [typeof(DbFunctions), typeof(string), typeof(string)])!;
+ private static readonly MethodInfo MatchesPhraseMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesPhrase),
+ [typeof(DbFunctions), typeof(string), typeof(string)]
+ )!;
- private static readonly MethodInfo MatchesPhraseSlopMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesPhrase), [typeof(DbFunctions), typeof(string), typeof(string), typeof(int)])!;
+ private static readonly MethodInfo MatchesPhraseSlopMethod =
+ typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesPhrase),
+ [typeof(DbFunctions), typeof(string), typeof(string), typeof(int)]
+ )!;
// ── Term Search ───────────────────────────────────────────────────
- private static readonly MethodInfo MatchesTermMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesTerm), [typeof(DbFunctions), typeof(string), typeof(string)])!;
+ private static readonly MethodInfo MatchesTermMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesTerm),
+ [typeof(DbFunctions), typeof(string), typeof(string)]
+ )!;
- private static readonly MethodInfo MatchesTermSetMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesTermSet), [typeof(DbFunctions), typeof(string), typeof(string[])])!;
+ private static readonly MethodInfo MatchesTermSetMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesTermSet),
+ [typeof(DbFunctions), typeof(string), typeof(string[])]
+ )!;
// ── Fuzzy Search ──────────────────────────────────────────────────
- private static readonly MethodInfo MatchesFuzzyMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesFuzzy), [typeof(DbFunctions), typeof(string), typeof(string), typeof(int)])!;
-
- private static readonly MethodInfo MatchesFuzzyFullMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesFuzzy), [typeof(DbFunctions), typeof(string), typeof(string), typeof(int), typeof(bool), typeof(bool)])!;
-
- private static readonly MethodInfo MatchesAllFuzzyMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesAllFuzzy), [typeof(DbFunctions), typeof(string), typeof(string), typeof(int)])!;
-
- private static readonly MethodInfo MatchesAllFuzzyFullMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesAllFuzzy), [typeof(DbFunctions), typeof(string), typeof(string), typeof(int), typeof(bool), typeof(bool)])!;
-
- private static readonly MethodInfo MatchesTermFuzzyMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesTermFuzzy), [typeof(DbFunctions), typeof(string), typeof(string), typeof(int)])!;
-
- private static readonly MethodInfo MatchesTermFuzzyFullMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesTermFuzzy), [typeof(DbFunctions), typeof(string), typeof(string), typeof(int), typeof(bool), typeof(bool)])!;
+ private static readonly MethodInfo MatchesFuzzyMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesFuzzy),
+ [typeof(DbFunctions), typeof(string), typeof(string), typeof(int)]
+ )!;
+
+ private static readonly MethodInfo MatchesFuzzyFullMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesFuzzy),
+ [
+ typeof(DbFunctions),
+ typeof(string),
+ typeof(string),
+ typeof(int),
+ typeof(bool),
+ typeof(bool),
+ ]
+ )!;
+
+ private static readonly MethodInfo MatchesAllFuzzyMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesAllFuzzy),
+ [typeof(DbFunctions), typeof(string), typeof(string), typeof(int)]
+ )!;
+
+ private static readonly MethodInfo MatchesAllFuzzyFullMethod =
+ typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesAllFuzzy),
+ [
+ typeof(DbFunctions),
+ typeof(string),
+ typeof(string),
+ typeof(int),
+ typeof(bool),
+ typeof(bool),
+ ]
+ )!;
+
+ private static readonly MethodInfo MatchesTermFuzzyMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesTermFuzzy),
+ [typeof(DbFunctions), typeof(string), typeof(string), typeof(int)]
+ )!;
+
+ private static readonly MethodInfo MatchesTermFuzzyFullMethod =
+ typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesTermFuzzy),
+ [
+ typeof(DbFunctions),
+ typeof(string),
+ typeof(string),
+ typeof(int),
+ typeof(bool),
+ typeof(bool),
+ ]
+ )!;
// ── Boost ─────────────────────────────────────────────────────────
- private static readonly MethodInfo MatchesBoostedMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesBoosted), [typeof(DbFunctions), typeof(string), typeof(string), typeof(double)])!;
+ private static readonly MethodInfo MatchesBoostedMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesBoosted),
+ [typeof(DbFunctions), typeof(string), typeof(string), typeof(double)]
+ )!;
- private static readonly MethodInfo MatchesAllBoostedMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesAllBoosted), [typeof(DbFunctions), typeof(string), typeof(string), typeof(double)])!;
+ private static readonly MethodInfo MatchesAllBoostedMethod =
+ typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesAllBoosted),
+ [typeof(DbFunctions), typeof(string), typeof(string), typeof(double)]
+ )!;
// ── Fuzzy + Boost Combined ────────────────────────────────────────
- private static readonly MethodInfo MatchesFuzzyBoostedMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesFuzzyBoosted), [typeof(DbFunctions), typeof(string), typeof(string), typeof(int), typeof(double)])!;
-
- private static readonly MethodInfo MatchesAllFuzzyBoostedMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MatchesAllFuzzyBoosted), [typeof(DbFunctions), typeof(string), typeof(string), typeof(int), typeof(double)])!;
+ private static readonly MethodInfo MatchesFuzzyBoostedMethod =
+ typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesFuzzyBoosted),
+ [typeof(DbFunctions), typeof(string), typeof(string), typeof(int), typeof(double)]
+ )!;
+
+ private static readonly MethodInfo MatchesAllFuzzyBoostedMethod =
+ typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MatchesAllFuzzyBoosted),
+ [typeof(DbFunctions), typeof(string), typeof(string), typeof(int), typeof(double)]
+ )!;
// ── BM25 Scoring ──────────────────────────────────────────────────
- private static readonly MethodInfo ScoreMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.Score), [typeof(DbFunctions), typeof(object)])!;
+ private static readonly MethodInfo ScoreMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.Score),
+ [typeof(DbFunctions), typeof(object)]
+ )!;
// ── Snippets ──────────────────────────────────────────────────────
- private static readonly MethodInfo SnippetMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.Snippet), [typeof(DbFunctions), typeof(string)])!;
+ private static readonly MethodInfo SnippetMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.Snippet),
+ [typeof(DbFunctions), typeof(string)]
+ )!;
- private static readonly MethodInfo SnippetParamsMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.Snippet), [typeof(DbFunctions), typeof(string), typeof(string), typeof(string), typeof(int)])!;
+ private static readonly MethodInfo SnippetParamsMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.Snippet),
+ [typeof(DbFunctions), typeof(string), typeof(string), typeof(string), typeof(int)]
+ )!;
- private static readonly MethodInfo SnippetsMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.Snippets), [typeof(DbFunctions), typeof(string), typeof(int), typeof(int), typeof(int)])!;
+ private static readonly MethodInfo SnippetsMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.Snippets),
+ [typeof(DbFunctions), typeof(string), typeof(int), typeof(int), typeof(int)]
+ )!;
// ── Parse Query ───────────────────────────────────────────────────
- private static readonly MethodInfo ParseMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.Parse), [typeof(DbFunctions), typeof(object), typeof(string)])!;
+ private static readonly MethodInfo ParseMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.Parse),
+ [typeof(DbFunctions), typeof(object), typeof(string)]
+ )!;
- private static readonly MethodInfo ParseFullMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.Parse), [typeof(DbFunctions), typeof(object), typeof(string), typeof(bool), typeof(bool)])!;
+ private static readonly MethodInfo ParseFullMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.Parse),
+ [typeof(DbFunctions), typeof(object), typeof(string), typeof(bool), typeof(bool)]
+ )!;
// ── Regex Search ──────────────────────────────────────────────────
- private static readonly MethodInfo RegexMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.Regex), [typeof(DbFunctions), typeof(string), typeof(string)])!;
+ private static readonly MethodInfo RegexMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.Regex),
+ [typeof(DbFunctions), typeof(string), typeof(string)]
+ )!;
// ── Phrase Prefix ─────────────────────────────────────────────────
- private static readonly MethodInfo PhrasePrefixMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.PhrasePrefix), [typeof(DbFunctions), typeof(string), typeof(string[])])!;
+ private static readonly MethodInfo PhrasePrefixMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.PhrasePrefix),
+ [typeof(DbFunctions), typeof(string), typeof(string[])]
+ )!;
- private static readonly MethodInfo PhrasePrefixMaxMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.PhrasePrefix), [typeof(DbFunctions), typeof(string), typeof(int), typeof(string[])])!;
+ private static readonly MethodInfo PhrasePrefixMaxMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.PhrasePrefix),
+ [typeof(DbFunctions), typeof(string), typeof(int), typeof(string[])]
+ )!;
// ── More Like This ────────────────────────────────────────────────
- private static readonly MethodInfo MoreLikeThisMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MoreLikeThis), [typeof(DbFunctions), typeof(object), typeof(int)])!;
+ private static readonly MethodInfo MoreLikeThisMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MoreLikeThis),
+ [typeof(DbFunctions), typeof(object), typeof(int)]
+ )!;
- private static readonly MethodInfo MoreLikeThisFieldsMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.MoreLikeThis), [typeof(DbFunctions), typeof(object), typeof(int), typeof(string[])])!;
+ private static readonly MethodInfo MoreLikeThisFieldsMethod =
+ typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.MoreLikeThis),
+ [typeof(DbFunctions), typeof(object), typeof(int), typeof(string[])]
+ )!;
// ── JSON Query Search ──────────────────────────────────────────
- private static readonly MethodInfo JsonSearchMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.JsonSearch), [typeof(DbFunctions), typeof(object), typeof(string)])!;
-
- public ParadeDbMethodCallTranslator(ISqlExpressionFactory sql, IRelationalTypeMappingSource typeMappingSource) {
+ private static readonly MethodInfo JsonSearchMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.JsonSearch),
+ [typeof(DbFunctions), typeof(object), typeof(string)]
+ )!;
+
+ public ParadeDbMethodCallTranslator(
+ ISqlExpressionFactory sql,
+ IRelationalTypeMappingSource typeMappingSource
+ )
+ {
_sql = sql;
_typeMappingSource = typeMappingSource;
}
- public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList arguments,
- IDiagnosticsLogger logger) {
+ public SqlExpression Translate(
+ SqlExpression instance,
+ MethodInfo method,
+ IReadOnlyList arguments,
+ IDiagnosticsLogger logger
+ )
+ {
// arguments[0] is always DbFunctions (ignored)
// ── Basic Search ──────────────────────────────────────────
@@ -130,7 +224,11 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadO
return MakeBinaryBool(arguments[1], "###", arguments[2]);
if (method == MatchesPhraseSlopMethod)
- return MakeBinaryBool(arguments[1], "###", WithModifier(arguments[2], BuildSlopSuffix(arguments[3])));
+ return MakeBinaryBool(
+ arguments[1],
+ "###",
+ WithModifier(arguments[2], BuildSlopSuffix(arguments[3]))
+ );
// ── Term Search ───────────────────────────────────────────
if (method == MatchesTermMethod)
@@ -141,137 +239,246 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadO
// ── Fuzzy Search ──────────────────────────────────────────
if (method == MatchesFuzzyMethod)
- return MakeBinaryBool(arguments[1], "|||", WithModifier(arguments[2], BuildFuzzySuffix(arguments[3])));
+ return MakeBinaryBool(
+ arguments[1],
+ "|||",
+ WithModifier(arguments[2], BuildFuzzySuffix(arguments[3]))
+ );
if (method == MatchesFuzzyFullMethod)
- return MakeBinaryBool(arguments[1], "@@@",
- BuildFuzzyMatchFunc(arguments[2], arguments[3], arguments[4], arguments[5], conjunctionMode: false));
+ return MakeBinaryBool(
+ arguments[1],
+ "@@@",
+ BuildFuzzyMatchFunc(
+ arguments[2],
+ arguments[3],
+ arguments[4],
+ arguments[5],
+ conjunctionMode: false
+ )
+ );
if (method == MatchesAllFuzzyMethod)
- return MakeBinaryBool(arguments[1], "&&&", WithModifier(arguments[2], BuildFuzzySuffix(arguments[3])));
+ return MakeBinaryBool(
+ arguments[1],
+ "&&&",
+ WithModifier(arguments[2], BuildFuzzySuffix(arguments[3]))
+ );
if (method == MatchesAllFuzzyFullMethod)
- return MakeBinaryBool(arguments[1], "@@@",
- BuildFuzzyMatchFunc(arguments[2], arguments[3], arguments[4], arguments[5], conjunctionMode: true));
+ return MakeBinaryBool(
+ arguments[1],
+ "@@@",
+ BuildFuzzyMatchFunc(
+ arguments[2],
+ arguments[3],
+ arguments[4],
+ arguments[5],
+ conjunctionMode: true
+ )
+ );
if (method == MatchesTermFuzzyMethod)
- return MakeBinaryBool(arguments[1], "===", WithModifier(arguments[2], BuildFuzzySuffix(arguments[3])));
+ return MakeBinaryBool(
+ arguments[1],
+ "===",
+ WithModifier(arguments[2], BuildFuzzySuffix(arguments[3]))
+ );
if (method == MatchesTermFuzzyFullMethod)
- return MakeBinaryBool(arguments[1], "@@@",
- BuildFuzzyTermFunc(arguments[2], arguments[3], arguments[4], arguments[5]));
+ return MakeBinaryBool(
+ arguments[1],
+ "@@@",
+ BuildFuzzyTermFunc(arguments[2], arguments[3], arguments[4], arguments[5])
+ );
// ── Boost ─────────────────────────────────────────────────
if (method == MatchesBoostedMethod)
- return MakeBinaryBool(arguments[1], "|||", WithModifier(arguments[2], BuildBoostSuffix(arguments[3])));
+ return MakeBinaryBool(
+ arguments[1],
+ "|||",
+ WithModifier(arguments[2], BuildBoostSuffix(arguments[3]))
+ );
if (method == MatchesAllBoostedMethod)
- return MakeBinaryBool(arguments[1], "&&&", WithModifier(arguments[2], BuildBoostSuffix(arguments[3])));
+ return MakeBinaryBool(
+ arguments[1],
+ "&&&",
+ WithModifier(arguments[2], BuildBoostSuffix(arguments[3]))
+ );
// ── Fuzzy + Boost Combined ────────────────────────────────
if (method == MatchesFuzzyBoostedMethod)
- return MakeBinaryBool(arguments[1], "|||",
- WithModifier(arguments[2], BuildFuzzySuffix(arguments[3]) + BuildBoostSuffix(arguments[4])));
+ return MakeBinaryBool(
+ arguments[1],
+ "|||",
+ WithModifier(
+ arguments[2],
+ BuildFuzzySuffix(arguments[3]) + BuildBoostSuffix(arguments[4])
+ )
+ );
if (method == MatchesAllFuzzyBoostedMethod)
- return MakeBinaryBool(arguments[1], "&&&",
- WithModifier(arguments[2], BuildFuzzySuffix(arguments[3]) + BuildBoostSuffix(arguments[4])));
+ return MakeBinaryBool(
+ arguments[1],
+ "&&&",
+ WithModifier(
+ arguments[2],
+ BuildFuzzySuffix(arguments[3]) + BuildBoostSuffix(arguments[4])
+ )
+ );
// ── BM25 Scoring ──────────────────────────────────────────
if (method == ScoreMethod)
- return _sql.Function("pdb.score", [Map(arguments[1])],
- nullable: true, argumentsPropagateNullability: [true],
- typeof(double), _typeMappingSource.FindMapping(typeof(double)));
+ return _sql.Function(
+ "pdb.score",
+ [Map(arguments[1])],
+ nullable: true,
+ argumentsPropagateNullability: [true],
+ typeof(double),
+ _typeMappingSource.FindMapping(typeof(double))
+ );
// ── Snippets ──────────────────────────────────────────────
if (method == SnippetMethod)
- return _sql.Function("pdb.snippet", [Map(arguments[1])],
- nullable: true, argumentsPropagateNullability: [true],
- typeof(string), _typeMappingSource.FindMapping(typeof(string)));
+ return _sql.Function(
+ "pdb.snippet",
+ [Map(arguments[1])],
+ nullable: true,
+ argumentsPropagateNullability: [true],
+ typeof(string),
+ _typeMappingSource.FindMapping(typeof(string))
+ );
if (method == SnippetParamsMethod)
- return new ParadeDbNamedArgFunctionExpression("pdb.snippet",
+ return new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippet",
[Map(arguments[1])],
[
("start_tag", Map(arguments[2])),
("end_tag", Map(arguments[3])),
- ("max_num_chars", Map(arguments[4]))
+ ("max_num_chars", Map(arguments[4])),
],
- typeof(string), _typeMappingSource.FindMapping(typeof(string)));
+ typeof(string),
+ _typeMappingSource.FindMapping(typeof(string))
+ );
if (method == SnippetsMethod)
- return new ParadeDbNamedArgFunctionExpression("pdb.snippets",
+ return new ParadeDbNamedArgFunctionExpression(
+ "pdb.snippets",
[Map(arguments[1])],
[
("max_num_chars", Map(arguments[2])),
("\"limit\"", Map(arguments[3])),
- ("\"offset\"", Map(arguments[4]))
+ ("\"offset\"", Map(arguments[4])),
],
- typeof(string[]), _typeMappingSource.FindMapping(typeof(string[])));
+ typeof(string[]),
+ _typeMappingSource.FindMapping(typeof(string[]))
+ );
// ── Parse Query ───────────────────────────────────────────
- if (method == ParseMethod) {
- var parseFunc = _sql.Function("pdb.parse", [Map(arguments[2])],
- nullable: true, argumentsPropagateNullability: [true],
- typeof(bool), _typeMappingSource.FindMapping(typeof(bool)));
+ if (method == ParseMethod)
+ {
+ var parseFunc = _sql.Function(
+ "pdb.parse",
+ [Map(arguments[2])],
+ nullable: true,
+ argumentsPropagateNullability: [true],
+ typeof(bool),
+ _typeMappingSource.FindMapping(typeof(bool))
+ );
return MakeBinaryBool(arguments[1], "@@@", parseFunc);
}
- if (method == ParseFullMethod) {
- var parseFunc = new ParadeDbNamedArgFunctionExpression("pdb.parse",
+ if (method == ParseFullMethod)
+ {
+ var parseFunc = new ParadeDbNamedArgFunctionExpression(
+ "pdb.parse",
[Map(arguments[2])],
- [
- ("lenient", Map(arguments[3])),
- ("conjunction_mode", Map(arguments[4]))
- ],
- typeof(bool), _typeMappingSource.FindMapping(typeof(bool)));
+ [("lenient", Map(arguments[3])), ("conjunction_mode", Map(arguments[4]))],
+ typeof(bool),
+ _typeMappingSource.FindMapping(typeof(bool))
+ );
return MakeBinaryBool(arguments[1], "@@@", parseFunc);
}
// ── Regex Search ──────────────────────────────────────────
- if (method == RegexMethod) {
- var regexFunc = _sql.Function("pdb.regex", [Map(arguments[2])],
- nullable: true, argumentsPropagateNullability: [true],
- typeof(bool), _typeMappingSource.FindMapping(typeof(bool)));
+ if (method == RegexMethod)
+ {
+ var regexFunc = _sql.Function(
+ "pdb.regex",
+ [Map(arguments[2])],
+ nullable: true,
+ argumentsPropagateNullability: [true],
+ typeof(bool),
+ _typeMappingSource.FindMapping(typeof(bool))
+ );
return MakeBinaryBool(arguments[1], "@@@", regexFunc);
}
// ── Phrase Prefix ─────────────────────────────────────────
- if (method == PhrasePrefixMethod) {
- var phrasePrefixFunc = _sql.Function("pdb.phrase_prefix", [Map(arguments[2])],
- nullable: true, argumentsPropagateNullability: [true],
- typeof(bool), _typeMappingSource.FindMapping(typeof(bool)));
+ if (method == PhrasePrefixMethod)
+ {
+ var phrasePrefixFunc = _sql.Function(
+ "pdb.phrase_prefix",
+ [Map(arguments[2])],
+ nullable: true,
+ argumentsPropagateNullability: [true],
+ typeof(bool),
+ _typeMappingSource.FindMapping(typeof(bool))
+ );
return MakeBinaryBool(arguments[1], "@@@", phrasePrefixFunc);
}
- if (method == PhrasePrefixMaxMethod) {
- var phrasePrefixFunc = new ParadeDbNamedArgFunctionExpression("pdb.phrase_prefix",
+ if (method == PhrasePrefixMaxMethod)
+ {
+ var phrasePrefixFunc = new ParadeDbNamedArgFunctionExpression(
+ "pdb.phrase_prefix",
[Map(arguments[3])],
[("max_expansion", Map(arguments[2]))],
- typeof(bool), _typeMappingSource.FindMapping(typeof(bool)));
+ typeof(bool),
+ _typeMappingSource.FindMapping(typeof(bool))
+ );
return MakeBinaryBool(arguments[1], "@@@", phrasePrefixFunc);
}
// ── More Like This ────────────────────────────────────────
- if (method == MoreLikeThisMethod) {
- var mltFunc = _sql.Function("pdb.more_like_this", [Map(arguments[2])],
- nullable: true, argumentsPropagateNullability: [true],
- typeof(bool), _typeMappingSource.FindMapping(typeof(bool)));
+ if (method == MoreLikeThisMethod)
+ {
+ var mltFunc = _sql.Function(
+ "pdb.more_like_this",
+ [Map(arguments[2])],
+ nullable: true,
+ argumentsPropagateNullability: [true],
+ typeof(bool),
+ _typeMappingSource.FindMapping(typeof(bool))
+ );
return MakeBinaryBool(arguments[1], "@@@", mltFunc);
}
- if (method == MoreLikeThisFieldsMethod) {
- var mltFunc = _sql.Function("pdb.more_like_this", [Map(arguments[2]), Map(arguments[3])],
- nullable: true, argumentsPropagateNullability: [true, true],
- typeof(bool), _typeMappingSource.FindMapping(typeof(bool)));
+ if (method == MoreLikeThisFieldsMethod)
+ {
+ var mltFunc = _sql.Function(
+ "pdb.more_like_this",
+ [Map(arguments[2]), Map(arguments[3])],
+ nullable: true,
+ argumentsPropagateNullability: [true, true],
+ typeof(bool),
+ _typeMappingSource.FindMapping(typeof(bool))
+ );
return MakeBinaryBool(arguments[1], "@@@", mltFunc);
}
// ── JSON Query Search ──────────────────────────────────────
- if (method == JsonSearchMethod) {
+ if (method == JsonSearchMethod)
+ {
var jsonExpr = Map(arguments[2]);
var castExpr = new ParadeDbModifiedQueryExpression(
- jsonExpr, "::jsonb", jsonExpr.Type, jsonExpr.TypeMapping);
+ jsonExpr,
+ "::jsonb",
+ jsonExpr.Type,
+ jsonExpr.TypeMapping
+ );
return MakeBinaryBool(arguments[1], "@@@", castExpr);
}
@@ -282,10 +489,14 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadO
private SqlExpression Map(SqlExpression expr) => _sql.ApplyDefaultTypeMapping(expr);
- private PgUnknownBinaryExpression MakeBinaryBool(SqlExpression left, string op, SqlExpression right) =>
- new(Map(left), Map(right), op, typeof(bool), _typeMappingSource.FindMapping(typeof(bool)));
+ private PgUnknownBinaryExpression MakeBinaryBool(
+ SqlExpression left,
+ string op,
+ SqlExpression right
+ ) => new(Map(left), Map(right), op, typeof(bool), _typeMappingSource.FindMapping(typeof(bool)));
- private SqlExpression WithModifier(SqlExpression inner, string suffix) {
+ private SqlExpression WithModifier(SqlExpression inner, string suffix)
+ {
var mapped = Map(inner);
return new ParadeDbModifiedQueryExpression(mapped, suffix, mapped.Type, mapped.TypeMapping);
}
@@ -293,50 +504,82 @@ private SqlExpression WithModifier(SqlExpression inner, string suffix) {
private static int ExtractInt(SqlExpression expr) =>
expr is SqlConstantExpression { Value: int value }
? value
- : throw new InvalidOperationException("ParadeDB modifier parameters (distance, slop, maxExpansions) must be compile-time constants.");
+ : throw new InvalidOperationException(
+ "ParadeDB modifier parameters (distance, slop, maxExpansions) must be compile-time constants."
+ );
private static double ExtractDouble(SqlExpression expr) =>
expr is SqlConstantExpression { Value: double value }
? value
- : throw new InvalidOperationException("ParadeDB modifier parameters (boost factor) must be compile-time constants.");
+ : throw new InvalidOperationException(
+ "ParadeDB modifier parameters (boost factor) must be compile-time constants."
+ );
- private static string BuildFuzzySuffix(SqlExpression distanceExpr) {
+ private static string BuildFuzzySuffix(SqlExpression distanceExpr)
+ {
var distance = ExtractInt(distanceExpr);
return $"::pdb.fuzzy({distance})";
}
// pdb.fuzzy(...) typmod accepts only a single int; the 5-arg fuzzy overloads route
// through pdb.match / pdb.fuzzy_term function calls instead. See README and GH-15.
- private SqlExpression BuildFuzzyMatchFunc(SqlExpression queryExpr, SqlExpression distanceExpr,
- SqlExpression prefixExpr, SqlExpression transpExpr, bool conjunctionMode) {
- var named = new List<(string, SqlExpression)> {
+ private SqlExpression BuildFuzzyMatchFunc(
+ SqlExpression queryExpr,
+ SqlExpression distanceExpr,
+ SqlExpression prefixExpr,
+ SqlExpression transpExpr,
+ bool conjunctionMode
+ )
+ {
+ var named = new List<(string, SqlExpression)>
+ {
("distance", Map(distanceExpr)),
("transposition_cost_one", Map(transpExpr)),
("prefix", Map(prefixExpr)),
};
- if (conjunctionMode) named.Add(("conjunction_mode", _sql.Constant(true, _typeMappingSource.FindMapping(typeof(bool)))));
- return new ParadeDbNamedArgFunctionExpression("pdb.match",
- [Map(queryExpr)], named,
- typeof(bool), _typeMappingSource.FindMapping(typeof(bool)));
+ if (conjunctionMode)
+ named.Add(
+ (
+ "conjunction_mode",
+ _sql.Constant(true, _typeMappingSource.FindMapping(typeof(bool)))
+ )
+ );
+ return new ParadeDbNamedArgFunctionExpression(
+ "pdb.match",
+ [Map(queryExpr)],
+ named,
+ typeof(bool),
+ _typeMappingSource.FindMapping(typeof(bool))
+ );
}
- private SqlExpression BuildFuzzyTermFunc(SqlExpression queryExpr, SqlExpression distanceExpr,
- SqlExpression prefixExpr, SqlExpression transpExpr) {
+ private SqlExpression BuildFuzzyTermFunc(
+ SqlExpression queryExpr,
+ SqlExpression distanceExpr,
+ SqlExpression prefixExpr,
+ SqlExpression transpExpr
+ )
+ {
// pdb.fuzzy_term(value, distance, transposition_cost_one, prefix) — positional order.
- return _sql.Function("pdb.fuzzy_term",
+ return _sql.Function(
+ "pdb.fuzzy_term",
[Map(queryExpr), Map(distanceExpr), Map(transpExpr), Map(prefixExpr)],
- nullable: true, argumentsPropagateNullability: [true, true, true, true],
- typeof(bool), _typeMappingSource.FindMapping(typeof(bool)));
+ nullable: true,
+ argumentsPropagateNullability: [true, true, true, true],
+ typeof(bool),
+ _typeMappingSource.FindMapping(typeof(bool))
+ );
}
- private static string BuildBoostSuffix(SqlExpression boostExpr) {
+ private static string BuildBoostSuffix(SqlExpression boostExpr)
+ {
var boost = ExtractDouble(boostExpr);
return FormattableString.Invariant($"::pdb.boost({boost})");
}
- private static string BuildSlopSuffix(SqlExpression slopExpr) {
+ private static string BuildSlopSuffix(SqlExpression slopExpr)
+ {
var slop = ExtractInt(slopExpr);
return $"::pdb.slop({slop})";
}
-
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbMethodCallTranslatorPlugin.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbMethodCallTranslatorPlugin.cs
index 9facb29..c2fc926 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbMethodCallTranslatorPlugin.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbMethodCallTranslatorPlugin.cs
@@ -3,11 +3,15 @@
namespace Equibles.ParadeDB.EntityFrameworkCore;
-public sealed class ParadeDbMethodCallTranslatorPlugin : IMethodCallTranslatorPlugin {
+public sealed class ParadeDbMethodCallTranslatorPlugin : IMethodCallTranslatorPlugin
+{
public IEnumerable Translators { get; }
- public ParadeDbMethodCallTranslatorPlugin(ISqlExpressionFactory sqlExpressionFactory,
- IRelationalTypeMappingSource typeMappingSource) {
+ public ParadeDbMethodCallTranslatorPlugin(
+ ISqlExpressionFactory sqlExpressionFactory,
+ IRelationalTypeMappingSource typeMappingSource
+ )
+ {
Translators = [new ParadeDbMethodCallTranslator(sqlExpressionFactory, typeMappingSource)];
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbModelFinalizingConvention.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbModelFinalizingConvention.cs
index f4507d2..d297566 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbModelFinalizingConvention.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbModelFinalizingConvention.cs
@@ -8,40 +8,66 @@
namespace Equibles.ParadeDB.EntityFrameworkCore;
-public sealed class ParadeDbModelFinalizingConvention : IModelFinalizingConvention {
- public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext context) {
+public sealed class ParadeDbModelFinalizingConvention : IModelFinalizingConvention
+{
+ public void ProcessModelFinalizing(
+ IConventionModelBuilder modelBuilder,
+ IConventionContext context
+ )
+ {
var hasBm25Index = false;
- foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) {
+ foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
+ {
var attribute = entityType.ClrType.GetCustomAttribute();
ValidateNoOrphanFieldAttributes(entityType, attribute);
- if (attribute == null) continue;
+ if (attribute == null)
+ continue;
- var indexBuilder = entityType.Builder.HasIndex(attribute.Columns, fromDataAnnotation: true);
- if (indexBuilder == null) continue;
+ var indexBuilder = entityType.Builder.HasIndex(
+ attribute.Columns,
+ fromDataAnnotation: true
+ );
+ if (indexBuilder == null)
+ continue;
indexBuilder.HasAnnotation("Npgsql:IndexMethod", "bm25", fromDataAnnotation: true);
var keyProperty = entityType.FindProperty(attribute.KeyField);
var keyColumnName = keyProperty?.GetColumnName() ?? attribute.KeyField;
- indexBuilder.HasAnnotation("Npgsql:StorageParameter:key_field", keyColumnName, fromDataAnnotation: true);
+ indexBuilder.HasAnnotation(
+ "Npgsql:StorageParameter:key_field",
+ keyColumnName,
+ fromDataAnnotation: true
+ );
BuildFieldTypeAnnotations(entityType, attribute, indexBuilder);
hasBm25Index = true;
}
- if (hasBm25Index) {
- modelBuilder.HasAnnotation("Npgsql:PostgresExtension:pg_search", ",,", fromDataAnnotation: true);
+ if (hasBm25Index)
+ {
+ modelBuilder.HasAnnotation(
+ "Npgsql:PostgresExtension:pg_search",
+ ",,",
+ fromDataAnnotation: true
+ );
}
}
- private static void ValidateNoOrphanFieldAttributes(IConventionEntityType entityType, Bm25IndexAttribute attribute) {
- foreach (var property in entityType.GetProperties()) {
+ private static void ValidateNoOrphanFieldAttributes(
+ IConventionEntityType entityType,
+ Bm25IndexAttribute attribute
+ )
+ {
+ foreach (var property in entityType.GetProperties())
+ {
var propertyInfo = property.PropertyInfo;
- if (propertyInfo == null) continue;
+ if (propertyInfo == null)
+ continue;
var hasFieldAttr =
propertyInfo.GetCustomAttribute() != null
@@ -50,62 +76,85 @@ private static void ValidateNoOrphanFieldAttributes(IConventionEntityType entity
|| propertyInfo.GetCustomAttribute() != null
|| propertyInfo.GetCustomAttribute() != null;
- if (!hasFieldAttr) continue;
+ if (!hasFieldAttr)
+ continue;
- if (attribute == null) {
+ if (attribute == null)
+ {
throw new InvalidOperationException(
- $"Property '{entityType.ClrType.Name}.{property.Name}' has a BM25 field attribute, " +
- "but the entity has no [Bm25Index] attribute.");
+ $"Property '{entityType.ClrType.Name}.{property.Name}' has a BM25 field attribute, "
+ + "but the entity has no [Bm25Index] attribute."
+ );
}
- if (Array.IndexOf(attribute.Columns, property.Name) < 0) {
+ if (Array.IndexOf(attribute.Columns, property.Name) < 0)
+ {
throw new InvalidOperationException(
- $"Property '{entityType.ClrType.Name}.{property.Name}' has a BM25 field attribute, " +
- "but it is not listed in the [Bm25Index] columns.");
+ $"Property '{entityType.ClrType.Name}.{property.Name}' has a BM25 field attribute, "
+ + "but it is not listed in the [Bm25Index] columns."
+ );
}
}
}
- private static void BuildFieldTypeAnnotations(IConventionEntityType entityType, Bm25IndexAttribute attribute,
- IConventionIndexBuilder indexBuilder) {
+ private static void BuildFieldTypeAnnotations(
+ IConventionEntityType entityType,
+ Bm25IndexAttribute attribute,
+ IConventionIndexBuilder indexBuilder
+ )
+ {
var textFields = new JsonObject();
var numericFields = new JsonObject();
var booleanFields = new JsonObject();
var datetimeFields = new JsonObject();
var jsonFields = new JsonObject();
- foreach (var propertyName in attribute.Columns) {
- if (propertyName == attribute.KeyField) continue;
+ foreach (var propertyName in attribute.Columns)
+ {
+ if (propertyName == attribute.KeyField)
+ continue;
var property = entityType.FindProperty(propertyName);
var propertyInfo = property?.PropertyInfo;
- if (propertyInfo == null) continue;
+ if (propertyInfo == null)
+ continue;
var columnName = property.GetColumnName() ?? propertyName;
var textAttr = propertyInfo.GetCustomAttribute();
- if (textAttr != null) {
- textFields[columnName] = Bm25StorageParameterBuilder.BuildTextField(textAttr, propertyName);
+ if (textAttr != null)
+ {
+ textFields[columnName] = Bm25StorageParameterBuilder.BuildTextField(
+ textAttr,
+ propertyName
+ );
continue;
}
var numAttr = propertyInfo.GetCustomAttribute();
- if (numAttr != null) {
+ if (numAttr != null)
+ {
numericFields[columnName] = Bm25StorageParameterBuilder.BuildNumericField(numAttr);
continue;
}
var boolAttr = propertyInfo.GetCustomAttribute();
- if (boolAttr != null) {
+ if (boolAttr != null)
+ {
booleanFields[columnName] = Bm25StorageParameterBuilder.BuildBooleanField(boolAttr);
continue;
}
var dtAttr = propertyInfo.GetCustomAttribute();
- if (dtAttr != null) {
+ if (dtAttr != null)
+ {
datetimeFields[columnName] = Bm25StorageParameterBuilder.BuildDateTimeField(dtAttr);
continue;
}
var jsonAttr = propertyInfo.GetCustomAttribute();
- if (jsonAttr != null) {
- jsonFields[columnName] = Bm25StorageParameterBuilder.BuildJsonField(jsonAttr, propertyName);
+ if (jsonAttr != null)
+ {
+ jsonFields[columnName] = Bm25StorageParameterBuilder.BuildJsonField(
+ jsonAttr,
+ propertyName
+ );
}
}
@@ -116,11 +165,18 @@ private static void BuildFieldTypeAnnotations(IConventionEntityType entityType,
AddFieldGroupParameter(indexBuilder, "json_fields", jsonFields);
}
- private static void AddFieldGroupParameter(IConventionIndexBuilder indexBuilder, string name, JsonObject group) {
- if (group.Count == 0) return;
+ private static void AddFieldGroupParameter(
+ IConventionIndexBuilder indexBuilder,
+ string name,
+ JsonObject group
+ )
+ {
+ if (group.Count == 0)
+ return;
indexBuilder.HasAnnotation(
$"Npgsql:StorageParameter:{name}",
Bm25StorageParameterBuilder.Serialize(group),
- fromDataAnnotation: true);
+ fromDataAnnotation: true
+ );
}
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbModifiedQueryExpression.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbModifiedQueryExpression.cs
index c94910f..13740fa 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbModifiedQueryExpression.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbModifiedQueryExpression.cs
@@ -9,23 +9,33 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
/// Wraps an inner SQL expression with a ParadeDB modifier suffix.
/// Example: 'shoes'::pdb.fuzzy(2)::pdb.boost(2.0)
///
-public sealed class ParadeDbModifiedQueryExpression : SqlExpression {
+public sealed class ParadeDbModifiedQueryExpression : SqlExpression
+{
public SqlExpression InnerExpression { get; }
public string ModifierSuffix { get; }
- public ParadeDbModifiedQueryExpression(SqlExpression innerExpression, string modifierSuffix,
- Type type, RelationalTypeMapping typeMapping)
- : base(type, typeMapping) {
+ public ParadeDbModifiedQueryExpression(
+ SqlExpression innerExpression,
+ string modifierSuffix,
+ Type type,
+ RelationalTypeMapping typeMapping
+ )
+ : base(type, typeMapping)
+ {
InnerExpression = innerExpression;
ModifierSuffix = modifierSuffix;
}
- protected override Expression VisitChildren(ExpressionVisitor visitor) {
+ protected override Expression VisitChildren(ExpressionVisitor visitor)
+ {
var visited = (SqlExpression)visitor.Visit(InnerExpression);
- return visited == InnerExpression ? this : new ParadeDbModifiedQueryExpression(visited, ModifierSuffix, Type, TypeMapping);
+ return visited == InnerExpression
+ ? this
+ : new ParadeDbModifiedQueryExpression(visited, ModifierSuffix, Type, TypeMapping);
}
- protected override void Print(ExpressionPrinter expressionPrinter) {
+ protected override void Print(ExpressionPrinter expressionPrinter)
+ {
expressionPrinter.Visit(InnerExpression);
expressionPrinter.Append(ModifierSuffix);
}
@@ -40,5 +50,6 @@ public override Expression Quote() =>
throw new NotSupportedException("ParadeDB expressions do not support precompiled queries.");
#endif
- public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), InnerExpression, ModifierSuffix);
+ public override int GetHashCode() =>
+ HashCode.Combine(base.GetHashCode(), InnerExpression, ModifierSuffix);
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbNamedArgFunctionExpression.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbNamedArgFunctionExpression.cs
index 39f2768..f81ea99 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbNamedArgFunctionExpression.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbNamedArgFunctionExpression.cs
@@ -9,52 +9,72 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
/// SQL expression for functions with PostgreSQL named parameters (name => value).
/// Example: pdb.snippet("Content", start_tag => '<b>', max_num_chars => 100)
///
-public sealed class ParadeDbNamedArgFunctionExpression : SqlExpression {
+public sealed class ParadeDbNamedArgFunctionExpression : SqlExpression
+{
public string FunctionName { get; }
public IReadOnlyList PositionalArgs { get; }
public IReadOnlyList<(string Name, SqlExpression Value)> NamedArgs { get; }
- public ParadeDbNamedArgFunctionExpression(string functionName,
+ public ParadeDbNamedArgFunctionExpression(
+ string functionName,
IReadOnlyList positionalArgs,
IReadOnlyList<(string Name, SqlExpression Value)> namedArgs,
- Type type, RelationalTypeMapping typeMapping)
- : base(type, typeMapping) {
+ Type type,
+ RelationalTypeMapping typeMapping
+ )
+ : base(type, typeMapping)
+ {
FunctionName = functionName;
PositionalArgs = positionalArgs;
NamedArgs = namedArgs;
}
- protected override Expression VisitChildren(ExpressionVisitor visitor) {
+ protected override Expression VisitChildren(ExpressionVisitor visitor)
+ {
var positionalChanged = false;
var newPositional = new SqlExpression[PositionalArgs.Count];
- for (var i = 0; i < PositionalArgs.Count; i++) {
+ for (var i = 0; i < PositionalArgs.Count; i++)
+ {
newPositional[i] = (SqlExpression)visitor.Visit(PositionalArgs[i]);
positionalChanged |= newPositional[i] != PositionalArgs[i];
}
var namedChanged = false;
var newNamed = new (string Name, SqlExpression Value)[NamedArgs.Count];
- for (var i = 0; i < NamedArgs.Count; i++) {
+ for (var i = 0; i < NamedArgs.Count; i++)
+ {
var visitedValue = (SqlExpression)visitor.Visit(NamedArgs[i].Value);
newNamed[i] = (NamedArgs[i].Name, visitedValue);
namedChanged |= visitedValue != NamedArgs[i].Value;
}
- if (!positionalChanged && !namedChanged) return this;
+ if (!positionalChanged && !namedChanged)
+ return this;
- return new ParadeDbNamedArgFunctionExpression(FunctionName, newPositional, newNamed, Type, TypeMapping);
+ return new ParadeDbNamedArgFunctionExpression(
+ FunctionName,
+ newPositional,
+ newNamed,
+ Type,
+ TypeMapping
+ );
}
- protected override void Print(ExpressionPrinter expressionPrinter) {
+ protected override void Print(ExpressionPrinter expressionPrinter)
+ {
expressionPrinter.Append(FunctionName).Append("(");
- for (var i = 0; i < PositionalArgs.Count; i++) {
- if (i > 0) expressionPrinter.Append(", ");
+ for (var i = 0; i < PositionalArgs.Count; i++)
+ {
+ if (i > 0)
+ expressionPrinter.Append(", ");
expressionPrinter.Visit(PositionalArgs[i]);
}
- for (var i = 0; i < NamedArgs.Count; i++) {
- if (i > 0 || PositionalArgs.Count > 0) expressionPrinter.Append(", ");
+ for (var i = 0; i < NamedArgs.Count; i++)
+ {
+ if (i > 0 || PositionalArgs.Count > 0)
+ expressionPrinter.Append(", ");
expressionPrinter.Append(NamedArgs[i].Name).Append(" => ");
expressionPrinter.Visit(NamedArgs[i].Value);
}
@@ -62,19 +82,29 @@ protected override void Print(ExpressionPrinter expressionPrinter) {
expressionPrinter.Append(")");
}
- public override bool Equals(object obj) {
- if (obj is not ParadeDbNamedArgFunctionExpression other) return false;
- if (FunctionName != other.FunctionName) return false;
- if (PositionalArgs.Count != other.PositionalArgs.Count) return false;
- if (NamedArgs.Count != other.NamedArgs.Count) return false;
-
- for (var i = 0; i < PositionalArgs.Count; i++) {
- if (!PositionalArgs[i].Equals(other.PositionalArgs[i])) return false;
+ public override bool Equals(object obj)
+ {
+ if (obj is not ParadeDbNamedArgFunctionExpression other)
+ return false;
+ if (FunctionName != other.FunctionName)
+ return false;
+ if (PositionalArgs.Count != other.PositionalArgs.Count)
+ return false;
+ if (NamedArgs.Count != other.NamedArgs.Count)
+ return false;
+
+ for (var i = 0; i < PositionalArgs.Count; i++)
+ {
+ if (!PositionalArgs[i].Equals(other.PositionalArgs[i]))
+ return false;
}
- for (var i = 0; i < NamedArgs.Count; i++) {
- if (NamedArgs[i].Name != other.NamedArgs[i].Name) return false;
- if (!NamedArgs[i].Value.Equals(other.NamedArgs[i].Value)) return false;
+ for (var i = 0; i < NamedArgs.Count; i++)
+ {
+ if (NamedArgs[i].Name != other.NamedArgs[i].Name)
+ return false;
+ if (!NamedArgs[i].Value.Equals(other.NamedArgs[i].Value))
+ return false;
}
return true;
@@ -85,12 +115,15 @@ public override Expression Quote() =>
throw new NotSupportedException("ParadeDB expressions do not support precompiled queries.");
#endif
- public override int GetHashCode() {
+ public override int GetHashCode()
+ {
var hash = new HashCode();
hash.Add(base.GetHashCode());
hash.Add(FunctionName);
- foreach (var arg in PositionalArgs) hash.Add(arg);
- foreach (var (name, value) in NamedArgs) {
+ foreach (var arg in PositionalArgs)
+ hash.Add(arg);
+ foreach (var (name, value) in NamedArgs)
+ {
hash.Add(name);
hash.Add(value);
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbParameterBasedSqlProcessor.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbParameterBasedSqlProcessor.cs
index 0ada972..07510ab 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbParameterBasedSqlProcessor.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbParameterBasedSqlProcessor.cs
@@ -4,52 +4,82 @@
namespace Equibles.ParadeDB.EntityFrameworkCore;
-public class ParadeDbParameterBasedSqlProcessor : NpgsqlParameterBasedSqlProcessor {
+public class ParadeDbParameterBasedSqlProcessor : NpgsqlParameterBasedSqlProcessor
+{
private readonly RelationalParameterBasedSqlProcessorDependencies _dependencies;
#if NET8_0
private readonly bool _useRelationalNulls;
- public ParadeDbParameterBasedSqlProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies,
- bool useRelationalNulls)
- : base(dependencies, useRelationalNulls) {
+ public ParadeDbParameterBasedSqlProcessor(
+ RelationalParameterBasedSqlProcessorDependencies dependencies,
+ bool useRelationalNulls
+ )
+ : base(dependencies, useRelationalNulls)
+ {
_dependencies = dependencies;
_useRelationalNulls = useRelationalNulls;
}
- protected override Expression ProcessSqlNullability(Expression selectExpression,
- IReadOnlyDictionary parametersValues, out bool canCache) {
- return new ParadeDbSqlNullabilityProcessor(_dependencies, _useRelationalNulls)
- .Process(selectExpression, parametersValues, out canCache);
+ protected override Expression ProcessSqlNullability(
+ Expression selectExpression,
+ IReadOnlyDictionary parametersValues,
+ out bool canCache
+ )
+ {
+ return new ParadeDbSqlNullabilityProcessor(_dependencies, _useRelationalNulls).Process(
+ selectExpression,
+ parametersValues,
+ out canCache
+ );
}
#elif NET9_0
private readonly RelationalParameterBasedSqlProcessorParameters _parameters;
- public ParadeDbParameterBasedSqlProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies,
- RelationalParameterBasedSqlProcessorParameters parameters)
- : base(dependencies, parameters) {
+ public ParadeDbParameterBasedSqlProcessor(
+ RelationalParameterBasedSqlProcessorDependencies dependencies,
+ RelationalParameterBasedSqlProcessorParameters parameters
+ )
+ : base(dependencies, parameters)
+ {
_dependencies = dependencies;
_parameters = parameters;
}
- protected override Expression ProcessSqlNullability(Expression selectExpression,
- IReadOnlyDictionary parametersValues, out bool canCache) {
- return new ParadeDbSqlNullabilityProcessor(_dependencies, _parameters)
- .Process(selectExpression, parametersValues, out canCache);
+ protected override Expression ProcessSqlNullability(
+ Expression selectExpression,
+ IReadOnlyDictionary parametersValues,
+ out bool canCache
+ )
+ {
+ return new ParadeDbSqlNullabilityProcessor(_dependencies, _parameters).Process(
+ selectExpression,
+ parametersValues,
+ out canCache
+ );
}
#else
private readonly RelationalParameterBasedSqlProcessorParameters _parameters;
- public ParadeDbParameterBasedSqlProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies,
- RelationalParameterBasedSqlProcessorParameters parameters)
- : base(dependencies, parameters) {
+ public ParadeDbParameterBasedSqlProcessor(
+ RelationalParameterBasedSqlProcessorDependencies dependencies,
+ RelationalParameterBasedSqlProcessorParameters parameters
+ )
+ : base(dependencies, parameters)
+ {
_dependencies = dependencies;
_parameters = parameters;
}
- protected override Expression ProcessSqlNullability(Expression expression, ParametersCacheDecorator parametersDecorator) {
- return new ParadeDbSqlNullabilityProcessor(_dependencies, _parameters)
- .Process(expression, parametersDecorator);
+ protected override Expression ProcessSqlNullability(
+ Expression expression,
+ ParametersCacheDecorator parametersDecorator
+ )
+ {
+ return new ParadeDbSqlNullabilityProcessor(_dependencies, _parameters).Process(
+ expression,
+ parametersDecorator
+ );
}
#endif
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbParameterBasedSqlProcessorFactory.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbParameterBasedSqlProcessorFactory.cs
index 0203c04..5d5997b 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbParameterBasedSqlProcessorFactory.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbParameterBasedSqlProcessorFactory.cs
@@ -3,19 +3,24 @@
namespace Equibles.ParadeDB.EntityFrameworkCore;
-public class ParadeDbParameterBasedSqlProcessorFactory : NpgsqlParameterBasedSqlProcessorFactory {
+public class ParadeDbParameterBasedSqlProcessorFactory : NpgsqlParameterBasedSqlProcessorFactory
+{
private readonly RelationalParameterBasedSqlProcessorDependencies _dependencies;
- public ParadeDbParameterBasedSqlProcessorFactory(RelationalParameterBasedSqlProcessorDependencies dependencies)
- : base(dependencies) {
+ public ParadeDbParameterBasedSqlProcessorFactory(
+ RelationalParameterBasedSqlProcessorDependencies dependencies
+ )
+ : base(dependencies)
+ {
_dependencies = dependencies;
}
#if NET8_0
- public override RelationalParameterBasedSqlProcessor Create(bool useRelationalNulls)
- => new ParadeDbParameterBasedSqlProcessor(_dependencies, useRelationalNulls);
+ public override RelationalParameterBasedSqlProcessor Create(bool useRelationalNulls) =>
+ new ParadeDbParameterBasedSqlProcessor(_dependencies, useRelationalNulls);
#else
- public override RelationalParameterBasedSqlProcessor Create(RelationalParameterBasedSqlProcessorParameters parameters)
- => new ParadeDbParameterBasedSqlProcessor(_dependencies, parameters);
+ public override RelationalParameterBasedSqlProcessor Create(
+ RelationalParameterBasedSqlProcessorParameters parameters
+ ) => new ParadeDbParameterBasedSqlProcessor(_dependencies, parameters);
#endif
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbQuerySqlGenerator.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbQuerySqlGenerator.cs
index 6c7bb41..51271df 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbQuerySqlGenerator.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbQuerySqlGenerator.cs
@@ -5,31 +5,40 @@
namespace Equibles.ParadeDB.EntityFrameworkCore;
-public class ParadeDbQuerySqlGenerator : NpgsqlQuerySqlGenerator {
- public ParadeDbQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies,
+public class ParadeDbQuerySqlGenerator : NpgsqlQuerySqlGenerator
+{
+ public ParadeDbQuerySqlGenerator(
+ QuerySqlGeneratorDependencies dependencies,
IRelationalTypeMappingSource typeMappingSource,
bool reverseNullOrderingEnabled,
- Version postgresVersion)
- : base(dependencies, typeMappingSource, reverseNullOrderingEnabled, postgresVersion) {
- }
-
- protected override Expression VisitExtension(Expression expression) {
- if (expression is ParadeDbModifiedQueryExpression modified) {
+ Version postgresVersion
+ )
+ : base(dependencies, typeMappingSource, reverseNullOrderingEnabled, postgresVersion) { }
+
+ protected override Expression VisitExtension(Expression expression)
+ {
+ if (expression is ParadeDbModifiedQueryExpression modified)
+ {
Visit(modified.InnerExpression);
Sql.Append(modified.ModifierSuffix);
return expression;
}
- if (expression is ParadeDbNamedArgFunctionExpression namedArg) {
+ if (expression is ParadeDbNamedArgFunctionExpression namedArg)
+ {
Sql.Append(namedArg.FunctionName).Append("(");
- for (var i = 0; i < namedArg.PositionalArgs.Count; i++) {
- if (i > 0) Sql.Append(", ");
+ for (var i = 0; i < namedArg.PositionalArgs.Count; i++)
+ {
+ if (i > 0)
+ Sql.Append(", ");
Visit(namedArg.PositionalArgs[i]);
}
- for (var i = 0; i < namedArg.NamedArgs.Count; i++) {
- if (i > 0 || namedArg.PositionalArgs.Count > 0) Sql.Append(", ");
+ for (var i = 0; i < namedArg.NamedArgs.Count; i++)
+ {
+ if (i > 0 || namedArg.PositionalArgs.Count > 0)
+ Sql.Append(", ");
Sql.Append(namedArg.NamedArgs[i].Name).Append(" => ");
Visit(namedArg.NamedArgs[i].Value);
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbQuerySqlGeneratorFactory.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbQuerySqlGeneratorFactory.cs
index 46ed022..88ab98f 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbQuerySqlGeneratorFactory.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbQuerySqlGeneratorFactory.cs
@@ -5,15 +5,19 @@
namespace Equibles.ParadeDB.EntityFrameworkCore;
-public class ParadeDbQuerySqlGeneratorFactory : NpgsqlQuerySqlGeneratorFactory {
+public class ParadeDbQuerySqlGeneratorFactory : NpgsqlQuerySqlGeneratorFactory
+{
private readonly QuerySqlGeneratorDependencies _dependencies;
private readonly IRelationalTypeMappingSource _typeMappingSource;
private readonly INpgsqlSingletonOptions _npgsqlOptions;
- public ParadeDbQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies,
+ public ParadeDbQuerySqlGeneratorFactory(
+ QuerySqlGeneratorDependencies dependencies,
IRelationalTypeMappingSource typeMappingSource,
- INpgsqlSingletonOptions npgsqlOptions)
- : base(dependencies, typeMappingSource, npgsqlOptions) {
+ INpgsqlSingletonOptions npgsqlOptions
+ )
+ : base(dependencies, typeMappingSource, npgsqlOptions)
+ {
_dependencies = dependencies;
_typeMappingSource = typeMappingSource;
_npgsqlOptions = npgsqlOptions;
@@ -24,5 +28,6 @@ public override QuerySqlGenerator Create() =>
_dependencies,
_typeMappingSource,
_npgsqlOptions.ReverseNullOrderingEnabled,
- _npgsqlOptions.PostgresVersion);
+ _npgsqlOptions.PostgresVersion
+ );
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbSearchExtensions.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbSearchExtensions.cs
index a26957d..ed5a02c 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbSearchExtensions.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbSearchExtensions.cs
@@ -7,19 +7,29 @@ namespace Equibles.ParadeDB.EntityFrameworkCore;
///
/// IQueryable extension methods for ParadeDB JSON query search and BM25 score ordering.
///
-public static class ParadeDbSearchExtensions {
- private static readonly MethodInfo JsonSearchMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.JsonSearch), [typeof(DbFunctions), typeof(object), typeof(string)])!;
+public static class ParadeDbSearchExtensions
+{
+ private static readonly MethodInfo JsonSearchMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.JsonSearch),
+ [typeof(DbFunctions), typeof(object), typeof(string)]
+ )!;
- private static readonly MethodInfo ScoreMethod = typeof(ParadeDbFunctions)
- .GetMethod(nameof(ParadeDbFunctions.Score), [typeof(DbFunctions), typeof(object)])!;
+ private static readonly MethodInfo ScoreMethod = typeof(ParadeDbFunctions).GetMethod(
+ nameof(ParadeDbFunctions.Score),
+ [typeof(DbFunctions), typeof(object)]
+ )!;
///
/// Adds a WHERE clause with a ParadeDB boolean JSON query built inline.
/// Translates to: keyField @@@ 'json'::jsonb.
///
- public static IQueryable JsonSearch(this IQueryable source,
- Expression> keyField, Action configure) where T : class {
+ public static IQueryable JsonSearch(
+ this IQueryable source,
+ Expression> keyField,
+ Action configure
+ )
+ where T : class
+ {
return source.JsonSearch(keyField, ParadeDbJsonQuery.Boolean(configure));
}
@@ -27,8 +37,13 @@ public static IQueryable JsonSearch(this IQueryable source,
/// Adds a WHERE clause with a ParadeDB JSON query.
/// Translates to: keyField @@@ 'json'::jsonb.
///
- public static IQueryable JsonSearch(this IQueryable source,
- Expression> keyField, ParadeDbJsonQuery query) where T : class {
+ public static IQueryable JsonSearch(
+ this IQueryable source,
+ Expression> keyField,
+ ParadeDbJsonQuery query
+ )
+ where T : class
+ {
var efFunctionsExpr = Expression.Property(null, typeof(EF), nameof(EF.Functions));
var keyBody = BoxIfNeeded(StripConvert(keyField.Body));
var jsonConstant = Expression.Constant(query.ToJson(), typeof(string));
@@ -42,28 +57,42 @@ public static IQueryable JsonSearch(this IQueryable source,
///
/// Orders by BM25 score descending (highest relevance first).
///
- public static IOrderedQueryable OrderByScoreDescending(this IQueryable source,
- Expression> keyField) where T : class {
+ public static IOrderedQueryable OrderByScoreDescending(
+ this IQueryable source,
+ Expression> keyField
+ )
+ where T : class
+ {
return ApplyScoreOrdering(source, keyField, nameof(Queryable.OrderByDescending));
}
///
/// Orders by BM25 score ascending.
///
- public static IOrderedQueryable OrderByScore(this IQueryable source,
- Expression> keyField) where T : class {
+ public static IOrderedQueryable OrderByScore(
+ this IQueryable source,
+ Expression> keyField
+ )
+ where T : class
+ {
return ApplyScoreOrdering(source, keyField, nameof(Queryable.OrderBy));
}
- private static IOrderedQueryable ApplyScoreOrdering(IQueryable source,
- Expression> keyField, string methodName) where T : class {
+ private static IOrderedQueryable ApplyScoreOrdering(
+ IQueryable source,
+ Expression> keyField,
+ string methodName
+ )
+ where T : class
+ {
var efFunctionsExpr = Expression.Property(null, typeof(EF), nameof(EF.Functions));
var keyBody = BoxIfNeeded(StripConvert(keyField.Body));
var scoreCall = Expression.Call(ScoreMethod, efFunctionsExpr, keyBody);
var scoreSelector = Expression.Lambda>(scoreCall, keyField.Parameters);
- var orderByMethod = typeof(Queryable).GetMethods()
+ var orderByMethod = typeof(Queryable)
+ .GetMethods()
.First(m => m.Name == methodName && m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), typeof(double));
@@ -77,7 +106,5 @@ private static Expression StripConvert(Expression expression) =>
: expression;
private static Expression BoxIfNeeded(Expression expression) =>
- expression.Type.IsValueType
- ? Expression.Convert(expression, typeof(object))
- : expression;
+ expression.Type.IsValueType ? Expression.Convert(expression, typeof(object)) : expression;
}
diff --git a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbSqlNullabilityProcessor.cs b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbSqlNullabilityProcessor.cs
index 6094bf2..771bfbb 100644
--- a/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbSqlNullabilityProcessor.cs
+++ b/Equibles.ParadeDB.EntityFrameworkCore/ParadeDbSqlNullabilityProcessor.cs
@@ -4,50 +4,76 @@
namespace Equibles.ParadeDB.EntityFrameworkCore;
-public class ParadeDbSqlNullabilityProcessor : NpgsqlSqlNullabilityProcessor {
+public class ParadeDbSqlNullabilityProcessor : NpgsqlSqlNullabilityProcessor
+{
#if NET8_0
- public ParadeDbSqlNullabilityProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies,
- bool useRelationalNulls)
- : base(dependencies, useRelationalNulls) {
- }
+ public ParadeDbSqlNullabilityProcessor(
+ RelationalParameterBasedSqlProcessorDependencies dependencies,
+ bool useRelationalNulls
+ )
+ : base(dependencies, useRelationalNulls) { }
#else
- public ParadeDbSqlNullabilityProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies,
- RelationalParameterBasedSqlProcessorParameters parameters)
- : base(dependencies, parameters) {
- }
+ public ParadeDbSqlNullabilityProcessor(
+ RelationalParameterBasedSqlProcessorDependencies dependencies,
+ RelationalParameterBasedSqlProcessorParameters parameters
+ )
+ : base(dependencies, parameters) { }
#endif
- protected override SqlExpression VisitCustomSqlExpression(SqlExpression sqlExpression,
- bool allowOptimizedExpansion, out bool nullable) {
- if (sqlExpression is ParadeDbModifiedQueryExpression modified) {
+ protected override SqlExpression VisitCustomSqlExpression(
+ SqlExpression sqlExpression,
+ bool allowOptimizedExpansion,
+ out bool nullable
+ )
+ {
+ if (sqlExpression is ParadeDbModifiedQueryExpression modified)
+ {
nullable = false;
var inner = Visit(modified.InnerExpression, allowOptimizedExpansion, out _);
return inner == modified.InnerExpression
? modified
- : new ParadeDbModifiedQueryExpression(inner, modified.ModifierSuffix, modified.Type, modified.TypeMapping);
+ : new ParadeDbModifiedQueryExpression(
+ inner,
+ modified.ModifierSuffix,
+ modified.Type,
+ modified.TypeMapping
+ );
}
- if (sqlExpression is ParadeDbNamedArgFunctionExpression namedArg) {
+ if (sqlExpression is ParadeDbNamedArgFunctionExpression namedArg)
+ {
nullable = true;
var positionalChanged = false;
var newPositional = new SqlExpression[namedArg.PositionalArgs.Count];
- for (var i = 0; i < namedArg.PositionalArgs.Count; i++) {
- newPositional[i] = Visit(namedArg.PositionalArgs[i], allowOptimizedExpansion, out _);
+ for (var i = 0; i < namedArg.PositionalArgs.Count; i++)
+ {
+ newPositional[i] = Visit(
+ namedArg.PositionalArgs[i],
+ allowOptimizedExpansion,
+ out _
+ );
positionalChanged |= newPositional[i] != namedArg.PositionalArgs[i];
}
var namedChanged = false;
var newNamed = new (string Name, SqlExpression Value)[namedArg.NamedArgs.Count];
- for (var i = 0; i < namedArg.NamedArgs.Count; i++) {
+ for (var i = 0; i < namedArg.NamedArgs.Count; i++)
+ {
var visited = Visit(namedArg.NamedArgs[i].Value, allowOptimizedExpansion, out _);
newNamed[i] = (namedArg.NamedArgs[i].Name, visited);
namedChanged |= visited != namedArg.NamedArgs[i].Value;
}
- if (!positionalChanged && !namedChanged) return namedArg;
- return new ParadeDbNamedArgFunctionExpression(namedArg.FunctionName, newPositional, newNamed,
- namedArg.Type, namedArg.TypeMapping);
+ if (!positionalChanged && !namedChanged)
+ return namedArg;
+ return new ParadeDbNamedArgFunctionExpression(
+ namedArg.FunctionName,
+ newPositional,
+ newNamed,
+ namedArg.Type,
+ namedArg.TypeMapping
+ );
}
return base.VisitCustomSqlExpression(sqlExpression, allowOptimizedExpansion, out nullable);