Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/DocumentDbTests/Indexes/computed_indexes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,73 @@ public class ApiResponseRecord
public string Response { get; set; }
public DateTime RequestTimeUtc { get; set; }
}

public class computed_indexes_jsonpropertyname : OneOffConfigurationsContext
{
[Fact]
public async Task datetimeoffset_index_ddl_uses_json_property_name()
{
StoreOptions(_ =>
{
_.UseSystemTextJsonForSerialization(EnumStorage.AsInteger, Casing.Default);
_.Schema.For<Bug4253Doc>().Index(x => x.Timestamp, c => c.Name = "idx_b4253_ts");
_.AutoCreateSchemaObjects = AutoCreate.All;
});

await theStore.Storage.Database.ApplyAllConfiguredChangesToDatabaseAsync(AutoCreate.CreateOrUpdate);

var table = await theStore.Tenancy.Default.Database.ExistingTableFor(typeof(Bug4253Doc));
var index = table.IndexFor("idx_b4253_ts");

index.ShouldNotBeNull();
var ddl = index.ToDDL(table);

// Must reference the JSON key 'ts' from [JsonPropertyName], not the C# name 'Timestamp'
ddl.ShouldContain("'ts'");
ddl.ShouldNotContain("'Timestamp'");
}

[Fact]
public async Task datetimeoffset_required_init_index_ddl_uses_json_property_name()
{
StoreOptions(_ =>
{
_.UseSystemTextJsonForSerialization(EnumStorage.AsInteger, Casing.Default);
_.Schema.For<Bug4253RequiredInitDoc>().Index(x => x.Timestamp, c => c.Name = "idx_b4253_req_ts");
_.AutoCreateSchemaObjects = AutoCreate.All;
});

await theStore.Storage.Database.ApplyAllConfiguredChangesToDatabaseAsync(AutoCreate.CreateOrUpdate);

var table = await theStore.Tenancy.Default.Database.ExistingTableFor(typeof(Bug4253RequiredInitDoc));
var index = table.IndexFor("idx_b4253_req_ts");

index.ShouldNotBeNull();
var ddl = index.ToDDL(table);

ddl.ShouldContain("'ts'");
ddl.ShouldNotContain("'Timestamp'");
}
}

public class Bug4253Doc
{
public Guid Id { get; set; }

[System.Text.Json.Serialization.JsonPropertyName("ts")]
public DateTimeOffset Timestamp { get; set; }

[System.Text.Json.Serialization.JsonPropertyName("auid")]
public int ActorUserId { get; set; }
}

public class Bug4253RequiredInitDoc
{
public Guid Id { get; set; }

[System.Text.Json.Serialization.JsonPropertyName("ts")]
public required DateTimeOffset Timestamp { get; init; }

[System.Text.Json.Serialization.JsonPropertyName("auid")]
public required int ActorUserId { get; init; }
}
190 changes: 190 additions & 0 deletions src/LinqTests/Acceptance/json_naming_attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Marten.Testing.Harness;
using Newtonsoft.Json;
using Shouldly;
using Weasel.Core;

namespace LinqTests.Acceptance;

Expand Down Expand Up @@ -48,6 +49,156 @@ public async Task recognize_stj_json_property_in_linq()
command.CommandText.ShouldBe("select d.id, d.data from atts.mt_doc_stjdoc as d where d.data ->> 'shade' = :p0;");

}

[Fact]
public void recognize_stj_json_property_on_datetimeoffset_in_linq()
{
using var store = DocumentStore.For(opts =>
{
opts.Connection(ConnectionSource.ConnectionString);
opts.DatabaseSchemaName = "atts";
opts.UseSystemTextJsonForSerialization(EnumStorage.AsInteger, Casing.Default);
});

using var session = store.LightweightSession();

var cutoff = DateTimeOffset.UtcNow.AddDays(-1);
var command = session.Query<StjDateTimeOffsetDoc>()
.Where(x => x.Timestamp >= cutoff)
.ToCommand();

// The JSON key 'ts' (from [JsonPropertyName]) must appear; 'Timestamp' (C# name) must not
command.CommandText.ShouldContain("'ts'");
command.CommandText.ShouldNotContain("'Timestamp'");
}

[Fact]
public void recognize_stj_json_property_on_datetimeoffset_required_init_in_linq()
{
using var store = DocumentStore.For(opts =>
{
opts.Connection(ConnectionSource.ConnectionString);
opts.DatabaseSchemaName = "atts";
opts.UseSystemTextJsonForSerialization(EnumStorage.AsInteger, Casing.Default);
});

using var session = store.LightweightSession();

var cutoff = DateTimeOffset.UtcNow.AddDays(-1);
var command = session.Query<StjRequiredInitDateTimeOffsetDoc>()
.Where(x => x.Timestamp >= cutoff)
.ToCommand();

command.CommandText.ShouldContain("'ts'");
command.CommandText.ShouldNotContain("'Timestamp'");
}

[Fact]
public void recognize_stj_json_property_on_datetime_in_linq()
{
using var store = DocumentStore.For(opts =>
{
opts.Connection(ConnectionSource.ConnectionString);
opts.DatabaseSchemaName = "atts";
opts.UseSystemTextJsonForSerialization(EnumStorage.AsInteger, Casing.Default);
});

using var session = store.LightweightSession();

var cutoff = DateTime.UtcNow.AddDays(-1);
var command = session.Query<StjTemporalDoc>()
.Where(x => x.DateTime >= cutoff)
.ToCommand();

command.CommandText.ShouldContain("'dt'");
command.CommandText.ShouldNotContain("'DateTime'");
}

[Fact]
public void recognize_stj_json_property_on_dateonly_in_linq()
{
using var store = DocumentStore.For(opts =>
{
opts.Connection(ConnectionSource.ConnectionString);
opts.DatabaseSchemaName = "atts";
opts.UseSystemTextJsonForSerialization(EnumStorage.AsInteger, Casing.Default);
});

using var session = store.LightweightSession();

var cutoff = DateOnly.FromDateTime(DateTime.UtcNow);
var command = session.Query<StjTemporalDoc>()
.Where(x => x.DateOnly >= cutoff)
.ToCommand();

command.CommandText.ShouldContain("'d_only'");
command.CommandText.ShouldNotContain("'DateOnly'");
}

[Fact]
public void recognize_stj_json_property_on_timeonly_in_linq()
{
using var store = DocumentStore.For(opts =>
{
opts.Connection(ConnectionSource.ConnectionString);
opts.DatabaseSchemaName = "atts";
opts.UseSystemTextJsonForSerialization(EnumStorage.AsInteger, Casing.Default);
});

using var session = store.LightweightSession();

var cutoff = TimeOnly.FromDateTime(DateTime.UtcNow);
var command = session.Query<StjTemporalDoc>()
.Where(x => x.TimeOnly >= cutoff)
.ToCommand();

command.CommandText.ShouldContain("'t_only'");
command.CommandText.ShouldNotContain("'TimeOnly'");
}

[Fact]
public async Task end_to_end_query_with_datetimeoffset_json_property_name()
{
using var store = DocumentStore.For(opts =>
{
opts.Connection(ConnectionSource.ConnectionString);
opts.DatabaseSchemaName = "atts_dto_e2e";
opts.UseSystemTextJsonForSerialization(EnumStorage.AsInteger, Casing.Default);
});

await store.Advanced.Clean.CompletelyRemoveAllAsync();

await using (var session = store.LightweightSession())
{
session.Store(new StjDateTimeOffsetDoc
{
Id = Guid.NewGuid(),
Timestamp = DateTimeOffset.UtcNow,
ActorUserId = 42
});
await session.SaveChangesAsync();
}

await using (var session = store.QuerySession())
{
var cutoff = DateTimeOffset.UtcNow.AddDays(-1);

var totalCount = await session.Query<StjDateTimeOffsetDoc>().CountAsync();
totalCount.ShouldBe(1);

// The bug: this silently returns 0 even though the data matches
var recent = await session.Query<StjDateTimeOffsetDoc>()
.Where(x => x.Timestamp >= cutoff)
.CountAsync();
recent.ShouldBe(1);

// Sanity: the int filter with JsonPropertyName works
var byActor = await session.Query<StjDateTimeOffsetDoc>()
.Where(x => x.ActorUserId == 42)
.CountAsync();
byActor.ShouldBe(1);
}
}
}

public class AttributedDoc
Expand All @@ -65,3 +216,42 @@ public class StjDoc
[JsonPropertyName("shade")]
public string Color { get; set; }
}

public class StjDateTimeOffsetDoc
{
public Guid Id { get; set; }

[JsonPropertyName("ts")]
public DateTimeOffset Timestamp { get; set; }

[JsonPropertyName("auid")]
public int ActorUserId { get; set; }
}

public class StjRequiredInitDateTimeOffsetDoc
{
public Guid Id { get; set; }

[JsonPropertyName("ts")]
public required DateTimeOffset Timestamp { get; init; }

[JsonPropertyName("auid")]
public required int ActorUserId { get; init; }
}

public class StjTemporalDoc
{
public Guid Id { get; set; }

[JsonPropertyName("dt")]
public DateTime DateTime { get; set; }

[JsonPropertyName("dto")]
public DateTimeOffset DateTimeOffset { get; set; }

[JsonPropertyName("d_only")]
public DateOnly DateOnly { get; set; }

[JsonPropertyName("t_only")]
public TimeOnly TimeOnly { get; set; }
}
Loading