Skip to content

Commit eb87752

Browse files
feat: add support for postgres XML data type
1 parent 4519bf8 commit eb87752

30 files changed

Lines changed: 657 additions & 213 deletions

Drivers/Generators/CommonGen.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public static string GetMethodParameterList(string argInterface, IEnumerable<Par
3636
return dbDriver.Options.UseDapper ? null : DefaultWriterFn;
3737
}
3838

39+
// TODO: extract AddWithValue statement generation to a method + possible override for Npgsql for type override
3940
public string AddParametersToCommand(Query query)
4041
{
4142
return query.Params.Select(p =>

Drivers/NpgsqlDriver.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,30 @@ public NpgsqlDriver(
177177
readerFn: ordinal => $"reader.GetBoolean({ordinal})",
178178
readerArrayFn: ordinal => $"reader.GetFieldValue<bool[]>({ordinal})"
179179
),
180+
["XmlDocument"] = new(
181+
new()
182+
{
183+
{ "xml", new(NpgsqlTypeOverride: "NpgsqlDbType.Xml") }
184+
},
185+
readerFn: ordinal => $$"""
186+
(new Func<NpgsqlDataReader, int, XmlDocument>((r, o) =>
187+
{
188+
var xmlDoc = new XmlDocument();
189+
xmlDoc.LoadXml(r.GetString(o));
190+
return xmlDoc;
191+
}))({{Variable.Reader.AsVarName()}}, {{ordinal}})
192+
""",
193+
writerFn: (el, notNull, isDapper) =>
194+
{
195+
if (notNull)
196+
return $"{el}.OuterXml";
197+
var nullValue = isDapper ? "null" : "(object)DBNull.Value";
198+
return $"{el} != null ? {el}.OuterXml : {nullValue}";
199+
},
200+
usingDirective: "System.Xml",
201+
sqlMapper: "SqlMapper.AddTypeHandler(typeof(XmlDocument), new XmlDocumentTypeHandler());",
202+
sqlMapperImpl: XmlDocumentTypeHandler
203+
),
180204
["NpgsqlPoint"] = new(
181205
new()
182206
{
@@ -288,6 +312,28 @@ public NpgsqlDriver(
288312

289313
public override string TransactionClassName => "NpgsqlTransaction";
290314

315+
protected const string XmlDocumentTypeHandler =
316+
"""
317+
private class XmlDocumentTypeHandler : SqlMapper.TypeHandler<XmlDocument>
318+
{
319+
public override XmlDocument Parse(object value)
320+
{
321+
if (value is string s)
322+
{
323+
var xmlDoc = new XmlDocument();
324+
xmlDoc.LoadXml(s);
325+
return xmlDoc;
326+
}
327+
throw new DataException($"Cannot convert {value?.GetType()} to XmlDocument");
328+
}
329+
330+
public override void SetValue(IDbDataParameter parameter, XmlDocument value)
331+
{
332+
parameter.Value = value.OuterXml;
333+
}
334+
}
335+
""";
336+
291337
public override ISet<string> GetUsingDirectivesForQueries()
292338
{
293339
return base.GetUsingDirectivesForQueries().AddRangeExcludeNulls(

docs/04_Postgres.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ we consider support for the different data types separately for batch inserts an
6565
| json |||
6666
| jsonb |||
6767
| jsonpath |||
68-
| xml | ||
68+
| xml | ||
6969
| enum |||
7070

7171
*** `time with time zone` is not useful and not recommended to use by Postgres themselves -

end2end/EndToEndScaffold/Config.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public enum KnownTestType
5555
PostgresNetworkDataTypes,
5656
PostgresJsonDataTypes,
5757
PostgresInvalidJson,
58+
PostgresXmlDataTypes,
5859

5960
ArrayAsParam,
6061
MultipleArraysAsParams,
@@ -201,6 +202,7 @@ internal static class Config
201202
KnownTestType.PostgresJsonDataTypes,
202203
KnownTestType.PostgresInvalidJson,
203204
KnownTestType.PostgresNetworkDataTypes,
205+
KnownTestType.PostgresXmlDataTypes,
204206

205207
KnownTestType.PostgresStringCopyFrom,
206208
KnownTestType.PostgresIntegerCopyFrom,
@@ -243,6 +245,7 @@ internal static class Config
243245
KnownTestType.PostgresJsonDataTypes,
244246
KnownTestType.PostgresInvalidJson,
245247
KnownTestType.PostgresNetworkDataTypes,
248+
KnownTestType.PostgresXmlDataTypes,
246249

247250
KnownTestType.PostgresStringCopyFrom,
248251
KnownTestType.PostgresIntegerCopyFrom,

end2end/EndToEndScaffold/Program.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ private static string GetFileContents(string testClassName, bool isLegacyDotnet)
4242
var optionalUsingPostgresTypes = config.TestNamespace.Contains("Npgsql") ? "using NpgsqlTypes;" : string.Empty;
4343
var optionalUsingSystemNet = config.TestNamespace.Contains("Npgsql") ? "using System.Net;" : string.Empty;
4444
var optionalUsingSystemNetNetworkInformation = config.TestNamespace.Contains("Npgsql") ? "using System.Net.NetworkInformation;" : string.Empty;
45-
var namespaceToTest = isLegacyDotnet ? config.LegacyTestNamespace : config.TestNamespace;
4645
var optionalUsingSystemTextJson = config.TestNamespace.Contains("MySqlConnector") || config.TestNamespace.Contains("Npgsql") ? "using System.Text.Json;" : string.Empty;
46+
var optionalUsingSystemXml = config.TestNamespace.Contains("Npgsql") ? "using System.Xml;" : string.Empty;
47+
var namespaceToTest = isLegacyDotnet ? config.LegacyTestNamespace : config.TestNamespace;
4748

4849
return ParseCompilationUnit(
4950
$$"""
@@ -52,6 +53,7 @@ private static string GetFileContents(string testClassName, bool isLegacyDotnet)
5253
{{optionalUsingSystemNet}}
5354
{{optionalUsingSystemNetNetworkInformation}}
5455
{{optionalUsingSystemTextJson}}
56+
{{optionalUsingSystemXml}}
5557
using NUnit.Framework;
5658
using NUnit.Framework.Legacy;
5759
using System;

end2end/EndToEndScaffold/Templates/PostgresTests.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,43 @@ void AssertSingularEquals(QuerySql.GetPostgresTypesCntRow x, QuerySql.GetPostgre
954954
}
955955
}
956956
"""
957+
},
958+
[KnownTestType.PostgresXmlDataTypes] = new TestImpl
959+
{
960+
Impl = $$"""
961+
[Test]
962+
[TestCase("<root><child>Good morning xml, the world says hello</child></root>")]
963+
[TestCase(null)]
964+
public async Task TestPostgresXmlDataTypes(string cXml)
965+
{
966+
XmlDocument parsedXml = null;
967+
if (cXml != null)
968+
{
969+
parsedXml = new XmlDocument();
970+
parsedXml.LoadXml(cXml);
971+
}
972+
973+
await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs
974+
{
975+
CXml = parsedXml
976+
});
977+
978+
var expected = new QuerySql.GetPostgresTypesRow
979+
{
980+
CXml = parsedXml
981+
};
982+
983+
var actual = await QuerySql.GetPostgresTypes();
984+
AssertSingularEquals(expected, actual{{Consts.UnknownRecordValuePlaceholder}});
985+
986+
void AssertSingularEquals(QuerySql.GetPostgresTypesRow x, QuerySql.GetPostgresTypesRow y)
987+
{
988+
if (x.CXml == null && y.CXml == null)
989+
return;
990+
Assert.That(x.CXml.OuterXml, Is.EqualTo(y.CXml.OuterXml));
991+
}
992+
}
993+
"""
957994
}
958995
};
959996
}

end2end/EndToEndTests/NpgsqlDapperTester.generated.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Net;
44
using System.Net.NetworkInformation;
55
using System.Text.Json;
6+
using System.Xml;
67
using NUnit.Framework;
78
using NUnit.Framework.Legacy;
89
using System;
@@ -825,6 +826,33 @@ public void TestPostgresInvalidJson()
825826
Assert.ThrowsAsync<Npgsql.PostgresException>(async () => await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJsonpath = "SOME INVALID JSONPATH" }));
826827
}
827828

829+
[Test]
830+
[TestCase("<root><child>Good morning xml, the world says hello</child></root>")]
831+
[TestCase(null)]
832+
public async Task TestPostgresXmlDataTypes(string cXml)
833+
{
834+
XmlDocument parsedXml = null;
835+
if (cXml != null)
836+
{
837+
parsedXml = new XmlDocument();
838+
parsedXml.LoadXml(cXml);
839+
}
840+
841+
await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CXml = parsedXml });
842+
var expected = new QuerySql.GetPostgresTypesRow
843+
{
844+
CXml = parsedXml
845+
};
846+
var actual = await QuerySql.GetPostgresTypes();
847+
AssertSingularEquals(expected, actual);
848+
void AssertSingularEquals(QuerySql.GetPostgresTypesRow x, QuerySql.GetPostgresTypesRow y)
849+
{
850+
if (x.CXml == null && y.CXml == null)
851+
return;
852+
Assert.That(x.CXml.OuterXml, Is.EqualTo(y.CXml.OuterXml));
853+
}
854+
}
855+
828856
[Test]
829857
public async Task TestArray()
830858
{

end2end/EndToEndTests/NpgsqlTester.generated.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Net;
44
using System.Net.NetworkInformation;
55
using System.Text.Json;
6+
using System.Xml;
67
using NUnit.Framework;
78
using NUnit.Framework.Legacy;
89
using System;
@@ -825,6 +826,33 @@ public void TestPostgresInvalidJson()
825826
Assert.ThrowsAsync<Npgsql.PostgresException>(async () => await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJsonpath = "SOME INVALID JSONPATH" }));
826827
}
827828

829+
[Test]
830+
[TestCase("<root><child>Good morning xml, the world says hello</child></root>")]
831+
[TestCase(null)]
832+
public async Task TestPostgresXmlDataTypes(string cXml)
833+
{
834+
XmlDocument parsedXml = null;
835+
if (cXml != null)
836+
{
837+
parsedXml = new XmlDocument();
838+
parsedXml.LoadXml(cXml);
839+
}
840+
841+
await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CXml = parsedXml });
842+
var expected = new QuerySql.GetPostgresTypesRow
843+
{
844+
CXml = parsedXml
845+
};
846+
var actual = await QuerySql.GetPostgresTypes();
847+
AssertSingularEquals(expected, actual.Value);
848+
void AssertSingularEquals(QuerySql.GetPostgresTypesRow x, QuerySql.GetPostgresTypesRow y)
849+
{
850+
if (x.CXml == null && y.CXml == null)
851+
return;
852+
Assert.That(x.CXml.OuterXml, Is.EqualTo(y.CXml.OuterXml));
853+
}
854+
}
855+
828856
[Test]
829857
public async Task TestArray()
830858
{

end2end/EndToEndTestsLegacy/NpgsqlDapperTester.generated.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Net;
44
using System.Net.NetworkInformation;
55
using System.Text.Json;
6+
using System.Xml;
67
using NUnit.Framework;
78
using NUnit.Framework.Legacy;
89
using System;
@@ -825,6 +826,33 @@ public void TestPostgresInvalidJson()
825826
Assert.ThrowsAsync<Npgsql.PostgresException>(async () => await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJsonpath = "SOME INVALID JSONPATH" }));
826827
}
827828

829+
[Test]
830+
[TestCase("<root><child>Good morning xml, the world says hello</child></root>")]
831+
[TestCase(null)]
832+
public async Task TestPostgresXmlDataTypes(string cXml)
833+
{
834+
XmlDocument parsedXml = null;
835+
if (cXml != null)
836+
{
837+
parsedXml = new XmlDocument();
838+
parsedXml.LoadXml(cXml);
839+
}
840+
841+
await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CXml = parsedXml });
842+
var expected = new QuerySql.GetPostgresTypesRow
843+
{
844+
CXml = parsedXml
845+
};
846+
var actual = await QuerySql.GetPostgresTypes();
847+
AssertSingularEquals(expected, actual);
848+
void AssertSingularEquals(QuerySql.GetPostgresTypesRow x, QuerySql.GetPostgresTypesRow y)
849+
{
850+
if (x.CXml == null && y.CXml == null)
851+
return;
852+
Assert.That(x.CXml.OuterXml, Is.EqualTo(y.CXml.OuterXml));
853+
}
854+
}
855+
828856
[Test]
829857
public async Task TestArray()
830858
{

end2end/EndToEndTestsLegacy/NpgsqlTester.generated.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Net;
44
using System.Net.NetworkInformation;
55
using System.Text.Json;
6+
using System.Xml;
67
using NUnit.Framework;
78
using NUnit.Framework.Legacy;
89
using System;
@@ -825,6 +826,33 @@ public void TestPostgresInvalidJson()
825826
Assert.ThrowsAsync<Npgsql.PostgresException>(async () => await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CJsonpath = "SOME INVALID JSONPATH" }));
826827
}
827828

829+
[Test]
830+
[TestCase("<root><child>Good morning xml, the world says hello</child></root>")]
831+
[TestCase(null)]
832+
public async Task TestPostgresXmlDataTypes(string cXml)
833+
{
834+
XmlDocument parsedXml = null;
835+
if (cXml != null)
836+
{
837+
parsedXml = new XmlDocument();
838+
parsedXml.LoadXml(cXml);
839+
}
840+
841+
await QuerySql.InsertPostgresTypes(new QuerySql.InsertPostgresTypesArgs { CXml = parsedXml });
842+
var expected = new QuerySql.GetPostgresTypesRow
843+
{
844+
CXml = parsedXml
845+
};
846+
var actual = await QuerySql.GetPostgresTypes();
847+
AssertSingularEquals(expected, actual);
848+
void AssertSingularEquals(QuerySql.GetPostgresTypesRow x, QuerySql.GetPostgresTypesRow y)
849+
{
850+
if (x.CXml == null && y.CXml == null)
851+
return;
852+
Assert.That(x.CXml.OuterXml, Is.EqualTo(y.CXml.OuterXml));
853+
}
854+
}
855+
828856
[Test]
829857
public async Task TestArray()
830858
{

0 commit comments

Comments
 (0)