Skip to content

Commit 856e145

Browse files
Imbrucedjiayuasu
andauthored
[SEDONA-743] Add geom from mysql function, to support loading MySQL geometry object directly to Sedona. (#2287)
* SEDONA-738 Fix unit tests. * SEDONA-738 Fix unit tests. * SEDONA-738 Fix unit tests. * SEDONA-738 Fix unit tests. * SEDONA-738 Fix unit tests. * SEDONA-738 Fix unit tests. * SEDONA-743 Add geom from mysql function. * SEDONA-743 Add geom from mysql function. * SEDONA-743 Add geom from mysql function. * SEDONA-743 Add geom from mysql function. * SEDONA-743 Add geom from mysql function. * Update docs/api/sql/Constructor.md --------- Co-authored-by: Jia Yu <[email protected]>
1 parent 4e40828 commit 856e145

File tree

7 files changed

+134
-1
lines changed

7 files changed

+134
-1
lines changed

common/src/main/java/org/apache/sedona/common/Constructors.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
package org.apache.sedona.common;
2020

2121
import java.io.IOException;
22+
import java.nio.ByteBuffer;
23+
import java.nio.ByteOrder;
2224
import javax.xml.parsers.ParserConfigurationException;
2325
import org.apache.sedona.common.enums.FileDataSplitter;
2426
import org.apache.sedona.common.enums.GeometryType;
@@ -302,4 +304,21 @@ public static Geometry geomFromGML(String gml)
302304
public static Geometry geomFromKML(String kml) throws ParseException {
303305
return new KMLReader().read(kml);
304306
}
307+
308+
public static Geometry geomFromMySQL(byte[] binary) throws ParseException {
309+
ByteBuffer buffer = ByteBuffer.wrap(binary);
310+
311+
buffer.order(ByteOrder.LITTLE_ENDIAN);
312+
int srid = buffer.getInt();
313+
314+
byte[] wkb = new byte[buffer.remaining()];
315+
316+
buffer.get(wkb);
317+
318+
Geometry geom = geomFromWKB(wkb);
319+
320+
geom.setSRID(srid);
321+
322+
return geom;
323+
}
305324
}

docs/api/sql/Constructor.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,3 +842,32 @@ Output:
842842
```
843843
POLYGON ((-74.0428197 40.6867969, -74.0421975 40.6921336, -74.050802 40.6912794, -74.0428197 40.6867969))
844844
```
845+
846+
## ST_GeomFromMySQL
847+
848+
Introduction: Construct a Geometry from MySQL Geometry binary.
849+
850+
Format: `ST_GeomFromMySQL (binary: Binary)`
851+
852+
Since: `v1.8.0`
853+
854+
SQL Example
855+
856+
```sql
857+
SELECT
858+
ST_GeomFromMySQL(geomWKB) AS geom,
859+
ST_SRID(ST_GeomFromMySQL(geomWKB)) AS srid
860+
FROM mysql_table
861+
```
862+
863+
Output:
864+
865+
```
866+
+-------------+----+
867+
| geom|srid|
868+
+-------------+----+
869+
|POINT (20 10)|4326|
870+
|POINT (40 30)|4326|
871+
|POINT (60 50)|4326|
872+
+-------------+----+
873+
```

spark/common/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,18 @@
259259
<version>1.20.0</version>
260260
<scope>test</scope>
261261
</dependency>
262+
<dependency>
263+
<groupId>org.testcontainers</groupId>
264+
<artifactId>mysql</artifactId>
265+
<version>1.20.0</version>
266+
<scope>test</scope>
267+
</dependency>
268+
<dependency>
269+
<groupId>com.mysql</groupId>
270+
<artifactId>mysql-connector-j</artifactId>
271+
<version>8.4.0</version>
272+
<scope>test</scope>
273+
</dependency>
262274
<dependency>
263275
<groupId>io.minio</groupId>
264276
<artifactId>minio</artifactId>

spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ object Catalog extends AbstractCatalog {
215215
function[ST_MLineFromText](0),
216216
function[ST_GeomCollFromText](0),
217217
function[ST_GeogCollFromText](0),
218+
function[ST_GeomFromMySQL](),
218219
function[ST_Split](),
219220
function[ST_S2CellIDs](),
220221
function[ST_S2ToGeom](),

spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,3 +624,11 @@ private[apache] case class ST_GeomCollFromText(inputExpressions: Seq[Expression]
624624
copy(inputExpressions = newChildren)
625625
}
626626
}
627+
628+
private[apache] case class ST_GeomFromMySQL(inputExpressions: Seq[Expression])
629+
extends InferredExpression(Constructors.geomFromMySQL _) {
630+
631+
protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
632+
copy(inputExpressions = newChildren)
633+
}
634+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CREATE TABLE points
2+
(
3+
name VARCHAR(50),
4+
location GEOMETRY
5+
);
6+
7+
INSERT INTO points (name, location)
8+
VALUES ('Point A',
9+
ST_GeomFromText('POINT(10 20)', 4326));
10+
11+
INSERT INTO points (name, location)
12+
VALUES ('Point B',
13+
ST_GeomFromText('POINT(30 40)', 4326));
14+
15+
INSERT INTO points (name, location)
16+
VALUES ('Point C',
17+
ST_GeomFromText('POINT(50 60)', 4326));

spark/common/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ package org.apache.sedona.sql
2121
import org.apache.sedona.core.formatMapper.GeoJsonReader
2222
import org.apache.sedona.core.formatMapper.shapefileParser.ShapefileReader
2323
import org.apache.sedona.sql.utils.Adapter
24+
import org.apache.spark.sql.Row
2425
import org.locationtech.jts.geom.{Geometry, LineString}
26+
import org.scalatest.matchers.should.Matchers
27+
import org.testcontainers.containers.MySQLContainer
2528

26-
class constructorTestScala extends TestBaseScala {
29+
class constructorTestScala extends TestBaseScala with Matchers {
2730

2831
import sparkSession.implicits._
2932

@@ -626,5 +629,49 @@ class constructorTestScala extends TestBaseScala {
626629
val actualSrid = actualGeom.getSRID
627630
assert(4326 == actualSrid)
628631
}
632+
633+
it("should properly read data from MySQL") {
634+
val runTest = (jdbcURL: String) => {
635+
val tableName = "points"
636+
val properties = new java.util.Properties()
637+
properties.setProperty("user", "sedona")
638+
properties.setProperty("password", "sedona")
639+
640+
sparkSession.read
641+
.jdbc(jdbcURL, tableName, properties)
642+
.selectExpr(
643+
"ST_GeomFromMySQL(location) as geom",
644+
"ST_SRID(ST_GeomFromMySQL(location)) AS srid")
645+
.show
646+
647+
val elements = sparkSession.read
648+
.jdbc(jdbcURL, tableName, properties)
649+
.selectExpr(
650+
"ST_GeomFromMySQL(location) as geom",
651+
"ST_SRID(ST_GeomFromMySQL(location)) AS srid")
652+
.selectExpr("ST_AsText(geom) as geom", "srid")
653+
.collect()
654+
655+
elements.length shouldBe 3
656+
elements should contain theSameElementsAs Seq(
657+
Row("POINT (20 10)", 4326),
658+
Row("POINT (40 30)", 4326),
659+
Row("POINT (60 50)", 4326))
660+
}
661+
662+
val mysql = new MySQLContainer("mysql:8.4.0")
663+
mysql.withInitScript("mysql/init_mysql.sql")
664+
mysql.withUsername("sedona")
665+
mysql.withPassword("sedona")
666+
mysql.withDatabaseName("sedona")
667+
668+
mysql.start()
669+
670+
try {
671+
runTest(mysql.getJdbcUrl)
672+
} finally {
673+
mysql.stop()
674+
}
675+
}
629676
}
630677
}

0 commit comments

Comments
 (0)