Skip to content

Commit 458cd12

Browse files
feat: support JSONPATH Postgres data type
1 parent cca3dda commit 458cd12

26 files changed

Lines changed: 404 additions & 184 deletions

File tree

Drivers/NpgsqlDriver.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ public NpgsqlDriver(
6868
{ "text", new() },
6969
{ "bpchar", new() },
7070
{ "tinytext", new() },
71-
{ "varchar", new() }
71+
{ "varchar", new() },
72+
{ "jsonpath", new(NpgsqlTypeOverride: "NpgsqlDbType.JsonPath") }
7273
},
7374
readerFn: ordinal => $"reader.GetString({ordinal})",
7475
readerArrayFn: ordinal => $"reader.GetFieldValue<string[]>({ordinal})"

docs/04_Postgres.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@ we consider support for the different data types separately for batch inserts an
5656
| uuid |||
5757
| json |||
5858
| jsonb |||
59-
| jsonpath |||
59+
| jsonpath |||
60+
| xml |||
61+
| enum |||
6062

61-
*** Time with time zone is not useful and not recommended to use by Postgres themselves -
63+
*** `time with time zone` is not useful and not recommended to use by Postgres themselves -
6264
see [here](https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME) -
6365
so we decided not to implement support for it.
6466

docs/05_MySql.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ we consider support for the different data types separately for batch inserts an
6969
| blob |||
7070
| mediumblob |||
7171
| longblob |||
72-
| enum || |
72+
| enum || |
7373
| set |||
7474
| json |||
7575
| geometry |||

end2end/EndToEndScaffold/Templates/PostgresTests.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -462,9 +462,11 @@ void AssertSingularEquals(QuerySql.GetPostgresTypesCntRow x, QuerySql.GetPostgre
462462
{
463463
Impl = $$"""
464464
[Test]
465-
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}")]
466-
[TestCase(null)]
467-
public async Task TestPostgresJsonDataTypes(string cJson)
465+
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}", "$.\"name\"")]
466+
[TestCase(null, null)]
467+
public async Task TestPostgresJsonDataTypes(
468+
string cJson,
469+
string cJsonpath)
468470
{
469471
JsonElement? cParsedJson = null;
470472
if (cJson != null)
@@ -474,14 +476,16 @@ await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs
474476
{
475477
CJson = cParsedJson,
476478
CJsonb = cParsedJson,
477-
CJsonStringOverride = cJson
479+
CJsonStringOverride = cJson,
480+
CJsonpath = cJsonpath
478481
});
479482
480483
var expected = new QuerySql.GetPostgresTypesRow
481484
{
482485
CJson = cParsedJson,
483486
CJsonb = cParsedJson,
484-
CJsonStringOverride = cJson
487+
CJsonStringOverride = cJson,
488+
CJsonpath = cJsonpath
485489
};
486490
487491
var actual = await QuerySql.GetPostgresTypes();
@@ -496,6 +500,7 @@ void AssertSingularEquals(QuerySql.GetPostgresTypesRow x, QuerySql.GetPostgresTy
496500
if (x.CJsonb.HasValue)
497501
Assert.That(x.CJsonb.Value.GetRawText(), Is.EqualTo(y.CJsonb.Value.GetRawText()));
498502
Assert.That(x.CJsonStringOverride, Is.EqualTo(y.CJsonStringOverride));
503+
Assert.That(x.CJsonpath, Is.EqualTo(y.CJsonpath));
499504
}
500505
}
501506
"""
@@ -511,6 +516,12 @@ public void TestPostgresInvalidJson()
511516
{
512517
CJsonStringOverride = "SOME INVALID JSON"
513518
}));
519+
520+
Assert.ThrowsAsync<Npgsql.PostgresException>(async () => await
521+
QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs
522+
{
523+
CJsonpath = "SOME INVALID JSONPATH"
524+
}));
514525
}
515526
"""
516527
},

end2end/EndToEndTests/NpgsqlDapperTester.generated.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -752,19 +752,20 @@ void AssertSingularEquals(QuerySql.GetPostgresGeoTypesRow x, QuerySql.GetPostgre
752752
}
753753

754754
[Test]
755-
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}")]
756-
[TestCase(null)]
757-
public async Task TestPostgresJsonDataTypes(string cJson)
755+
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}", "$.\"name\"")]
756+
[TestCase(null, null)]
757+
public async Task TestPostgresJsonDataTypes(string cJson, string cJsonpath)
758758
{
759759
JsonElement? cParsedJson = null;
760760
if (cJson != null)
761761
cParsedJson = JsonDocument.Parse(cJson).RootElement;
762-
await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJson = cParsedJson, CJsonb = cParsedJson, CJsonStringOverride = cJson });
762+
await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJson = cParsedJson, CJsonb = cParsedJson, CJsonStringOverride = cJson, CJsonpath = cJsonpath });
763763
var expected = new QuerySql.GetPostgresTypesRow
764764
{
765765
CJson = cParsedJson,
766766
CJsonb = cParsedJson,
767-
CJsonStringOverride = cJson
767+
CJsonStringOverride = cJson,
768+
CJsonpath = cJsonpath
768769
};
769770
var actual = await QuerySql.GetPostgresTypes();
770771
AssertSingularEquals(expected, actual);
@@ -777,13 +778,15 @@ void AssertSingularEquals(QuerySql.GetPostgresTypesRow x, QuerySql.GetPostgresTy
777778
if (x.CJsonb.HasValue)
778779
Assert.That(x.CJsonb.Value.GetRawText(), Is.EqualTo(y.CJsonb.Value.GetRawText()));
779780
Assert.That(x.CJsonStringOverride, Is.EqualTo(y.CJsonStringOverride));
781+
Assert.That(x.CJsonpath, Is.EqualTo(y.CJsonpath));
780782
}
781783
}
782784

783785
[Test]
784786
public void TestPostgresInvalidJson()
785787
{
786788
Assert.ThrowsAsync<Npgsql.PostgresException>(async () => await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJsonStringOverride = "SOME INVALID JSON" }));
789+
Assert.ThrowsAsync<Npgsql.PostgresException>(async () => await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJsonpath = "SOME INVALID JSONPATH" }));
787790
}
788791

789792
[Test]

end2end/EndToEndTests/NpgsqlTester.generated.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -752,19 +752,20 @@ void AssertSingularEquals(QuerySql.GetPostgresGeoTypesRow x, QuerySql.GetPostgre
752752
}
753753

754754
[Test]
755-
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}")]
756-
[TestCase(null)]
757-
public async Task TestPostgresJsonDataTypes(string cJson)
755+
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}", "$.\"name\"")]
756+
[TestCase(null, null)]
757+
public async Task TestPostgresJsonDataTypes(string cJson, string cJsonpath)
758758
{
759759
JsonElement? cParsedJson = null;
760760
if (cJson != null)
761761
cParsedJson = JsonDocument.Parse(cJson).RootElement;
762-
await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJson = cParsedJson, CJsonb = cParsedJson, CJsonStringOverride = cJson });
762+
await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJson = cParsedJson, CJsonb = cParsedJson, CJsonStringOverride = cJson, CJsonpath = cJsonpath });
763763
var expected = new QuerySql.GetPostgresTypesRow
764764
{
765765
CJson = cParsedJson,
766766
CJsonb = cParsedJson,
767-
CJsonStringOverride = cJson
767+
CJsonStringOverride = cJson,
768+
CJsonpath = cJsonpath
768769
};
769770
var actual = await QuerySql.GetPostgresTypes();
770771
AssertSingularEquals(expected, actual.Value);
@@ -777,13 +778,15 @@ void AssertSingularEquals(QuerySql.GetPostgresTypesRow x, QuerySql.GetPostgresTy
777778
if (x.CJsonb.HasValue)
778779
Assert.That(x.CJsonb.Value.GetRawText(), Is.EqualTo(y.CJsonb.Value.GetRawText()));
779780
Assert.That(x.CJsonStringOverride, Is.EqualTo(y.CJsonStringOverride));
781+
Assert.That(x.CJsonpath, Is.EqualTo(y.CJsonpath));
780782
}
781783
}
782784

783785
[Test]
784786
public void TestPostgresInvalidJson()
785787
{
786788
Assert.ThrowsAsync<Npgsql.PostgresException>(async () => await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJsonStringOverride = "SOME INVALID JSON" }));
789+
Assert.ThrowsAsync<Npgsql.PostgresException>(async () => await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJsonpath = "SOME INVALID JSONPATH" }));
787790
}
788791

789792
[Test]

end2end/EndToEndTestsLegacy/NpgsqlDapperTester.generated.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -752,19 +752,20 @@ void AssertSingularEquals(QuerySql.GetPostgresGeoTypesRow x, QuerySql.GetPostgre
752752
}
753753

754754
[Test]
755-
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}")]
756-
[TestCase(null)]
757-
public async Task TestPostgresJsonDataTypes(string cJson)
755+
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}", "$.\"name\"")]
756+
[TestCase(null, null)]
757+
public async Task TestPostgresJsonDataTypes(string cJson, string cJsonpath)
758758
{
759759
JsonElement? cParsedJson = null;
760760
if (cJson != null)
761761
cParsedJson = JsonDocument.Parse(cJson).RootElement;
762-
await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJson = cParsedJson, CJsonb = cParsedJson, CJsonStringOverride = cJson });
762+
await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJson = cParsedJson, CJsonb = cParsedJson, CJsonStringOverride = cJson, CJsonpath = cJsonpath });
763763
var expected = new QuerySql.GetPostgresTypesRow
764764
{
765765
CJson = cParsedJson,
766766
CJsonb = cParsedJson,
767-
CJsonStringOverride = cJson
767+
CJsonStringOverride = cJson,
768+
CJsonpath = cJsonpath
768769
};
769770
var actual = await QuerySql.GetPostgresTypes();
770771
AssertSingularEquals(expected, actual);
@@ -777,13 +778,15 @@ void AssertSingularEquals(QuerySql.GetPostgresTypesRow x, QuerySql.GetPostgresTy
777778
if (x.CJsonb.HasValue)
778779
Assert.That(x.CJsonb.Value.GetRawText(), Is.EqualTo(y.CJsonb.Value.GetRawText()));
779780
Assert.That(x.CJsonStringOverride, Is.EqualTo(y.CJsonStringOverride));
781+
Assert.That(x.CJsonpath, Is.EqualTo(y.CJsonpath));
780782
}
781783
}
782784

783785
[Test]
784786
public void TestPostgresInvalidJson()
785787
{
786788
Assert.ThrowsAsync<Npgsql.PostgresException>(async () => await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJsonStringOverride = "SOME INVALID JSON" }));
789+
Assert.ThrowsAsync<Npgsql.PostgresException>(async () => await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJsonpath = "SOME INVALID JSONPATH" }));
787790
}
788791

789792
[Test]

end2end/EndToEndTestsLegacy/NpgsqlTester.generated.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -752,19 +752,20 @@ void AssertSingularEquals(QuerySql.GetPostgresGeoTypesRow x, QuerySql.GetPostgre
752752
}
753753

754754
[Test]
755-
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}")]
756-
[TestCase(null)]
757-
public async Task TestPostgresJsonDataTypes(string cJson)
755+
[TestCase("{\"name\": \"Swordfishtrombones\", \"year\": 1983}", "$.\"name\"")]
756+
[TestCase(null, null)]
757+
public async Task TestPostgresJsonDataTypes(string cJson, string cJsonpath)
758758
{
759759
JsonElement? cParsedJson = null;
760760
if (cJson != null)
761761
cParsedJson = JsonDocument.Parse(cJson).RootElement;
762-
await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJson = cParsedJson, CJsonb = cParsedJson, CJsonStringOverride = cJson });
762+
await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJson = cParsedJson, CJsonb = cParsedJson, CJsonStringOverride = cJson, CJsonpath = cJsonpath });
763763
var expected = new QuerySql.GetPostgresTypesRow
764764
{
765765
CJson = cParsedJson,
766766
CJsonb = cParsedJson,
767-
CJsonStringOverride = cJson
767+
CJsonStringOverride = cJson,
768+
CJsonpath = cJsonpath
768769
};
769770
var actual = await QuerySql.GetPostgresTypes();
770771
AssertSingularEquals(expected, actual);
@@ -777,13 +778,15 @@ void AssertSingularEquals(QuerySql.GetPostgresTypesRow x, QuerySql.GetPostgresTy
777778
if (x.CJsonb.HasValue)
778779
Assert.That(x.CJsonb.Value.GetRawText(), Is.EqualTo(y.CJsonb.Value.GetRawText()));
779780
Assert.That(x.CJsonStringOverride, Is.EqualTo(y.CJsonStringOverride));
781+
Assert.That(x.CJsonpath, Is.EqualTo(y.CJsonpath));
780782
}
781783
}
782784

783785
[Test]
784786
public void TestPostgresInvalidJson()
785787
{
786788
Assert.ThrowsAsync<Npgsql.PostgresException>(async () => await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJsonStringOverride = "SOME INVALID JSON" }));
789+
Assert.ThrowsAsync<Npgsql.PostgresException>(async () => await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJsonpath = "SOME INVALID JSONPATH" }));
787790
}
788791

789792
[Test]

examples/NpgsqlDapperExample/Models.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class PostgresType
4242
public JsonElement? CJson { get; init; }
4343
public JsonElement? CJsonStringOverride { get; init; }
4444
public JsonElement? CJsonb { get; init; }
45+
public string? CJsonpath { get; init; }
4546
public Guid? CUuid { get; init; }
4647
public byte[]? CBytea { get; init; }
4748
public bool[]? CBooleanArray { get; init; }

examples/NpgsqlDapperExample/QuerySql.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ public async Task<List<GetAuthorsByBookNameRow>> GetAuthorsByBookName(GetAuthors
518518
}
519519
}
520520

521-
private const string InsertPostgresTypesSql = "INSERT INTO postgres_types(c_boolean, c_bit, c_smallint, c_integer, c_bigint, c_real, c_numeric, c_decimal, c_double_precision, c_money, c_date, c_time, c_timestamp, c_timestamp_with_tz, c_interval, c_char, c_varchar, c_character_varying, c_bpchar, c_text, c_uuid, c_json, c_json_string_override, c_jsonb, c_bytea, c_boolean_array, c_text_array, c_integer_array, c_decimal_array, c_date_array, c_timestamp_array) VALUES ( @c_boolean, @c_bit, @c_smallint, @c_integer, @c_bigint, @c_real, @c_numeric, @c_decimal, @c_double_precision, @c_money, @c_date, @c_time, @c_timestamp, @c_timestamp_with_tz, @c_interval, @c_char, @c_varchar, @c_character_varying, @c_bpchar, @c_text, @c_uuid, @c_json :: json, @c_json_string_override :: json, @c_jsonb :: jsonb, @c_bytea, @c_boolean_array, @c_text_array, @c_integer_array, @c_decimal_array, @c_date_array, @c_timestamp_array ) ";
521+
private const string InsertPostgresTypesSql = "INSERT INTO postgres_types(c_boolean, c_bit, c_smallint, c_integer, c_bigint, c_real, c_numeric, c_decimal, c_double_precision, c_money, c_date, c_time, c_timestamp, c_timestamp_with_tz, c_interval, c_char, c_varchar, c_character_varying, c_bpchar, c_text, c_uuid, c_json, c_json_string_override, c_jsonb, c_jsonpath, c_bytea, c_boolean_array, c_text_array, c_integer_array, c_decimal_array, c_date_array, c_timestamp_array) VALUES ( @c_boolean, @c_bit, @c_smallint, @c_integer, @c_bigint, @c_real, @c_numeric, @c_decimal, @c_double_precision, @c_money, @c_date, @c_time, @c_timestamp, @c_timestamp_with_tz, @c_interval, @c_char, @c_varchar, @c_character_varying, @c_bpchar, @c_text, @c_uuid, @c_json :: json, @c_json_string_override :: json, @c_jsonb :: jsonb, @c_jsonpath :: jsonpath, @c_bytea, @c_boolean_array, @c_text_array, @c_integer_array, @c_decimal_array, @c_date_array, @c_timestamp_array ) ";
522522
public class InsertPostgresTypesArgs
523523
{
524524
public bool? CBoolean { get; init; }
@@ -545,6 +545,7 @@ public class InsertPostgresTypesArgs
545545
public JsonElement? CJson { get; init; }
546546
public string? CJsonStringOverride { get; init; }
547547
public JsonElement? CJsonb { get; init; }
548+
public string? CJsonpath { get; init; }
548549
public byte[]? CBytea { get; init; }
549550
public bool[]? CBooleanArray { get; init; }
550551
public string[]? CTextArray { get; init; }
@@ -580,6 +581,7 @@ public async Task InsertPostgresTypes(InsertPostgresTypesArgs args)
580581
queryParams.Add("c_json", args.CJson.HasValue ? args.CJson.Value.GetRawText() : null);
581582
queryParams.Add("c_json_string_override", args.CJsonStringOverride);
582583
queryParams.Add("c_jsonb", args.CJsonb.HasValue ? args.CJsonb.Value.GetRawText() : null);
584+
queryParams.Add("c_jsonpath", args.CJsonpath);
583585
queryParams.Add("c_bytea", args.CBytea);
584586
queryParams.Add("c_boolean_array", args.CBooleanArray);
585587
queryParams.Add("c_text_array", args.CTextArray);
@@ -670,7 +672,7 @@ public async Task InsertPostgresTypesBatch(List<InsertPostgresTypesBatchArgs> ar
670672
}
671673
}
672674

673-
private const string GetPostgresTypesSql = "SELECT c_boolean, c_bit, c_smallint, c_integer, c_bigint, c_decimal, c_numeric, c_real, c_double_precision, c_money, c_date, c_time, c_timestamp, c_timestamp_with_tz, c_interval, c_char, c_varchar, c_character_varying, c_bpchar, c_text, c_json, c_json_string_override, c_jsonb, c_uuid, c_bytea, c_boolean_array, c_text_array, c_integer_array, c_decimal_array, c_date_array, c_timestamp_array FROM postgres_types LIMIT 1";
675+
private const string GetPostgresTypesSql = "SELECT c_boolean, c_bit, c_smallint, c_integer, c_bigint, c_decimal, c_numeric, c_real, c_double_precision, c_money, c_date, c_time, c_timestamp, c_timestamp_with_tz, c_interval, c_char, c_varchar, c_character_varying, c_bpchar, c_text, c_json, c_json_string_override, c_jsonb, c_jsonpath, c_uuid, c_bytea, c_boolean_array, c_text_array, c_integer_array, c_decimal_array, c_date_array, c_timestamp_array FROM postgres_types LIMIT 1";
674676
public class GetPostgresTypesRow
675677
{
676678
public bool? CBoolean { get; init; }
@@ -696,6 +698,7 @@ public class GetPostgresTypesRow
696698
public JsonElement? CJson { get; init; }
697699
public string? CJsonStringOverride { get; init; }
698700
public JsonElement? CJsonb { get; init; }
701+
public string? CJsonpath { get; init; }
699702
public Guid? CUuid { get; init; }
700703
public byte[]? CBytea { get; init; }
701704
public bool[]? CBooleanArray { get; init; }

0 commit comments

Comments
 (0)