Skip to content

Commit 0066970

Browse files
committed
Add MySQL spatial type support
Extends AbstractMySQLPlatform with GEOMETRY type support and enhances MySQLSchemaManager to introspect spatial columns with geometryType and SRID properties. MySQL supports GEOMETRY types (POINT, LINESTRING, POLYGON, etc.) with optional SRID constraints using the conditional comment syntax for MySQL 8.0.3+.
1 parent ce08488 commit 0066970

File tree

8 files changed

+627
-53
lines changed

8 files changed

+627
-53
lines changed

src/Platforms/AbstractMySQLPlatform.php

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
use function sprintf;
3535
use function str_replace;
3636
use function strtolower;
37+
use function strtoupper;
3738

3839
/**
3940
* Provides the base implementation for the lowest versions of supported MySQL-like database platforms.
@@ -224,23 +225,34 @@ public function getBooleanTypeDeclarationSQL(array $column): string
224225
*/
225226
public function getGeometryTypeDeclarationSQL(array $column): string
226227
{
227-
throw NotSupported::new(__METHOD__);
228+
$geometryType = $column['geometryType'] ?? 'GEOMETRY';
229+
$srid = $column['srid'] ?? null;
230+
231+
$sql = strtoupper($geometryType);
232+
233+
if ($srid !== null) {
234+
// MySQL 8.0.3+ supports SRID attribute using conditional comment syntax
235+
$sql .= sprintf(' /*!80003 SRID %d */', $srid);
236+
}
237+
238+
return $sql;
228239
}
229240

230241
/**
231242
* {@inheritDoc}
232243
*/
233244
public function getGeometryFromGeoJSONSQL(string $sqlExpr): string
234245
{
235-
throw NotSupported::new(__METHOD__);
246+
return sprintf('ST_GeomFromGeoJSON(%s)', $sqlExpr);
236247
}
237248

238249
/**
239250
* {@inheritDoc}
240251
*/
241252
public function getGeometryAsGeoJSONSQL(string $sqlExpr): string
242253
{
243-
throw NotSupported::new(__METHOD__);
254+
// MySQL 5.7.5+ / MariaDB 10.2.4+ - maxdecimaldigits=15 (full precision), options=2 (include SRID in CRS)
255+
return sprintf('ST_AsGeoJSON(%s, 15, 2)', $sqlExpr);
244256
}
245257

246258
/**
@@ -808,38 +820,47 @@ public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level):
808820
protected function initializeDoctrineTypeMappings(): void
809821
{
810822
$this->doctrineTypeMapping = [
811-
'bigint' => Types::BIGINT,
812-
'binary' => Types::BINARY,
813-
'blob' => Types::BLOB,
814-
'char' => Types::STRING,
815-
'date' => Types::DATE_MUTABLE,
816-
'datetime' => Types::DATETIME_MUTABLE,
817-
'decimal' => Types::DECIMAL,
818-
'double' => Types::FLOAT,
819-
'enum' => Types::ENUM,
820-
'float' => Types::SMALLFLOAT,
821-
'int' => Types::INTEGER,
822-
'integer' => Types::INTEGER,
823-
'json' => Types::JSON,
824-
'longblob' => Types::BLOB,
825-
'longtext' => Types::TEXT,
826-
'mediumblob' => Types::BLOB,
827-
'mediumint' => Types::INTEGER,
828-
'mediumtext' => Types::TEXT,
829-
'numeric' => Types::DECIMAL,
830-
'real' => Types::FLOAT,
831-
'set' => Types::SIMPLE_ARRAY,
832-
'smallint' => Types::SMALLINT,
833-
'string' => Types::STRING,
834-
'text' => Types::TEXT,
835-
'time' => Types::TIME_MUTABLE,
836-
'timestamp' => Types::DATETIME_MUTABLE,
837-
'tinyblob' => Types::BLOB,
838-
'tinyint' => Types::BOOLEAN,
839-
'tinytext' => Types::TEXT,
840-
'varbinary' => Types::BINARY,
841-
'varchar' => Types::STRING,
842-
'year' => Types::DATE_MUTABLE,
823+
'bigint' => Types::BIGINT,
824+
'binary' => Types::BINARY,
825+
'blob' => Types::BLOB,
826+
'char' => Types::STRING,
827+
'date' => Types::DATE_MUTABLE,
828+
'datetime' => Types::DATETIME_MUTABLE,
829+
'decimal' => Types::DECIMAL,
830+
'double' => Types::FLOAT,
831+
'enum' => Types::ENUM,
832+
'float' => Types::SMALLFLOAT,
833+
'geomcollection' => Types::GEOMETRY,
834+
'geometry' => Types::GEOMETRY,
835+
'geometrycollection' => Types::GEOMETRY,
836+
'int' => Types::INTEGER,
837+
'integer' => Types::INTEGER,
838+
'json' => Types::JSON,
839+
'linestring' => Types::GEOMETRY,
840+
'longblob' => Types::BLOB,
841+
'longtext' => Types::TEXT,
842+
'mediumblob' => Types::BLOB,
843+
'mediumint' => Types::INTEGER,
844+
'mediumtext' => Types::TEXT,
845+
'multilinestring' => Types::GEOMETRY,
846+
'multipoint' => Types::GEOMETRY,
847+
'multipolygon' => Types::GEOMETRY,
848+
'numeric' => Types::DECIMAL,
849+
'point' => Types::GEOMETRY,
850+
'polygon' => Types::GEOMETRY,
851+
'real' => Types::FLOAT,
852+
'set' => Types::SIMPLE_ARRAY,
853+
'smallint' => Types::SMALLINT,
854+
'string' => Types::STRING,
855+
'text' => Types::TEXT,
856+
'time' => Types::TIME_MUTABLE,
857+
'timestamp' => Types::DATETIME_MUTABLE,
858+
'tinyblob' => Types::BLOB,
859+
'tinyint' => Types::BOOLEAN,
860+
'tinytext' => Types::TEXT,
861+
'varbinary' => Types::BINARY,
862+
'varchar' => Types::STRING,
863+
'year' => Types::DATE_MUTABLE,
843864
];
844865
}
845866

src/Platforms/MySQL/MySQLMetadataProvider.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,29 @@ private function createTableColumn(array $row): TableColumnMetadataRow
280280
case 'enum':
281281
$editor->setValues($this->parseEnumExpression($columnType));
282282
break;
283+
284+
case 'geometry':
285+
case 'point':
286+
case 'linestring':
287+
case 'polygon':
288+
case 'multipoint':
289+
case 'multilinestring':
290+
case 'multipolygon':
291+
case 'geomcollection':
292+
case 'geometrycollection':
293+
$parameters = $this->parseSpatialTypeParameters($columnType);
294+
if ($parameters !== null) {
295+
[$geometryType, $srid] = $parameters;
296+
if ($geometryType !== null) {
297+
$editor->setGeometryType($geometryType);
298+
}
299+
300+
if ($srid !== null) {
301+
$editor->setSrid($srid);
302+
}
303+
}
304+
305+
break;
283306
}
284307

285308
if ($this->platform instanceof MariaDBPlatform) {
@@ -314,6 +337,37 @@ private function parseEnumExpression(string $expression): array
314337
);
315338
}
316339

340+
/**
341+
* Parses MySQL spatial type parameters from COLUMN_TYPE.
342+
*
343+
* MySQL spatial columns may include SRID in a comment: "point /*!80003 SRID 4326 *\/"
344+
*
345+
* @return array{string|null, int|null}|null [geometryType, srid] or null if no parameters
346+
*/
347+
private function parseSpatialTypeParameters(string $columnType): ?array
348+
{
349+
// Extract the geometry type (the first word before any space or comment)
350+
if (preg_match('/^(\w+)/', $columnType, $typeMatches) !== 1) {
351+
return null;
352+
}
353+
354+
$geometryType = $typeMatches[1];
355+
356+
// Normalize MySQL's abbreviated "geomcollection" to "geometrycollection"
357+
if ($geometryType === 'geomcollection') {
358+
$geometryType = 'geometrycollection';
359+
}
360+
361+
// Extract SRID from MySQL conditional comment if present
362+
// Format: /*!80003 SRID 4326 */
363+
$srid = null;
364+
if (preg_match('/SRID\s+(\d+)/', $columnType, $sridMatches) === 1) {
365+
$srid = (int) $sridMatches[1];
366+
}
367+
368+
return [$geometryType, $srid];
369+
}
370+
317371
/**
318372
* Return Doctrine/Mysql-compatible column default values for MariaDB 10.2.7+ servers.
319373
*

src/Schema/MySQLSchemaManager.php

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,14 @@ protected function _getPortableTableColumnDefinition(array $tableColumn): Column
118118
{
119119
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
120120

121-
$dbType = $tableColumn['type'];
122-
$length = null;
123-
$scale = 0;
124-
$precision = null;
125-
$fixed = false;
126-
$values = [];
121+
$dbType = $tableColumn['type'];
122+
$length = null;
123+
$scale = 0;
124+
$precision = null;
125+
$fixed = false;
126+
$values = [];
127+
$geometryType = null;
128+
$srid = null;
127129

128130
$type = $this->platform->getDoctrineTypeMapping($dbType);
129131

@@ -173,6 +175,36 @@ protected function _getPortableTableColumnDefinition(array $tableColumn): Column
173175
$scale = (int) $tableColumn['numeric_scale'];
174176
}
175177

178+
break;
179+
180+
case 'geometry':
181+
case 'point':
182+
case 'linestring':
183+
case 'polygon':
184+
case 'multipoint':
185+
case 'multilinestring':
186+
case 'multipolygon':
187+
case 'geomcollection':
188+
case 'geometrycollection':
189+
// For MySQL, the dbType directly represents the geometry subtype
190+
$geometryType = $dbType;
191+
192+
// Normalize MySQL's abbreviated "geomcollection" to "geometrycollection"
193+
if ($geometryType === 'geomcollection') {
194+
$geometryType = 'geometrycollection';
195+
}
196+
197+
// MySQL 8.0.3+ stores SRID in information_schema.COLUMNS.SRS_ID
198+
if (isset($tableColumn['srs_id']) && $tableColumn['srs_id'] !== null) {
199+
$srid = (int) $tableColumn['srs_id'];
200+
} else {
201+
// MariaDB and older MySQL versions store SRID in COLUMN_TYPE as conditional comment
202+
// Format: /*!80003 SRID 4326 */
203+
if (preg_match('/SRID\s+(\d+)/', $tableColumn['column_type'], $sridMatches) === 1) {
204+
$srid = (int) $sridMatches[1];
205+
}
206+
}
207+
176208
break;
177209
}
178210

@@ -213,6 +245,14 @@ protected function _getPortableTableColumnDefinition(array $tableColumn): Column
213245
$column->setPlatformOption('charset', $tableColumn['characterset']);
214246
$column->setPlatformOption('collation', $tableColumn['collation']);
215247

248+
if ($geometryType !== null) {
249+
$column->setPlatformOption('geometryType', $geometryType);
250+
}
251+
252+
if ($srid !== null) {
253+
$column->setPlatformOption('srid', $srid);
254+
}
255+
216256
return $column;
217257
}
218258

@@ -355,6 +395,12 @@ protected function selectTableColumns(string $databaseName, ?string $tableName =
355395
$params[] = $tableName;
356396
}
357397

398+
// MariaDB does not support SRS_ID column in information_schema.COLUMNS
399+
// For MariaDB, SRID is parsed from COLUMN_TYPE conditional comments
400+
$sridColumn = $this->platform instanceof MariaDBPlatform
401+
? 'NULL AS srs_id'
402+
: 'c.SRS_ID AS srs_id';
403+
358404
$sql = sprintf(
359405
<<<'SQL'
360406
SELECT
@@ -372,7 +418,8 @@ protected function selectTableColumns(string $databaseName, ?string $tableName =
372418
c.EXTRA,
373419
c.COLUMN_COMMENT AS comment,
374420
c.CHARACTER_SET_NAME AS characterset,
375-
c.COLLATION_NAME AS collation
421+
c.COLLATION_NAME AS collation,
422+
%s
376423
FROM information_schema.COLUMNS c
377424
INNER JOIN information_schema.TABLES t
378425
ON t.TABLE_NAME = c.TABLE_NAME
@@ -382,6 +429,7 @@ protected function selectTableColumns(string $databaseName, ?string $tableName =
382429
c.ORDINAL_POSITION
383430
SQL,
384431
$this->platform->getColumnTypeSQLSnippet('c', $databaseName),
432+
$sridColumn,
385433
implode(' AND ', $conditions),
386434
);
387435

0 commit comments

Comments
 (0)