diff --git a/DbSchema/CodeGeneration/CodeGenerator.cs b/DbSchema/CodeGeneration/CodeGenerator.cs index a163a2c..d92d6ed 100644 --- a/DbSchema/CodeGeneration/CodeGenerator.cs +++ b/DbSchema/CodeGeneration/CodeGenerator.cs @@ -274,8 +274,8 @@ public static void GetColumnName(DatabaseTable table, DatabaseColumn column, boo columnTypeName = columnType; } - else if(column.DataType.DotNetType.IsAssignableTo(typeof(IGeography_UnsupportedType))) { - columnTypeName = $"{nameof(IGeography_UnsupportedType)}"; + else if(column.DataType.DotNetType.IsAssignableTo(typeof(IGeography))) { + columnTypeName = $"{nameof(IGeography)}"; } else { columnTypeName = columnType; diff --git a/Documentation.md b/Documentation.md index 24c05b3..0ca5afc 100644 --- a/Documentation.md +++ b/Documentation.md @@ -30,8 +30,9 @@ - [Functions](#functions) - [Custom Functions](#custom-functions) - [Supported Data Types](#supported-data-types) -- [Key Columns](#key-columns) -- [Not Supported Data Types](#not-supported-data-types) + - [Key Columns](#key-columns) + - [Geography Types](#geography-types) + - [Not Supported Data Types](#not-supported-data-types) - [Transaction Isolation Levels](#transaction-isolation-levels) - [Query Builder Caching](#query-builder-caching) - [Executing Custom SQL](#executing-custom-sql) @@ -831,6 +832,125 @@ public sealed class ShipperTable : ATable { } ``` +## Geography Types + +Geography types are partially supported. These are complex data types that cannot be returned directly via a query. Instead, a range of functions are used to return infomation about the various geography types. + +**Note: Currently Geography functions have only been implemented for Sql Server. Some functions may work on PostgreSql but others are Sql Server Only.** + +A custom function can be created if you require one that is not implemented by this library. Tip: View the source code and use the implementation of these functions as a guide on how to create a new function. + +These are the geography functions currently implemented: + +| Function | Sql | +| ------------------ | --- | +| GeographyPoint | `geography::Point(...)` | +| STArea | `.STArea()` | +| STEquals | `.STEquals(...)` | +| STAsBinary | `.STAsBinary()` | +| STAsText | `.STAsText()` | +| STContains | `.STContains(...)` | +| STDistance | `.STDistance(...)` | +| STGeomFromText | `geography::STGeomFromText(...)` | +| STPointFromText | `geography::STPointFromText(...)` | +| STLineFromText | `geography::STLineFromText(...)` | +| STPolyFromText | `geography::STPolyFromText(...)` | +| STMPointFromText | `geography::STMPointFromText(...)` | +| STMLineFromText | `geography::STMLineFromText(...)` | +| STMPolyFromText | `geography::STMPolyFromText(...)` | +| STGeomCollFromText | `geography::STGeomCollFromText(...)` | +| STGeomCollFromWKB | `geography::STGeomCollFromWKB(...)` | +| STGeomFromWKB | `geography::STGeomFromWKB(...)` | +| STPointFromWKB | `geography::STPointFromWKB(...)` | +| STLineFromWKB | `geography::STLineFromWKB(...)` | +| STPolyFromWKB | `geography::STPolyFromWKB(...)` | +| STMPointFromWKB | `geography::STMPointFromWKB(...)` | +| STMLineFromWKB | `geography::STMLineFromWKB(...)` | +| STMPolyFromWKB | `geography::STMPolyFromWKB(...)` | +| Longitude | `.Long` | +| Latitude | `.Lat` | +| GeographyParse | `geography::Parse(...)` | + +Here is a code example querying the database with a subset of the geography functions listed above: + +```C# +GeoTestTable table = GeoTestTable.Instance; + +//Define a 'STPointFromText(...)' sql function +STPointFromText stPointFromText = new STPointFromText(kwText: "POINT(-122.34900 47.65100)"); + +GuidKey guid = GuidKey.ValueOf(Guid.NewGuid()); + +using(Transaction transaction = new Transaction(TestDatabase.Database)) { + + //Insert a record into the database + NonQueryResult insertResult = Query + .Insert(table) + .Values(values => values + .Set(table.Guid, guid) + .Set(table.Geography, stPointFromText) + ) + .Execute(transaction); + /* + INSERT INTO dbo.GeoTest(gtGuid,gtGeography) VALUES('f876dc6c-4ada-48cd-ade3-e61323d2b416',geography::STPointFromText('POINT(-122.34900 47.65100)', 4326)) + */ + transaction.Commit(); +} + +//Define a 'geography::Point' sql function +GeographyPoint geographyPoint = new GeographyPoint(latitude: 47.65100, longitude: -122.34900); + +//Define a 'STDistance()' sql function +STDistance distance = new STDistance(table.Geography, geographyPoint); + +//Define a 'STAsBinary()' sql function +STAsBinary stAsBinary = new STAsBinary(table.Geography); + +//Define a 'STAsText()' sql function +STAsText stAsText = new STAsText(table.Geography); + +//Define a '.Long' sql property +Longitude longitude = new Longitude(table.Geography); + +//Define a '.Lat' sql property +Latitude latitude = new Latitude(table.Geography); + +var result = Query + .Select( + row => new { + Guid = row.Get(table.Guid), + Distance = row.Get(distance), + Binary = row.Get(stAsBinary), + Text = row.Get(stAsText), + Longitude = row.Get(longitude), + Latitude = row.Get(latitude) + } + ) + .From(table) + .Where(new STEquals(table.Geography, geographyPoint) == 1) + .Execute(TestDatabase.Database); +/* + SELECT gtGuid, + gtGeography.STDistance(geography::Point(47.651,-122.349,4326)), + gtGeography.STAsBinary(), + gtGeography.STAsText(), + gtGeography.Long, + gtGeography.Lat + FROM dbo.GeoTest + WHERE gtGeography.STEquals(geography::Point(47.651,-122.349,4326)) = 1 +*/ +Assert.AreEqual(result.Rows.Count, 1); + +var row = result.Rows[0]; + +Assert.AreEqual(row.Guid, guid); +Assert.AreEqual(row.Distance, 0); +Assert.AreEqual(BitConverter.ToString(row.Binary!).Replace("-", ""), "01010000007593180456965EC017D9CEF753D34740"); +Assert.AreEqual(row.Text, "POINT (-122.349 47.651)"); +Assert.AreEqual(row.Longitude, -122.349); +Assert.AreEqual(row.Latitude, 47.651); +``` + ## Not Supported Data Types Most non-common or custom database types are not supported. If the type can be read in using a basic .net type (e.g. `int`, `string`, `decimal`) in `ado.net` (e.g. `reader.GetString(ordinal)`) then it should work. @@ -1122,4 +1242,4 @@ public sealed class TerritoryTable : ATable { RegionId = new Column>(this, columnName: "RegionId"); } } -``` +``` \ No newline at end of file diff --git a/QueryLite/Databases/Functions/OGCDataTypes.cs b/QueryLite/Databases/Functions/OGCDataTypes.cs new file mode 100644 index 0000000..6a6e181 --- /dev/null +++ b/QueryLite/Databases/Functions/OGCDataTypes.cs @@ -0,0 +1,289 @@ +/* + * MIT License + * + * Copyright (c) 2023 EndsOfTheEarth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + **/ +namespace QueryLite.Databases.Functions { + + /* + Standard Type Methods yet to be implemented + + STBuffer + STConvexHull + STCurveN (geography Data Type) + STCurveToLine (geography Data Type) + STDifference + STDimension + STDisjoint + STEndpoint + STGeometryN + STGeometryType + STIntersection + STIntersects + STIsClosed + STIsEmpty + STIsValid + STLength + STNumCurves (geography Data Type) + STNumGeometries + STNumPoints + STOverlaps + STPointN + STSrid + STStartPoint + STSymDifference + STUnion + STWithin + */ + + /// + /// Geography function for defining a Point + /// e.g. geography::Point(Latitude},Longitude,SRID) + /// + public sealed class GeographyPoint : Function, IGeographySqlType { + + public double Latitude { get; } + public double Longitude { get; } + public int SRID { get; } + + public GeographyPoint(double latitude, double longitude, int srid = 4326) : base(name: "geography::Point") { + Latitude = latitude; + Longitude = longitude; + SRID = srid; + } + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + + parameters.AddParameter(database, typeof(double), Latitude, out string latitudeParam); + parameters.AddParameter(database, typeof(double), Longitude, out string longitudeParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + + return $"geography::Point({latitudeParam},{longitudeParam},{sridParam})"; + } + else { + return $"geography::Point({Latitude},{Longitude},{SRID})"; + } + } + } + + public sealed class STArea : NullableFunction { + + private AColumn? Column { get; } + private IGeographySqlType? OCGType { get; } + + public STArea(AColumn column) : base(name: "STArea") { + Column = column; + } + public STArea(IGeographySqlType? oCGType) : base(name: "STArea") { + OCGType = oCGType; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(Column is not null) { + return useAlias ? $"{Column.Table.Alias}.{SqlHelper.EncloseColumnName(Column)}.STArea()" : $"{SqlHelper.EncloseColumnName(Column)}.STArea()"; + } + else { + return $"{OCGType!.GetSql(database, useAlias: useAlias, parameters)}.STArea()"; + } + } + } + + public sealed class STEquals : NullableFunction { + + private AColumn ColumnA { get; } + private AColumn? ColumnB { get; } + private IGeographySqlType? ToGeography { get; } + + public STEquals(AColumn columnA, AColumn columnB) : base(name: "STEquals") { + ColumnA = columnA; + ColumnB = columnB; + } + public STEquals(AColumn columnA, IGeographySqlType? geographyB) : base(name: "STEquals") { + ColumnA = columnA; + ToGeography = geographyB; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + string sql; + + if(ColumnB is not null) { + + if(useAlias) { + sql = $"{ColumnA.Table.Alias}.{SqlHelper.EncloseColumnName(ColumnA)}.STEquals({ColumnB.Table.Alias}.{SqlHelper.EncloseColumnName(ColumnB)})"; + } + else { + sql = $"{SqlHelper.EncloseColumnName(ColumnA)}.STEquals({SqlHelper.EncloseColumnName(ColumnB)})"; + } + } + else { + + string toPointSql = ToGeography!.GetSql(database, useAlias: useAlias, parameters); + + if(useAlias) { + sql = $"{ColumnA.Table.Alias}.{SqlHelper.EncloseColumnName(ColumnA)}.STEquals({toPointSql})"; + } + else { + sql = $"{SqlHelper.EncloseColumnName(ColumnA)}.STEquals({toPointSql})"; + } + } + return sql; + } + } + + public sealed class STAsBinary : NullableFunction { + + private AColumn? Column { get; } + private IGeographySqlType? OCGType { get; } + + public STAsBinary(AColumn column) : base(name: "STAsBinary") { + Column = column; + } + public STAsBinary(IGeographySqlType? oCGType) : base(name: "STAsBinary") { + OCGType = oCGType; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(Column is not null) { + return useAlias ? $"{Column.Table.Alias}.{SqlHelper.EncloseColumnName(Column)}.STAsBinary()" : $"{SqlHelper.EncloseColumnName(Column)}.STAsBinary()"; + } + else { + return $"{OCGType!.GetSql(database, useAlias: useAlias, parameters)}.STAsBinary()"; + } + } + } + + public sealed class STAsText : NullableFunction { + + private AColumn? Column { get; } + private IGeographySqlType? OCGType { get; } + + public STAsText(AColumn column) : base(name: "STAsText") { + Column = column; + } + public STAsText(IGeographySqlType? oCGType) : base(name: "STAsText") { + OCGType = oCGType; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(Column is not null) { + return useAlias ? $"{Column.Table.Alias}.{SqlHelper.EncloseColumnName(Column)}.STAsText()" : $"{SqlHelper.EncloseColumnName(Column)}.STAsText()"; + } + else { + return $"{OCGType!.GetSql(database, useAlias: useAlias, parameters)}.STAsText()"; + } + } + } + + public sealed class STContains : NullableFunction { + + private AColumn ColumnA { get; } + private AColumn? ColumnB { get; } + private IGeographySqlType? ToGeography { get; } + + public STContains(AColumn columnA, AColumn columnB) : base(name: "STContains") { + ColumnA = columnA; + ColumnB = columnB; + } + public STContains(AColumn columnA, IGeographySqlType? geographyB) : base(name: "STContains") { + ColumnA = columnA; + ToGeography = geographyB; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + string sql; + + if(ColumnB is not null) { + + if(useAlias) { + sql = $"{ColumnA.Table.Alias}.{SqlHelper.EncloseColumnName(ColumnA)}.STContains({ColumnB.Table.Alias}.{SqlHelper.EncloseColumnName(ColumnB)})"; + } + else { + sql = $"{SqlHelper.EncloseColumnName(ColumnA)}.STContains({SqlHelper.EncloseColumnName(ColumnB)})"; + } + } + else { + + string toPointSql = ToGeography!.GetSql(database, useAlias: useAlias, parameters); + + if(useAlias) { + sql = $"{ColumnA.Table.Alias}.{SqlHelper.EncloseColumnName(ColumnA)}.STContains({toPointSql})"; + } + else { + sql = $"{SqlHelper.EncloseColumnName(ColumnA)}.STContains({toPointSql})"; + } + } + return sql; + } + } + + /// + /// Geography function for measuring the shortest distance between two geographies + /// e.g. select columnA.STDistance(columnB) from table + /// + public sealed class STDistance : NullableFunction { + + private AColumn FromColumn { get; } + private AColumn? ToColumn { get; } + private IGeographySqlType? ToGeography { get; } + + public STDistance(AColumn fromColumn, AColumn toColumn) : base(name: "STDistance") { + FromColumn = fromColumn; + ToColumn = toColumn; + } + public STDistance(AColumn fromColumn, IGeographySqlType? toGeography) : base(name: "STDistance") { + FromColumn = fromColumn; + ToGeography = toGeography; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + string sql; + + if(ToColumn is not null) { + + if(useAlias) { + sql = $"{FromColumn.Table.Alias}.{SqlHelper.EncloseColumnName(FromColumn)}.STDistance({ToColumn.Table.Alias}.{SqlHelper.EncloseColumnName(ToColumn)})"; + } + else { + sql = $"{SqlHelper.EncloseColumnName(FromColumn)}.STDistance({SqlHelper.EncloseColumnName(ToColumn)})"; + } + } + else { + + string toPointSql = ToGeography!.GetSql(database, useAlias: useAlias, parameters); + + if(useAlias) { + sql = $"{FromColumn.Table.Alias}.{SqlHelper.EncloseColumnName(FromColumn)}.STDistance({toPointSql})"; + } + else { + sql = $"{SqlHelper.EncloseColumnName(FromColumn)}.STDistance({toPointSql})"; + } + } + return sql; + } + } +} \ No newline at end of file diff --git a/QueryLite/Databases/Functions/OGCStaticGeographyMethods.cs b/QueryLite/Databases/Functions/OGCStaticGeographyMethods.cs new file mode 100644 index 0000000..20ab064 --- /dev/null +++ b/QueryLite/Databases/Functions/OGCStaticGeographyMethods.cs @@ -0,0 +1,398 @@ +/* + * MIT License + * + * Copyright (c) 2023 EndsOfTheEarth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + **/ +namespace QueryLite.Databases.Functions { + + public interface IGeographySqlType { + + internal string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters); + } + + public sealed class STGeomFromText : NullableFunction, IGeographySqlType { + + private string KwText { get; } + public int SRID { get; } + + public STGeomFromText(string kwText, int srid = 4326) : base(name: "STGeomFromText") { + KwText = kwText; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(string), KwText, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STGeomFromText({kwtParam},{sridParam})"; + } + else { + return $"geography::STGeomFromText('{Helpers.EscapeForSql(KwText)}', {SRID})"; + } + } + } + + public sealed class STPointFromText : NullableFunction, IGeographySqlType { + + private string KwText { get; } + public int SRID { get; } + + public STPointFromText(string kwText, int srid = 4326) : base(name: "STPointFromText") { + KwText = kwText; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(string), KwText, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STPointFromText({kwtParam},{sridParam})"; + } + else { + return $"geography::STPointFromText('{Helpers.EscapeForSql(KwText)}', {SRID})"; + } + } + } + + public sealed class STLineFromText : NullableFunction, IGeographySqlType { + + private string KwText { get; } + public int SRID { get; } + + public STLineFromText(string kwText, int srid = 4326) : base(name: "STLineFromText") { + KwText = kwText; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(string), KwText, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STLineFromText({kwtParam},{sridParam})"; + } + else { + return $"geography::STLineFromText('{Helpers.EscapeForSql(KwText)}', {SRID})"; + } + } + } + + public sealed class STPolyFromText : NullableFunction, IGeographySqlType { + + private string KwText { get; } + public int SRID { get; } + + public STPolyFromText(string kwText, int srid = 4326) : base(name: "STPolyFromText") { + KwText = kwText; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(string), KwText, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STPolyFromText({kwtParam},{sridParam})"; + } + else { + return $"geography::STPolyFromText('{Helpers.EscapeForSql(KwText)}', {SRID})"; + } + } + } + + public sealed class STMPointFromText : NullableFunction, IGeographySqlType { + + private string KwText { get; } + public int SRID { get; } + + public STMPointFromText(string kwText, int srid = 4326) : base(name: "STMPointFromText") { + KwText = kwText; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(string), KwText, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STMPointFromText({kwtParam},{sridParam})"; + } + else { + return $"geography::STMPointFromText('{Helpers.EscapeForSql(KwText)}', {SRID})"; + } + } + } + + public sealed class STMLineFromText : NullableFunction, IGeographySqlType { + + private string KwText { get; } + public int SRID { get; } + + public STMLineFromText(string kwText, int srid = 4326) : base(name: "STMLineFromText") { + KwText = kwText; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(string), KwText, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STMLineFromText({kwtParam},{sridParam})"; + } + else { + return $"geography::STMLineFromText('{Helpers.EscapeForSql(KwText)}', {SRID})"; + } + } + } + + public sealed class STMPolyFromText : NullableFunction, IGeographySqlType { + + private string KwText { get; } + public int SRID { get; } + + public STMPolyFromText(string kwText, int srid = 4326) : base(name: "STMPolyFromText") { + KwText = kwText; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(string), KwText, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STMPolyFromText({kwtParam},{sridParam})"; + } + else { + return $"geography::STMPolyFromText('{Helpers.EscapeForSql(KwText)}', {SRID})"; + } + } + } + + public sealed class STGeomCollFromText : NullableFunction, IGeographySqlType { + + private string KwText { get; } + public int SRID { get; } + + public STGeomCollFromText(string kwText, int srid = 4326) : base(name: "STGeomCollFromText") { + KwText = kwText; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(string), KwText, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STGeomCollFromText({kwtParam},{sridParam})"; + } + else { + return $"geography::STGeomCollFromText('{Helpers.EscapeForSql(KwText)}', {SRID})"; + } + } + } + + public sealed class STGeomCollFromWKB : NullableFunction, IGeographySqlType { + + private string KwBinary { get; } + public int SRID { get; } + + public STGeomCollFromWKB(string kwBinary, int srid = 4326) : base(name: "STGeomCollFromWKB") { + KwBinary = kwBinary; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(byte[]), KwBinary, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STGeomCollFromWKB({kwtParam},{sridParam})"; + } + else { + return $"geography::STGeomCollFromWKB({Helpers.EscapeForSql(KwBinary)}, {SRID})"; + } + } + } + + public sealed class STGeomFromWKB : NullableFunction, IGeographySqlType { + + private string KwBinary { get; } + public int SRID { get; } + + public STGeomFromWKB(string kwBinary, int srid = 4326) : base(name: "STGeomFromWKB") { + KwBinary = kwBinary; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(byte[]), KwBinary, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STGeomFromWKB({kwtParam},{sridParam})"; + } + else { + return $"geography::STGeomFromWKB({Helpers.EscapeForSql(KwBinary)}, {SRID})"; + } + } + } + + public sealed class STPointFromWKB : NullableFunction, IGeographySqlType { + + private string KwBinary { get; } + public int SRID { get; } + + public STPointFromWKB(string kwBinary, int srid = 4326) : base(name: "STPointFromWKB") { + KwBinary = kwBinary; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(byte[]), KwBinary, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STPointFromWKB({kwtParam},{sridParam})"; + } + else { + return $"geography::STPointFromWKB({Helpers.EscapeForSql(KwBinary)}, {SRID})"; + } + } + } + + public sealed class STLineFromWKB : NullableFunction, IGeographySqlType { + + private string KwBinary { get; } + public int SRID { get; } + + public STLineFromWKB(string kwBinary, int srid = 4326) : base(name: "STLineFromWKB") { + KwBinary = kwBinary; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(byte[]), KwBinary, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STLineFromWKB({kwtParam},{sridParam})"; + } + else { + return $"geography::STLineFromWKB({Helpers.EscapeForSql(KwBinary)}, {SRID})"; + } + } + } + + public sealed class STPolyFromWKB : NullableFunction, IGeographySqlType { + + private string KwBinary { get; } + public int SRID { get; } + + public STPolyFromWKB(string kwBinary, int srid = 4326) : base(name: "STPolyFromWKB") { + KwBinary = kwBinary; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(byte[]), KwBinary, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STPolyFromWKB({kwtParam},{sridParam})"; + } + else { + return $"geography::STPolyFromWKB({Helpers.EscapeForSql(KwBinary)}, {SRID})"; + } + } + } + + public sealed class STMPointFromWKB : NullableFunction, IGeographySqlType { + + private string KwBinary { get; } + public int SRID { get; } + + public STMPointFromWKB(string kwBinary, int srid = 4326) : base(name: "STMPointFromWKB") { + KwBinary = kwBinary; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(byte[]), KwBinary, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STMPointFromWKB({kwtParam},{sridParam})"; + } + else { + return $"geography::STMPointFromWKB({Helpers.EscapeForSql(KwBinary)}, {SRID})"; + } + } + } + + public sealed class STMLineFromWKB : NullableFunction, IGeographySqlType { + + private string KwBinary { get; } + public int SRID { get; } + + public STMLineFromWKB(string kwBinary, int srid = 4326) : base(name: "STMLineFromWKB") { + KwBinary = kwBinary; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(byte[]), KwBinary, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STMLineFromWKB({kwtParam},{sridParam})"; + } + else { + return $"geography::STMLineFromWKB({Helpers.EscapeForSql(KwBinary)}, {SRID})"; + } + } + } + + public sealed class STMPolyFromWKB : NullableFunction, IGeographySqlType { + + private string KwBinary { get; } + public int SRID { get; } + + public STMPolyFromWKB(string kwBinary, int srid = 4326) : base(name: "STMPolyFromWKB") { + KwBinary = kwBinary; + SRID = srid; + } + + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(byte[]), KwBinary, out string kwtParam); + parameters.AddParameter(database, typeof(int), SRID, out string sridParam); + return $"geography::STMPolyFromWKB({kwtParam},{sridParam})"; + } + else { + return $"geography::STMPolyFromWKB({Helpers.EscapeForSql(KwBinary)}, {SRID})"; + } + } + } +} \ No newline at end of file diff --git a/QueryLite/Databases/Helpers.cs b/QueryLite/Databases/Helpers.cs index 28dbb76..3687119 100644 --- a/QueryLite/Databases/Helpers.cs +++ b/QueryLite/Databases/Helpers.cs @@ -73,6 +73,30 @@ public static void AppendEncloseAlias(StringBuilder sql, string value) { sql.Append(value); } } + + public static string EncloseTableName(ITable table) { + + string value = table.TableName; + + if(table.Enclose || value.Contains(' ')) { + value = $"[{value}]"; + } + return value; + } + + public static string EncloseColumnName(IColumn column) { + + string value = column.ColumnName; + + if(column.Enclose || value.Contains(' ')) { + value = $"[{value}]"; + } + return value; + } + + public static string EncloseSchemaName(string value) { + return value.Contains(' ') ? $"[{value}]" : value; + } } /// diff --git a/QueryLite/Databases/PostgreSql/Collectors/ResultRowCollector.cs b/QueryLite/Databases/PostgreSql/Collectors/ResultRowCollector.cs index 12cdcac..b61b1f7 100644 --- a/QueryLite/Databases/PostgreSql/Collectors/ResultRowCollector.cs +++ b/QueryLite/Databases/PostgreSql/Collectors/ResultRowCollector.cs @@ -593,6 +593,28 @@ public bool Get(Function function) { return _reader.GetByte(_ordinal) == 1; } + + public Bit Get(Function column) { + + _ordinal++; + + if(_reader.IsDBNull(_ordinal)) { + return Bit.ValueOf(false); + } + + return Bit.ValueOf(_reader.GetBoolean(_ordinal)); + } + + public Bit? Get(NullableFunction column) { + + _ordinal++; + + if(_reader.IsDBNull(_ordinal)) { + return null; + } + return Bit.ValueOf(_reader.GetBoolean(_ordinal)); + } + public short Get(Function function) { _ordinal++; diff --git a/QueryLite/Databases/PostgreSql/Collectors/ReturningFieldCollector.cs b/QueryLite/Databases/PostgreSql/Collectors/ReturningFieldCollector.cs index e44c8de..2274988 100644 --- a/QueryLite/Databases/PostgreSql/Collectors/ReturningFieldCollector.cs +++ b/QueryLite/Databases/PostgreSql/Collectors/ReturningFieldCollector.cs @@ -277,6 +277,14 @@ public bool Get(Function column) { return Add(column); } + public Bit Get(Function column) { + return Add(column); + } + + public Bit? Get(NullableFunction column) { + return Add(column); + } + public short Get(Function column) { return Add(column); } diff --git a/QueryLite/Databases/PostgreSql/Collectors/SelectFieldCollector.cs b/QueryLite/Databases/PostgreSql/Collectors/SelectFieldCollector.cs index 6f2b857..7244cc1 100644 --- a/QueryLite/Databases/PostgreSql/Collectors/SelectFieldCollector.cs +++ b/QueryLite/Databases/PostgreSql/Collectors/SelectFieldCollector.cs @@ -296,6 +296,14 @@ public bool Get(Function column) { return Add(column); } + public Bit Get(Function column) { + return Add(column); + } + + public Bit? Get(NullableFunction column) { + return Add(column); + } + public short Get(Function column) { return Add(column); } diff --git a/QueryLite/Databases/PostgreSql/Collectors/SetValuesParameterCollector.cs b/QueryLite/Databases/PostgreSql/Collectors/SetValuesParameterCollector.cs index 2e25078..36f2467 100644 --- a/QueryLite/Databases/PostgreSql/Collectors/SetValuesParameterCollector.cs +++ b/QueryLite/Databases/PostgreSql/Collectors/SetValuesParameterCollector.cs @@ -98,7 +98,7 @@ private ISetValuesCollector AddParameter(IColumn column, NpgsqlDbType dbType, ob return this; } - private ISetValuesCollector AddFunction(IColumn column, NpgsqlDbType dbType, IFunction function) { + private ISetValuesCollector AddFunction(IColumn column, IFunction function) { if(_collectorMode == CollectorMode.Insert) { @@ -336,67 +336,71 @@ public ISetValuesCollector Set(NullableColumn column, byte[]? value) { } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Varchar, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Unknown, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Boolean, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Boolean, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Numeric, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Smallint, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Integer, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Bigint, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Real, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Double, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Time, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Timestamp, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Date, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.TimestampTz, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Smallint, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, NpgsqlDbType.Bytea, value); + return AddFunction(column, value); + } + + public ISetValuesCollector Set(AColumn column, AFunction value) { + return AddFunction(column, value); } } @@ -793,5 +797,9 @@ public ISetValuesCollector Set(AColumn column, AFunction value) { public ISetValuesCollector Set(AColumn column, AFunction value) { return SetValue(column, value.GetSql(_database, useAlias: false, parameters: null)); } + + public ISetValuesCollector Set(AColumn column, AFunction value) { + return SetValue(column, value.GetSql(_database, useAlias: false, parameters: null)); + } } } \ No newline at end of file diff --git a/QueryLite/Databases/SqlServer/Collectors/ResultRowCollector.cs b/QueryLite/Databases/SqlServer/Collectors/ResultRowCollector.cs index 7272170..ce8f7e6 100644 --- a/QueryLite/Databases/SqlServer/Collectors/ResultRowCollector.cs +++ b/QueryLite/Databases/SqlServer/Collectors/ResultRowCollector.cs @@ -590,6 +590,26 @@ public bool Get(Function function) { return _reader.GetByte(_ordinal) == 1; } + public Bit Get(Function column) { + + _ordinal++; + + if(_reader.IsDBNull(_ordinal)) { + return Bit.ValueOf(false); + } + return Bit.ValueOf(_reader.GetBoolean(_ordinal)); + } + + public Bit? Get(NullableFunction column) { + + _ordinal++; + + if(_reader.IsDBNull(_ordinal)) { + return null; + } + return Bit.ValueOf(_reader.GetBoolean(_ordinal)); + } + public short Get(Function function) { _ordinal++; diff --git a/QueryLite/Databases/SqlServer/Collectors/ReturningFieldCollector.cs b/QueryLite/Databases/SqlServer/Collectors/ReturningFieldCollector.cs index bae1290..e2dd324 100644 --- a/QueryLite/Databases/SqlServer/Collectors/ReturningFieldCollector.cs +++ b/QueryLite/Databases/SqlServer/Collectors/ReturningFieldCollector.cs @@ -281,6 +281,14 @@ public bool Get(Function column) { return Add(column); } + public Bit Get(Function column) { + return Add(column); + } + + public Bit? Get(NullableFunction column) { + return Add(column); + } + public short Get(Function column) { return Add(column); } diff --git a/QueryLite/Databases/SqlServer/Collectors/SelectFieldCollector.cs b/QueryLite/Databases/SqlServer/Collectors/SelectFieldCollector.cs index 1a8098b..af68008 100644 --- a/QueryLite/Databases/SqlServer/Collectors/SelectFieldCollector.cs +++ b/QueryLite/Databases/SqlServer/Collectors/SelectFieldCollector.cs @@ -297,6 +297,14 @@ public bool Get(Function column) { return Add(column); } + public Bit Get(Function column) { + return Add(column); + } + + public Bit? Get(NullableFunction column) { + return Add(column); + } + public short Get(Function column) { return Add(column); } diff --git a/QueryLite/Databases/SqlServer/Collectors/SetValuesParameterCollector.cs b/QueryLite/Databases/SqlServer/Collectors/SetValuesParameterCollector.cs index 325d256..2b1f50a 100644 --- a/QueryLite/Databases/SqlServer/Collectors/SetValuesParameterCollector.cs +++ b/QueryLite/Databases/SqlServer/Collectors/SetValuesParameterCollector.cs @@ -101,7 +101,7 @@ private ISetValuesCollector AddParameter(IColumn column, SqlDbType dbType, objec return this; } - private ISetValuesCollector AddFunction(IColumn column, SqlDbType dbType, IFunction function) { + private ISetValuesCollector AddFunction(IColumn column, IFunction function) { if(_collectorMode == CollectorMode.Insert) { @@ -343,67 +343,71 @@ public ISetValuesCollector Set(NullableColumn column, byte[]? value) { } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.NVarChar, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.UniqueIdentifier, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.Bit, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.Bit, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.Decimal, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.SmallInt, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.Int, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.BigInt, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.Real, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.Float, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.Time, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.DateTime, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.Date, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.DateTimeOffset, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.SmallInt, value); + return AddFunction(column, value); } public ISetValuesCollector Set(AColumn column, AFunction value) { - return AddFunction(column, SqlDbType.Binary, value); + return AddFunction(column, value); + } + + public ISetValuesCollector Set(AColumn column, AFunction value) { + return AddFunction(column, value); } } @@ -804,5 +808,9 @@ public ISetValuesCollector Set(AColumn column, AFunction value) { public ISetValuesCollector Set(AColumn column, AFunction value) { return SetValue(column, value.GetSql(_database, useAlias: false, parameters: null)); } + + public ISetValuesCollector Set(AColumn column, AFunction value) { + return SetValue(column, value.GetSql(_database, useAlias: false, parameters: null)); + } } } \ No newline at end of file diff --git a/QueryLite/Databases/SqlServer/Functions/GeographyFunctions.cs b/QueryLite/Databases/SqlServer/Functions/GeographyFunctions.cs new file mode 100644 index 0000000..95f6dc3 --- /dev/null +++ b/QueryLite/Databases/SqlServer/Functions/GeographyFunctions.cs @@ -0,0 +1,70 @@ +/* + * MIT License + * + * Copyright (c) 2023 EndsOfTheEarth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + **/ +namespace QueryLite.Databases.SqlServer.Functions { + + //.ShortestLineTo ( geography_other ) + + public sealed class Longitude : NullableFunction { + + public AColumn Column { get; } + + public Longitude(AColumn column) : base(name: "Longitude") { + Column = column; + } + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + return useAlias ? $"{Column.Table.Alias}.{SqlHelper.EncloseColumnName(Column)}.Long" : $"{SqlHelper.EncloseColumnName(Column)}.Long"; + } + } + + public sealed class Latitude : NullableFunction { + + public AColumn Column { get; } + + public Latitude(AColumn column) : base(name: "Latitude") { + Column = column; + } + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + return useAlias ? $"{Column.Table.Alias}.{SqlHelper.EncloseColumnName(Column)}.Lat" : $"{SqlHelper.EncloseColumnName(Column)}.Lat"; + } + } + + public sealed class GeographyParse : NullableFunction { + + public string KwText { get; } + + public GeographyParse(string kwtText) : base(name: "geography::Parse") { + KwText = kwtText; + } + public override string GetSql(IDatabase database, bool useAlias, IParametersBuilder? parameters) { + + if(parameters != null) { + parameters.AddParameter(database, typeof(string), KwText, out string kwtParam); + return $"geography::Parse({kwtParam})"; + } + else { + return $"geography::Parse('{Helpers.EscapeForSql(KwText)}')"; + } + } + } +} \ No newline at end of file diff --git a/QueryLite/DbSchema/SqlServerSchemaLoader.cs b/QueryLite/DbSchema/SqlServerSchemaLoader.cs index 844148d..d6d6e1f 100644 --- a/QueryLite/DbSchema/SqlServerSchemaLoader.cs +++ b/QueryLite/DbSchema/SqlServerSchemaLoader.cs @@ -375,7 +375,7 @@ static SqlServerTypes() { _Lookup.Add("datetimeoffset", typeof(DateTimeOffset)); _Lookup.Add("date", typeof(DateOnly)); _Lookup.Add("time", typeof(TimeOnly)); - _Lookup.Add("geography", typeof(IGeography_UnsupportedType)); + _Lookup.Add("geography", typeof(IGeography)); } public static Type? GetDotNetType(string typeName) { diff --git a/QueryLite/Function.cs b/QueryLite/Function.cs index 15afd08..e138c51 100644 --- a/QueryLite/Function.cs +++ b/QueryLite/Function.cs @@ -34,7 +34,7 @@ public interface IFunction : IField { string Name { get; } /// - /// Returns the function sql. This is used during sql query deneration + /// Returns the function sql. This is used during sql query generation /// /// /// Should table aliases be included in sql diff --git a/QueryLite/Interfaces/InsertInterfaces.cs b/QueryLite/Interfaces/InsertInterfaces.cs index 09e13ea..a09dfb0 100644 --- a/QueryLite/Interfaces/InsertInterfaces.cs +++ b/QueryLite/Interfaces/InsertInterfaces.cs @@ -147,6 +147,8 @@ public interface ISetValuesCollector { public ISetValuesCollector Set(AColumn column, AFunction value); + public ISetValuesCollector Set(AColumn column, AFunction value); + public ISetValuesCollector Set(AColumn column, AFunction value); public ISetValuesCollector Set(AColumn column, AFunction value); diff --git a/QueryLite/MetaData.cs b/QueryLite/MetaData.cs index bcbd5dd..b09f310 100644 --- a/QueryLite/MetaData.cs +++ b/QueryLite/MetaData.cs @@ -27,7 +27,7 @@ namespace QueryLite { /// - /// Description attribute. Used to add human readable descriptions to table classes and table columns. These attrubutes are used to auto generate schema documentation. + /// Description attribute. Used to add human readable descriptions to table classes and table columns. These attributes are used to auto generate schema documentation. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] public sealed class DescriptionAttribute : Attribute { @@ -53,9 +53,9 @@ public sealed class SuppressColumnTypeValidationAttribute : Attribute { public interface IUnsupportedType { } /// - /// This is a place older interface to indicate that the Sql Server Geography type is unsupported by Query Lite. + /// Geography type. Note: This cannot be queried directly but can be used with geography sql functions /// - public interface IGeography_UnsupportedType : IUnsupportedType { } + public interface IGeography { } public sealed class PrimaryKey { diff --git a/QueryLite/Result/FieldCollector.cs b/QueryLite/Result/FieldCollector.cs index 13f9ef4..71dce77 100644 --- a/QueryLite/Result/FieldCollector.cs +++ b/QueryLite/Result/FieldCollector.cs @@ -297,6 +297,16 @@ public bool Get(Function column) { return default; } + public Bit Get(Function column) { + Fields.Add(column); + return default; + } + + public Bit? Get(NullableFunction column) { + Fields.Add(column); + return default; + } + public short Get(Function column) { Fields.Add(column); return default; diff --git a/QueryLite/Result/IResultRow.cs b/QueryLite/Result/IResultRow.cs index df6c05a..bb73cd6 100644 --- a/QueryLite/Result/IResultRow.cs +++ b/QueryLite/Result/IResultRow.cs @@ -107,6 +107,9 @@ public interface IResultRow { public bool Get(Function column); public bool? Get(NullableFunction column); + public Bit Get(Function column); + public Bit? Get(NullableFunction column); + public short Get(Function column); public short? Get(NullableFunction column); diff --git a/QueryLiteTest/CreateTables Sql Server.sql b/QueryLiteTest/CreateTables Sql Server.sql index 6c62e5a..4b1b5cc 100644 --- a/QueryLiteTest/CreateTables Sql Server.sql +++ b/QueryLiteTest/CreateTables Sql Server.sql @@ -34,4 +34,12 @@ CREATE TABLE Child ( CONSTRAINT pk_Child PRIMARY KEY(Id), CONSTRAINT fk_Child_Parent FOREIGN KEY(ParentId) REFERENCES Parent(Id), CONSTRAINT fk_Child_Parent_Id2 FOREIGN KEY(ParentId) REFERENCES Parent(Id2) +); + +CREATE TABLE GeoTest ( + + gtGuid UNIQUEIDENTIFIER NOT NULL, + gtGeography GEOGRAPHY NOT NULL, + + CONSTRAINT pk_GeoTest PRIMARY KEY(gtGuid) ); \ No newline at end of file diff --git a/QueryLiteTest/Database.cs b/QueryLiteTest/Database.cs index a7da89d..dfe5b2e 100644 --- a/QueryLiteTest/Database.cs +++ b/QueryLiteTest/Database.cs @@ -1,11 +1,12 @@ using QueryLite; using QueryLite.Databases.PostgreSql; +using QueryLite.Databases.SqlServer; namespace QueryLiteTest { public static class TestDatabase { - //public static IDatabase Database { get; set; } = new SqlServerDatabase(name: "QueryLiteTest", connectionString: "Server=localhost;Database=QueryLiteTesting;Trusted_Connection=True;"); - public static IDatabase Database { get; set; } = new PostgreSqlDatabase(name: "QueryLiteTest", connectionString: "Server=127.0.0.1;Port=5432;Database=QueryLiteTesting;User Id=postgres;Password=1;", schemaMap: schema => schema == "dbo" ? "public" : schema); + public static IDatabase Database { get; set; } = new SqlServerDatabase(name: "QueryLiteTest", connectionString: "Server=localhost;Database=QueryLiteTesting;Trusted_Connection=True;"); + //public static IDatabase Database { get; set; } = new PostgreSqlDatabase(name: "QueryLiteTest", connectionString: "Server=127.0.0.1;Port=5432;Database=QueryLiteTesting;User Id=postgres;Password=1;", schemaMap: schema => schema == "dbo" ? "public" : schema); } } \ No newline at end of file diff --git a/QueryLiteTest/Tables/Geography.cs b/QueryLiteTest/Tables/Geography.cs new file mode 100644 index 0000000..d2ef30a --- /dev/null +++ b/QueryLiteTest/Tables/Geography.cs @@ -0,0 +1,23 @@ + +namespace QueryLiteTest.Tables { + + using QueryLite; + + public interface IGeoTest { } + + public sealed class GeoTestTable : ATable { + + public static readonly GeoTestTable Instance = new GeoTestTable(); + + public Column> Guid { get; } + public Column Geography { get; } + + public override PrimaryKey? PrimaryKey => new PrimaryKey(table: this, constraintName: "pk_GeoTest", Guid); + + private GeoTestTable() : base(tableName: "GeoTest", schemaName: "dbo") { + + Guid = new Column>(this, columnName: "gtGuid"); + Geography = new Column(this, columnName: "gtGeography", length: int.MaxValue); + } + } +} \ No newline at end of file diff --git a/QueryLiteTest/Tests/AllFieldsPreparedTest.cs b/QueryLiteTest/Tests/AllFieldsPreparedTest.cs index f1d5ca7..b431216 100644 --- a/QueryLiteTest/Tests/AllFieldsPreparedTest.cs +++ b/QueryLiteTest/Tests/AllFieldsPreparedTest.cs @@ -202,9 +202,18 @@ public void RunSchemaValidator() { ChildTable.Instance }; + if(TestDatabase.Database.DatabaseType == DatabaseType.SqlServer) { + tables.Add(GeoTestTable.Instance); + } + ValidationResult result = SchemaValidator.ValidateTables(TestDatabase.Database, tables, settings); - Assert.AreEqual(result.TableValidation.Count, 3); + if(TestDatabase.Database.DatabaseType == DatabaseType.SqlServer) { + Assert.AreEqual(result.TableValidation.Count, 4); + } + else { + Assert.AreEqual(result.TableValidation.Count, 3); + } foreach(TableValidation val in result.TableValidation) { diff --git a/QueryLiteTest/Tests/AllFieldsTest.cs b/QueryLiteTest/Tests/AllFieldsTest.cs index 1789c77..7136d67 100644 --- a/QueryLiteTest/Tests/AllFieldsTest.cs +++ b/QueryLiteTest/Tests/AllFieldsTest.cs @@ -114,12 +114,21 @@ public void RunSchemaValidator() { List tables = new List() { AllTypesTable.Instance, ParentTable.Instance, - ChildTable.Instance + ChildTable.Instance }; + if(TestDatabase.Database.DatabaseType == DatabaseType.SqlServer) { + tables.Add(GeoTestTable.Instance); + } + ValidationResult result = SchemaValidator.ValidateTables(TestDatabase.Database, tables, settings); - Assert.AreEqual(result.TableValidation.Count, 3); + if(TestDatabase.Database.DatabaseType == DatabaseType.SqlServer) { + Assert.AreEqual(result.TableValidation.Count, 4); + } + else { + Assert.AreEqual(result.TableValidation.Count, 3); + } foreach(TableValidation val in result.TableValidation) { diff --git a/QueryLiteTest/Tests/GeographyTest.cs b/QueryLiteTest/Tests/GeographyTest.cs new file mode 100644 index 0000000..ff94f0a --- /dev/null +++ b/QueryLiteTest/Tests/GeographyTest.cs @@ -0,0 +1,308 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using QueryLite; +using QueryLite.Databases.Functions; +using QueryLite.Databases.SqlServer.Functions; +using QueryLiteTest.Tables; +using System; + +namespace QueryLiteTest.Tests { + + [TestClass] + public sealed class GeographyTest { + + [TestInitialize] + public void ClearTable() { + + if(TestDatabase.Database.DatabaseType != DatabaseType.SqlServer) { + return; + } + + GeoTestTable table = GeoTestTable.Instance; + + using(Transaction transaction = new Transaction(TestDatabase.Database)) { + + Query.Delete(table) + .NoWhereCondition() + .Execute(transaction, TimeoutLevel.ShortDelete); + + COUNT_ALL count = COUNT_ALL.Instance; + + QueryResult result = Query + .Select( + result => result.Get(count) + ) + .From(table) + .Execute(transaction); + + Assert.AreEqual(result.Rows.Count, 1); + Assert.AreEqual(result.RowsEffected, 0); + + int? countValue = result.Rows[0]; + + Assert.IsNotNull(countValue); + Assert.AreEqual(countValue, 0); + + transaction.Commit(); + } + } + + [TestCleanup] + public void CleanUp() { + Settings.UseParameters = false; + } + + + [TestMethod] + public void TestGeographyFunctionsWithoutParams() { + + if(TestDatabase.Database.DatabaseType != DatabaseType.SqlServer) { + return; + } + + Settings.UseParameters = false; + TestGeographyFunctions(); + } + + [TestMethod] + public void TestGeographyFunctionsWithParams() { + + if(TestDatabase.Database.DatabaseType != DatabaseType.SqlServer) { + return; + } + + try { + + Settings.UseParameters = true; + + TestGeographyFunctions(); + } + finally { + Settings.UseParameters = false; + } + } + + private void TestGeographyFunctions() { + + GeoTestTable table = GeoTestTable.Instance; + + STPointFromText stPointFromText = new STPointFromText(kwText: "POINT(-122.34900 47.65100)"); + + GuidKey guid = GuidKey.ValueOf(Guid.NewGuid()); + + using(Transaction transaction = new Transaction(TestDatabase.Database)) { + + NonQueryResult insertResult = Query + .Insert(table) + .Values(values => values + .Set(table.Guid, guid) + .Set(table.Geography, stPointFromText) + ) + .Execute(transaction); + + transaction.Commit(); + } + + GeographyPoint geographyPoint = new GeographyPoint(latitude: 47.65100, longitude: -122.34900); + STDistance distance = new STDistance(table.Geography, geographyPoint); + STAsBinary stAsBinary = new STAsBinary(table.Geography); + STAsText stAsText = new STAsText(table.Geography); + + Longitude longitude = new Longitude(table.Geography); + Latitude latitude = new Latitude(table.Geography); + + STAsText stGeomFromTextAsText = new STAsText(new STGeomFromText(kwText: "LINESTRING(-121.360 47.646,-122.343 47.601)")); + STAsText stLineFromTextAsText = new STAsText(new STLineFromText(kwText: "LINESTRING(-123.360 47.496,-121.323 47.256)")); + STAsText stPolyFromTextAsText = new STAsText(new STPolyFromText(kwText: "POLYGON((-121.358 47.643, -121.348 47.629, -121.348 47.678, -121.358 47.668, -121.358 47.643))")); + STAsText stMPointFromText = new STAsText(new STMPointFromText(kwText: "MULTIPOINT((-121.361 46.156), (-121.343 47.444))")); + STAsText stMLineFromText = new STAsText(new STMLineFromText(kwText: "MULTILINESTRING ((-121.358 47.653, -121.348 47.649, -121.348 47.658, -121.358 47.658, -121.358 47.653), (-121.357 47.654, -121.357 47.657, -121.349 47.657, -121.349 47.651, -121.357 47.654))")); + STAsText stMPolyFromText = new STAsText(new STMPolyFromText(kwText: "MULTIPOLYGON(((-111.228 47.313, -122.348 47.649, -122.358 47.658, -111.228 47.313)), ((-222.341 27.111, -122.341 46.661, -122.351 46.661, -222.341 27.111)))")); + + var result = Query + .Select( + row => new { + Guid = row.Get(table.Guid), + Distance = row.Get(distance), + Binary = row.Get(stAsBinary), + Text = row.Get(stAsText), + Longitude = row.Get(longitude), + Latitude = row.Get(latitude), + STGeomFromTextAsText = row.Get(stGeomFromTextAsText), + STLineFromTextAsText = row.Get(stLineFromTextAsText), + STPolyFromTextAsText = row.Get(stPolyFromTextAsText), + STMPointFromText = row.Get(stMPointFromText), + STMLineFromText = row.Get(stMLineFromText), + STMPolyFromText = row.Get(stMPolyFromText) + } + ) + .From(table) + .Where(new STEquals(table.Geography, geographyPoint) == 1) + .Execute(TestDatabase.Database); + + Assert.AreEqual(result.Rows.Count, 1); + + var row = result.Rows[0]; + + Assert.AreEqual(row.Guid, guid); + Assert.AreEqual(row.Distance, 0); + Assert.AreEqual(BitConverter.ToString(row.Binary!).Replace("-", ""), "01010000007593180456965EC017D9CEF753D34740"); + Assert.AreEqual(row.Text, "POINT (-122.349 47.651)"); + Assert.AreEqual(row.Longitude, -122.349); + Assert.AreEqual(row.Latitude, 47.651); + Assert.AreEqual(row.STGeomFromTextAsText, "LINESTRING (-121.36 47.646, -122.343 47.601)"); + Assert.AreEqual(row.STLineFromTextAsText, "LINESTRING (-123.36 47.496, -121.323 47.256)"); + Assert.AreEqual(row.STPolyFromTextAsText, "POLYGON ((-121.358 47.643, -121.348 47.629, -121.348 47.678, -121.358 47.668, -121.358 47.643))"); + Assert.AreEqual(row.STMPointFromText, "MULTIPOINT ((-121.361 46.156), (-121.343 47.444))"); + Assert.AreEqual(row.STMLineFromText, "MULTILINESTRING ((-121.358 47.653, -121.348 47.649, -121.348 47.658, -121.358 47.658, -121.358 47.653), (-121.357 47.654, -121.357 47.657, -121.349 47.657, -121.349 47.651, -121.357 47.654))"); + Assert.AreEqual(row.STMPolyFromText, "MULTIPOLYGON (((-111.228 47.313, -122.348 47.649, -122.358 47.658, -111.228 47.313)), ((-222.341 27.111, -122.341 46.661, -122.351 46.661, -222.341 27.111)))"); + } + + [TestMethod] + public void TestGeographyDistanceWithoutParams() { + + if(TestDatabase.Database.DatabaseType != DatabaseType.SqlServer) { + return; + } + + Settings.UseParameters = false; + TestGeographyDistance(); + } + + [TestMethod] + public void TestGeographyDistanceWithParams() { + + if(TestDatabase.Database.DatabaseType != DatabaseType.SqlServer) { + return; + } + + try { + + Settings.UseParameters = true; + TestGeographyDistance(); + } + finally { + Settings.UseParameters = false; + } + } + + private void TestGeographyDistance() { + + GeoTestTable table = GeoTestTable.Instance; + + STLineFromText stLineFromText = new STLineFromText(kwText: "LINESTRING(-122.360 47.656, -122.343 47.656)"); + + GuidKey guid = GuidKey.ValueOf(Guid.NewGuid()); + + using(Transaction transaction = new Transaction(TestDatabase.Database)) { + + NonQueryResult insertResult = Query + .Insert(table) + .Values(values => values + .Set(table.Guid, guid) + .Set(table.Geography, stLineFromText) + ) + .Execute(transaction); + + transaction.Commit(); + } + + GeographyPoint geographyPoint = new GeographyPoint(latitude: 47.65100, longitude: -122.34900); + STDistance distance = new STDistance(table.Geography, geographyPoint); + + var result = Query + .Select( + row => new { + Guid = row.Get(table.Guid), + Distance = row.Get(distance) + } + ) + .From(table) + .Execute(TestDatabase.Database); + + Assert.AreEqual(result.Rows.Count, 1); + + var row = result.Rows[0]; + + Assert.AreEqual(row.Guid, guid); + + Assert.AreEqual(row.Distance, 555.94977427172694); + } + + [TestMethod] + public void TestSTFunctionsWithoutParams() { + + if(TestDatabase.Database.DatabaseType != DatabaseType.SqlServer) { + return; + } + + Settings.UseParameters = false; + TestSTFunctions(); + } + + [TestMethod] + public void TestSTFunctionsWithParams() { + + if(TestDatabase.Database.DatabaseType != DatabaseType.SqlServer) { + return; + } + + try { + + Settings.UseParameters = true; + TestSTFunctions(); + } + finally { + Settings.UseParameters = false; + } + } + + private void TestSTFunctions() { + + GeoTestTable table = GeoTestTable.Instance; + + //Sql server only + GeographyParse geography_Parse = new GeographyParse(kwtText: "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-122.200928 47.454094, -122.810669 47.00648, -122.942505 46.687131, -121.14624 45.786679, -119.119263 46.183634), (-119.119263 46.183634, -119.273071 47.107523, -120.640869 47.569114, -122.200928 47.454094)))"); + + GuidKey guid = GuidKey.ValueOf(Guid.NewGuid()); + + using(Transaction transaction = new Transaction(TestDatabase.Database)) { + + NonQueryResult insertResult = Query + .Insert(table) + .Values(values => values + .Set(table.Guid, guid) + .Set(table.Geography, geography_Parse) + ) + .Execute(transaction); + + transaction.Commit(); + } + + STContains stContainsA = new STContains(table.Geography, new GeographyPoint(latitude: 46.893985, longitude: -121.703796)); + STContains stContainsB = new STContains(table.Geography, new GeographyPoint(latitude: 47.65100, longitude: -122.34900)); + + STArea stArea = new STArea(table.Geography); + + var result = Query + .Select( + row => new { + Guid = row.Get(table.Guid), + ContainsA = row.Get(stContainsA), + ContainsB = row.Get(stContainsB), + Area = row.Get(stArea) + } + ) + .From(table) + .Execute(TestDatabase.Database); + + Assert.AreEqual(result.Rows.Count, 1); + + var row = result.Rows[0]; + + Assert.AreEqual(row.Guid, guid); + + Assert.AreEqual(row.ContainsA, Bit.TRUE); + Assert.AreEqual(row.ContainsB, Bit.FALSE); + Assert.AreEqual(row.Area, 45023599772.742432); + } + } +} \ No newline at end of file