Skip to content

Commit

Permalink
Improve MariaDB support (#524)
Browse files Browse the repository at this point in the history
* Improve MariaDB support

* Fix pre-commit config

* Fix CI and minor improvements

---------

Co-authored-by: Tom Cook <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 3, 2024
1 parent 4a5887a commit f93cf5a
Show file tree
Hide file tree
Showing 26 changed files with 362 additions and 101 deletions.
82 changes: 57 additions & 25 deletions .github/workflows/test_and_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,28 +54,53 @@ jobs:
--health-interval 10s
--health-timeout 5s
--health-retries 5
mysql:
image: mysql:latest
ports:
- 3307:3306
env:
MYSQL_USER: gis
MYSQL_PASSWORD: gis
MYSQL_DATABASE: gis
MYSQL_ROOT_PASSWORD: gis
# Set health checks to wait until MySQL has started
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
mariadb:
image: mariadb:latest
ports:
- 3308:3306
env:
MARIADB_USER: gis
MARIADB_PASSWORD: gis
MARIADB_DATABASE: gis
MARIADB_ROOT_PASSWORD: gis
# Set health checks to wait until MariaDB has started
options: >-
--health-cmd="healthcheck.sh --connect --innodb_initialized"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:

# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v4

# Setup Conda for Python and Pypy
- name: Install Conda environment with Micromamba
uses: mamba-org/setup-micromamba@v1
# Setup Python
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
environment-name: test_${{ matrix.python-version.flag }}
cache-environment: true
create-args: >-
${{ matrix.python-version.pkg_name }}
libgdal
libspatialite==5.0.1
pyproj
condarc: |
channels:
- conda-forge
- defaults
channel_priority: strict
python-version: ${{ matrix.python-version.flag }}

# Install MariaDB
- name: Install MariaDB and SpatiaLite
run: |
sudo apt-get install -y mariadb-server mariadb-client libsqlite3-mod-spatialite libgdal-dev gdal-bin rasterio
# Config PostgreSQL
- name: Configure PostgreSQL
Expand All @@ -95,13 +120,17 @@ jobs:
# Drop PostGIS Topology extension to "gis" database
psql -h localhost -p 5432 -U gis -d gis -c 'DROP EXTENSION IF EXISTS postgis_topology;'
# Setup MySQL
- name: Set up MySQL
# Check MySQL
- name: Check MySQL
run: |
mysql --user=gis --password=gis --host=127.0.0.1 -P 3307 -e "SELECT VERSION();"
mysql --user=root --password=gis --host=127.0.0.1 -P 3307 -e "GRANT ALL PRIVILEGES ON *.* TO 'gis'@'%' WITH GRANT OPTION;"
# Check MariaDB
- name: Check MariaDB
run: |
sudo systemctl start mysql
sudo mysql --user=root --password=root --host=127.0.0.1 -e "CREATE USER 'gis'@'%' IDENTIFIED BY 'gis';"
sudo mysql --user=root --password=root --host=127.0.0.1 -e "GRANT ALL PRIVILEGES ON *.* TO 'gis'@'%' WITH GRANT OPTION;"
mysql --user=gis --password=gis -e "CREATE DATABASE gis;"
mysql --user=gis --password=gis --host=127.0.0.1 -P 3308 -e "SELECT VERSION();"
mysql --user=root --password=gis --host=127.0.0.1 -P 3308 -e "GRANT ALL PRIVILEGES ON *.* TO 'gis'@'%' WITH GRANT OPTION;"
# Check python version
- name: Display Python version
Expand All @@ -114,16 +143,19 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools
pip install tox-gh-actions
python -m pip install tox-gh-actions
# Run the test suite
- name: Run the tests
env:
SPATIALITE_LIBRARY_PATH: /home/runner/micromamba/envs/test_${{ matrix.python-version.flag }}/lib/mod_spatialite.so
PROJ_LIB: /home/runner/micromamba/envs/test_${{ matrix.python-version.flag }}/share/proj
SPATIALITE_LIBRARY_PATH: /usr/lib/x86_64-linux-gnu/mod_spatialite.so
COVERAGE_FILE: .coverage
PYTEST_MYSQL_DB_URL: mysql://gis:[email protected]/gis
PYTEST_MYSQL_DB_URL: mysql://gis:[email protected]:3307/gis
PYTEST_MARIADB_DB_URL: mariadb://gis:[email protected]:3308/gis
run: |
if [[ ${{ matrix.python-version.flag }} == 'pypy3.8' ]]; then
export PYTEST_ADDOPTS='--ignore=tests/gallery/test_insert_raster.py'
fi;
# Run the unit test suite with SQLAlchemy=1.4.* and then with the latest version of SQLAlchemy
tox -vv
Expand Down
2 changes: 0 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
default_language_version:
python: python3.8
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
Expand Down
4 changes: 2 additions & 2 deletions doc/admin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ PostgreSQL-specific objects
:private-members:
:show-inheritance:

MySQL-specific objects
---------------------------
MySQL/MariadDB-specific objects
-------------------------------

.. automodule:: geoalchemy2.admin.dialects.mysql
:members:
Expand Down
2 changes: 1 addition & 1 deletion doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ GeoAlchemy 2 also supports the following dialects:
* `GeoPackage <http://www.geopackage.org/spec/>`_

Note that using GeoAlchemy 2 with these dialects may require some specific configuration on the
application side.
application side. It also may not be optimal for performance.

GeoAlchemy 2 aims to be simpler than its predecessor, `GeoAlchemy
<https://pypi.python.org/pypi/GeoAlchemy>`_. Simpler to use, and simpler
Expand Down
80 changes: 74 additions & 6 deletions geoalchemy2/admin/dialects/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
from geoalchemy2.admin.dialects.common import _check_spatial_type
from geoalchemy2.admin.dialects.common import _spatial_idx_name
from geoalchemy2.admin.dialects.common import setup_create_drop
from geoalchemy2.elements import WKBElement
from geoalchemy2.elements import WKTElement
from geoalchemy2.shape import to_shape
from geoalchemy2.types import Geography
from geoalchemy2.types import Geometry

Expand All @@ -31,11 +34,16 @@ def reflect_geometry_column(inspector, table, column_info):
column_name = column_info.get("name")
schema = table.schema or inspector.default_schema_name

if inspector.dialect.name == "mariadb":
select_srid = "-1, "
else:
select_srid = "SRS_ID, "

# Check geometry type, SRID and if the column is nullable
geometry_type_query = """SELECT DATA_TYPE, SRS_ID, IS_NULLABLE
geometry_type_query = """SELECT DATA_TYPE, {}IS_NULLABLE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '{}' and COLUMN_NAME = '{}'""".format(
table.name, column_name
select_srid, table.name, column_name
)
if schema is not None:
geometry_type_query += """ and table_schema = '{}'""".format(schema)
Expand Down Expand Up @@ -176,25 +184,85 @@ def _compile_GeomFromWKB_MySql(element, compiler, **kw):
return "{}({})".format(element.identifier, compiled)


def _compile_GeomFromText_MariaDB(element, compiler, **kw):
element.identifier = "ST_GeomFromText"
compiled = compiler.process(element.clauses, **kw)
try:
clauses = list(element.clauses)
data_element = WKTElement(clauses[0].value)
srid = max(0, data_element.srid)
if srid <= 0:
srid = max(0, element.type.srid)
if len(clauses) > 1 and srid > 0:
clauses[1].value = srid
except Exception:
srid = max(0, element.type.srid)

if srid > 0:
res = "{}({}, {})".format(element.identifier, compiled, srid)
else:
res = "{}({})".format(element.identifier, compiled)
return res


def _compile_GeomFromWKB_MariaDB(element, compiler, **kw):
element.identifier = "ST_GeomFromText"

try:
clauses = list(element.clauses)
data_element = WKBElement(clauses[0].value)
srid = max(0, data_element.srid)
if srid <= 0:
srid = max(0, element.type.srid)
clauses[0].value = to_shape(data_element).wkt.encode("utf-8")
if len(clauses) > 1 and srid > 0:
clauses[1].value = srid
except Exception:
srid = max(0, element.type.srid)
compiled = compiler.process(element.clauses, **kw)

if srid > 0:
res = "{}({}, {})".format(element.identifier, compiled, srid)
else:
res = "{}({})".format(element.identifier, compiled)
return res


@compiles(functions.ST_GeomFromText, "mysql") # type: ignore
@compiles(functions.ST_GeomFromText, "mariadb") # type: ignore
def _MySQL_ST_GeomFromText(element, compiler, **kw):
return _compile_GeomFromText_MySql(element, compiler, **kw)


@compiles(functions.ST_GeomFromEWKT, "mysql") # type: ignore
@compiles(functions.ST_GeomFromEWKT, "mariadb") # type: ignore
def _MySQL_ST_GeomFromEWKT(element, compiler, **kw):
return _compile_GeomFromText_MySql(element, compiler, **kw)


@compiles(functions.ST_GeomFromText, "mariadb") # type: ignore
def _MariaDB_ST_GeomFromText(element, compiler, **kw):
return _compile_GeomFromText_MariaDB(element, compiler, **kw)


@compiles(functions.ST_GeomFromEWKT, "mariadb") # type: ignore
def _MariaDB_ST_GeomFromEWKT(element, compiler, **kw):
return _compile_GeomFromText_MariaDB(element, compiler, **kw)


@compiles(functions.ST_GeomFromWKB, "mysql") # type: ignore
@compiles(functions.ST_GeomFromWKB, "mariadb") # type: ignore
def _MySQL_ST_GeomFromWKB(element, compiler, **kw):
return _compile_GeomFromWKB_MySql(element, compiler, **kw)


@compiles(functions.ST_GeomFromEWKB, "mysql") # type: ignore
@compiles(functions.ST_GeomFromEWKB, "mariadb") # type: ignore
def _MySQL_ST_GeomFromEWKB(element, compiler, **kw):
return _compile_GeomFromWKB_MySql(element, compiler, **kw)


@compiles(functions.ST_GeomFromWKB, "mariadb") # type: ignore
def _MariaDB_ST_GeomFromWKB(element, compiler, **kw):
return _compile_GeomFromWKB_MariaDB(element, compiler, **kw)


@compiles(functions.ST_GeomFromEWKB, "mariadb") # type: ignore
def _MariaDB_ST_GeomFromEWKB(element, compiler, **kw):
return _compile_GeomFromWKB_MariaDB(element, compiler, **kw)
8 changes: 4 additions & 4 deletions geoalchemy2/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def select_dialect(dialect_name):
known_dialects = {
"geopackage": dialects.geopackage,
"mysql": dialects.mysql,
"mariadb": dialects.mysql,
"mariadb": dialects.mariadb,
"postgresql": dialects.postgresql,
"sqlite": dialects.sqlite,
}
Expand Down Expand Up @@ -198,17 +198,17 @@ def check_ctor_args(geometry_type, srid, dimension, use_typmod, nullable):
return geometry_type, srid


@compiles(_GISType, "mariadb")
@compiles(_GISType, "mysql")
def get_col_spec(self, *args, **kwargs):
@compiles(_GISType, "mariadb")
def get_col_spec_mysql(self, compiler, *args, **kwargs):
if self.geometry_type is not None:
spec = "%s" % self.geometry_type
else:
spec = "GEOMETRY"

if not self.nullable or self.spatial_index:
spec += " NOT NULL"
if self.srid > 0:
if self.srid > 0 and compiler.dialect.name != "mariadb":
spec += " SRID %d" % self.srid
return spec

Expand Down
1 change: 1 addition & 0 deletions geoalchemy2/types/dialects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from geoalchemy2.types.dialects import common # noqa
from geoalchemy2.types.dialects import geopackage # noqa
from geoalchemy2.types.dialects import mariadb # noqa
from geoalchemy2.types.dialects import mysql # noqa
from geoalchemy2.types.dialects import postgresql # noqa
from geoalchemy2.types.dialects import sqlite # noqa
47 changes: 47 additions & 0 deletions geoalchemy2/types/dialects/mariadb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""This module defines specific functions for MySQL dialect."""

from geoalchemy2.elements import WKBElement
from geoalchemy2.elements import WKTElement
from geoalchemy2.elements import _SpatialElement
from geoalchemy2.exc import ArgumentError
from geoalchemy2.shape import to_shape


def bind_processor_process(spatial_type, bindvalue):
if isinstance(bindvalue, str):
wkt_match = WKTElement._REMOVE_SRID.match(bindvalue)
srid = wkt_match.group(2)
try:
if srid is not None:
srid = int(srid)
except (ValueError, TypeError): # pragma: no cover
raise ArgumentError(
f"The SRID ({srid}) of the supplied value can not be casted to integer"
)

if srid is not None and srid != spatial_type.srid:
raise ArgumentError(
f"The SRID ({srid}) of the supplied value is different "
f"from the one of the column ({spatial_type.srid})"
)
return wkt_match.group(3)

if (
isinstance(bindvalue, _SpatialElement)
and bindvalue.srid != -1
and bindvalue.srid != spatial_type.srid
):
raise ArgumentError(
f"The SRID ({bindvalue.srid}) of the supplied value is different "
f"from the one of the column ({spatial_type.srid})"
)

if isinstance(bindvalue, WKTElement):
bindvalue = bindvalue.as_wkt()
if bindvalue.srid <= 0:
bindvalue.srid = spatial_type.srid
return bindvalue
elif isinstance(bindvalue, WKBElement):
# With MariaDB we use Shapely to convert the WKBElement to an EWKT string
return to_shape(bindvalue).wkt
return bindvalue
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ requires = [
"wheel",
"setuptools_scm[toml]>=3.4",
]
build-backend = "setuptools.build_meta"

[tool.setuptools_scm]

# BLACK
[tool.black]
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ pytest
pytest-cov
pytest-html
pytest-mypy
rasterio
rasterio;implementation_name!='pypy'
4 changes: 2 additions & 2 deletions test_container/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ COPY ./helpers/install_requirements.sh /
RUN /install_requirements.sh

COPY ./helpers/init_postgres.sh /
env PGDATA="/var/lib/postgresql/data"
env POSTGRES_PATH="/usr/lib/postgresql/16"
ENV PGDATA="/var/lib/postgresql/data"
ENV POSTGRES_PATH="/usr/lib/postgresql/16"
RUN su postgres -c /init_postgres.sh

ENV SPATIALITE_LIBRARY_PATH="/usr/lib/x86_64-linux-gnu/mod_spatialite.so"
Expand Down
19 changes: 19 additions & 0 deletions test_container/Dockerfile_mariadb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM ubuntu:22.04

COPY ./helpers/install_requirements.sh /
RUN /install_requirements.sh

RUN apt-get update; apt-get install -y mariadb-server mariadb-client; rm -rf /var/lib/apt/lists/*;

COPY ./helpers/init_postgres.sh /
ENV PGDATA="/var/lib/postgresql/data"
ENV POSTGRES_PATH="/usr/lib/postgresql/16"
RUN su postgres -c /init_postgres.sh

ENV SPATIALITE_LIBRARY_PATH="/usr/lib/x86_64-linux-gnu/mod_spatialite.so"

COPY ./helpers/init_mariadb.sh /
RUN /init_mariadb.sh

COPY ./helpers/entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
Loading

0 comments on commit f93cf5a

Please sign in to comment.