Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add spatial dialect for MySQL 8 #135

Merged
merged 1 commit into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ jobs:
DB_INIT: docker run -d -e MYSQL_ROOT_PASSWORD=nhsp_test -p 13306:3306 -v ./Tests.NHibernate.Spatial.MySQL57/initdb:/docker-entrypoint-initdb.d mysql:5.7-debian
TEST_PROJECT: Tests.NHibernate.Spatial.MySQL57

- DB: MySQL80 (MySQL 8.0)
DB_INIT: docker run -d -e MYSQL_ROOT_PASSWORD=nhsp_test -p 13307:3306 -v ./Tests.NHibernate.Spatial.MySQL80/initdb:/docker-entrypoint-initdb.d mysql:8.0
TEST_PROJECT: Tests.NHibernate.Spatial.MySQL80

- DB: MySQL80 (MySQL 8.3)
DB_INIT: docker run -d -e MYSQL_ROOT_PASSWORD=nhsp_test -p 13307:3306 -v ./Tests.NHibernate.Spatial.MySQL80/initdb:/docker-entrypoint-initdb.d mysql:8.3
TEST_PROJECT: Tests.NHibernate.Spatial.MySQL80

- DB: PostGis20 (PostgreSQL 12 PostGIS 2.5)
DB_INIT: docker run -d -e POSTGRES_PASSWORD=nhsp_test -p 15432:5432 -v ./Tests.NHibernate.Spatial.PostGis20/initdb:/docker-entrypoint-initdb.d postgis/postgis:12-2.5
TEST_PROJECT: Tests.NHibernate.Spatial.PostGis20
Expand Down
26 changes: 10 additions & 16 deletions NHibernate.Spatial.MySQL/Dialect/MySQL57SpatialDialect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ namespace NHibernate.Spatial.Dialect
public class MySQL57SpatialDialect : MySQL5Dialect, ISpatialDialect
{
protected const string DialectPrefix = SpatialDialect.IsoPrefix;
protected static readonly IType geometryType = new CustomType(typeof(MySQL57GeometryType), null);
private static readonly IType geometryType = new CustomType(typeof(MySQLGeometryType), null);

/// <summary>
/// Initializes a new instance of the <see cref="MySQLDialect"/> class.
Expand Down Expand Up @@ -125,7 +125,7 @@ protected override void RegisterFunctions()
RegisterSpatialFunction("GeometryType", NHibernateUtil.String);

RegisterSpatialFunction("Area", NHibernateUtil.Double);
RegisterSpatialFunction("Length", "GLength", NHibernateUtil.Double);
RegisterSpatialFunction("Length", NHibernateUtil.Double);
RegisterSpatialFunction("X", NHibernateUtil.Double);
RegisterSpatialFunction("Y", NHibernateUtil.Double);

Expand All @@ -150,12 +150,12 @@ protected void RegisterSpatialFunction(string standardName, string dialectName,

protected void RegisterSpatialFunction(string name, IType returnedType, int allowedArgsCount)
{
RegisterSpatialFunction(name, name, returnedType, allowedArgsCount);
RegisterSpatialFunction(name, SpatialDialect.IsoPrefix + name, returnedType, allowedArgsCount);
}

protected void RegisterSpatialFunction(string name, IType returnedType)
{
RegisterSpatialFunction(name, name, returnedType);
RegisterSpatialFunction(name, SpatialDialect.IsoPrefix + name, returnedType);
}

protected void RegisterSpatialFunction(string name, int allowedArgsCount)
Expand Down Expand Up @@ -193,7 +193,7 @@ protected void RegisterSpatialFunction(SpatialAnalysis analysis)
/// <returns></returns>
public virtual IGeometryUserType CreateGeometryUserType()
{
return new MySQL57GeometryType();
return new MySQLGeometryType();
}

/// <summary>
Expand All @@ -208,15 +208,9 @@ public virtual IGeometryUserType CreateGeometryUserType()
/// <param name="geometry">The geometry.</param>
/// <param name="srid">The srid.</param>
/// <returns></returns>
public SqlString GetSpatialTransformString(object geometry, int srid)
public virtual SqlString GetSpatialTransformString(object geometry, int srid)
{
return new SqlStringBuilder()
.Add("Transform(")
.AddObject(geometry)
.Add(",")
.Add(srid.ToString())
.Add(")")
.ToSqlString();
throw new NotSupportedException("MySQL 5.7 does not support spatial transform");
}

/// <summary>
Expand Down Expand Up @@ -491,7 +485,7 @@ public string GetSpatialCreateString(string schema)
/// </summary>
/// <param name="schema">The schema.</param>
/// <returns></returns>
private string QuoteSchema(string schema)
protected string QuoteSchema(string schema)
{
if (string.IsNullOrEmpty(schema))
{
Expand All @@ -511,7 +505,7 @@ private string QuoteSchema(string schema)
/// <param name="dimension">The dimension.</param>
/// <param name="isNullable">Whether or not the column is nullable</param>
/// <returns></returns>
public string GetSpatialCreateString(string schema, string table, string column, int srid, string subtype, int dimension, bool isNullable)
public virtual string GetSpatialCreateString(string schema, string table, string column, int srid, string subtype, int dimension, bool isNullable)
{
var builder = new StringBuilder();

Expand Down Expand Up @@ -568,7 +562,7 @@ public string GetSpatialDropString(string schema, string table, string column)
/// <value>
/// <c>true</c> if it supports spatial metadata; otherwise, <c>false</c>.
/// </value>
public bool SupportsSpatialMetadata(MetadataClass metadataClass)
public virtual bool SupportsSpatialMetadata(MetadataClass metadataClass)
{
return false;
}
Expand Down
148 changes: 148 additions & 0 deletions NHibernate.Spatial.MySQL/Dialect/MySQL80SpatialDialect.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
using NHibernate.Spatial.Metadata;
using NHibernate.SqlCommand;
using System;
using System.Data;
using System.Text;

namespace NHibernate.Spatial.Dialect
{
public class MySQL80SpatialDialect : MySQL57SpatialDialect
{
public MySQL80SpatialDialect()
{
// See: https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Dialect/MySQL8Dialect.cs#L7C48-L7C73
RegisterColumnType(DbType.Boolean, "BOOLEAN");
}

/// <summary>
/// Gets the spatial transform string.
/// </summary>
/// <param name="geometry">The geometry.</param>
/// <param name="srid">The srid.</param>
/// <returns></returns>
public override SqlString GetSpatialTransformString(object geometry, int srid)
{
return new SqlStringBuilder()
.Add(DialectPrefix)
.Add("Transform(")
.AddObject(geometry)
.Add(",")
.Add(srid.ToString())
.Add(")")
.ToSqlString();
}

/// <summary>
/// Gets the spatial validation string.
/// </summary>
/// <param name="geometry">The geometry.</param>
/// <param name="validation">The validation.</param>
/// <param name="criterion">if set to <c>true</c> [criterion].</param>
/// <returns></returns>
public override SqlString GetSpatialValidationString(object geometry, SpatialValidation validation, bool criterion)
{
return new SqlStringBuilder()
.Add(DialectPrefix)
.Add(validation.ToString())
.Add("(")
.AddObject(geometry)
.Add(")")
.ToSqlString();
}

/// <summary>
/// Gets the spatial aggregate string.
/// </summary>
/// <param name="geometry">The geometry.</param>
/// <param name="aggregate">The aggregate.</param>
/// <returns></returns>
public override SqlString GetSpatialAggregateString(object geometry, SpatialAggregate aggregate)
{
switch (aggregate)
{
case SpatialAggregate.Collect:
return new SqlStringBuilder()
.Add(DialectPrefix)
.Add(aggregate.ToString())
.Add("(")
.AddObject(geometry)
.Add(")")
.ToSqlString();

case SpatialAggregate.ConvexHull:
case SpatialAggregate.Envelope:
// MySQL only directly supports the ST_Collect spatial aggregate function, therefore
// we mimic these spatial agg functions by grouping the geometries from each row into
// a geometry collection and then performing the function on the geometry collection
// See: https://forums.mysql.com/read.php?23,249284,249284#msg-249284
var collectAggregate = GetSpatialAggregateString(geometry, SpatialAggregate.Collect);
return new SqlStringBuilder()
.Add(DialectPrefix)
.Add(aggregate.ToString())
.Add("(")
.Add(collectAggregate)
.Add(")")
.ToSqlString();

case SpatialAggregate.Intersection:
case SpatialAggregate.Union:
throw new NotSupportedException($"MySQL does not support {aggregate} spatial aggregate function");

default:
throw new ArgumentException("Invalid spatial aggregate argument");
}
}

/// <summary>
/// Gets the spatial create string.
/// </summary>
/// <param name="schema">The schema.</param>
/// <param name="table">The table.</param>
/// <param name="column">The column.</param>
/// <param name="srid">The srid.</param>
/// <param name="subtype">The subtype.</param>
/// <param name="dimension">The dimension.</param>
/// <param name="isNullable">Whether or not the column is nullable</param>
/// <returns></returns>
public override string GetSpatialCreateString(string schema, string table, string column, int srid, string subtype, int dimension, bool isNullable)
{
var builder = new StringBuilder();

string quotedSchema = QuoteSchema(schema);
string quoteForTableName = QuoteForTableName(table);
string quoteForColumnName = QuoteForColumnName(column);

builder.AppendFormat("ALTER TABLE {0}{1} DROP COLUMN {2}"
, quotedSchema
, quoteForTableName
, quoteForColumnName
);

builder.Append(MultipleQueriesSeparator);

builder.AppendFormat("ALTER TABLE {0}{1} ADD {2} {3} {4} SRID {5}"
, quotedSchema
, quoteForTableName
, quoteForColumnName
, subtype
, isNullable ? "NULL" : "NOT NULL"
, srid
);

builder.Append(MultipleQueriesSeparator);

return builder.ToString();
}

/// <summary>
/// Gets a value indicating whether it supports spatial metadata.
/// </summary>
/// <value>
/// <c>true</c> if it supports spatial metadata; otherwise, <c>false</c>.
/// </value>
public override bool SupportsSpatialMetadata(MetadataClass metadataClass)
{
return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">

<class name="NHibernate.Spatial.Metadata.GeometryColumn, NHibernate.Spatial"
schema="INFORMATION_SCHEMA"
table="ST_GEOMETRY_COLUMNS"
lazy="false"
mutable="false">
<composite-id>
<key-property name="TableCatalog" column="TABLE_CATALOG" />
<key-property name="TableSchema" column="TABLE_SCHEMA" />
<key-property name="TableName" column="TABLE_NAME" />
<key-property name="Name" column="COLUMN_NAME" />
</composite-id>
<property name="SRID" column="SRS_ID" />
<property name="Subtype" column="GEOMETRY_TYPE_NAME" />
</class>
</hibernate-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">

<class name="NHibernate.Spatial.Metadata.SpatialReferenceSystem, NHibernate.Spatial"
schema="INFORMATION_SCHEMA"
table="ST_SPATIAL_REFERENCE_SYSTEMS"
lazy="false"
mutable="false">
<id name="SRID" column="SRS_ID" type="Int32">
<generator class="assigned" />
</id>
<property name="AuthorityName" column="ORGANIZATION" type="String" />
<property name="AuthoritySRID" column="ORGANIZATION_COORDSYS_ID" type="Int32" />
<property name="WellKnownText" column="DEFINITION" type="String" />
</class>
</hibernate-mapping>
5 changes: 5 additions & 0 deletions NHibernate.Spatial.MySQL/NHibernate.Spatial.MySQL.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

<ItemGroup>
<EmbeddedResource Include="Metadata\GeometryColumn.MySQL80SpatialDialect.hbm.xml" />
<EmbeddedResource Include="Metadata\SpatialReferenceSystem.MySQL80SpatialDialect.hbm.xml" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ namespace NHibernate.Spatial.Type
/// This class maps MySQLDbType.Geometry to and from Geometry
/// </summary>
[Serializable]
public class MySQL57GeometryAdapterType : ImmutableType
public class MySQLGeometryAdapterType : ImmutableType
{
public MySQL57GeometryAdapterType()
public MySQLGeometryAdapterType()
: base(SqlTypeFactory.Byte) // Any arbitrary type can be passed as parameter
{ }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ namespace NHibernate.Spatial.Type
/// <summary>
/// MySQL geometry type (to be used in models) that supports the changes introduced in MySQL 5.7
/// </summary>
public class MySQL57GeometryType : GeometryTypeBase<MySqlGeometry?>
public class MySQLGeometryType : GeometryTypeBase<MySqlGeometry?>
{
private static readonly NullableType MySQL57GeometryAdapterType = new MySQL57GeometryAdapterType();
private static readonly NullableType MySQLGeometryAdapterType = new MySQLGeometryAdapterType();

public MySQL57GeometryType()
: base(MySQL57GeometryAdapterType)
public MySQLGeometryType()
: base(MySQLGeometryAdapterType)
{ }

protected override void SetDefaultSRID(Geometry geometry)
Expand Down
6 changes: 6 additions & 0 deletions NHibernate.Spatial.sln
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
NHibernate.Spatial.props = NHibernate.Spatial.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.NHibernate.Spatial.MySQL80", "Tests.NHibernate.Spatial.MySQL80\Tests.NHibernate.Spatial.MySQL80.csproj", "{D6643E3E-D004-41A7-B1AB-96D52018FE17}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -68,6 +70,10 @@ Global
{12E29E47-4760-427E-8EF9-5D2F39B0E980}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12E29E47-4760-427E-8EF9-5D2F39B0E980}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12E29E47-4760-427E-8EF9-5D2F39B0E980}.Release|Any CPU.Build.0 = Release|Any CPU
{D6643E3E-D004-41A7-B1AB-96D52018FE17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6643E3E-D004-41A7-B1AB-96D52018FE17}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6643E3E-D004-41A7-B1AB-96D52018FE17}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6643E3E-D004-41A7-B1AB-96D52018FE17}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
13 changes: 5 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,11 @@ NHibernate binaries.

## Supported Databases

| Package | Minimum Version | CI Tests |
|----------------------------|-------------------------------------------|---------------------------------------------------|
| NHibernate.Spatial.MsSql | SQL Server 2012 | SQL Server 2017, SQL Server 2019, SQL Server 2022 |
| NHibernate.Spatial.MySQL | MySQL 5.7 | MySQL 5.7 |
| NHibernate.Spatial.PostGis | PostgreSQL 12 w/ PostGIS 2.5 <sup>1</sup> | PostgreSQL 12 w/ PostGIS 2.5 |

<sup>1</sup> PostgreSQL 9.1 w/ PostGIS 2.0 or later will likely work, but are not explicitly
supported here as they are EOL (see [here](https://trac.osgeo.org/postgis/wiki/UsersWikiPostgreSQLPostGIS#PostGISSupportMatrix)).
| Package | Dialects | CI Tests |
|----------------------------|--------------------------|---------------------------------------------------|
| NHibernate.Spatial.MsSql | SQL Server 2012 | SQL Server 2017, SQL Server 2019, SQL Server 2022 |
| NHibernate.Spatial.MySQL | MySQL 5.7, MySQL 8.0 | MySQL 5.7, MySQL 8.0, MySQL 8.3 |
| NHibernate.Spatial.PostGis | PostGIS 2.0 | PostGIS 2.5 (PostgreSQL 12) |

## Getting Started

Expand Down
15 changes: 0 additions & 15 deletions Tests.NHibernate.Spatial.MySQL57/MySQL57CriteriaFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,6 @@ namespace Tests.NHibernate.Spatial
[TestFixture]
public class MySQL57CriteriaFixture : CriteriaFixture
{
[Test]
[Ignore("Empty geometry collections not supported by MySql.Data.Types.MySqlGeometry")]
public override void CountNull()
{ }

[Test]
[Ignore("Empty geometry collections not supported by MySql.Data.Types.MySqlGeometry")]
public override void CountSpatialEmpty()
{ }

[Test]
[Ignore("Empty geometry collections not supported by MySql.Data.Types.MySqlGeometry")]
public override void IsDirty()
{ }

protected override void Configure(Configuration configuration)
{
TestConfiguration.Configure(configuration);
Expand Down
Loading
Loading