From 1e372dda04bb19c51adccde6772f772ef93ddc0f Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 2 Sep 2025 19:47:46 +0300 Subject: [PATCH 01/27] feat(#305): add support for PostGIS measurement, overlay and geometry processing functions --- .../Query/AST/Functions/PostGIS/ST_3DArea.php | 30 +++++++++++++++++ .../AST/Functions/PostGIS/ST_3DDistance.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_3DLength.php | 30 +++++++++++++++++ .../AST/Functions/PostGIS/ST_3DPerimeter.php | 30 +++++++++++++++++ .../Query/AST/Functions/PostGIS/ST_Area.php | 30 +++++++++++++++++ .../Query/AST/Functions/PostGIS/ST_Area2D.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Azimuth.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Boundary.php | 32 ++++++++++++++++++ .../Query/AST/Functions/PostGIS/ST_Buffer.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Centroid.php | 32 ++++++++++++++++++ .../AST/Functions/PostGIS/ST_ClipByBox2D.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Collect.php | 31 +++++++++++++++++ .../PostGIS/ST_CollectionExtract.php | 31 +++++++++++++++++ .../PostGIS/ST_CollectionHomogenize.php | 30 +++++++++++++++++ .../AST/Functions/PostGIS/ST_ConvexHull.php | 30 +++++++++++++++++ .../AST/Functions/PostGIS/ST_CurveToLine.php | 30 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Difference.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Distance.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Envelope.php | 30 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Extrude.php | 33 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_Force2D.php | 30 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Force3D.php | 30 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Force4D.php | 30 +++++++++++++++++ .../Functions/PostGIS/ST_FrechetDistance.php | 31 +++++++++++++++++ .../PostGIS/ST_HausdorffDistance.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Intersection.php | 31 +++++++++++++++++ .../Query/AST/Functions/PostGIS/ST_Length.php | 30 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Length2D.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_LineToCurve.php | 30 +++++++++++++++++ .../AST/Functions/PostGIS/ST_MaxDistance.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Perimeter.php | 30 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Project.php | 32 ++++++++++++++++++ .../AST/Functions/PostGIS/ST_Reverse.php | 31 +++++++++++++++++ .../Query/AST/Functions/PostGIS/ST_Rotate.php | 31 +++++++++++++++++ .../Query/AST/Functions/PostGIS/ST_Scale.php | 32 ++++++++++++++++++ .../AST/Functions/PostGIS/ST_Simplify.php | 31 +++++++++++++++++ .../PostGIS/ST_SimplifyPreserveTopology.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_SimplifyVW.php | 31 +++++++++++++++++ .../Query/AST/Functions/PostGIS/ST_Split.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Subdivide.php | 31 +++++++++++++++++ .../Functions/PostGIS/ST_SymDifference.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Transform.php | 31 +++++++++++++++++ .../AST/Functions/PostGIS/ST_Translate.php | 32 ++++++++++++++++++ .../AST/Functions/PostGIS/ST_UnaryUnion.php | 30 +++++++++++++++++ .../Query/AST/Functions/PostGIS/ST_Union.php | 31 +++++++++++++++++ 45 files changed, 1387 insertions(+) create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DArea.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DDistance.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DLength.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DPerimeter.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2D.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Azimuth.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Boundary.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Buffer.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Centroid.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2D.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Collect.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionExtract.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionHomogenize.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ConvexHull.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CurveToLine.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Difference.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Distance.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Envelope.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Extrude.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force2D.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force3D.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force4D.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_FrechetDistance.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_HausdorffDistance.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Intersection.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2D.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineToCurve.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_MaxDistance.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Perimeter.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Project.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Reverse.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Rotate.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Scale.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Simplify.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyPreserveTopology.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyVW.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Split.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Subdivide.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SymDifference.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Transform.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Translate.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnaryUnion.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Union.php diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DArea.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DArea.php new file mode 100644 index 00000000..6dd05b33 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DArea.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_3DAREA(g.geometry) FROM Entity g" + * Returns numeric 3D area value. + */ +class ST_3DArea extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_3DArea(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DDistance.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DDistance.php new file mode 100644 index 00000000..4d8c4371 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DDistance.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_3DDISTANCE(g1.geometry, g2.geometry) FROM Entity g1, Entity g2" + * Returns numeric 3D distance value. + */ +class ST_3DDistance extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_3DDistance(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DLength.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DLength.php new file mode 100644 index 00000000..956b87f9 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DLength.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_3DLENGTH(g.geometry) FROM Entity g" + * Returns numeric 3D length value. + */ +class ST_3DLength extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_3DLength(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DPerimeter.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DPerimeter.php new file mode 100644 index 00000000..979ecbad --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DPerimeter.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_3DPERIMETER(g.geometry) FROM Entity g" + * Returns numeric 3D perimeter value. + */ +class ST_3DPerimeter extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_3DPerimeter(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area.php new file mode 100644 index 00000000..a2b59f0e --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_AREA(g.geometry) FROM Entity g" + * Returns numeric area value. + */ +class ST_Area extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Area(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2D.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2D.php new file mode 100644 index 00000000..81e85a85 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2D.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_AREA2D(g.geometry) FROM Entity g" + * Returns numeric 2D area value. + */ +class ST_Area2D extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Area2D(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Azimuth.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Azimuth.php new file mode 100644 index 00000000..5109c2e5 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Azimuth.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_AZIMUTH(g.geometry1, g.geometry2) FROM Entity g" + * Returns azimuth angle in radians. + */ +class ST_Azimuth extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Azimuth(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Boundary.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Boundary.php new file mode 100644 index 00000000..975d3e88 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Boundary.php @@ -0,0 +1,32 @@ + + * + * @example Using it in DQL: "SELECT ST_BOUNDARY(g.geometry) FROM Entity g" + * Returns geometry representing the boundary. + */ +class ST_Boundary extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Boundary(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Buffer.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Buffer.php new file mode 100644 index 00000000..61c06ac6 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Buffer.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_BUFFER(g.geometry, 10) FROM Entity g" + * Returns buffered geometry. + */ +class ST_Buffer extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Buffer(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('Literal'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Centroid.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Centroid.php new file mode 100644 index 00000000..c4877dc7 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Centroid.php @@ -0,0 +1,32 @@ + + * + * @example Using it in DQL: "SELECT ST_CENTROID(g.geometry) FROM Entity g" + * Returns point geometry representing the centroid. + */ +class ST_Centroid extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Centroid(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2D.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2D.php new file mode 100644 index 00000000..fe32fd37 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2D.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_CLIPBYBOX2D(g.geometry, 'BOX(0 0, 100 100)') FROM Entity g" + * Returns clipped geometry. + */ +class ST_ClipByBox2D extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_ClipByBox2D(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('Literal'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Collect.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Collect.php new file mode 100644 index 00000000..b12f2b24 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Collect.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_COLLECT(g.geometry1, g.geometry2) FROM Entity g" + * Returns geometry collection. + */ +class ST_Collect extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Collect(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionExtract.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionExtract.php new file mode 100644 index 00000000..1cece76e --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionExtract.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_COLLECTIONEXTRACT(g.geometry, 1) FROM Entity g" + * Returns collection with extracted geometry type. + */ +class ST_CollectionExtract extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_CollectionExtract(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('Literal'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionHomogenize.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionHomogenize.php new file mode 100644 index 00000000..a56dbf2b --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionHomogenize.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_COLLECTIONHOMOGENIZE(g.geometry) FROM Entity g" + * Returns homogenized geometry. + */ +class ST_CollectionHomogenize extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_CollectionHomogenize(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ConvexHull.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ConvexHull.php new file mode 100644 index 00000000..6ed908f5 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ConvexHull.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_CONVEXHULL(g.geometry) FROM Entity g" + * Returns convex hull geometry. + */ +class ST_ConvexHull extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_ConvexHull(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CurveToLine.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CurveToLine.php new file mode 100644 index 00000000..8b0c36f6 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CurveToLine.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_CURVETOLINE(g.geometry) FROM Entity g" + * Returns linear geometry. + */ +class ST_CurveToLine extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_CurveToLine(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Difference.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Difference.php new file mode 100644 index 00000000..e451874f --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Difference.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_DIFFERENCE(g1.geometry, g2.geometry) FROM Entity g1, Entity g2" + * Returns geometry representing the difference. + */ +class ST_Difference extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Difference(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Distance.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Distance.php new file mode 100644 index 00000000..145e2a65 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Distance.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_DISTANCE(g1.geometry, g2.geometry) FROM Entity g1, Entity g2" + * Returns numeric distance value. + */ +class ST_Distance extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Distance(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Envelope.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Envelope.php new file mode 100644 index 00000000..e3b5f0fd --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Envelope.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_ENVELOPE(g.geometry) FROM Entity g" + * Returns bounding box polygon. + */ +class ST_Envelope extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Envelope(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Extrude.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Extrude.php new file mode 100644 index 00000000..f7b966f2 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Extrude.php @@ -0,0 +1,33 @@ + + * + * @example Using it in DQL: "SELECT ST_EXTRUDE(g.geometry, 0, 0, 10) FROM Entity g" + * Returns 3D extruded geometry. + */ +class ST_Extrude extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Extrude(%s, %s, %s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('Literal'); + $this->addNodeMapping('Literal'); + $this->addNodeMapping('Literal'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force2D.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force2D.php new file mode 100644 index 00000000..5dc1e13c --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force2D.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_FORCE2D(g.geometry) FROM Entity g" + * Returns 2D geometry with Z and M coordinates removed. + */ +class ST_Force2D extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Force2D(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force3D.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force3D.php new file mode 100644 index 00000000..9c509377 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force3D.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_FORCE3D(g.geometry) FROM Entity g" + * Returns 3D geometry. + */ +class ST_Force3D extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Force3D(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force4D.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force4D.php new file mode 100644 index 00000000..f3b56529 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force4D.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_FORCE4D(g.geometry) FROM Entity g" + * Returns 4D geometry. + */ +class ST_Force4D extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Force4D(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_FrechetDistance.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_FrechetDistance.php new file mode 100644 index 00000000..9796baa1 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_FrechetDistance.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_FRECHETDISTANCE(g1.geometry, g2.geometry) FROM Entity g1, Entity g2" + * Returns numeric FrΓ©chet distance value. + */ +class ST_FrechetDistance extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_FrechetDistance(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_HausdorffDistance.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_HausdorffDistance.php new file mode 100644 index 00000000..2f8447f4 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_HausdorffDistance.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_HAUSDORFFDISTANCE(g1.geometry, g2.geometry) FROM Entity g1, Entity g2" + * Returns numeric Hausdorff distance value. + */ +class ST_HausdorffDistance extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_HausdorffDistance(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Intersection.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Intersection.php new file mode 100644 index 00000000..38f5064d --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Intersection.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_INTERSECTION(g1.geometry, g2.geometry) FROM Entity g1, Entity g2" + * Returns geometry representing the intersection. + */ +class ST_Intersection extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Intersection(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length.php new file mode 100644 index 00000000..28c619d2 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_LENGTH(g.geometry) FROM Entity g" + * Returns numeric length value. + */ +class ST_Length extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Length(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2D.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2D.php new file mode 100644 index 00000000..2bc2a9eb --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2D.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_LENGTH2D(g.geometry) FROM Entity g" + * Returns numeric 2D length value. + */ +class ST_Length2D extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Length2D(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineToCurve.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineToCurve.php new file mode 100644 index 00000000..76c90797 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineToCurve.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_LINETOCURVE(g.geometry) FROM Entity g" + * Returns curved geometry where possible. + */ +class ST_LineToCurve extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_LineToCurve(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_MaxDistance.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_MaxDistance.php new file mode 100644 index 00000000..c066ac4b --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_MaxDistance.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_MAXDISTANCE(g1.geometry, g2.geometry) FROM Entity g1, Entity g2" + * Returns numeric maximum distance value. + */ +class ST_MaxDistance extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_MaxDistance(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Perimeter.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Perimeter.php new file mode 100644 index 00000000..c060468b --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Perimeter.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_PERIMETER(g.geometry) FROM Entity g" + * Returns numeric perimeter value. + */ +class ST_Perimeter extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Perimeter(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Project.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Project.php new file mode 100644 index 00000000..9e6443b2 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Project.php @@ -0,0 +1,32 @@ + + * + * @example Using it in DQL: "SELECT ST_PROJECT(g.geometry, 1000, 45) FROM Entity g" + * Returns projected point. + */ +class ST_Project extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Project(%s, %s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('Literal'); + $this->addNodeMapping('Literal'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Reverse.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Reverse.php new file mode 100644 index 00000000..2a7f021d --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Reverse.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_REVERSE(g.geometry) FROM Entity g" + * Returns geometry with reversed point order. + */ +class ST_Reverse extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Reverse(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Rotate.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Rotate.php new file mode 100644 index 00000000..3f0f7f42 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Rotate.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_ROTATE(g.geometry, 1.5708) FROM Entity g" + * Returns rotated geometry. + */ +class ST_Rotate extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Rotate(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('Literal'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Scale.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Scale.php new file mode 100644 index 00000000..602705be --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Scale.php @@ -0,0 +1,32 @@ + + * + * @example Using it in DQL: "SELECT ST_SCALE(g.geometry, 2, 2) FROM Entity g" + * Returns scaled geometry. + */ +class ST_Scale extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Scale(%s, %s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('Literal'); + $this->addNodeMapping('Literal'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Simplify.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Simplify.php new file mode 100644 index 00000000..12c1a02f --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Simplify.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_SIMPLIFY(g.geometry, 0.5) FROM Entity g" + * Returns simplified geometry. + */ +class ST_Simplify extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Simplify(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('Literal'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyPreserveTopology.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyPreserveTopology.php new file mode 100644 index 00000000..9c4ecd6b --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyPreserveTopology.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_SIMPLIFYPRESERVETOPOLOGY(g.geometry, 0.5) FROM Entity g" + * Returns simplified geometry preserving topology. + */ +class ST_SimplifyPreserveTopology extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_SimplifyPreserveTopology(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('Literal'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyVW.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyVW.php new file mode 100644 index 00000000..1a226d60 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyVW.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_SIMPLIFYVW(g.geometry, 0.5) FROM Entity g" + * Returns simplified geometry using Visvalingam-Whyatt algorithm. + */ +class ST_SimplifyVW extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_SimplifyVW(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('Literal'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Split.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Split.php new file mode 100644 index 00000000..2375f47a --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Split.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_SPLIT(g.geometry, g.blade) FROM Entity g" + * Returns geometry collection representing the split result. + */ +class ST_Split extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Split(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Subdivide.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Subdivide.php new file mode 100644 index 00000000..1755e55d --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Subdivide.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_SUBDIVIDE(g.geometry, 256) FROM Entity g" + * Returns set of subdivided geometries. + */ +class ST_Subdivide extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Subdivide(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('Literal'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SymDifference.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SymDifference.php new file mode 100644 index 00000000..f2cc80ba --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SymDifference.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_SYMDIFFERENCE(g1.geometry, g2.geometry) FROM Entity g1, Entity g2" + * Returns geometry representing the symmetric difference. + */ +class ST_SymDifference extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_SymDifference(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Transform.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Transform.php new file mode 100644 index 00000000..560443df --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Transform.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_TRANSFORM(g.geometry, 4326) FROM Entity g" + * Returns transformed geometry in the specified SRID. + */ +class ST_Transform extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Transform(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('Literal'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Translate.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Translate.php new file mode 100644 index 00000000..daf5adc0 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Translate.php @@ -0,0 +1,32 @@ + + * + * @example Using it in DQL: "SELECT ST_TRANSLATE(g.geometry, 10, 20) FROM Entity g" + * Returns translated geometry. + */ +class ST_Translate extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Translate(%s, %s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('Literal'); + $this->addNodeMapping('Literal'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnaryUnion.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnaryUnion.php new file mode 100644 index 00000000..5d7547ec --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnaryUnion.php @@ -0,0 +1,30 @@ + + * + * @example Using it in DQL: "SELECT ST_UNARYUNION(g.geometry) FROM Entity g" + * Returns unioned geometry. + */ +class ST_UnaryUnion extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_UnaryUnion(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Union.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Union.php new file mode 100644 index 00000000..80e27fb2 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Union.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT ST_UNION(g1.geometry, g2.geometry) FROM Entity g1, Entity g2" + * Returns geometry representing the union. + */ +class ST_Union extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ST_Union(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} From 44f1f7fc08de4ffb82b32f92d83052d9c79de1de Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 2 Sep 2025 20:07:02 +0300 Subject: [PATCH 02/27] no message --- .../Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2D.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2D.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2D.php index 2bc2a9eb..093d37fd 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2D.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2D.php @@ -9,8 +9,7 @@ /** * Implementation of PostGIS ST_Length2D() function. * - * Returns the 2D length of the geometry if it is a LineString or MultiLineString. - * For areal geometries, the 2D perimeter is returned. + * Returns the 2D length of a LineString/MultiLineString or 2D perimeter of areal geometries. * Ignores Z coordinates. * * @see https://postgis.net/docs/ST_Length2D.html @@ -19,7 +18,7 @@ * @author Martin Georgiev * * @example Using it in DQL: "SELECT ST_LENGTH2D(g.geometry) FROM Entity g" - * Returns numeric 2D length value. + * Returns 2D length as numeric value. */ class ST_Length2D extends BaseFunction { From fd81371050219c79a68c54aa00526642840ce0c8 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 2 Sep 2025 21:34:51 +0300 Subject: [PATCH 03/27] no message --- .../Query/AST/Functions/PostGIS/ST_3DArea.php | 30 ----------------- .../AST/Functions/PostGIS/ST_Extrude.php | 33 ------------------- 2 files changed, 63 deletions(-) delete mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DArea.php delete mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Extrude.php diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DArea.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DArea.php deleted file mode 100644 index 6dd05b33..00000000 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DArea.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * @example Using it in DQL: "SELECT ST_3DAREA(g.geometry) FROM Entity g" - * Returns numeric 3D area value. - */ -class ST_3DArea extends BaseFunction -{ - protected function customizeFunction(): void - { - $this->setFunctionPrototype('ST_3DArea(%s)'); - $this->addNodeMapping('StringPrimary'); - } -} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Extrude.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Extrude.php deleted file mode 100644 index f7b966f2..00000000 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Extrude.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * @example Using it in DQL: "SELECT ST_EXTRUDE(g.geometry, 0, 0, 10) FROM Entity g" - * Returns 3D extruded geometry. - */ -class ST_Extrude extends BaseFunction -{ - protected function customizeFunction(): void - { - $this->setFunctionPrototype('ST_Extrude(%s, %s, %s, %s)'); - $this->addNodeMapping('StringPrimary'); - $this->addNodeMapping('Literal'); - $this->addNodeMapping('Literal'); - $this->addNodeMapping('Literal'); - } -} From 16258ad6c7c20b09f0d06ec3836a3f6d49c7a1a1 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 2 Sep 2025 22:01:08 +0300 Subject: [PATCH 04/27] add unit tests --- .../Functions/PostGIS/ST_3DDistanceTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_3DLengthTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_3DPerimeterTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_Area2DTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_AreaTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_AzimuthTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_BoundaryTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_BufferTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_CentroidTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_ClipByBox2DTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_CollectTest.php | 32 +++++++++++++++++++ .../PostGIS/ST_CollectionExtractTest.php | 32 +++++++++++++++++++ .../PostGIS/ST_CollectionHomogenizeTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_ConvexHullTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_CurveToLineTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_DifferenceTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_DistanceTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_EnvelopeTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_Force2DTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_Force3DTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_Force4DTest.php | 32 +++++++++++++++++++ .../PostGIS/ST_FrechetDistanceTest.php | 32 +++++++++++++++++++ .../PostGIS/ST_HausdorffDistanceTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_IntersectionTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_Length2DTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_LengthTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_LineToCurveTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_MaxDistanceTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_PerimeterTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_ProjectTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_ReverseTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_RotateTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_ScaleTest.php | 32 +++++++++++++++++++ .../ST_SimplifyPreserveTopologyTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_SimplifyTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_SimplifyVWTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_SplitTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_SubdivideTest.php | 32 +++++++++++++++++++ .../PostGIS/ST_SymDifferenceTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_TransformTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_TranslateTest.php | 32 +++++++++++++++++++ .../Functions/PostGIS/ST_UnaryUnionTest.php | 32 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_UnionTest.php | 32 +++++++++++++++++++ 43 files changed, 1376 insertions(+) create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DDistanceTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DLengthTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DPerimeterTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2DTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AreaTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AzimuthTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BoundaryTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BufferTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CentroidTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2DTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionExtractTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionHomogenizeTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ConvexHullTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CurveToLineTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DifferenceTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DistanceTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_EnvelopeTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force2DTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force3DTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force4DTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_FrechetDistanceTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_HausdorffDistanceTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_IntersectionTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2DTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LengthTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineToCurveTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_MaxDistanceTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_PerimeterTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ReverseTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_RotateTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ScaleTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyPreserveTopologyTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyVWTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SplitTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SubdivideTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SymDifferenceTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TransformTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TranslateTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnaryUnionTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnionTest.php diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DDistanceTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DDistanceTest.php new file mode 100644 index 00000000..f5bebf40 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DDistanceTest.php @@ -0,0 +1,32 @@ + ST_3DDistance::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_3DDistance(c0_.geometry1, c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_3DDISTANCE(g.geometry1, g.geometry2) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DLengthTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DLengthTest.php new file mode 100644 index 00000000..7978e874 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DLengthTest.php @@ -0,0 +1,32 @@ + ST_3DLength::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_3DLength(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_3DLENGTH(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DPerimeterTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DPerimeterTest.php new file mode 100644 index 00000000..f7aaa8a9 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DPerimeterTest.php @@ -0,0 +1,32 @@ + ST_3DPerimeter::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_3DPerimeter(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_3DPERIMETER(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2DTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2DTest.php new file mode 100644 index 00000000..e4be8c49 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2DTest.php @@ -0,0 +1,32 @@ + ST_Area2D::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Area2D(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_AREA2D(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AreaTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AreaTest.php new file mode 100644 index 00000000..20195a1a --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AreaTest.php @@ -0,0 +1,32 @@ + ST_Area::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Area(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_AREA(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AzimuthTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AzimuthTest.php new file mode 100644 index 00000000..4bb01665 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AzimuthTest.php @@ -0,0 +1,32 @@ + ST_Azimuth::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Azimuth(c0_.geometry1, c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_AZIMUTH(g.geometry1, g.geometry2) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BoundaryTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BoundaryTest.php new file mode 100644 index 00000000..bb4171fb --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BoundaryTest.php @@ -0,0 +1,32 @@ + ST_Boundary::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Boundary(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_BOUNDARY(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BufferTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BufferTest.php new file mode 100644 index 00000000..357e15e9 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BufferTest.php @@ -0,0 +1,32 @@ + ST_Buffer::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Buffer(c0_.geometry1, 10) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_BUFFER(g.geometry1, 10) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CentroidTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CentroidTest.php new file mode 100644 index 00000000..5eff870c --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CentroidTest.php @@ -0,0 +1,32 @@ + ST_Centroid::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Centroid(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_CENTROID(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2DTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2DTest.php new file mode 100644 index 00000000..29827af5 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2DTest.php @@ -0,0 +1,32 @@ + ST_ClipByBox2D::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + "SELECT ST_ClipByBox2D(c0_.geometry1, 'BOX(0 0, 10 10)') AS sclr_0 FROM ContainsGeometries c0_", + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_CLIPBYBOX2D(g.geometry1, \'BOX(0 0, 10 10)\') FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectTest.php new file mode 100644 index 00000000..af9a94df --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectTest.php @@ -0,0 +1,32 @@ + ST_Collect::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Collect(c0_.geometry1, c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_COLLECT(g.geometry1, g.geometry2) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionExtractTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionExtractTest.php new file mode 100644 index 00000000..0ed948f8 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionExtractTest.php @@ -0,0 +1,32 @@ + ST_CollectionExtract::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_CollectionExtract(c0_.geometry1, 1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_COLLECTIONEXTRACT(g.geometry1, 1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionHomogenizeTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionHomogenizeTest.php new file mode 100644 index 00000000..3e7e2fb4 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectionHomogenizeTest.php @@ -0,0 +1,32 @@ + ST_CollectionHomogenize::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_CollectionHomogenize(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_COLLECTIONHOMOGENIZE(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ConvexHullTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ConvexHullTest.php new file mode 100644 index 00000000..1de5f6b1 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ConvexHullTest.php @@ -0,0 +1,32 @@ + ST_ConvexHull::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_ConvexHull(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_CONVEXHULL(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CurveToLineTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CurveToLineTest.php new file mode 100644 index 00000000..d8296400 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CurveToLineTest.php @@ -0,0 +1,32 @@ + ST_CurveToLine::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_CurveToLine(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_CURVETOLINE(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DifferenceTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DifferenceTest.php new file mode 100644 index 00000000..7db47096 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DifferenceTest.php @@ -0,0 +1,32 @@ + ST_Difference::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Difference(c0_.geometry1, c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_DIFFERENCE(g.geometry1, g.geometry2) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DistanceTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DistanceTest.php new file mode 100644 index 00000000..d1e58c12 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DistanceTest.php @@ -0,0 +1,32 @@ + ST_Distance::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Distance(c0_.geometry1, c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_DISTANCE(g.geometry1, g.geometry2) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_EnvelopeTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_EnvelopeTest.php new file mode 100644 index 00000000..36f5928e --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_EnvelopeTest.php @@ -0,0 +1,32 @@ + ST_Envelope::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Envelope(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_ENVELOPE(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force2DTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force2DTest.php new file mode 100644 index 00000000..77ff5f8b --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force2DTest.php @@ -0,0 +1,32 @@ + ST_Force2D::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Force2D(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_FORCE2D(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force3DTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force3DTest.php new file mode 100644 index 00000000..bb52ae3c --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force3DTest.php @@ -0,0 +1,32 @@ + ST_Force3D::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Force3D(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_FORCE3D(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force4DTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force4DTest.php new file mode 100644 index 00000000..fb61ad9c --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force4DTest.php @@ -0,0 +1,32 @@ + ST_Force4D::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Force4D(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_FORCE4D(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_FrechetDistanceTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_FrechetDistanceTest.php new file mode 100644 index 00000000..1a65d40c --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_FrechetDistanceTest.php @@ -0,0 +1,32 @@ + ST_FrechetDistance::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_FrechetDistance(c0_.geometry1, c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_FRECHETDISTANCE(g.geometry1, g.geometry2) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_HausdorffDistanceTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_HausdorffDistanceTest.php new file mode 100644 index 00000000..60f97fb9 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_HausdorffDistanceTest.php @@ -0,0 +1,32 @@ + ST_HausdorffDistance::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_HausdorffDistance(c0_.geometry1, c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_HAUSDORFFDISTANCE(g.geometry1, g.geometry2) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_IntersectionTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_IntersectionTest.php new file mode 100644 index 00000000..7a26d28b --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_IntersectionTest.php @@ -0,0 +1,32 @@ + ST_Intersection::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Intersection(c0_.geometry1, c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_INTERSECTION(g.geometry1, g.geometry2) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2DTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2DTest.php new file mode 100644 index 00000000..012da4e5 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2DTest.php @@ -0,0 +1,32 @@ + ST_Length2D::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Length2D(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_LENGTH2D(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LengthTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LengthTest.php new file mode 100644 index 00000000..0a0a1b20 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LengthTest.php @@ -0,0 +1,32 @@ + ST_Length::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Length(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_LENGTH(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineToCurveTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineToCurveTest.php new file mode 100644 index 00000000..edd5b006 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineToCurveTest.php @@ -0,0 +1,32 @@ + ST_LineToCurve::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_LineToCurve(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_LINETOCURVE(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_MaxDistanceTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_MaxDistanceTest.php new file mode 100644 index 00000000..ff074f9c --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_MaxDistanceTest.php @@ -0,0 +1,32 @@ + ST_MaxDistance::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_MaxDistance(c0_.geometry1, c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_MAXDISTANCE(g.geometry1, g.geometry2) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_PerimeterTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_PerimeterTest.php new file mode 100644 index 00000000..156470ca --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_PerimeterTest.php @@ -0,0 +1,32 @@ + ST_Perimeter::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Perimeter(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_PERIMETER(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php new file mode 100644 index 00000000..3067be2b --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php @@ -0,0 +1,32 @@ + ST_Project::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Project(c0_.geometry1, 1000, 0.785398) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_PROJECT(g.geometry1, 1000, 0.785398) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ReverseTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ReverseTest.php new file mode 100644 index 00000000..880ddcb4 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ReverseTest.php @@ -0,0 +1,32 @@ + ST_Reverse::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Reverse(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_REVERSE(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_RotateTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_RotateTest.php new file mode 100644 index 00000000..98dc0288 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_RotateTest.php @@ -0,0 +1,32 @@ + ST_Rotate::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Rotate(c0_.geometry1, 0.785398) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_ROTATE(g.geometry1, 0.785398) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ScaleTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ScaleTest.php new file mode 100644 index 00000000..2628edc1 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ScaleTest.php @@ -0,0 +1,32 @@ + ST_Scale::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Scale(c0_.geometry1, 2, 2) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_SCALE(g.geometry1, 2, 2) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyPreserveTopologyTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyPreserveTopologyTest.php new file mode 100644 index 00000000..7d8236f1 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyPreserveTopologyTest.php @@ -0,0 +1,32 @@ + ST_SimplifyPreserveTopology::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_SimplifyPreserveTopology(c0_.geometry1, 0.5) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_SIMPLIFYPRESERVETOPOLOGY(g.geometry1, 0.5) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyTest.php new file mode 100644 index 00000000..74e7787e --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyTest.php @@ -0,0 +1,32 @@ + ST_Simplify::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Simplify(c0_.geometry1, 0.5) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_SIMPLIFY(g.geometry1, 0.5) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyVWTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyVWTest.php new file mode 100644 index 00000000..d9a3a26c --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyVWTest.php @@ -0,0 +1,32 @@ + ST_SimplifyVW::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_SimplifyVW(c0_.geometry1, 0.5) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_SIMPLIFYVW(g.geometry1, 0.5) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SplitTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SplitTest.php new file mode 100644 index 00000000..720fd6c4 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SplitTest.php @@ -0,0 +1,32 @@ + ST_Split::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Split(c0_.geometry1, c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_SPLIT(g.geometry1, g.geometry2) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SubdivideTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SubdivideTest.php new file mode 100644 index 00000000..1378db34 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SubdivideTest.php @@ -0,0 +1,32 @@ + ST_Subdivide::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Subdivide(c0_.geometry1, 256) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_SUBDIVIDE(g.geometry1, 256) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SymDifferenceTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SymDifferenceTest.php new file mode 100644 index 00000000..3d8ca722 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SymDifferenceTest.php @@ -0,0 +1,32 @@ + ST_SymDifference::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_SymDifference(c0_.geometry1, c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_SYMDIFFERENCE(g.geometry1, g.geometry2) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TransformTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TransformTest.php new file mode 100644 index 00000000..efa2aa4a --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TransformTest.php @@ -0,0 +1,32 @@ + ST_Transform::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Transform(c0_.geometry1, 4326) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_TRANSFORM(g.geometry1, 4326) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TranslateTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TranslateTest.php new file mode 100644 index 00000000..0e600b36 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TranslateTest.php @@ -0,0 +1,32 @@ + ST_Translate::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Translate(c0_.geometry1, 1, 1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_TRANSLATE(g.geometry1, 1, 1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnaryUnionTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnaryUnionTest.php new file mode 100644 index 00000000..366e8bfd --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnaryUnionTest.php @@ -0,0 +1,32 @@ + ST_UnaryUnion::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_UnaryUnion(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_UNARYUNION(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnionTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnionTest.php new file mode 100644 index 00000000..0fa47397 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnionTest.php @@ -0,0 +1,32 @@ + ST_Union::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'SELECT ST_Union(c0_.geometry1, c0_.geometry2) AS sclr_0 FROM ContainsGeometries c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'SELECT ST_UNION(g.geometry1, g.geometry2) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + ]; + } +} From fc6c285f5d5190f1d4c81f82803eef417a9b7e9a Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 01:30:08 +0300 Subject: [PATCH 05/27] no message --- .../AST/Functions/PostGIS/ST_ClipByBox2D.php | 4 ++-- .../Query/AST/Functions/PostGIS/ST_Project.php | 6 +++--- .../PostGIS/ST_LineCrossingDirectionTest.php | 2 +- .../Functions/PostGIS/ST_RelateMatchTest.php | 2 +- .../PostGIS/SpatialOperatorTestCase.php | 18 ++++++++++++------ .../Functions/PostGIS/ST_ClipByBox2DTest.php | 6 ++++-- .../AST/Functions/PostGIS/ST_ProjectTest.php | 6 ++++-- 7 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2D.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2D.php index fe32fd37..b7054674 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2D.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2D.php @@ -17,7 +17,7 @@ * * @author Martin Georgiev * - * @example Using it in DQL: "SELECT ST_CLIPBYBOX2D(g.geometry, 'BOX(0 0, 100 100)') FROM Entity g" + * @example Using it in DQL: "SELECT ST_CLIPBYBOX2D(g.geometry, ST_Envelope(g.geometry)) FROM Entity g" * Returns clipped geometry. */ class ST_ClipByBox2D extends BaseFunction @@ -26,6 +26,6 @@ protected function customizeFunction(): void { $this->setFunctionPrototype('ST_ClipByBox2D(%s, %s)'); $this->addNodeMapping('StringPrimary'); - $this->addNodeMapping('Literal'); + $this->addNodeMapping('StringPrimary'); } } diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Project.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Project.php index 9e6443b2..6438452e 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Project.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Project.php @@ -17,7 +17,7 @@ * * @author Martin Georgiev * - * @example Using it in DQL: "SELECT ST_PROJECT(g.geometry, 1000, 45) FROM Entity g" + * @example Using it in DQL: "SELECT ST_PROJECT(g.geometry, :distance, :azimuth) FROM Entity g" * Returns projected point. */ class ST_Project extends BaseFunction @@ -26,7 +26,7 @@ protected function customizeFunction(): void { $this->setFunctionPrototype('ST_Project(%s, %s, %s)'); $this->addNodeMapping('StringPrimary'); - $this->addNodeMapping('Literal'); - $this->addNodeMapping('Literal'); + $this->addNodeMapping('SimpleArithmeticExpression'); + $this->addNodeMapping('SimpleArithmeticExpression'); } } diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineCrossingDirectionTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineCrossingDirectionTest.php index 238eccb5..4450a55f 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineCrossingDirectionTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineCrossingDirectionTest.php @@ -79,7 +79,7 @@ public function can_filter_crossing_lines_in_where_clause(): void WHERE ST_LINECROSSINGDIRECTION(\'LINESTRING(0 0, 2 2)\', \'LINESTRING(0 2, 2 0)\') = 1'; $result = $this->executeDqlQuery($dql); - $this->assertGreaterThan(0, \count($result)); + $this->assertCount(9, $result); $this->assertEquals(1, $result[0]['result']); } } diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_RelateMatchTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_RelateMatchTest.php index 1772ae83..94808bcc 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_RelateMatchTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_RelateMatchTest.php @@ -70,7 +70,7 @@ public function can_filter_geometries_by_spatial_relationship_pattern(): void WHERE ST_RELATEMATCH(ST_RELATE(g.geometry1, g.geometry2), \'FF0FFF0F2\') = TRUE'; $result = $this->executeDqlQuery($dql); - $this->assertGreaterThan(0, \count($result)); + $this->assertCount(1, $result); $this->assertTrue($result[0]['matches_disjoint_points']); $this->assertEquals(1, $result[0]['id']); } diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/SpatialOperatorTestCase.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/SpatialOperatorTestCase.php index 56312e67..90c8b4f6 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/SpatialOperatorTestCase.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/SpatialOperatorTestCase.php @@ -44,20 +44,20 @@ protected function insertTestDataForSpatialFixture(): void // Insert test data with various spatial relationships $testData = [ [ - 'geometry1' => 'POINT(0 0)', - 'geometry2' => 'POINT(1 1)', + 'geometry1' => 'SRID=4326;POINT(0 0)', + 'geometry2' => 'SRID=4326;POINT(1 1)', 'geography1' => 'SRID=4326;POINT(-9.1393 38.7223)', 'geography2' => 'SRID=4326;POINT(-0.1276 51.5074)', ], [ - 'geometry1' => 'POLYGON((0 0, 0 4, 4 4, 4 0, 0 0))', - 'geometry2' => 'POLYGON((1 1, 1 3, 3 3, 3 1, 1 1))', + 'geometry1' => 'SRID=4326;POLYGON((0 0, 0 4, 4 4, 4 0, 0 0))', + 'geometry2' => 'SRID=4326;POLYGON((1 1, 1 3, 3 3, 3 1, 1 1))', 'geography1' => 'SRID=4326;POLYGON((-9.2 38.6, -9.2 38.8, -9.0 38.8, -9.0 38.6, -9.2 38.6))', // Lisbon area 'geography2' => 'SRID=4326;POLYGON((-0.2 51.4, -0.2 51.6, 0.0 51.6, 0.0 51.4, -0.2 51.4))', // London area ], [ - 'geometry1' => 'LINESTRING(0 0, 1 1, 2 2)', - 'geometry2' => 'LINESTRING(3 3, 4 4, 5 5)', + 'geometry1' => 'SRID=4326;LINESTRING(0 0, 1 1, 2 2)', + 'geometry2' => 'SRID=4326;LINESTRING(3 3, 4 4, 5 5)', 'geography1' => 'SRID=4326;LINESTRING(-9.1393 38.7223, -9.1293 38.7323)', // Lisbon area 'geography2' => 'SRID=4326;LINESTRING(-0.1276 51.5074, -0.1176 51.5174)', // London area ], @@ -91,6 +91,12 @@ protected function insertTestDataForSpatialFixture(): void 'geography1' => 'SRID=4326;POLYGON((-9.2 38.6, -9.2 38.8, -9.0 38.8, -9.0 38.6, -9.2 38.6))', // Lisbon area 'geography2' => 'SRID=4326;POLYGON((-0.2 51.4, -0.2 51.6, 0.0 51.6, 0.0 51.4, -0.2 51.4))', // London area ], + [ + 'geometry1' => 'LINESTRING(0 0, 4 4)', + 'geometry2' => 'POINT(2 2)', + 'geography1' => 'SRID=4326;LINESTRING(-9.1393 38.7223, -9.1293 38.7323)', // Lisbon area + 'geography2' => 'SRID=4326;LINESTRING(-0.1276 51.5074, -0.1176 51.5174)', // London area + ], ]; foreach ($testData as $row) { diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2DTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2DTest.php index 29827af5..9f4e0541 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2DTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2DTest.php @@ -19,14 +19,16 @@ protected function getStringFunctions(): array protected function getExpectedSqlStatements(): array { return [ - "SELECT ST_ClipByBox2D(c0_.geometry1, 'BOX(0 0, 10 10)') AS sclr_0 FROM ContainsGeometries c0_", + 'clips geometry with literal box' => "SELECT ST_ClipByBox2D(c0_.geometry1, 'BOX(0 0, 4 4)') AS sclr_0 FROM ContainsGeometries c0_", + 'clips geometry with parameter placeholder' => 'SELECT ST_ClipByBox2D(c0_.geometry1, ?) AS sclr_0 FROM ContainsGeometries c0_', ]; } protected function getDqlStatements(): array { return [ - 'SELECT ST_CLIPBYBOX2D(g.geometry1, \'BOX(0 0, 10 10)\') FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + 'clips geometry with literal box' => 'SELECT ST_CLIPBYBOX2D(g.geometry1, \'BOX(0 0, 4 4)\') FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + 'clips geometry with parameter placeholder' => 'SELECT ST_CLIPBYBOX2D(g.geometry1, :box_param) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', ]; } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php index 3067be2b..6fe3ee64 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php @@ -19,14 +19,16 @@ protected function getStringFunctions(): array protected function getExpectedSqlStatements(): array { return [ - 'SELECT ST_Project(c0_.geometry1, 1000, 0.785398) AS sclr_0 FROM ContainsGeometries c0_', + 'projects with literal values' => 'SELECT ST_Project(c0_.geometry1, 1000, 0.785398) AS sclr_0 FROM ContainsGeometries c0_', + 'projects with arithmetic expressions' => 'SELECT ST_Project(c0_.geometry1, 1000 * 2, 0.785398 + 0.1) AS sclr_0 FROM ContainsGeometries c0_', ]; } protected function getDqlStatements(): array { return [ - 'SELECT ST_PROJECT(g.geometry1, 1000, 0.785398) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + 'projects with literal values' => 'SELECT ST_PROJECT(g.geometry1, 1000, 0.785398) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', + 'projects with arithmetic expressions' => 'SELECT ST_PROJECT(g.geometry1, 1000 * 2, 0.785398 + 0.1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', ]; } } From 92b3fa3330e9d3035e9502e05f9436def9800844 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 01:43:12 +0300 Subject: [PATCH 06/27] no message --- .../Query/AST/Functions/PostGIS/ST_Area2D.php | 31 ------------------ .../AST/Functions/PostGIS/ST_Area2DTest.php | 32 ------------------- 2 files changed, 63 deletions(-) delete mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2D.php delete mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2DTest.php diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2D.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2D.php deleted file mode 100644 index 81e85a85..00000000 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2D.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * @example Using it in DQL: "SELECT ST_AREA2D(g.geometry) FROM Entity g" - * Returns numeric 2D area value. - */ -class ST_Area2D extends BaseFunction -{ - protected function customizeFunction(): void - { - $this->setFunctionPrototype('ST_Area2D(%s)'); - $this->addNodeMapping('StringPrimary'); - } -} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2DTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2DTest.php deleted file mode 100644 index e4be8c49..00000000 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Area2DTest.php +++ /dev/null @@ -1,32 +0,0 @@ - ST_Area2D::class, - ]; - } - - protected function getExpectedSqlStatements(): array - { - return [ - 'SELECT ST_Area2D(c0_.geometry1) AS sclr_0 FROM ContainsGeometries c0_', - ]; - } - - protected function getDqlStatements(): array - { - return [ - 'SELECT ST_AREA2D(g.geometry1) FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g', - ]; - } -} From 39e0412911e4ec43cd6f3625670ef06a5981b790 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 01:56:28 +0300 Subject: [PATCH 07/27] no message --- .../ORM/Query/AST/Functions/PostGIS/StrictlyBelowTest.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/StrictlyBelowTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/StrictlyBelowTest.php index 427b0828..0313f0f0 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/StrictlyBelowTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/StrictlyBelowTest.php @@ -19,7 +19,6 @@ protected function getStringFunctions(): array #[Test] public function strictly_below_returns_false_with_overlapping_polygons(): void { - // Overlapping polygons are not strictly below each other $dql = 'SELECT STRICTLY_BELOW(g.geometry1, g.geometry2) as result FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g WHERE g.id = 2'; @@ -31,7 +30,6 @@ public function strictly_below_returns_false_with_overlapping_polygons(): void #[Test] public function strictly_below_returns_true_when_geometry_is_lower(): void { - // POINT(0 0) should be strictly below POINT(1 1) $dql = 'SELECT STRICTLY_BELOW(g.geometry1, g.geometry2) as result FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g WHERE g.id = 1'; @@ -43,7 +41,6 @@ public function strictly_below_returns_true_when_geometry_is_lower(): void #[Test] public function strictly_below_returns_true_with_linestrings(): void { - // First linestring should be strictly below second linestring $dql = 'SELECT STRICTLY_BELOW(g.geometry1, g.geometry2) as result FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g WHERE g.id = 3'; @@ -55,7 +52,6 @@ public function strictly_below_returns_true_with_linestrings(): void #[Test] public function strictly_below_returns_false_with_identical_geometries(): void { - // Identical geometries are not strictly below each other $dql = 'SELECT STRICTLY_BELOW(g.geometry1, g.geometry1) as result FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g WHERE g.id = 1'; From f3c30f0a502d2ddcb3317f784fc8d3ee44f582a4 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 02:02:49 +0300 Subject: [PATCH 08/27] integration tests --- .../AST/Functions/PostGIS/ST_DistanceTest.php | 51 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_LengthTest.php | 51 +++++++++++++++++++ .../Functions/PostGIS/ST_MaxDistanceTest.php | 51 +++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DistanceTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LengthTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_MaxDistanceTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DistanceTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DistanceTest.php new file mode 100644 index 00000000..97f47004 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DistanceTest.php @@ -0,0 +1,51 @@ + ST_Distance::class, + ]; + } + + #[Test] + public function returns_distance_between_points(): void + { + $dql = 'SELECT ST_DISTANCE(g.geometry1, g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(1.4142135623730951, $result[0]['result'], 0.0000000000000001, 'Distance between POINT(0 0) and POINT(1 1) = √2'); + } + + #[Test] + public function returns_zero_for_identical_geometries(): void + { + $dql = 'SELECT ST_DISTANCE(g.geometry1, g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result']); + } + + #[Test] + public function returns_zero_for_distance_between_overlapping_polygons(): void + { + $dql = 'SELECT ST_DISTANCE(g.geometry1, g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LengthTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LengthTest.php new file mode 100644 index 00000000..b8777c86 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LengthTest.php @@ -0,0 +1,51 @@ + ST_Length::class, + ]; + } + + #[Test] + public function returns_length_for_linestring(): void + { + $dql = 'SELECT ST_LENGTH(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.0000000000000001, 'Length of LINESTRING(0 0, 1 1, 2 2) = √2 + √2 = 2√2'); + } + + #[Test] + public function returns_perimeter_for_polygon(): void + { + $dql = 'SELECT ST_LENGTH(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result']); + } + + #[Test] + public function returns_zero_for_point_as_it_has_no_length(): void + { + $dql = 'SELECT ST_LENGTH(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_MaxDistanceTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_MaxDistanceTest.php new file mode 100644 index 00000000..fad02684 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_MaxDistanceTest.php @@ -0,0 +1,51 @@ + ST_MaxDistance::class, + ]; + } + + #[Test] + public function returns_maximum_distance_between_points(): void + { + $dql = 'SELECT ST_MAXDISTANCE(g.geometry1, g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(1.4142135623730951, $result[0]['result'], 0.0000000000000001, 'Maximum distance between POINT(0 0) and POINT(1 1) = √2'); + } + + #[Test] + public function returns_zero_for_identical_geometries(): void + { + $dql = 'SELECT ST_MAXDISTANCE(g.geometry1, g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result']); + } + + #[Test] + public function returns_maximum_distance_between_polygons(): void + { + $dql = 'SELECT ST_MAXDISTANCE(g.geometry1, g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(4.242640687119285, $result[0]['result'], 0.0000000000000001, 'Maximum distance between polygon corners (0,0) to (3,3) = √18'); + } +} From 380578077fd552e853e8494cf59979e1ece84e4e Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 02:10:42 +0300 Subject: [PATCH 09/27] no message --- .../AST/Functions/PostGIS/ST_AreaTest.php | 51 ++++++++++++++++++ .../AST/Functions/PostGIS/ST_BufferTest.php | 53 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AreaTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BufferTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AreaTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AreaTest.php new file mode 100644 index 00000000..7718c59b --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AreaTest.php @@ -0,0 +1,51 @@ + ST_Area::class, + ]; + } + + #[Test] + public function returns_area_for_polygon(): void + { + $dql = 'SELECT ST_AREA(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result']); + } + + #[Test] + public function returns_zero_for_linestring(): void + { + $dql = 'SELECT ST_AREA(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result']); + } + + #[Test] + public function returns_zero_for_point(): void + { + $dql = 'SELECT ST_AREA(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BufferTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BufferTest.php new file mode 100644 index 00000000..b3091368 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BufferTest.php @@ -0,0 +1,53 @@ + ST_Area::class, + 'ST_BUFFER' => ST_Buffer::class, + ]; + } + + #[Test] + public function returns_buffered_point(): void + { + $dql = 'SELECT ST_AREA(ST_BUFFER(g.geometry1, 1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(3.121445152258052, $result[0]['result'], 0.0000000000000001, 'Buffer of radius 1 around a point shall create a polygon approximation of a circle'); + } + + #[Test] + public function returns_buffered_polygon(): void + { + $dql = 'SELECT ST_AREA(ST_BUFFER(g.geometry1, 0.5)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(24.780361288064512, $result[0]['result'], 0.0000000000000001, 'Buffer of 0.5 around 4x4 polygon increases area from 16 to approximately 24.78'); + } + + #[Test] + public function returns_buffered_linestring(): void + { + $dql = 'SELECT ST_AREA(ST_BUFFER(g.geometry1, 0.2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(1.2562286559887985, $result[0]['result'], 0.0000000000000001, 'Buffer of 0.2 around LINESTRING(0 0, 1 1, 2 2) creates a polygon with specific area'); + } +} From f4aa12a7bd0c9152b8035df5936651f365c3526d Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 13:26:53 +0300 Subject: [PATCH 10/27] no message --- .../Functions/PostGIS/ST_3DDistanceTest.php | 51 +++++++++++++ .../AST/Functions/PostGIS/ST_AzimuthTest.php | 63 ++++++++++++++++ .../AST/Functions/PostGIS/ST_LengthTest.php | 24 +++++- .../Functions/PostGIS/ST_PerimeterTest.php | 73 +++++++++++++++++++ .../PostGIS/SpatialOperatorTestCase.php | 46 ++++++------ 5 files changed, 235 insertions(+), 22 deletions(-) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DDistanceTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AzimuthTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_PerimeterTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DDistanceTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DDistanceTest.php new file mode 100644 index 00000000..d71f0cd4 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DDistanceTest.php @@ -0,0 +1,51 @@ + ST_3DDistance::class, + ]; + } + + #[Test] + public function returns_3d_distance_between_points(): void + { + $dql = 'SELECT ST_3DDISTANCE(g.geometry1, g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(1.4142135623730951, $result[0]['result'], 0.0000000000000001); + } + + #[Test] + public function returns_3d_distance_between_polygons(): void + { + $dql = 'SELECT ST_3DDISTANCE(g.geometry1, g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result']); + } + + #[Test] + public function returns_3d_distance_between_linestrings(): void + { + $dql = 'SELECT ST_3DDISTANCE(g.geometry1, g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(1.4142135623730951, $result[0]['result'], 0.0000000000000001); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AzimuthTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AzimuthTest.php new file mode 100644 index 00000000..91125d30 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_AzimuthTest.php @@ -0,0 +1,63 @@ + ST_Azimuth::class, + ]; + } + + #[Test] + public function returns_azimuth_between_two_known_points(): void + { + $dql = 'SELECT ST_AZIMUTH(g.geometry1, g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(0.7853981633974483, $result[0]['result'], 0.0000000000000001); + } + + #[Test] + public function returns_null_for_identical_points(): void + { + $dql = 'SELECT ST_AZIMUTH(g.geometry1, g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertNull($result[0]['result'], 'PostGIS behavior expects that azimuth is undefined for identical points'); + } + + #[Test] + public function returns_valid_azimuth_range(): void + { + $dql = 'SELECT ST_AZIMUTH(g.geometry1, g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertGreaterThanOrEqual(0, $result[0]['result']); + $this->assertLessThan(2 * M_PI, $result[0]['result']); + } + + #[Test] + public function returns_pi_for_south_direction(): void + { + $dql = 'SELECT ST_AZIMUTH(g.geometry2, g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(3.9269908169872423, $result[0]['result'], 0.0000000000000001); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LengthTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LengthTest.php index b8777c86..0d40c6c9 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LengthTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LengthTest.php @@ -35,7 +35,7 @@ public function returns_perimeter_for_polygon(): void WHERE g.id = 2'; $result = $this->executeDqlQuery($dql); - $this->assertEquals(16, $result[0]['result']); + $this->assertEquals(0, $result[0]['result'], 'PostGIS behavior expects length of 0 for polygons in geographic coordinate systems'); } #[Test] @@ -48,4 +48,26 @@ public function returns_zero_for_point_as_it_has_no_length(): void $result = $this->executeDqlQuery($dql); $this->assertEquals(0, $result[0]['result']); } + + #[Test] + public function returns_actual_length_for_projected_coordinate_system_linestring(): void + { + $dql = 'SELECT ST_LENGTH(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 10'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(2000, $result[0]['result']); + } + + #[Test] + public function returns_zero_for_projected_coordinate_system_polygon(): void + { + $dql = 'SELECT ST_LENGTH(g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 10'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result'], 'PostGIS design mandates that ST_Length is for linear geometries only, so this is not expected to compute a length'); + } } diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_PerimeterTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_PerimeterTest.php new file mode 100644 index 00000000..e944923f --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_PerimeterTest.php @@ -0,0 +1,73 @@ + ST_Perimeter::class, + ]; + } + + #[Test] + public function returns_perimeter_for_polygon(): void + { + $dql = 'SELECT ST_PERIMETER(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result']); + } + + #[Test] + public function returns_perimeter_for_smaller_polygon(): void + { + $dql = 'SELECT ST_PERIMETER(g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(8, $result[0]['result']); + } + + #[Test] + public function returns_zero_for_point(): void + { + $dql = 'SELECT ST_PERIMETER(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result']); + } + + #[Test] + public function returns_zero_for_linestring(): void + { + $dql = 'SELECT ST_PERIMETER(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result'], 'PostGIS behavior is that perimeter is only defined for areal geometries'); + } + + #[Test] + public function returns_perimeter_for_non_srid_polygon(): void + { + $dql = 'SELECT ST_PERIMETER(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 4'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(8, $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/SpatialOperatorTestCase.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/SpatialOperatorTestCase.php index 90c8b4f6..e4d9ad4c 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/SpatialOperatorTestCase.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/SpatialOperatorTestCase.php @@ -8,6 +8,8 @@ abstract class SpatialOperatorTestCase extends BaseTestCase { + private const TABLE_NAME = 'containsgeometries'; + protected function setUp(): void { parent::setUp(); @@ -17,12 +19,10 @@ protected function setUp(): void protected function createTestTableForSpatialFixture(): void { - $tableName = 'containsgeometries'; - $this->createTestSchema(); - $this->dropTestTableIfItExists($tableName); + $this->dropTestTableIfItExists(self::TABLE_NAME); - $fullTableName = \sprintf('%s.%s', self::DATABASE_SCHEMA, $tableName); + $fullTableName = \sprintf('%s.%s', self::DATABASE_SCHEMA, self::TABLE_NAME); $sql = \sprintf(' CREATE TABLE %s ( id SERIAL PRIMARY KEY, @@ -38,67 +38,71 @@ protected function createTestTableForSpatialFixture(): void protected function insertTestDataForSpatialFixture(): void { - $tableName = 'containsgeometries'; - $fullTableName = \sprintf('%s.%s', self::DATABASE_SCHEMA, $tableName); - // Insert test data with various spatial relationships $testData = [ - [ + 'id=1' => [ 'geometry1' => 'SRID=4326;POINT(0 0)', 'geometry2' => 'SRID=4326;POINT(1 1)', 'geography1' => 'SRID=4326;POINT(-9.1393 38.7223)', 'geography2' => 'SRID=4326;POINT(-0.1276 51.5074)', ], - [ + 'id=2' => [ 'geometry1' => 'SRID=4326;POLYGON((0 0, 0 4, 4 4, 4 0, 0 0))', 'geometry2' => 'SRID=4326;POLYGON((1 1, 1 3, 3 3, 3 1, 1 1))', 'geography1' => 'SRID=4326;POLYGON((-9.2 38.6, -9.2 38.8, -9.0 38.8, -9.0 38.6, -9.2 38.6))', // Lisbon area - 'geography2' => 'SRID=4326;POLYGON((-0.2 51.4, -0.2 51.6, 0.0 51.6, 0.0 51.4, -0.2 51.4))', // London area + 'geography2' => 'SRID=4326;POLYGON((-0.2 51.4, -0.2 51.6, 0.0 51.6, 0.0 51.4, -0.2 51.4))', // London area ], - [ + 'id=3' => [ 'geometry1' => 'SRID=4326;LINESTRING(0 0, 1 1, 2 2)', 'geometry2' => 'SRID=4326;LINESTRING(3 3, 4 4, 5 5)', 'geography1' => 'SRID=4326;LINESTRING(-9.1393 38.7223, -9.1293 38.7323)', // Lisbon area 'geography2' => 'SRID=4326;LINESTRING(-0.1276 51.5074, -0.1176 51.5174)', // London area ], - [ + 'id=4' => [ 'geometry1' => 'POLYGON((0 0, 0 2, 2 2, 2 0, 0 0))', 'geometry2' => 'POLYGON((1 1, 1 3, 3 3, 3 1, 1 1))', 'geography1' => 'SRID=4326;POLYGON((-9.2 38.6, -9.2 38.8, -9.0 38.8, -9.0 38.6, -9.2 38.6))', // Lisbon area - 'geography2' => 'SRID=4326;POLYGON((-0.2 51.4, -0.2 51.6, 0.0 51.6, 0.0 51.4, -0.2 51.4))', // London area + 'geography2' => 'SRID=4326;POLYGON((-0.2 51.4, -0.2 51.6, 0.0 51.6, 0.0 51.4, -0.2 51.4))', // London area ], - [ + 'id=5' => [ 'geometry1' => 'LINESTRING(-1 -1, 5 5)', 'geometry2' => 'POLYGON((0 0, 0 4, 4 4, 4 0, 0 0))', 'geography1' => 'SRID=4326;LINESTRING(-9.1393 38.7223, -9.1293 38.7323)', // Lisbon area 'geography2' => 'SRID=4326;LINESTRING(-0.1276 51.5074, -0.1176 51.5174)', // London area ], - [ + 'id=6' => [ 'geometry1' => 'POLYGON((1 1, 1 3, 3 3, 3 1, 1 1))', 'geometry2' => 'POLYGON((0 0, 0 4, 4 4, 4 0, 0 0))', 'geography1' => 'SRID=4326;POLYGON((-9.2 38.6, -9.2 38.8, -9.0 38.8, -9.0 38.6, -9.2 38.6))', // Lisbon area - 'geography2' => 'SRID=4326;POLYGON((-0.2 51.4, -0.2 51.6, 0.0 51.6, 0.0 51.4, -0.2 51.4))', // London area + 'geography2' => 'SRID=4326;POLYGON((-0.2 51.4, -0.2 51.6, 0.0 51.6, 0.0 51.4, -0.2 51.4))', // London area ], - [ + 'id=7' => [ 'geometry1' => 'POLYGON((0 0, 0 2, 2 2, 2 0, 0 0))', 'geometry2' => 'POLYGON((0 0, 0 2, 2 2, 2 0, 0 0))', 'geography1' => 'SRID=4326;POLYGON((-9.2 38.6, -9.2 38.8, -9.0 38.8, -9.0 38.6, -9.2 38.6))', // Lisbon area - 'geography2' => 'SRID=4326;POLYGON((-0.2 51.4, -0.2 51.6, 0.0 51.6, 0.0 51.4, -0.2 51.4))', // London area + 'geography2' => 'SRID=4326;POLYGON((-0.2 51.4, -0.2 51.6, 0.0 51.6, 0.0 51.4, -0.2 51.4))', // London area ], - [ + 'id=8' => [ 'geometry1' => 'POLYGON((0 0, 0 2, 2 2, 2 0, 0 0))', 'geometry2' => 'POLYGON((2 0, 2 2, 4 2, 4 0, 2 0))', 'geography1' => 'SRID=4326;POLYGON((-9.2 38.6, -9.2 38.8, -9.0 38.8, -9.0 38.6, -9.2 38.6))', // Lisbon area - 'geography2' => 'SRID=4326;POLYGON((-0.2 51.4, -0.2 51.6, 0.0 51.6, 0.0 51.4, -0.2 51.4))', // London area + 'geography2' => 'SRID=4326;POLYGON((-0.2 51.4, -0.2 51.6, 0.0 51.6, 0.0 51.4, -0.2 51.4))', // London area ], - [ + 'id=9' => [ 'geometry1' => 'LINESTRING(0 0, 4 4)', 'geometry2' => 'POINT(2 2)', 'geography1' => 'SRID=4326;LINESTRING(-9.1393 38.7223, -9.1293 38.7323)', // Lisbon area 'geography2' => 'SRID=4326;LINESTRING(-0.1276 51.5074, -0.1176 51.5174)', // London area ], + 'id=10 (projected coordinate system geometries for testing with linear measurements)' => [ + 'geometry1' => 'SRID=3857;LINESTRING(0 0, 1000 0, 1000 1000)', // L-shaped line: 1000m + 1000m = 2000m total in Web Mercator + 'geometry2' => 'SRID=3857;POLYGON((0 0, 0 1000, 1000 1000, 1000 0, 0 0))', // 1km x 1km square + 'geography1' => 'SRID=4326;POLYGON((-9.2 38.6, -9.2 38.8, -9.0 38.8, -9.0 38.6, -9.2 38.6))', // Lisbon area + 'geography2' => 'SRID=4326;POLYGON((-0.2 51.4, -0.2 51.6, 0.0 51.6, 0.0 51.4, -0.2 51.4))', // London area + ], ]; + $fullTableName = \sprintf('%s.%s', self::DATABASE_SCHEMA, self::TABLE_NAME); foreach ($testData as $row) { $sql = \sprintf(' INSERT INTO %s (geometry1, geometry2, geography1, geography2) From 13ee267a7fd9e61b27f7589a7af30a350d377a23 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 13:59:09 +0300 Subject: [PATCH 11/27] no message --- .../PostGIS/ST_HausdorffDistanceTest.php | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_HausdorffDistanceTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_HausdorffDistanceTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_HausdorffDistanceTest.php new file mode 100644 index 00000000..15670dff --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_HausdorffDistanceTest.php @@ -0,0 +1,51 @@ + ST_HausdorffDistance::class, + ]; + } + + #[Test] + public function returns_hausdorff_distance_between_points(): void + { + $dql = 'SELECT ST_HAUSDORFFDISTANCE(g.geometry1, g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(1.4142135623730951, $result[0]['result'], 0.0000000000000001); + } + + #[Test] + public function returns_zero_for_identical_geometries(): void + { + $dql = 'SELECT ST_HAUSDORFFDISTANCE(g.geometry1, g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result']); + } + + #[Test] + public function returns_hausdorff_distance_between_polygons(): void + { + $dql = 'SELECT ST_HAUSDORFFDISTANCE(g.geometry1, g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(1.4142135623730951, $result[0]['result'], 0.0000000000000001); + } +} From 9d2d952cb27f76dd0e2d9599935956aea0bd3834 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 15:04:24 +0300 Subject: [PATCH 12/27] no message --- .../Functions/PostGIS/ST_3DPerimeterTest.php | 40 ++++++++++++++ .../AST/Functions/PostGIS/ST_EnvelopeTest.php | 55 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DPerimeterTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_EnvelopeTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DPerimeterTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DPerimeterTest.php new file mode 100644 index 00000000..44fdc7a2 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_3DPerimeterTest.php @@ -0,0 +1,40 @@ + ST_3DPerimeter::class, + ]; + } + + #[Test] + public function returns_3d_perimeter_for_polygon(): void + { + $dql = 'SELECT ST_3DPERIMETER(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result']); + } + + #[Test] + public function returns_zero_for_point(): void + { + $dql = 'SELECT ST_3DPERIMETER(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_EnvelopeTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_EnvelopeTest.php new file mode 100644 index 00000000..194dca92 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_EnvelopeTest.php @@ -0,0 +1,55 @@ + ST_Area::class, + 'ST_ENVELOPE' => ST_Envelope::class, + 'ST_EQUALS' => ST_Equals::class, + ]; + } + + #[Test] + public function returns_envelope_of_point(): void + { + $dql = 'SELECT ST_EQUALS(ST_ENVELOPE(g.geometry1), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'PostGIS behavior expects envelope of a point is the point itself'); + } + + #[Test] + public function returns_envelope_of_polygon(): void + { + $dql = 'SELECT ST_AREA(ST_ENVELOPE(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result'], 'Envelope of POLYGON((0 0, 0 4, 4 4, 4 0, 0 0)) should have area = 16'); + } + + #[Test] + public function returns_envelope_of_linestring(): void + { + $dql = 'SELECT ST_AREA(ST_ENVELOPE(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(4, $result[0]['result'], 'Envelope of LINESTRING(0 0, 1 1, 2 2) should be POLYGON((0 0, 0 2, 2 2, 2 0, 0 0)) with area = 4'); + } +} From ce6f90f58a9e93fc3ad7b040f876eba624ff3fb0 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 20:28:48 +0300 Subject: [PATCH 13/27] no message --- .../AST/Functions/PostGIS/ST_BoundaryTest.php | 53 +++++++++++++++++ .../Functions/PostGIS/ST_DifferenceTest.php | 55 ++++++++++++++++++ .../AST/Functions/PostGIS/ST_SimplifyTest.php | 57 +++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BoundaryTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DifferenceTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BoundaryTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BoundaryTest.php new file mode 100644 index 00000000..3ef58356 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_BoundaryTest.php @@ -0,0 +1,53 @@ + ST_Boundary::class, + 'ST_LENGTH' => ST_Length::class, + ]; + } + + #[Test] + public function returns_boundary_for_polygon(): void + { + $dql = 'SELECT ST_LENGTH(ST_BOUNDARY(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result'], 'boundary of POLYGON((0 0, 0 4, 4 4, 4 0, 0 0)) should be a LineString with perimeter = 16'); + } + + #[Test] + public function returns_boundary_for_linestring(): void + { + $dql = 'SELECT ST_LENGTH(ST_BOUNDARY(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result'], 'boundary of linestring should return MultiPoint with zero length'); + } + + #[Test] + public function returns_empty_for_point(): void + { + $dql = 'SELECT ST_LENGTH(ST_BOUNDARY(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result'], 'boundary of point should return empty geometry with zero length'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DifferenceTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DifferenceTest.php new file mode 100644 index 00000000..3db78451 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_DifferenceTest.php @@ -0,0 +1,55 @@ + ST_Area::class, + 'ST_DIFFERENCE' => ST_Difference::class, + 'ST_LENGTH' => ST_Length::class, + ]; + } + + #[Test] + public function returns_difference_between_polygons(): void + { + $dql = 'SELECT ST_AREA(ST_DIFFERENCE(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(12, $result[0]['result'], 'should calculate correct difference area: outer polygon minus inner polygon'); + } + + #[Test] + public function returns_difference_between_other_polygons(): void + { + $dql = 'SELECT ST_AREA(ST_DIFFERENCE(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 4'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(3, $result[0]['result'], 'should calculate correct difference area: smaller polygon minus overlapping larger polygon'); + } + + #[Test] + public function returns_difference_between_linestrings(): void + { + $dql = 'SELECT ST_LENGTH(ST_DIFFERENCE(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.0000000000000001, 'should preserve original linestring length when geometries do not overlap'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyTest.php new file mode 100644 index 00000000..20aaa6e2 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SimplifyTest.php @@ -0,0 +1,57 @@ + ST_Area::class, + 'ST_EQUALS' => ST_Equals::class, + 'ST_LENGTH' => ST_Length::class, + 'ST_SIMPLIFY' => ST_Simplify::class, + ]; + } + + #[Test] + public function returns_simplified_linestring(): void + { + $dql = 'SELECT ST_LENGTH(ST_SIMPLIFY(g.geometry1, 0.1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.0000000000000001, 'should preserve length for straight linestrings'); + } + + #[Test] + public function returns_simplified_polygon(): void + { + $dql = 'SELECT ST_AREA(ST_SIMPLIFY(g.geometry1, 0.2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result'], 'should preserve area for rectangular polygons'); + } + + #[Test] + public function returns_original_point(): void + { + $dql = 'SELECT ST_EQUALS(ST_SIMPLIFY(g.geometry1, 0.1), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'should return unchanged point for point geometries'); + } +} From 8a1b6374a312d172064dc841d7aa99cf1d921ce6 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 21:58:16 +0300 Subject: [PATCH 14/27] no message --- .../Functions/PostGIS/ST_ClipByBox2DTest.php | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2DTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2DTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2DTest.php new file mode 100644 index 00000000..0da57ea6 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ClipByBox2DTest.php @@ -0,0 +1,55 @@ + ST_Area::class, + 'ST_CLIPBYBOX2D' => ST_ClipByBox2D::class, + 'ST_ENVELOPE' => ST_Envelope::class, + ]; + } + + #[Test] + public function will_preserve_full_area_when_clipping_box_contains_entire_polygon(): void + { + $dql = 'SELECT ST_AREA(ST_CLIPBYBOX2D(g.geometry1, \'BOX(0 0, 4 4)\')) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result']); + } + + #[Test] + public function will_preserve_full_geometry_area_when_clipping_with_geometry_envelope(): void + { + $dql = 'SELECT ST_AREA(ST_CLIPBYBOX2D(g.geometry1, ST_Envelope(g.geometry1))) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result']); + } + + #[Test] + public function will_preserve_full_geometry_area_when_clipping_with_parameter_placeholder(): void + { + $dql = 'SELECT ST_AREA(ST_CLIPBYBOX2D(g.geometry1, :box_param)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql, ['box_param' => 'BOX(0 0, 4 4)']); + $this->assertEquals(16, $result[0]['result']); + } +} From dd79042da4ff2581f60a2d1e12823734beb4881c Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 22:03:01 +0300 Subject: [PATCH 15/27] no message --- .../Functions/PostGIS/ST_CurveToLineTest.php | 57 +++++++++++++++++++ .../Functions/PostGIS/ST_SubdivideTest.php | 57 +++++++++++++++++++ .../Functions/PostGIS/ST_TranslateTest.php | 57 +++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CurveToLineTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SubdivideTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TranslateTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CurveToLineTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CurveToLineTest.php new file mode 100644 index 00000000..853db98a --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CurveToLineTest.php @@ -0,0 +1,57 @@ + ST_Area::class, + 'ST_CURVETOLINE' => ST_CurveToLine::class, + 'ST_EQUALS' => ST_Equals::class, + 'ST_LENGTH' => ST_Length::class, + ]; + } + + #[Test] + public function converts_curve_to_line(): void + { + $dql = 'SELECT ST_LENGTH(ST_CURVETOLINE(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.0000000000000001, 'should preserve length for straight linestrings'); + } + + #[Test] + public function handles_polygon_geometry(): void + { + $dql = 'SELECT ST_AREA(ST_CURVETOLINE(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result'], 'should preserve area for straight-edged polygons'); + } + + #[Test] + public function handles_point_geometry(): void + { + $dql = 'SELECT ST_EQUALS(ST_CURVETOLINE(g.geometry1), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'should return unchanged point for point geometries'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SubdivideTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SubdivideTest.php new file mode 100644 index 00000000..70b7450d --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SubdivideTest.php @@ -0,0 +1,57 @@ + ST_Area::class, + 'ST_EQUALS' => ST_Equals::class, + 'ST_LENGTH' => ST_Length::class, + 'ST_SUBDIVIDE' => ST_Subdivide::class, + ]; + } + + #[Test] + public function will_preserve_a_subdivided_polygon_area(): void + { + $dql = 'SELECT ST_AREA(ST_SUBDIVIDE(g.geometry1, 10)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result']); + } + + #[Test] + public function will_preserve_a_subdivided_linestring_length(): void + { + $dql = 'SELECT ST_LENGTH(ST_SUBDIVIDE(g.geometry1, 10)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.0000000000000001); + } + + #[Test] + public function returns_original_geometry_when_vertex_count_sufficient(): void + { + $dql = 'SELECT ST_EQUALS(ST_SUBDIVIDE(g.geometry1, 100), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TranslateTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TranslateTest.php new file mode 100644 index 00000000..1776245c --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TranslateTest.php @@ -0,0 +1,57 @@ + ST_Area::class, + 'ST_DISTANCE' => ST_Distance::class, + 'ST_LENGTH' => ST_Length::class, + 'ST_TRANSLATE' => ST_Translate::class, + ]; + } + + #[Test] + public function translates_point_by_offset(): void + { + $dql = 'SELECT ST_DISTANCE(g.geometry1, ST_TRANSLATE(g.geometry1, 10.0, 10.0)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(14.142135623730951, $result[0]['result'], 0.0000000000000001, 'ST_Translate should move point by expected distance'); + } + + #[Test] + public function translates_polygon_by_offset(): void + { + $dql = 'SELECT ST_AREA(ST_TRANSLATE(g.geometry1, 5.0, 5.0)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result'], 'ST_Translate should preserve polygon area'); + } + + #[Test] + public function translates_linestring_by_offset(): void + { + $dql = 'SELECT ST_LENGTH(ST_TRANSLATE(g.geometry1, 2.0, 2.0)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.0000000000000001, 'ST_Translate should preserve linestring length'); + } +} From 2ceba28c5d37faaad5fa68a0ae7ba57d24f6cf9f Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 22:11:45 +0300 Subject: [PATCH 16/27] no message --- .../AST/Functions/PostGIS/ST_LineCrossingDirectionTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineCrossingDirectionTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineCrossingDirectionTest.php index 4450a55f..4eed7e5b 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineCrossingDirectionTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineCrossingDirectionTest.php @@ -79,7 +79,7 @@ public function can_filter_crossing_lines_in_where_clause(): void WHERE ST_LINECROSSINGDIRECTION(\'LINESTRING(0 0, 2 2)\', \'LINESTRING(0 2, 2 0)\') = 1'; $result = $this->executeDqlQuery($dql); - $this->assertCount(9, $result); + $this->assertCount(10, $result); $this->assertEquals(1, $result[0]['result']); } } From 99cd11ced59a1d9a7c9069c9ee01e5d370ae39d8 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 23:53:57 +0300 Subject: [PATCH 17/27] no message --- .../Functions/PostGIS/ST_ConvexHullTest.php | 57 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_Force2DTest.php | 57 +++++++++++++++++++ .../Functions/PostGIS/ST_LineToCurveTest.php | 57 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_ReverseTest.php | 57 +++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ConvexHullTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force2DTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineToCurveTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ReverseTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ConvexHullTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ConvexHullTest.php new file mode 100644 index 00000000..2477b574 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ConvexHullTest.php @@ -0,0 +1,57 @@ + ST_Area::class, + 'ST_CONVEXHULL' => ST_ConvexHull::class, + 'ST_EQUALS' => ST_Equals::class, + 'ST_LENGTH' => ST_Length::class, + ]; + } + + #[Test] + public function returns_convex_hull_of_point(): void + { + $dql = 'SELECT ST_EQUALS(ST_CONVEXHULL(g.geometry1), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'convex hull of a point should be the point itself'); + } + + #[Test] + public function returns_convex_hull_of_polygon(): void + { + $dql = 'SELECT ST_AREA(ST_CONVEXHULL(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result'], 'convex hull should preserve area for rectangular polygons'); + } + + #[Test] + public function returns_convex_hull_of_linestring(): void + { + $dql = 'SELECT ST_LENGTH(ST_CONVEXHULL(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.0000000000000001, 'convex hull should preserve length for collinear linestring'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force2DTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force2DTest.php new file mode 100644 index 00000000..11c8bc88 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Force2DTest.php @@ -0,0 +1,57 @@ + ST_Area::class, + 'ST_EQUALS' => ST_Equals::class, + 'ST_FORCE2D' => ST_Force2D::class, + 'ST_LENGTH' => ST_Length::class, + ]; + } + + #[Test] + public function returns_2d_point(): void + { + $dql = 'SELECT ST_EQUALS(ST_FORCE2D(g.geometry1), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'should return unchanged 2D point'); + } + + #[Test] + public function returns_2d_polygon(): void + { + $dql = 'SELECT ST_AREA(ST_FORCE2D(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result'], 'should preserve polygon area'); + } + + #[Test] + public function returns_2d_linestring(): void + { + $dql = 'SELECT ST_LENGTH(ST_FORCE2D(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.000000000000001, 'should preserve linestring length'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineToCurveTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineToCurveTest.php new file mode 100644 index 00000000..8446d3fb --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_LineToCurveTest.php @@ -0,0 +1,57 @@ + ST_Area::class, + 'ST_EQUALS' => ST_Equals::class, + 'ST_LENGTH' => ST_Length::class, + 'ST_LINETOCURVE' => ST_LineToCurve::class, + ]; + } + + #[Test] + public function converts_line_to_curve(): void + { + $dql = 'SELECT ST_LENGTH(ST_LINETOCURVE(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.000000000000001, 'should preserve linestring length'); + } + + #[Test] + public function handles_polygon_geometry(): void + { + $dql = 'SELECT ST_AREA(ST_LINETOCURVE(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result'], 'should preserve polygon area'); + } + + #[Test] + public function handles_point_geometry(): void + { + $dql = 'SELECT ST_EQUALS(ST_LINETOCURVE(g.geometry1), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'should return unchanged point for point geometries'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ReverseTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ReverseTest.php new file mode 100644 index 00000000..1a7d8ee3 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ReverseTest.php @@ -0,0 +1,57 @@ + ST_Area::class, + 'ST_EQUALS' => ST_Equals::class, + 'ST_LENGTH' => ST_Length::class, + 'ST_REVERSE' => ST_Reverse::class, + ]; + } + + #[Test] + public function returns_reversed_linestring(): void + { + $dql = 'SELECT ST_LENGTH(ST_REVERSE(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.0000000000000001, 'should preserve linestring length'); + } + + #[Test] + public function returns_reversed_polygon(): void + { + $dql = 'SELECT ST_AREA(ST_REVERSE(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result'], 'should preserve polygon area'); + } + + #[Test] + public function returns_original_point(): void + { + $dql = 'SELECT ST_EQUALS(ST_REVERSE(g.geometry1), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'should return unchanged point for point geometries'); + } +} From 5a6d3efc41b752553e761e636a8a07a3bd60938f Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Wed, 3 Sep 2025 23:57:26 +0300 Subject: [PATCH 18/27] no message --- .../AST/Functions/PostGIS/ST_RotateTest.php | 57 +++++++++++++++++++ .../AST/Functions/PostGIS/ST_SplitTest.php | 42 ++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_RotateTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SplitTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_RotateTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_RotateTest.php new file mode 100644 index 00000000..85d7195a --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_RotateTest.php @@ -0,0 +1,57 @@ + ST_Area::class, + 'ST_DISTANCE' => ST_Distance::class, + 'ST_LENGTH' => ST_Length::class, + 'ST_ROTATE' => ST_Rotate::class, + ]; + } + + #[Test] + public function rotates_point_around_origin(): void + { + $dql = 'SELECT ST_DISTANCE(ST_ROTATE(g.geometry1, 0.785398), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result'], 'should not move point at origin'); + } + + #[Test] + public function rotates_polygon_around_origin(): void + { + $dql = 'SELECT ST_AREA(ST_ROTATE(g.geometry1, 1.570796)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(16, $result[0]['result'], 0.000000000000002, 'should preserve polygon area'); + } + + #[Test] + public function rotates_linestring_around_origin(): void + { + $dql = 'SELECT ST_LENGTH(ST_ROTATE(g.geometry1, 0.523599)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.000000000000001, 'should preserve linestring length'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SplitTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SplitTest.php new file mode 100644 index 00000000..5459e379 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SplitTest.php @@ -0,0 +1,42 @@ + ST_Length::class, + 'ST_SPLIT' => ST_Split::class, + ]; + } + + #[Test] + public function returns_split_geometry_when_linestring_crosses_point(): void + { + $dql = 'SELECT ST_LENGTH(ST_SPLIT(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 9'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(5.656854249492381, $result[0]['result'], 0.000000000000001, 'should preserve total linestring length'); + } + + #[Test] + public function returns_original_geometry_when_no_split_is_possible(): void + { + $dql = 'SELECT ST_LENGTH(ST_SPLIT(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.000000000000001, 'should preserve linestring length when no split occurs'); + } +} From fef6689379d06299ea8cfaf2a40ac2a15b63d964 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 4 Sep 2025 00:03:24 +0300 Subject: [PATCH 19/27] no message --- .../AST/Functions/PostGIS/ST_CentroidTest.php | 68 +++++++++++++++++++ .../Functions/PostGIS/ST_IntersectionTest.php | 55 +++++++++++++++ .../AST/Functions/PostGIS/ST_ScaleTest.php | 57 ++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CentroidTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_IntersectionTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ScaleTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CentroidTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CentroidTest.php new file mode 100644 index 00000000..52b3f084 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CentroidTest.php @@ -0,0 +1,68 @@ + ST_Centroid::class, + 'ST_CONTAINS' => ST_Contains::class, + 'ST_DISTANCE' => ST_Distance::class, + 'ST_EQUALS' => ST_Equals::class, + ]; + } + + #[Test] + public function returns_centroid_for_point(): void + { + $dql = 'SELECT ST_EQUALS(ST_CENTROID(g.geometry1), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result']); + } + + #[Test] + public function returns_centroid_for_polygon(): void + { + $dql = 'SELECT ST_CONTAINS(g.geometry1, ST_CENTROID(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result']); + } + + #[Test] + public function returns_centroid_for_linestring(): void + { + $dql = 'SELECT ST_DISTANCE(ST_CENTROID(g.geometry1), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result'], 'should return point on linestring (distance = 0)'); + } + + #[Test] + public function returns_centroid_for_overlapping_polygons(): void + { + $dql = 'SELECT ST_EQUALS(ST_CENTROID(g.geometry1), ST_CENTROID(g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'both polygons shall have the same centroid'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_IntersectionTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_IntersectionTest.php new file mode 100644 index 00000000..5b52783f --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_IntersectionTest.php @@ -0,0 +1,55 @@ + ST_Area::class, + 'ST_EQUALS' => ST_Equals::class, + 'ST_INTERSECTION' => ST_Intersection::class, + ]; + } + + #[Test] + public function returns_intersection_of_overlapping_polygons(): void + { + $dql = 'SELECT ST_AREA(ST_INTERSECTION(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 4'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(1, $result[0]['result'], 'should calculate correct overlapping area between polygons'); + } + + #[Test] + public function returns_empty_geometry_for_disjoint_geometries(): void + { + $dql = 'SELECT ST_AREA(ST_INTERSECTION(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result'], 'should return zero area for disjoint points'); + } + + #[Test] + public function returns_geometry_for_identical_geometries(): void + { + $dql = 'SELECT ST_EQUALS(ST_INTERSECTION(g.geometry1, g.geometry1), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'intersection of identical geometries should equal the original geometry'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ScaleTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ScaleTest.php new file mode 100644 index 00000000..882fcd90 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ScaleTest.php @@ -0,0 +1,57 @@ + ST_Area::class, + 'ST_EQUALS' => ST_Equals::class, + 'ST_LENGTH' => ST_Length::class, + 'ST_SCALE' => ST_Scale::class, + ]; + } + + #[Test] + public function scales_point_by_factors(): void + { + $dql = 'SELECT ST_EQUALS(ST_SCALE(g.geometry1, 2.0, 2.0), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'should return unchanged point at origin (0 * factor = 0)'); + } + + #[Test] + public function scales_polygon_by_factors(): void + { + $dql = 'SELECT ST_AREA(ST_SCALE(g.geometry1, 1.5, 1.5)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(36, $result[0]['result'], 0.000000000000001, 'should scale polygon area by factor squared (16 * 1.5 * 1.5 = 36)'); + } + + #[Test] + public function scales_linestring_by_factors(): void + { + $dql = 'SELECT ST_LENGTH(ST_SCALE(g.geometry1, 0.5, 0.5)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(1.4142135623730951, $result[0]['result'], 0.000000000000001, 'should scale linestring length by factor (2.828... * 0.5 = 1.414...)'); + } +} From 2fd901f7bb94f8f5d1e94fbdab8e5df8a8aa2ef4 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 4 Sep 2025 00:08:03 +0300 Subject: [PATCH 20/27] no message --- .../Functions/PostGIS/ST_UnaryUnionTest.php | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnaryUnionTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnaryUnionTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnaryUnionTest.php new file mode 100644 index 00000000..333a79ed --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnaryUnionTest.php @@ -0,0 +1,57 @@ + ST_Area::class, + 'ST_EQUALS' => ST_Equals::class, + 'ST_LENGTH' => ST_Length::class, + 'ST_UNARYUNION' => ST_UnaryUnion::class, + ]; + } + + #[Test] + public function performs_unary_union_on_polygon(): void + { + $dql = 'SELECT ST_AREA(ST_UNARYUNION(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(16, $result[0]['result'], 'should preserve polygon area'); + } + + #[Test] + public function performs_unary_union_on_linestring(): void + { + $dql = 'SELECT ST_LENGTH(ST_UNARYUNION(g.geometry1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.000000000000001, 'should preserve linestring length'); + } + + #[Test] + public function performs_unary_union_on_point(): void + { + $dql = 'SELECT ST_EQUALS(ST_UNARYUNION(g.geometry1), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'should return unchanged point for point geometries'); + } +} From 1167c010815e0194397fb7eab1861b21533c1d7d Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 4 Sep 2025 00:17:29 +0300 Subject: [PATCH 21/27] no message --- .../AST/Functions/PostGIS/ST_Length2DTest.php | 51 ++++++++++++ .../AST/Functions/PostGIS/ST_ProjectTest.php | 77 +++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2DTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2DTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2DTest.php new file mode 100644 index 00000000..96351ae7 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_Length2DTest.php @@ -0,0 +1,51 @@ + ST_Length2D::class, + ]; + } + + #[Test] + public function returns_length_for_linestring(): void + { + $dql = 'SELECT ST_LENGTH2D(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(2.8284271247461903, $result[0]['result'], 0.000000000000001, 'should return correct linestring length'); + } + + #[Test] + public function returns_zero_for_polygon(): void + { + $dql = 'SELECT ST_LENGTH2D(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result'], 'should return zero for polygon length (not a perimeter)'); + } + + #[Test] + public function returns_zero_for_point(): void + { + $dql = 'SELECT ST_LENGTH2D(g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php new file mode 100644 index 00000000..a1c5053b --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php @@ -0,0 +1,77 @@ + ST_Distance::class, + 'ST_EQUALS' => ST_Equals::class, + 'ST_PROJECT' => ST_Project::class, + ]; + } + + #[Test] + public function projects_point_with_literal_values(): void + { + $dql = 'SELECT ST_DISTANCE(g.geometry1, ST_PROJECT(ST_PROJECT(g.geometry1, 1000, 0.785398), 1000, 0.785398 + 3.14159)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(0, $result[0]['result'], 0.001, 'geometries with opposite directions should return to approximately the same point'); + } + + #[Test] + public function projects_point_with_arithmetic_expressions(): void + { + $dql = 'SELECT ST_DISTANCE(ST_PROJECT(g.geometry1, 1000, 0.785398), ST_PROJECT(g.geometry1, 1000 * 2, 0.785398 + 0.1)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertNotEquals(0, $result[0]['result'], 'geometries with different parameters should produce different results'); + } + + #[Test] + public function projects_point_with_field_references(): void + { + $dql = 'SELECT ST_EQUALS(ST_PROJECT(g.geometry1, 750, 0.523599), ST_PROJECT(g.geometry1, 750, 0.523599)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'should be deterministic for identical parameters'); + } + + #[Test] + public function projected_point_is_different_from_original(): void + { + $dql = 'SELECT ST_EQUALS(g.geometry1, ST_PROJECT(g.geometry1, 1000, 0)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertFalse($result[0]['result'], 'geometries with non-zero distance should produce different point'); + } + + #[Test] + public function projects_point_zero_distance_returns_same_point(): void + { + $dql = 'SELECT ST_DISTANCE(g.geometry1, ST_PROJECT(g.geometry1, 0, 0)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result']); + } +} From d35a98b6a2e856627dd2874d793cba99cb039188 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 4 Sep 2025 00:27:10 +0300 Subject: [PATCH 22/27] no message --- .../PostGIS/ST_FrechetDistanceTest.php | 51 +++++++++++++++++ .../PostGIS/ST_SymDifferenceTest.php | 55 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_FrechetDistanceTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SymDifferenceTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_FrechetDistanceTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_FrechetDistanceTest.php new file mode 100644 index 00000000..b017ba92 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_FrechetDistanceTest.php @@ -0,0 +1,51 @@ + ST_FrechetDistance::class, + ]; + } + + #[Test] + public function returns_frechet_distance_between_linestrings(): void + { + $dql = 'SELECT ST_FRECHETDISTANCE(g.geometry1, g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(4.242640687119285, $result[0]['result'], 0.000000000000001, 'should return correct distance between disjoint linestrings'); + } + + #[Test] + public function returns_zero_for_identical_geometries(): void + { + $dql = 'SELECT ST_FRECHETDISTANCE(g.geometry1, g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result']); + } + + #[Test] + public function returns_frechet_distance_between_polygons(): void + { + $dql = 'SELECT ST_FRECHETDISTANCE(g.geometry1, g.geometry2) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(1.4142135623730951, $result[0]['result'], 0.000000000000001, 'should return correct distance between overlapping polygons'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SymDifferenceTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SymDifferenceTest.php new file mode 100644 index 00000000..4d28a4bb --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_SymDifferenceTest.php @@ -0,0 +1,55 @@ + ST_Area::class, + 'ST_LENGTH' => ST_Length::class, + 'ST_SYMDIFFERENCE' => ST_SymDifference::class, + ]; + } + + #[Test] + public function returns_symmetric_difference_between_polygons(): void + { + $dql = 'SELECT ST_AREA(ST_SYMDIFFERENCE(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(12, $result[0]['result'], 'should calculate correct area: parts of each polygon not in the other'); + } + + #[Test] + public function returns_symmetric_difference_between_other_polygons(): void + { + $dql = 'SELECT ST_AREA(ST_SYMDIFFERENCE(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 4'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(6, $result[0]['result'], 'should calculate correct exclusive area for overlapping polygons'); + } + + #[Test] + public function returns_symmetric_difference_between_linestrings(): void + { + $dql = 'SELECT ST_LENGTH(ST_SYMDIFFERENCE(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(5.656854249492381, $result[0]['result'], 0.000000000000001, 'should preserve total length of disjoint linestrings'); + } +} From 56f229047fee8cccd6add3c5a9e5a3f114730f32 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 4 Sep 2025 00:42:42 +0300 Subject: [PATCH 23/27] no message --- .../AST/Functions/PostGIS/ST_CollectTest.php | 66 +++++++++++++++++++ .../Functions/PostGIS/ST_TransformTest.php | 53 +++++++++++++++ .../AST/Functions/PostGIS/ST_UnionTest.php | 55 ++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TransformTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnionTest.php diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectTest.php new file mode 100644 index 00000000..fb02d7bb --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_CollectTest.php @@ -0,0 +1,66 @@ + ST_Area::class, + 'ST_COLLECT' => ST_Collect::class, + 'ST_LENGTH' => ST_Length::class, + ]; + } + + #[Test] + public function collects_two_points_into_multipoint(): void + { + $dql = 'SELECT ST_AREA(ST_COLLECT(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result'], 'should return zero area for collected points'); + } + + #[Test] + public function collects_two_polygons_into_multipolygon(): void + { + $dql = 'SELECT ST_AREA(ST_COLLECT(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 2'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(20, $result[0]['result'], 'should sum areas of collected polygons'); + } + + #[Test] + public function collects_two_linestrings_into_multilinestring(): void + { + $dql = 'SELECT ST_LENGTH(ST_COLLECT(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 3'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(5.656854249492381, $result[0]['result'], 0.0000000000000001, 'should sum lengths of collected linestrings'); + } + + #[Test] + public function collects_mixed_geometry_types(): void + { + $dql = 'SELECT ST_LENGTH(ST_COLLECT(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 5'; + + $result = $this->executeDqlQuery($dql); + $this->assertEqualsWithDelta(8.48528137423857, $result[0]['result'], 0.000000000000001, 'should preserve linestring length in mixed geometry collection'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TransformTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TransformTest.php new file mode 100644 index 00000000..4563dc79 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_TransformTest.php @@ -0,0 +1,53 @@ + ST_Equals::class, + 'ST_TRANSFORM' => ST_Transform::class, + ]; + } + + #[Test] + public function returns_transformed_point_to_wgs84(): void + { + $dql = 'SELECT ST_EQUALS(g.geometry1, ST_TRANSFORM(g.geometry1, 4326)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'transformation to same SRID should return identical geometry'); + } + + #[Test] + public function returns_transformed_polygon_to_web_mercator(): void + { + $dql = 'SELECT ST_EQUALS(ST_TRANSFORM(g.geometry1, 4326), ST_TRANSFORM(g.geometry1, 4326)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 10'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'should be deterministic for same transformation'); + } + + #[Test] + public function returns_transformed_linestring_to_utm(): void + { + $dql = 'SELECT ST_EQUALS(g.geometry1, ST_TRANSFORM(g.geometry1, 3857)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 10'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'transformation to same SRID should return identical geometry'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnionTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnionTest.php new file mode 100644 index 00000000..7638461f --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_UnionTest.php @@ -0,0 +1,55 @@ + ST_Area::class, + 'ST_EQUALS' => ST_Equals::class, + 'ST_UNION' => ST_Union::class, + ]; + } + + #[Test] + public function returns_union_of_two_geometries(): void + { + $dql = 'SELECT ST_AREA(ST_UNION(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(0, $result[0]['result'], 'should return zero area for union of disjoint points'); + } + + #[Test] + public function returns_geometry_for_identical_geometries(): void + { + $dql = 'SELECT ST_EQUALS(ST_UNION(g.geometry1, g.geometry1), g.geometry1) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 1'; + + $result = $this->executeDqlQuery($dql); + $this->assertTrue($result[0]['result'], 'union of identical geometries should equal the original geometry'); + } + + #[Test] + public function returns_union_of_overlapping_polygons(): void + { + $dql = 'SELECT ST_AREA(ST_UNION(g.geometry1, g.geometry2)) as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsGeometries g + WHERE g.id = 4'; + + $result = $this->executeDqlQuery($dql); + $this->assertEquals(7, $result[0]['result'], 'should calculate correct combined area for overlapping polygons'); + } +} From 1fe919e24124b29fdf76a3bef5ee073c7a328860 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 4 Sep 2025 00:45:44 +0300 Subject: [PATCH 24/27] no message --- docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md | 190 +++++++++++++++++++++- 1 file changed, 189 insertions(+), 1 deletion(-) diff --git a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md index 9c70edd5..9b4a1499 100644 --- a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md +++ b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md @@ -266,9 +266,115 @@ SELECT e FROM Entity e WHERE ST_DWithin(e.geometry, 'POINT(0 0)', 1000) = TRUE -- Test topological relationships with intersection matrix SELECT e FROM Entity e WHERE ST_Relate(e.geometry1, e.geometry2, 'T*T***T**') = TRUE +-- Get intersection matrix between two geometries +SELECT e, ST_Relate(e.geometry1, e.geometry2) as matrix FROM Entity e + +-- Test if geometries are disjoint (no intersection) +SELECT e FROM Entity e WHERE ST_Relate(e.geometry1, e.geometry2, 'FF*FF****') = TRUE + +-- Test if one geometry contains another (contains relationship) +SELECT e FROM Entity e WHERE ST_Relate(e.geometry1, e.geometry2, 'T*****FF*') = TRUE + +-- Test if geometries touch (boundary intersection only) +SELECT e FROM Entity e WHERE ST_Relate(e.geometry1, e.geometry2, 'FT*******') = TRUE + -- Test if point is inside circle SELECT e FROM Entity e WHERE ST_PointInsideCircle(e.point, 0, 0, 1000) = TRUE -``` + +-- Analyze line crossing behavior +SELECT e, ST_LineCrossingDirection(e.line1, e.line2) as crossing FROM Entity e +WHERE ST_LineCrossingDirection(e.line1, e.line2) != 0 + +-- Find lines that cross from left to right +SELECT e FROM Entity e WHERE ST_LineCrossingDirection(e.line1, e.line2) = 1 + +-- Find lines that cross from right to left +SELECT e FROM Entity e WHERE ST_LineCrossingDirection(e.line1, e.line2) = -1 + +-- Find lines with multiple crossings +SELECT e FROM Entity e WHERE ST_LineCrossingDirection(e.line1, e.line2) = 2 + +-- Calculate areas and perimeters +SELECT e, ST_Area(e.polygon) as area, ST_Perimeter(e.polygon) as perimeter +FROM Entity e WHERE e.polygon IS NOT NULL + +-- Calculate 3D measurements +SELECT e, ST_3DLength(e.line3d) as length3d, ST_3DPerimeter(e.polygon3d) as perimeter3d +FROM Entity e WHERE e.line3d IS NOT NULL OR e.polygon3d IS NOT NULL + +-- Find geometries within distance +SELECT e, ST_Distance(e.geometry, 'POINT(0 0)') as distance +FROM Entity e ORDER BY distance LIMIT 10 + +-- Calculate azimuth between points +SELECT e, ST_Azimuth(e.point1, e.point2) as azimuth_radians, + DEGREES(ST_Azimuth(e.point1, e.point2)) as azimuth_degrees +FROM Entity e WHERE e.point1 IS NOT NULL AND e.point2 IS NOT NULL + +-- Geometric operations and transformations +-- Create buffer around geometry +SELECT e, ST_Buffer(e.geometry, 100) as buffered_geometry FROM Entity e + +-- Simplify complex geometries +SELECT e, ST_Simplify(e.complex_geometry, 0.5) as simplified_geometry FROM Entity e + +-- Transform coordinate systems +SELECT e, ST_Transform(e.geometry, 4326) as wgs84_geometry FROM Entity e + +-- Get convex hull +SELECT e, ST_ConvexHull(e.geometry) as convex_hull FROM Entity e + +-- Scale and rotate geometries +SELECT e, ST_Scale(ST_Rotate(e.geometry, PI()/4), 2, 2) as scaled_rotated_geometry FROM Entity e + +-- Array and JSON operations +-- Check if array contains specific elements +SELECT e FROM Entity e WHERE CONTAINS(e.tags, ARRAY['important', 'urgent']) = TRUE + +-- Find entities with overlapping arrays +SELECT e FROM Entity e WHERE OVERLAPS(e.categories, ARRAY['admin', 'user']) = TRUE + +-- Extract JSON field values +SELECT e, JSON_GET_FIELD_AS_TEXT(e.metadata, 'status') as status FROM Entity e + +-- Aggregate values into arrays +SELECT e.category, ARRAY_AGG(e.id) as entity_ids FROM Entity e GROUP BY e.category + +-- Build JSON objects +SELECT e.id, JSON_BUILD_OBJECT('name', e.name, 'type', e.type) as json_data FROM Entity e + +-- Text and pattern matching +-- Case-insensitive pattern matching +SELECT e FROM Entity e WHERE IREGEXP(e.name, '^admin.*') = TRUE + +-- Extract text using regex +SELECT e, REGEXP_SUBSTR(e.description, 'version [0-9.]+') as version FROM Entity e + +-- Replace text patterns +SELECT e, REGEXP_REPLACE(e.content, 'old_pattern', 'new_pattern') as updated_content FROM Entity e + +-- Check if text starts with specific string +SELECT e FROM Entity e WHERE STARTS_WITH(e.name, 'user_') = TRUE + +-- Full-text search +SELECT e FROM Entity e WHERE TSMATCH(e.search_vector, 'query & terms') = TRUE + +-- Date and range operations +-- Add days to date +SELECT e, DATE_ADD(e.created_at, 30) as expiry_date FROM Entity e + +-- Extract date components +SELECT e, DATE_EXTRACT(e.timestamp, 'YEAR') as year FROM Entity e + +-- Check date overlaps +SELECT e FROM Entity e WHERE DATE_OVERLAPS(e.period1, e.period2) = TRUE + +-- Create date ranges +SELECT e, DATERANGE(e.start_date, e.end_date) as date_range FROM Entity e + +-- Mathematical operations +SELECT e, POWER(e.value, 2) as squared, SQRT(e.value) as root FROM Entity e +WHERE e.value > 0 **πŸ“ Notes:** - `ST_Relate` is a variadic function that accepts 2 or 3 arguments: @@ -281,6 +387,88 @@ SELECT e FROM Entity e WHERE ST_PointInsideCircle(e.point, 0, 0, 1000) = TRUE - `2`: Multiple crossings - All other functions return boolean values and should be used with `= TRUE` or `= FALSE` in DQL +**πŸ” DE-9IM Intersection Matrix Patterns for ST_Relate:** + +The DE-9IM (Dimensionally Extended 9-Intersection Model) uses a 9-character pattern where each character represents the intersection between: +- Interior (I), Boundary (B), and Exterior (E) of geometry A +- Interior (I), Boundary (B), and Exterior (E) of geometry B + +Common patterns: +- `FF*FF****` = Disjoint (no intersection) +- `T*****FF*` = Contains (A contains B) +- `T*T***T**` = Intersects (geometries intersect) +- `FT*******` = Touches (boundary intersection only) +- `F**T*****` = Within (A is within B) +- `T*T***T**` = Overlaps (partial overlap) + +**πŸ“Š Function Return Types:** +- **Boolean functions**: Use with `= TRUE` or `= FALSE` in DQL +- **Numeric functions**: Return values for calculations and ordering +- **Geometry functions**: Return new geometries for further operations +- **Text functions**: Return strings for pattern matching and display + +## PostGIS Measurement Functions + +These functions calculate various measurements of geometries including lengths, areas, distances, and angles. + +| PostgreSQL functions | Register for DQL as | Description | Implemented by | +|---|---|---|---| +| ST_Length | ST_LENGTH | Returns the 2D length of LineString/MultiLineString or perimeter of areal geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Length` | +| ST_Length2D | ST_LENGTH2D | Returns the 2D length, ignoring Z coordinates | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Length2D` | +| ST_3DLength | ST_3DLENGTH | Returns the 3D length of LineString/MultiLineString or 3D perimeter of areal geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DLength` | +| ST_Area | ST_AREA | Returns the area of polygon/multi-polygon geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Area` | +| ST_Perimeter | ST_PERIMETER | Returns the 2D perimeter of polygon/multi-polygon geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Perimeter` | +| ST_3DPerimeter | ST_3DPERIMETER | Returns the 3D perimeter of polygon/multi-polygon geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DPerimeter` | +| ST_Distance | ST_DISTANCE | Returns the 2D distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Distance` | +| ST_3DDistance | ST_3DDISTANCE | Returns the 3D distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DDistance` | +| ST_Centroid | ST_CENTROID | Returns the geometric center of a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Centroid` | +| ST_MaxDistance | ST_MAXDISTANCE | Returns the maximum distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_MaxDistance` | +| ST_HausdorffDistance | ST_HAUSDORFFDISTANCE | Returns the Hausdorff distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_HausdorffDistance` | +| ST_FrechetDistance | ST_FRECHETDISTANCE | Returns the FrΓ©chet distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_FrechetDistance` | +| ST_Azimuth | ST_AZIMUTH | Returns the azimuth between two points in radians | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Azimuth` | +| ST_Project | ST_PROJECT | Projects a point along a geodesic by distance and azimuth | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Project` | + +## PostGIS Overlay Functions + +These functions perform geometric operations between geometries including intersection, union, difference, and splitting. + +| PostgreSQL functions | Register for DQL as | Description | Implemented by | +|---|---|---|---| +| ST_Intersection | ST_INTERSECTION | Returns the point set intersection of two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Intersection` | +| ST_Union | ST_UNION | Returns the point set union of two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Union` | +| ST_UnaryUnion | ST_UNARYUNION | Performs unary union on a geometry (dissolves internal boundaries) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_UnaryUnion` | +| ST_Difference | ST_DIFFERENCE | Returns the point set difference (A - B) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Difference` | +| ST_SymDifference | ST_SYMDIFFERENCE | Returns the symmetric difference of two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_SymDifference` | +| ST_Split | ST_SPLIT | Splits a geometry by another geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Split` | +| ST_Subdivide | ST_SUBDIVIDE | Subdivides a geometry into smaller parts | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Subdivide` | +| ST_ClipByBox2D | ST_CLIPBYBOX2D | Clips a geometry by a 2D box | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_ClipByBox2D` | +| ST_Collect | ST_COLLECT | Collects geometries into a geometry collection | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Collect` | +| ST_CollectionExtract | ST_COLLECTIONEXTRACT | Extracts a specific type from a geometry collection | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_CollectionExtract` | +| ST_CollectionHomogenize | ST_COLLECTIONHOMOGENIZE | Homogenizes a geometry collection | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_CollectionHomogenize` | + +## PostGIS Geometry Processing Functions + +These functions modify and transform geometries including buffering, simplification, coordinate system changes, and geometric transformations. + +| PostgreSQL functions | Register for DQL as | Description | Implemented by | +|---|---|---|---| +| ST_Buffer | ST_BUFFER | Creates a buffer around a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Buffer` | +| ST_Simplify | ST_SIMPLIFY | Simplifies geometry using Douglas-Peucker algorithm | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Simplify` | +| ST_SimplifyPreserveTopology | ST_SIMPLIFYPRESERVETOPOLOGY | Simplifies geometry while preserving topology | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_SimplifyPreserveTopology` | +| ST_SimplifyVW | ST_SIMPLIFYVW | Simplifies geometry using Visvalingam-Whyatt algorithm | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_SimplifyVW` | +| ST_ConvexHull | ST_CONVEXHULL | Returns the convex hull of a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_ConvexHull` | +| ST_Envelope | ST_ENVELOPE | Returns the bounding box as a polygon | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Envelope` | +| ST_Boundary | ST_BOUNDARY | Returns the boundary of a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Boundary` | +| ST_Transform | ST_TRANSFORM | Transforms geometry to different coordinate system | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Transform` | +| ST_Reverse | ST_REVERSE | Reverses the order of points in a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Reverse` | +| ST_Force2D | ST_FORCE2D | Forces geometry to 2D by removing Z/M coordinates | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Force2D` | +| ST_Force3D | ST_FORCE3D | Forces geometry to 3D by adding Z coordinate if needed | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Force3D` | +| ST_Force4D | ST_FORCE4D | Forces geometry to 4D by adding Z and M coordinates if needed | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Force4D` | +| ST_CurveToLine | ST_CURVETOLINE | Converts curved geometries to linear geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_CurveToLine` | +| ST_LineToCurve | ST_LINETOCURVE | Converts linear geometries to curved geometries where possible | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_LineToCurve` | +| ST_Scale | ST_SCALE | Scales a geometry by given factors | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Scale` | +| ST_Rotate | ST_ROTATE | Rotates a geometry by given angle | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Rotate` | +| ST_Translate | ST_TRANSLATE | Translates a geometry by given offsets | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Translate` | # Bonus Helpers From c88e421fb873ef5b9c2405e15243add4e00d78aa Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 4 Sep 2025 01:07:45 +0300 Subject: [PATCH 25/27] no message --- README.md | 7 +- docs/ARRAY-AND-JSON-FUNCTIONS.md | 149 ++++++ docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md | 577 +++++----------------- docs/DATE-AND-RANGE-FUNCTIONS.md | 167 +++++++ docs/INTEGRATING-WITH-DOCTRINE.md | 8 +- docs/INTEGRATING-WITH-LARAVEL.md | 11 +- docs/INTEGRATING-WITH-SYMFONY.md | 7 +- docs/MATHEMATICAL-FUNCTIONS.md | 190 +++++++ docs/SPATIAL-FUNCTIONS-AND-OPERATORS.md | 259 ++++++++++ docs/TEXT-AND-PATTERN-FUNCTIONS.md | 125 +++++ 10 files changed, 1033 insertions(+), 467 deletions(-) create mode 100644 docs/ARRAY-AND-JSON-FUNCTIONS.md create mode 100644 docs/DATE-AND-RANGE-FUNCTIONS.md create mode 100644 docs/MATHEMATICAL-FUNCTIONS.md create mode 100644 docs/SPATIAL-FUNCTIONS-AND-OPERATORS.md create mode 100644 docs/TEXT-AND-PATTERN-FUNCTIONS.md diff --git a/README.md b/README.md index 3fb9581a..d970dab9 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,12 @@ This package provides comprehensive Doctrine support for PostgreSQL features: Full documentation: - [Available Types](docs/AVAILABLE-TYPES.md) - [Value Objects for Range Types](docs/RANGE-TYPES.md) -- [Available Functions and Operators](docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md) +- [Available Functions and Operators](docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md) - Overview and cross-references + - [Array and JSON Functions](docs/ARRAY-AND-JSON-FUNCTIONS.md) + - [PostGIS Spatial Functions](docs/SPATIAL-FUNCTIONS-AND-OPERATORS.md) + - [Text and Pattern Functions](docs/TEXT-AND-PATTERN-FUNCTIONS.md) + - [Date and Range Functions](docs/DATE-AND-RANGE-FUNCTIONS.md) + - [Mathematical Functions](docs/MATHEMATICAL-FUNCTIONS.md) - [Common Use Cases and Examples](docs/USE-CASES-AND-EXAMPLES.md) - [Spatial Types](docs/SPATIAL-TYPES.md) - [Geometry Arrays](docs/GEOMETRY-ARRAYS.md) diff --git a/docs/ARRAY-AND-JSON-FUNCTIONS.md b/docs/ARRAY-AND-JSON-FUNCTIONS.md new file mode 100644 index 00000000..448d826c --- /dev/null +++ b/docs/ARRAY-AND-JSON-FUNCTIONS.md @@ -0,0 +1,149 @@ +# Array and JSON Functions and Operators + +This document covers PostgreSQL array and JSON/JSONB operators and functions available in this library. + +## Array and JSON Operators + +**⚠️ Important**: Some PostgreSQL operators have multiple meanings depending on the data types involved. This library provides specific DQL function names to avoid conflicts: + +| Operator | Array/JSON Usage | Spatial Usage | Text/Pattern Usage | +|---|---|---|---| +| `@>` | `CONTAINS` (arrays contain elements) | Works automatically with geometry/geography | N/A | +| `<@` | `IS_CONTAINED_BY` (element in array) | Works automatically with geometry/geography | N/A | +| `&&` | `OVERLAPS` (arrays/ranges overlap) | Works automatically with geometry/geography | N/A | + +**Usage Guidelines:** +- **Arrays/JSON**: Use `CONTAINS`, `IS_CONTAINED_BY`, `OVERLAPS` for array and JSON operations +- **Boolean operators**: All operators return boolean values and **should be used with `= TRUE` or `= FALSE` in DQL** + +### Array and JSON Operators + +| PostgreSQL operator | Register for DQL as | Implemented by | +|---|---|---| +| @> | CONTAINS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Contains` | +| <@ | IS_CONTAINED_BY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\IsContainedBy` | +| && | OVERLAPS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Overlaps` | +| ? | RIGHT_EXISTS_ON_LEFT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\TheRightExistsOnTheLeft` | +| ?& | ALL_ON_RIGHT_EXIST_ON_LEFT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\AllOnTheRightExistOnTheLeft` | +| ?\| | ANY_ON_RIGHT_EXISTS_ON_LEFT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\AnyOnTheRightExistsOnTheLeft` | +| @? | RETURNS_VALUE_FOR_JSON_VALUE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ReturnsValueForJsonValue` | +| #- | DELETE_AT_PATH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DeleteAtPath` | +| -> | JSON_GET_FIELD | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetField` | +| ->> | JSON_GET_FIELD_AS_TEXT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetFieldAsText`| +| #> | JSON_GET_OBJECT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetObject` | +| #>> | JSON_GET_OBJECT_AS_TEXT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetObjectAsText` | + +## Array Functions + +| PostgreSQL functions | Register for DQL as | Implemented by | +|---|---|---| +| all | ALL_OF | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\All` | +| any | ANY_OF | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Any` | +| array_agg | ARRAY_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayAgg` | +| array_append | ARRAY_APPEND | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayAppend` | +| array_cat | ARRAY_CAT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayCat` | +| array_dims | ARRAY_DIMENSIONS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayDimensions` | +| array_length | ARRAY_LENGTH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayLength` | +| array_ndims | ARRAY_NUMBER_OF_DIMENSIONS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayNumberOfDimensions` | +| array_position | ARRAY_POSITION | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayPosition` | +| array_positions | ARRAY_POSITIONS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayPositions` | +| array_prepend | ARRAY_PREPEND | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayPrepend` | +| array_remove | ARRAY_REMOVE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayRemove` | +| array_replace | ARRAY_REPLACE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayReplace` | +| array_shuffle | ARRAY_SHUFFLE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayShuffle` | +| array_to_json | ARRAY_TO_JSON | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayToJson` | +| array_to_string | ARRAY_TO_STRING | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayToString` | +| cardinality | ARRAY_CARDINALITY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Cardinality` | +| string_to_array | STRING_TO_ARRAY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StringToArray` | +| unnest | UNNEST | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Unnest` | + +## JSON Functions + +| PostgreSQL functions | Register for DQL as | Implemented by | +|---|---|---| +| json_agg | JSON_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonAgg` | +| json_array_length | JSON_ARRAY_LENGTH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonArrayLength` | +| json_build_object | JSON_BUILD_OBJECT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonBuildObject` | +| json_each | JSON_EACH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonEach` | +| json_each_text | JSON_EACH_TEXT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonEachText` | +| json_exists | JSON_EXISTS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonExists` | +| json_object_agg | JSON_OBJECT_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonObjectAgg` | +| json_object_keys | JSON_OBJECT_KEYS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonObjectKeys` | +| json_query | JSON_QUERY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonQuery` | +| json_scalar | JSON_SCALAR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonScalar` | +| json_serialize | JSON_SERIALIZE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonSerialize` | +| json_strip_nulls | JSON_STRIP_NULLS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonStripNulls` | +| json_typeof | JSON_TYPEOF | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonTypeof` | +| json_value | JSON_VALUE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonValue` | +| to_json | TO_JSON | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToJson` | +| to_jsonb | TO_JSONB | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToJsonb` | + +## JSONB Functions + +| PostgreSQL functions | Register for DQL as | Implemented by | +|---|---|---| +| jsonb_array_elements | JSONB_ARRAY_ELEMENTS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayElements` | +| jsonb_agg | JSONB_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbAgg` | +| jsonb_array_elements_text | JSONB_ARRAY_ELEMENTS_TEXT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayElementsText` | +| jsonb_array_length | JSONB_ARRAY_LENGTH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayLength` | +| jsonb_build_object | JSONB_BUILD_OBJECT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbBuildObject` | +| jsonb_each | JSONB_EACH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEach` | +| jsonb_each_text | JSONB_EACH_TEXT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEachText` | +| jsonb_exists | JSONB_EXISTS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbExists` | +| jsonb_insert | JSONB_INSERT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbInsert` | +| jsonb_object_agg | JSONB_OBJECT_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbObjectAgg` | +| jsonb_object_keys | JSONB_OBJECT_KEYS |`MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbObjectKeys` | +| jsonb_path_exists | JSONB_PATH_EXISTS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathExists` | +| jsonb_path_match | JSONB_PATH_MATCH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathMatch` | +| jsonb_path_query | JSONB_PATH_QUERY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathQuery` | +| jsonb_path_query_array | JSONB_PATH_QUERY_ARRAY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathQueryArray` | +| jsonb_path_query_first | JSONB_PATH_QUERY_FIRST | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathQueryFirst` | +| jsonb_pretty | JSONB_PRETTY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPretty` | +| jsonb_set | JSONB_SET | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbSet` | +| jsonb_set_lax | JSONB_SET_LAX | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbSetLax` | +| jsonb_strip_nulls | JSONB_STRIP_NULLS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbStripNulls` | + +## Bonus Helpers + +| PostgreSQL functions | Register for DQL as | Implemented by | +|---|---|---| +| array | ARRAY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Arr` | +| value = ANY(list of values) | IN_ARRAY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\InArray` | +| CAST(json ->> node as BIGINT) | JSON_GET_FIELD_AS_INTEGER | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetFieldAsInteger` | + +## Usage Examples + +```sql +-- Array and JSON operations +-- Check if array contains specific elements +SELECT e FROM Entity e WHERE CONTAINS(e.tags, ARRAY['important', 'urgent']) = TRUE + +-- Find entities with overlapping arrays +SELECT e FROM Entity e WHERE OVERLAPS(e.categories, ARRAY['admin', 'user']) = TRUE + +-- Extract JSON field values +SELECT e, JSON_GET_FIELD_AS_TEXT(e.metadata, 'status') as status FROM Entity e + +-- Aggregate values into arrays +SELECT e.category, ARRAY_AGG(e.id) as entity_ids FROM Entity e GROUP BY e.category + +-- Build JSON objects +SELECT e.id, JSON_BUILD_OBJECT('name', e.name, 'type', e.type) as json_data FROM Entity e + +-- Advanced array operations +-- Shuffle array elements +SELECT e, ARRAY_SHUFFLE(e.tags) as shuffled_tags FROM Entity e + +-- Replace array elements +SELECT e, ARRAY_REPLACE(e.categories, 'old', 'new') as updated_categories FROM Entity e + +-- Check array dimensions +SELECT e, ARRAY_DIMENSIONS(e.matrix) as dimensions FROM Entity e +WHERE ARRAY_NUMBER_OF_DIMENSIONS(e.matrix) > 1 +``` + +**πŸ’‘ Tips for Usage:** +1. **Boolean operators** should be used with `= TRUE` or `= FALSE` in DQL +2. **Array functions** provide efficient PostgreSQL array operations +3. **JSON functions** support both JSON and JSONB data types +4. **JSONB functions** offer better performance for complex JSON operations diff --git a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md index 9b4a1499..5f1e039d 100644 --- a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md +++ b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md @@ -1,4 +1,6 @@ -# Available Operators +# Available Functions and Operators + +This document provides an overview of PostgreSQL functions and operators available in this library. For detailed documentation of specific function categories, see the specialized documentation files linked below. ## Operator Conflicts and Usage Notes @@ -18,462 +20,117 @@ - **Text**: Use `REGEXP`, `IREGEXP` for pattern matching - **Boolean operators**: All spatial operators return boolean values and **should be used with `= TRUE` or `= FALSE` in DQL** -## General Operators - -| PostgreSQL operator | Register for DQL as | Implemented by | -|---|---|---| -| @> | CONTAINS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Contains` | -| <@ | IS_CONTAINED_BY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\IsContainedBy` | -| && | OVERLAPS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Overlaps` | -| ? | RIGHT_EXISTS_ON_LEFT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\TheRightExistsOnTheLeft` | -| ?& | ALL_ON_RIGHT_EXIST_ON_LEFT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\AllOnTheRightExistOnTheLeft` | -| ?\| | ANY_ON_RIGHT_EXISTS_ON_LEFT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\AnyOnTheRightExistsOnTheLeft` | -| @? | RETURNS_VALUE_FOR_JSON_VALUE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ReturnsValueForJsonValue` | -| #- | DELETE_AT_PATH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DeleteAtPath` | -| -> | JSON_GET_FIELD | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetField` | -| ->> | JSON_GET_FIELD_AS_TEXT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetFieldAsText`| -| #> | JSON_GET_OBJECT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetObject` | -| #>> | JSON_GET_OBJECT_AS_TEXT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetObjectAsText` | -| ilike | ILIKE ([Usage note](USE-CASES-AND-EXAMPLES.md)) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Ilike` | -| similar to | SIMILAR_TO | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\SimilarTo` | -| not similar to | NOT_SIMILAR_TO | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\NotSimilarTo` | -| ~ | REGEXP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Regexp` | -| ~* | IREGEXP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\IRegexp` | -| !~ | NOT_REGEXP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\NotRegexp` | -| !~* | NOT_IREGEXP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\NotIRegexp` | -| @@ | TSMATCH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Tsmatch` | -| \|\| | STRCONCAT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StrConcat` | - -## PostGIS Spatial Operators - -**⚠️ Important**: Some operators have dual meanings for different data types. Use the specific DQL function names to avoid conflicts: - -- **`@`**: Use `CONTAINS` for arrays/JSON, `SPATIAL_CONTAINED_BY` for geometry/geography -- **`~`**: Use `REGEXP` for text patterns, `SPATIAL_CONTAINS` for geometry/geography -- **`&&`**: Use `OVERLAPS` for arrays/JSON, spatial overlaps work automatically with geometry/geography - -**πŸ“ Compatibility Notes**: -- Most bounding box operators work primarily with **geometry** types -- **Geography** types have limited operator support (mainly `&&`, `<->`, `<@>`) -- **3D/n-dimensional operators** may require explicit type casting: `ST_GeomFromText('POINT Z(0 0 0)')` -- Some advanced operators (`&&&`, `<<#>>`) may not be available in all PostGIS versions - -### Bounding Box Operators - -These operators work with geometry and geography bounding boxes. All return boolean values and **should be used with `= TRUE` or `= FALSE` in DQL**. - -| PostgreSQL operator | Register for DQL as | Description | Implemented by | -|---|---|---|---| -| &< | OVERLAPS_LEFT | Returns TRUE if A's bounding box overlaps or is to the left of B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\OverlapsLeft` | -| &> | OVERLAPS_RIGHT | Returns TRUE if A's bounding box overlaps or is to the right of B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\OverlapsRight` | -| << | STRICTLY_LEFT | Returns TRUE if A's bounding box is strictly to the left of B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\StrictlyLeft` | -| >> | STRICTLY_RIGHT | Returns TRUE if A's bounding box is strictly to the right of B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\StrictlyRight` | -| @ | SPATIAL_CONTAINED_BY | Returns TRUE if A's bounding box is contained by B's (**spatial version**) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\SpatialContainedBy` | -| ~ | SPATIAL_CONTAINS | Returns TRUE if A's bounding box contains B's (**spatial version**) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\SpatialContains` | -| ~= | SPATIAL_SAME | Returns TRUE if A's bounding box is the same as B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\SpatialSame` | -| \|&> | OVERLAPS_ABOVE | Returns TRUE if A's bounding box overlaps or is above B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\OverlapsAbove` | -| \|>> | STRICTLY_ABOVE | Returns TRUE if A's bounding box is strictly above B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\StrictlyAbove` | -| &<\| | OVERLAPS_BELOW | Returns TRUE if A's bounding box overlaps or is below B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\OverlapsBelow` | -| <<\| | STRICTLY_BELOW | Returns TRUE if A's bounding box is strictly below B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\StrictlyBelow` | -| &&& | ND_OVERLAPS | Returns TRUE if A's n-D bounding box intersects B's n-D bounding box | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\NDimensionalOverlaps` | - -**Usage Examples:** -```sql --- Find geometries to the left of a reference point -SELECT e FROM Entity e WHERE STRICTLY_LEFT(e.geometry, 'POINT(0 0)') = TRUE - --- Find overlapping polygons -SELECT e FROM Entity e WHERE SPATIAL_CONTAINS(e.polygon, e.point) = TRUE - --- 3D spatial relationships -SELECT e FROM Entity e WHERE ND_OVERLAPS(e.geometry3d, 'POLYGON Z((0 0 0, 1 1 1, 2 2 2, 0 0 0))') = TRUE -``` - -### Distance Operators - -These operators calculate distances between geometries. All return numeric values. - -| PostgreSQL operator | Register for DQL as | Description | Implemented by | -|---|---|---|---| -| <-> | GEOMETRY_DISTANCE | Returns the 2D distance between A and B geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\GeometryDistance` | -| <@> | DISTANCE | Returns distance between points (legacy operator) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Distance` | -| \|=\| | TRAJECTORY_DISTANCE | Returns distance between trajectories at closest point of approach | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\TrajectoryDistance` | -| <#> | BOUNDING_BOX_DISTANCE | Returns the 2D distance between A and B bounding boxes | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\BoundingBoxDistance` | -| <<->> | ND_CENTROID_DISTANCE | Returns n-D distance between centroids of bounding boxes | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\NDimensionalCentroidDistance` | -| <<#>> | ND_BOUNDING_BOX_DISTANCE | Returns the n-D distance between A and B bounding boxes | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\NDimensionalBoundingBoxDistance` | - -**Usage Examples:** -```sql --- Find nearest geometries -SELECT e, GEOMETRY_DISTANCE(e.geometry, 'POINT(0 0)') as distance -FROM Entity e ORDER BY distance LIMIT 10 - --- Bounding box distance for index optimization -SELECT e FROM Entity e WHERE BOUNDING_BOX_DISTANCE(e.geometry, 'POINT(0 0)') < 1000 - --- 3D distance calculations -SELECT ND_CENTROID_DISTANCE(e.geometry3d1, e.geometry3d2) as distance FROM Entity e -``` - -# Available Functions - -| PostgreSQL functions | Register for DQL as | Implemented by | -|---|---|---| -| all | ALL_OF | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\All` | -| any | ANY_OF | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Any` | -| any_value | ANY_VALUE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\AnyValue` | -| array_agg | ARRAY_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayAgg` | -| array_append | ARRAY_APPEND | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayAppend` | -| array_cat | ARRAY_CAT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayCat` | -| array_dims | ARRAY_DIMENSIONS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayDimensions` | -| array_length | ARRAY_LENGTH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayLength` | -| array_ndims | ARRAY_NUMBER_OF_DIMENSIONS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayNumberOfDimensions` | -| array_position | ARRAY_POSITION | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayPosition` | -| array_positions | ARRAY_POSITIONS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayPositions` | -| array_prepend | ARRAY_PREPEND | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayPrepend` | -| array_remove | ARRAY_REMOVE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayRemove` | -| array_replace | ARRAY_REPLACE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayReplace` | -| array_shuffle | ARRAY_SHUFFLE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayShuffle` | -| array_to_json | ARRAY_TO_JSON | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayToJson` | -| array_to_string | ARRAY_TO_STRING | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayToString` | -| cardinality | ARRAY_CARDINALITY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Cardinality` | -| cast | CAST | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Cast` | -| ceil | CEIL | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Ceil` | -| date_add | DATE_ADD | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateAdd` | -| date_bin | DATE_BIN | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateBin` | -| date_subtract | DATE_SUBTRACT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateSubtract` | -| daterange | DATERANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Daterange` | -| extract | DATE_EXTRACT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateExtract` | -| floor | FLOOR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Floor` | -| greatest | GREATEST | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Greatest` | -| int4range | INT4RANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Int4range` | -| int8range | INT8RANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Int8range` | -| json_agg | JSON_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonAgg` | -| json_array_length | JSON_ARRAY_LENGTH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonArrayLength` | -| json_build_object | JSON_BUILD_OBJECT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonBuildObject` | -| json_each | JSON_EACH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonEach` | -| json_each_text | JSON_EACH_TEXT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonEachText` | -| json_exists | JSON_EXISTS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonExists` | -| json_object_agg | JSON_OBJECT_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonObjectAgg` | -| json_object_keys | JSON_OBJECT_KEYS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonObjectKeys` | -| json_query | JSON_QUERY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonQuery` | -| json_scalar | JSON_SCALAR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonScalar` | -| json_serialize | JSON_SERIALIZE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonSerialize` | -| json_strip_nulls | JSON_STRIP_NULLS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonStripNulls` | -| json_typeof | JSON_TYPEOF | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonTypeof` | -| json_value | JSON_VALUE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonValue` | -| jsonb_array_elements | JSONB_ARRAY_ELEMENTS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayElements` | -| jsonb_agg | JSONB_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbAgg` | -| jsonb_array_elements_text | JSONB_ARRAY_ELEMENTS_TEXT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayElementsText` | -| jsonb_array_length | JSONB_ARRAY_LENGTH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayLength` | -| jsonb_build_object | JSONB_BUILD_OBJECT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbBuildObject` | -| jsonb_each | JSONB_EACH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEach` | -| jsonb_each_text | JSONB_EACH_TEXT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEachText` | -| jsonb_exists | JSONB_EXISTS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbExists` | -| jsonb_insert | JSONB_INSERT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbInsert` | -| jsonb_object_agg | JSONB_OBJECT_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbObjectAgg` | -| jsonb_object_keys | JSONB_OBJECT_KEYS |`MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbObjectKeys` | -| jsonb_path_exists | JSONB_PATH_EXISTS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathExists` | -| jsonb_path_match | JSONB_PATH_MATCH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathMatch` | -| jsonb_path_query | JSONB_PATH_QUERY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathQuery` | -| jsonb_path_query_array | JSONB_PATH_QUERY_ARRAY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathQueryArray` | -| jsonb_path_query_first | JSONB_PATH_QUERY_FIRST | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathQueryFirst` | -| jsonb_pretty | JSONB_PRETTY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPretty` | -| jsonb_set | JSONB_SET | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbSet` | -| jsonb_set_lax | JSONB_SET_LAX | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbSetLax` | -| jsonb_strip_nulls | JSONB_STRIP_NULLS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbStripNulls` | -| least | LEAST | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Least` | -| numrange | NUMRANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Numrange` | -| overlaps | DATE_OVERLAPS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateOverlaps` | -| regexp_count | REGEXP_COUNT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpCount` | -| regexp_instr | REGEXP_INSTR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpInstr` | -| regexp_like | REGEXP_LIKE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpLike` | -| regexp_match | REGEXP_MATCH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpMatch` | -| regexp_replace | REGEXP_REPLACE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpReplace` | -| regexp_substr | REGEXP_SUBSTR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpSubstr` | -| round | ROUND | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Round` | -| row | ROW | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Row` | -| row_to_json | ROW_TO_JSON | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RowToJson` | -| split_part | SPLIT_PART | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\SplitPart` | -| starts_with | STARTS_WITH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StartsWith` | -| string_agg | STRING_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StringAgg` | -| string_to_array | STRING_TO_ARRAY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StringToArray` | -| to_char | TO_CHAR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToChar` | -| to_date | TO_DATE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToDate` | -| to_json | TO_JSON | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToJson` | -| to_jsonb | TO_JSONB | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToJsonb` | -| to_number | TO_NUMBER | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToNumber` | -| to_timestamp | TO_TIMESTAMP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTimestamp` | -| to_tsquery | TO_TSQUERY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTsquery` | -| to_tsvector | TO_TSVECTOR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTsvector` | -| trunc | TRUNC | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Trunc` | -| tsrange | TSRANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Tsrange` | -| tstzrange | TSTZRANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Tstzrange` | -| unaccent | UNACCENT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Unaccent` | -| unnest | UNNEST | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Unnest` | -| xmlagg | XML_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\XmlAgg` | -| cbrt | CBRT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Cbrt` | -| degrees | DEGREES | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Degrees` | -| exp | EXP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exp` | -| ln | LN | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Ln` | -| log | LOG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Log` | -| pi | PI | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Pi` | -| power | POWER | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Power` | -| radians | RADIANS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Radians` | -| random | RANDOM | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Random` | -| sign | SIGN | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Sign` | -| width_bucket | WIDTH_BUCKET | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\WidthBucket` | - -## PostGIS Spatial Relationship Functions - -These functions determine spatial relationships between geometries. Most return boolean values and **should be used with `= TRUE` or `= FALSE` in DQL**, but there are exceptions: `ST_Relate(geom, geom)` returns text (intersection matrix) and `ST_LineCrossingDirection` returns integer (crossing behavior). - -| PostgreSQL functions | Register for DQL as | Description | Implemented by | -|---|---|---|---| -| ST_3DDWithin | ST_3DDWITHIN | Tests if two 3D geometries are within a given 3D distance | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DDWithin` | -| ST_3DDFullyWithin | ST_3DDFULLYWITHIN | Tests if two 3D geometries are entirely within a given 3D distance | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DDFullyWithin` | -| ST_3DIntersects | ST_3DINTERSECTS | Tests if two geometries spatially intersect in 3D | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DIntersects` | -| ST_Contains | ST_CONTAINS | Tests if every point of B lies in A, and their interiors have a point in common | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Contains` | -| ST_ContainsProperly | ST_CONTAINSPROPERLY | Tests if every point of B lies in the interior of A | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_ContainsProperly` | -| ST_CoveredBy | ST_COVEREDBY | Tests if every point of A lies in B | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_CoveredBy` | -| ST_Covers | ST_COVERS | Tests if every point of B lies in A | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Covers` | -| ST_Crosses | ST_CROSSES | Tests if two geometries have some, but not all, interior points in common | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Crosses` | -| ST_DFullyWithin | ST_DFULLYWITHIN | Tests if a geometry is entirely inside a distance of another | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_DFullyWithin` | -| ST_Disjoint | ST_DISJOINT | Tests if two geometries have no points in common | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Disjoint` | -| ST_DWithin | ST_DWITHIN | Tests if two geometries are within a given distance | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_DWithin` | -| ST_Equals | ST_EQUALS | Tests if two geometries include the same set of points | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Equals` | -| ST_Intersects | ST_INTERSECTS | Tests if two geometries intersect (they have at least one point in common) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Intersects` | -| ST_LineCrossingDirection | ST_LINECROSSINGDIRECTION | Returns a number indicating the crossing behavior of two LineStrings | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_LineCrossingDirection` | -| ST_OrderingEquals | ST_ORDERINGEQUALS | Tests if two geometries represent the same geometry and have points in the same directional order | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_OrderingEquals` | -| ST_Overlaps | ST_OVERLAPS | Tests if two geometries have the same dimension and intersect, but each has at least one point not in the other | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Overlaps` | -| ST_PointInsideCircle | ST_POINTINSIDECIRCLE | Tests if a point geometry is inside a circle defined by a center and radius | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_PointInsideCircle` | -| ST_Relate | ST_RELATE | Tests if two geometries have a topological relationship matching an Intersection Matrix pattern, or computes their Intersection Matrix | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Relate` | -| ST_RelateMatch | ST_RELATEMATCH | Tests if a DE-9IM Intersection Matrix matches an Intersection Matrix pattern | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_RelateMatch` | -| ST_Touches | ST_TOUCHES | Tests if two geometries have at least one point in common, but their interiors do not intersect | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Touches` | -| ST_Within | ST_WITHIN | Tests if every point of A lies in B, and their interiors have a point in common | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Within` | - -**Usage Examples:** -```sql --- Test if geometries intersect -SELECT e FROM Entity e WHERE ST_Intersects(e.geometry, 'POINT(0 0)') = TRUE - --- Test if one geometry contains another -SELECT e FROM Entity e WHERE ST_Contains(e.polygon, e.point) = TRUE - --- Test if geometries are within a distance -SELECT e FROM Entity e WHERE ST_DWithin(e.geometry, 'POINT(0 0)', 1000) = TRUE - --- Test topological relationships with intersection matrix -SELECT e FROM Entity e WHERE ST_Relate(e.geometry1, e.geometry2, 'T*T***T**') = TRUE - --- Get intersection matrix between two geometries -SELECT e, ST_Relate(e.geometry1, e.geometry2) as matrix FROM Entity e - --- Test if geometries are disjoint (no intersection) -SELECT e FROM Entity e WHERE ST_Relate(e.geometry1, e.geometry2, 'FF*FF****') = TRUE - --- Test if one geometry contains another (contains relationship) -SELECT e FROM Entity e WHERE ST_Relate(e.geometry1, e.geometry2, 'T*****FF*') = TRUE - --- Test if geometries touch (boundary intersection only) -SELECT e FROM Entity e WHERE ST_Relate(e.geometry1, e.geometry2, 'FT*******') = TRUE - --- Test if point is inside circle -SELECT e FROM Entity e WHERE ST_PointInsideCircle(e.point, 0, 0, 1000) = TRUE - --- Analyze line crossing behavior -SELECT e, ST_LineCrossingDirection(e.line1, e.line2) as crossing FROM Entity e -WHERE ST_LineCrossingDirection(e.line1, e.line2) != 0 - --- Find lines that cross from left to right -SELECT e FROM Entity e WHERE ST_LineCrossingDirection(e.line1, e.line2) = 1 - --- Find lines that cross from right to left -SELECT e FROM Entity e WHERE ST_LineCrossingDirection(e.line1, e.line2) = -1 - --- Find lines with multiple crossings -SELECT e FROM Entity e WHERE ST_LineCrossingDirection(e.line1, e.line2) = 2 - --- Calculate areas and perimeters -SELECT e, ST_Area(e.polygon) as area, ST_Perimeter(e.polygon) as perimeter -FROM Entity e WHERE e.polygon IS NOT NULL - --- Calculate 3D measurements -SELECT e, ST_3DLength(e.line3d) as length3d, ST_3DPerimeter(e.polygon3d) as perimeter3d -FROM Entity e WHERE e.line3d IS NOT NULL OR e.polygon3d IS NOT NULL - --- Find geometries within distance -SELECT e, ST_Distance(e.geometry, 'POINT(0 0)') as distance -FROM Entity e ORDER BY distance LIMIT 10 - --- Calculate azimuth between points -SELECT e, ST_Azimuth(e.point1, e.point2) as azimuth_radians, - DEGREES(ST_Azimuth(e.point1, e.point2)) as azimuth_degrees -FROM Entity e WHERE e.point1 IS NOT NULL AND e.point2 IS NOT NULL - --- Geometric operations and transformations --- Create buffer around geometry -SELECT e, ST_Buffer(e.geometry, 100) as buffered_geometry FROM Entity e - --- Simplify complex geometries -SELECT e, ST_Simplify(e.complex_geometry, 0.5) as simplified_geometry FROM Entity e - --- Transform coordinate systems -SELECT e, ST_Transform(e.geometry, 4326) as wgs84_geometry FROM Entity e - --- Get convex hull -SELECT e, ST_ConvexHull(e.geometry) as convex_hull FROM Entity e - --- Scale and rotate geometries -SELECT e, ST_Scale(ST_Rotate(e.geometry, PI()/4), 2, 2) as scaled_rotated_geometry FROM Entity e - --- Array and JSON operations --- Check if array contains specific elements -SELECT e FROM Entity e WHERE CONTAINS(e.tags, ARRAY['important', 'urgent']) = TRUE - --- Find entities with overlapping arrays -SELECT e FROM Entity e WHERE OVERLAPS(e.categories, ARRAY['admin', 'user']) = TRUE - --- Extract JSON field values -SELECT e, JSON_GET_FIELD_AS_TEXT(e.metadata, 'status') as status FROM Entity e - --- Aggregate values into arrays -SELECT e.category, ARRAY_AGG(e.id) as entity_ids FROM Entity e GROUP BY e.category - --- Build JSON objects -SELECT e.id, JSON_BUILD_OBJECT('name', e.name, 'type', e.type) as json_data FROM Entity e - --- Text and pattern matching --- Case-insensitive pattern matching -SELECT e FROM Entity e WHERE IREGEXP(e.name, '^admin.*') = TRUE - --- Extract text using regex -SELECT e, REGEXP_SUBSTR(e.description, 'version [0-9.]+') as version FROM Entity e - --- Replace text patterns -SELECT e, REGEXP_REPLACE(e.content, 'old_pattern', 'new_pattern') as updated_content FROM Entity e - --- Check if text starts with specific string -SELECT e FROM Entity e WHERE STARTS_WITH(e.name, 'user_') = TRUE - --- Full-text search -SELECT e FROM Entity e WHERE TSMATCH(e.search_vector, 'query & terms') = TRUE - --- Date and range operations --- Add days to date -SELECT e, DATE_ADD(e.created_at, 30) as expiry_date FROM Entity e - --- Extract date components -SELECT e, DATE_EXTRACT(e.timestamp, 'YEAR') as year FROM Entity e - --- Check date overlaps -SELECT e FROM Entity e WHERE DATE_OVERLAPS(e.period1, e.period2) = TRUE - --- Create date ranges -SELECT e, DATERANGE(e.start_date, e.end_date) as date_range FROM Entity e - --- Mathematical operations -SELECT e, POWER(e.value, 2) as squared, SQRT(e.value) as root FROM Entity e -WHERE e.value > 0 - -**πŸ“ Notes:** -- `ST_Relate` is a variadic function that accepts 2 or 3 arguments: - - With 2 arguments: returns text (intersection matrix) - - With 3 arguments: returns boolean (relationship test) -- `ST_LineCrossingDirection` returns an integer (0, 1, -1, or 2) indicating crossing behavior: - - `0`: No crossing - - `1`: Left to right crossing - - `-1`: Right to left crossing - - `2`: Multiple crossings -- All other functions return boolean values and should be used with `= TRUE` or `= FALSE` in DQL - -**πŸ” DE-9IM Intersection Matrix Patterns for ST_Relate:** - -The DE-9IM (Dimensionally Extended 9-Intersection Model) uses a 9-character pattern where each character represents the intersection between: -- Interior (I), Boundary (B), and Exterior (E) of geometry A -- Interior (I), Boundary (B), and Exterior (E) of geometry B - -Common patterns: -- `FF*FF****` = Disjoint (no intersection) -- `T*****FF*` = Contains (A contains B) -- `T*T***T**` = Intersects (geometries intersect) -- `FT*******` = Touches (boundary intersection only) -- `F**T*****` = Within (A is within B) -- `T*T***T**` = Overlaps (partial overlap) - -**πŸ“Š Function Return Types:** -- **Boolean functions**: Use with `= TRUE` or `= FALSE` in DQL -- **Numeric functions**: Return values for calculations and ordering -- **Geometry functions**: Return new geometries for further operations -- **Text functions**: Return strings for pattern matching and display - -## PostGIS Measurement Functions - -These functions calculate various measurements of geometries including lengths, areas, distances, and angles. - -| PostgreSQL functions | Register for DQL as | Description | Implemented by | -|---|---|---|---| -| ST_Length | ST_LENGTH | Returns the 2D length of LineString/MultiLineString or perimeter of areal geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Length` | -| ST_Length2D | ST_LENGTH2D | Returns the 2D length, ignoring Z coordinates | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Length2D` | -| ST_3DLength | ST_3DLENGTH | Returns the 3D length of LineString/MultiLineString or 3D perimeter of areal geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DLength` | -| ST_Area | ST_AREA | Returns the area of polygon/multi-polygon geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Area` | -| ST_Perimeter | ST_PERIMETER | Returns the 2D perimeter of polygon/multi-polygon geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Perimeter` | -| ST_3DPerimeter | ST_3DPERIMETER | Returns the 3D perimeter of polygon/multi-polygon geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DPerimeter` | -| ST_Distance | ST_DISTANCE | Returns the 2D distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Distance` | -| ST_3DDistance | ST_3DDISTANCE | Returns the 3D distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DDistance` | -| ST_Centroid | ST_CENTROID | Returns the geometric center of a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Centroid` | -| ST_MaxDistance | ST_MAXDISTANCE | Returns the maximum distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_MaxDistance` | -| ST_HausdorffDistance | ST_HAUSDORFFDISTANCE | Returns the Hausdorff distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_HausdorffDistance` | -| ST_FrechetDistance | ST_FRECHETDISTANCE | Returns the FrΓ©chet distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_FrechetDistance` | -| ST_Azimuth | ST_AZIMUTH | Returns the azimuth between two points in radians | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Azimuth` | -| ST_Project | ST_PROJECT | Projects a point along a geodesic by distance and azimuth | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Project` | - -## PostGIS Overlay Functions - -These functions perform geometric operations between geometries including intersection, union, difference, and splitting. - -| PostgreSQL functions | Register for DQL as | Description | Implemented by | -|---|---|---|---| -| ST_Intersection | ST_INTERSECTION | Returns the point set intersection of two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Intersection` | -| ST_Union | ST_UNION | Returns the point set union of two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Union` | -| ST_UnaryUnion | ST_UNARYUNION | Performs unary union on a geometry (dissolves internal boundaries) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_UnaryUnion` | -| ST_Difference | ST_DIFFERENCE | Returns the point set difference (A - B) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Difference` | -| ST_SymDifference | ST_SYMDIFFERENCE | Returns the symmetric difference of two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_SymDifference` | -| ST_Split | ST_SPLIT | Splits a geometry by another geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Split` | -| ST_Subdivide | ST_SUBDIVIDE | Subdivides a geometry into smaller parts | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Subdivide` | -| ST_ClipByBox2D | ST_CLIPBYBOX2D | Clips a geometry by a 2D box | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_ClipByBox2D` | -| ST_Collect | ST_COLLECT | Collects geometries into a geometry collection | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Collect` | -| ST_CollectionExtract | ST_COLLECTIONEXTRACT | Extracts a specific type from a geometry collection | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_CollectionExtract` | -| ST_CollectionHomogenize | ST_COLLECTIONHOMOGENIZE | Homogenizes a geometry collection | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_CollectionHomogenize` | - -## PostGIS Geometry Processing Functions - -These functions modify and transform geometries including buffering, simplification, coordinate system changes, and geometric transformations. - -| PostgreSQL functions | Register for DQL as | Description | Implemented by | -|---|---|---|---| -| ST_Buffer | ST_BUFFER | Creates a buffer around a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Buffer` | -| ST_Simplify | ST_SIMPLIFY | Simplifies geometry using Douglas-Peucker algorithm | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Simplify` | -| ST_SimplifyPreserveTopology | ST_SIMPLIFYPRESERVETOPOLOGY | Simplifies geometry while preserving topology | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_SimplifyPreserveTopology` | -| ST_SimplifyVW | ST_SIMPLIFYVW | Simplifies geometry using Visvalingam-Whyatt algorithm | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_SimplifyVW` | -| ST_ConvexHull | ST_CONVEXHULL | Returns the convex hull of a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_ConvexHull` | -| ST_Envelope | ST_ENVELOPE | Returns the bounding box as a polygon | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Envelope` | -| ST_Boundary | ST_BOUNDARY | Returns the boundary of a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Boundary` | -| ST_Transform | ST_TRANSFORM | Transforms geometry to different coordinate system | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Transform` | -| ST_Reverse | ST_REVERSE | Reverses the order of points in a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Reverse` | -| ST_Force2D | ST_FORCE2D | Forces geometry to 2D by removing Z/M coordinates | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Force2D` | -| ST_Force3D | ST_FORCE3D | Forces geometry to 3D by adding Z coordinate if needed | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Force3D` | -| ST_Force4D | ST_FORCE4D | Forces geometry to 4D by adding Z and M coordinates if needed | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Force4D` | -| ST_CurveToLine | ST_CURVETOLINE | Converts curved geometries to linear geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_CurveToLine` | -| ST_LineToCurve | ST_LINETOCURVE | Converts linear geometries to curved geometries where possible | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_LineToCurve` | -| ST_Scale | ST_SCALE | Scales a geometry by given factors | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Scale` | -| ST_Rotate | ST_ROTATE | Rotates a geometry by given angle | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Rotate` | -| ST_Translate | ST_TRANSLATE | Translates a geometry by given offsets | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Translate` | - -# Bonus Helpers - -| PostgreSQL functions | Register for DQL as | Implemented by | -|---|---|---| -| array | ARRAY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Arr` | -| value = ANY(list of values) | IN_ARRAY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\InArray` | -| CAST(json ->> node as BIGINT) | JSON_GET_FIELD_AS_INTEGER | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetFieldAsInteger` | +## πŸ“š Function and Operator Categories + +This library provides comprehensive PostgreSQL function and operator support organized into the following categories: + +### **πŸ”— Array and JSON Functions** +Complete documentation for array manipulation and JSON/JSONB operations. +- **[Array and JSON Functions and Operators](ARRAY-AND-JSON-FUNCTIONS.md)** +- Includes: Array operators (`@>`, `<@`, `&&`), JSON operators (`->`, `->>`, `#>`, `#>>`), array functions, JSON functions, JSONB functions + +### **πŸ—ΊοΈ PostGIS Spatial Functions** +Complete documentation for PostGIS spatial operations and geometry processing. +- **[PostGIS Spatial Functions and Operators](SPATIAL-FUNCTIONS-AND-OPERATORS.md)** +- Includes: Bounding box operators, distance operators, spatial relationship functions, measurement functions, overlay functions, geometry processing functions + +### **πŸ“ Text and Pattern Functions** +Complete documentation for text processing, pattern matching, and regular expressions. +- **[Text and Pattern Functions and Operators](TEXT-AND-PATTERN-FUNCTIONS.md)** +- Includes: Text operators (`~`, `ilike`, `@@`), regular expression functions, text processing functions, full-text search functions + +### **πŸ“… Date and Range Functions** +Complete documentation for date/time operations and range type functions. +- **[Date, Time, and Range Functions](DATE-AND-RANGE-FUNCTIONS.md)** +- Includes: Date/time functions, range creation functions, range operators, temporal operations + +### **πŸ”’ Mathematical Functions** +Complete documentation for mathematical operations and utility functions. +- **[Mathematical and Utility Functions](MATHEMATICAL-FUNCTIONS.md)** +- Includes: Mathematical functions, type conversion functions, formatting functions, utility functions + +## πŸš€ Quick Reference + +### Most Commonly Used Functions + +**Array Operations:** +- `CONTAINS` (`@>`) - Test if array/range contains elements +- `OVERLAPS` (`&&`) - Test if arrays/ranges overlap +- `ARRAY_AGG` - Aggregate values into arrays + +**JSON Operations:** +- `JSON_GET_FIELD_AS_TEXT` (`->>`) - Extract JSON field as text +- `JSON_BUILD_OBJECT` - Build JSON objects +- `JSONB_PATH_EXISTS` - Test JSON path existence + +**Spatial Operations:** +- `ST_INTERSECTS` - Test if geometries intersect +- `ST_DISTANCE` - Calculate distance between geometries +- `ST_CONTAINS` - Test spatial containment + +**Text Operations:** +- `ILIKE` - Case-insensitive pattern matching +- `REGEXP` (`~`) - Regular expression matching +- `STARTS_WITH` - Test if text starts with substring + +**Date/Range Operations:** +- `DATE_ADD` - Add interval to date +- `DATE_EXTRACT` - Extract date components +- `DATERANGE` - Create date ranges + +**Mathematical Operations:** +- `GREATEST`/`LEAST` - Find maximum/minimum values +- `ROUND` - Round numeric values +- `RANDOM` - Generate random numbers + +## πŸ“‹ Summary of Available Function Categories + +### **Array & JSON Functions** +- **Array Operations**: Manipulate PostgreSQL arrays (append, remove, replace, shuffle) +- **JSON Functions**: Work with JSON/JSONB data types +- **JSONB Path Functions**: Advanced JSONB querying with path expressions + +### **Spatial Functions (PostGIS)** +- **Relationship Functions**: Test spatial relationships between geometries +- **Measurement Functions**: Calculate distances, areas, lengths, and angles +- **Overlay Functions**: Perform geometric operations (intersection, union, difference) +- **Processing Functions**: Transform, simplify, and modify geometries + +### **Text & Pattern Functions** +- **Regexp Functions**: Pattern matching and replacement +- **Text Functions**: String manipulation and searching +- **Full-Text Search**: PostgreSQL's text search capabilities + +### **Date & Range Functions** +- **Date Operations**: Add/subtract dates, extract components +- **Range Types**: Create and work with various range types +- **Overlap Testing**: Check if date ranges overlap + +### **Mathematical Functions** +- **Basic Math**: Power, square root, trigonometric functions +- **Aggregation**: Array and JSON aggregation functions +- **Utility Functions**: Random numbers, rounding, type casting + +### **Operators** +- **Array Operators**: Contains, overlaps, element testing +- **Spatial Operators**: Bounding box and distance operations +- **Text Operators**: Pattern matching and concatenation + +--- + +**πŸ’‘ Tips for Usage:** +1. **Boolean functions** should be used with `= TRUE` or `= FALSE` in DQL +2. **Spatial functions** work best with proper geometry types and indexes +3. **Array functions** provide efficient PostgreSQL array operations +4. **JSON functions** support both JSON and JSONB data types +5. **Range functions** provide efficient storage and querying for value ranges +6. **Mathematical functions** work with numeric types and return appropriate precision + +--- + +**πŸ“– For More Information:** +- [Available Types](AVAILABLE-TYPES.md) - PostgreSQL data types supported by this library +- [Value Objects for Range Types](RANGE-TYPES.md) - Working with range value objects +- [Common Use Cases and Examples](USE-CASES-AND-EXAMPLES.md) - Practical examples and patterns +- [Spatial Types](SPATIAL-TYPES.md) - PostGIS geometry and geography types +- [Geometry Arrays](GEOMETRY-ARRAYS.md) - Working with arrays of geometric types diff --git a/docs/DATE-AND-RANGE-FUNCTIONS.md b/docs/DATE-AND-RANGE-FUNCTIONS.md new file mode 100644 index 00000000..575f5b60 --- /dev/null +++ b/docs/DATE-AND-RANGE-FUNCTIONS.md @@ -0,0 +1,167 @@ +# Date, Time, and Range Functions + +This document covers PostgreSQL date, time, and range functions available in this library. + +## Date and Time Functions + +| PostgreSQL functions | Register for DQL as | Implemented by | +|---|---|---| +| date_add | DATE_ADD | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateAdd` | +| date_bin | DATE_BIN | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateBin` | +| date_subtract | DATE_SUBTRACT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateSubtract` | +| extract | DATE_EXTRACT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateExtract` | +| overlaps | DATE_OVERLAPS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateOverlaps` | +| to_date | TO_DATE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToDate` | +| to_timestamp | TO_TIMESTAMP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTimestamp` | + +## Range Functions + +PostgreSQL provides several range types for representing ranges of values. These functions create and work with range types. + +| PostgreSQL functions | Register for DQL as | Implemented by | +|---|---|---| +| daterange | DATERANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Daterange` | +| int4range | INT4RANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Int4range` | +| int8range | INT8RANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Int8range` | +| numrange | NUMRANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Numrange` | +| tsrange | TSRANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Tsrange` | +| tstzrange | TSTZRANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Tstzrange` | + +## Range Operators + +Range types work with the general operators for containment and overlap testing: + +| PostgreSQL operator | Register for DQL as | Description | Implemented by | +|---|---|---|---| +| @> | CONTAINS | Tests if range contains element or other range | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Contains` | +| <@ | IS_CONTAINED_BY | Tests if element or range is contained by range | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\IsContainedBy` | +| && | OVERLAPS | Tests if ranges overlap | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Overlaps` | + +## Usage Examples + +```sql +-- Date and time operations +-- Add days to date +SELECT e, DATE_ADD(e.created_at, 30) as expiry_date FROM Entity e + +-- Subtract days from date +SELECT e, DATE_SUBTRACT(e.created_at, 7) as week_ago FROM Entity e + +-- Extract date components +SELECT e, DATE_EXTRACT(e.timestamp, 'YEAR') as year, + DATE_EXTRACT(e.timestamp, 'MONTH') as month, + DATE_EXTRACT(e.timestamp, 'DAY') as day FROM Entity e + +-- Check date overlaps +SELECT e FROM Entity e WHERE DATE_OVERLAPS(e.period1, e.period2) = TRUE + +-- Convert string to date +SELECT e, TO_DATE(e.date_string, 'YYYY-MM-DD') as parsed_date FROM Entity e + +-- Convert string to timestamp +SELECT e, TO_TIMESTAMP(e.timestamp_string, 'YYYY-MM-DD HH24:MI:SS') as parsed_timestamp FROM Entity e + +-- Bin dates into intervals +SELECT e, DATE_BIN('1 day', e.created_at, '2023-01-01') as day_bin FROM Entity e + +-- Range operations +-- Create date ranges +SELECT e, DATERANGE(e.start_date, e.end_date) as date_range FROM Entity e + +-- Create inclusive date ranges +SELECT e, DATERANGE(e.start_date, e.end_date, '[]') as inclusive_range FROM Entity e + +-- Create timestamp ranges +SELECT e, TSRANGE(e.start_time, e.end_time) as time_range FROM Entity e + +-- Create timestamp with timezone ranges +SELECT e, TSTZRANGE(e.start_time_tz, e.end_time_tz) as tz_range FROM Entity e + +-- Create integer ranges +SELECT e, INT4RANGE(e.min_value, e.max_value) as int_range FROM Entity e + +-- Create numeric ranges +SELECT e, NUMRANGE(e.min_price, e.max_price) as price_range FROM Entity e + +-- Test if range contains value +SELECT e FROM Entity e WHERE CONTAINS(e.age_range, 25) = TRUE + +-- Test if range contains another range +SELECT e FROM Entity e WHERE CONTAINS(e.outer_range, e.inner_range) = TRUE + +-- Test if value is in range +SELECT e FROM Entity e WHERE IS_CONTAINED_BY(e.age, e.valid_age_range) = TRUE + +-- Test if ranges overlap +SELECT e FROM Entity e WHERE OVERLAPS(e.period1, e.period2) = TRUE + +-- Find entities with overlapping date ranges +SELECT e1, e2 FROM Entity e1, Entity e2 +WHERE e1.id != e2.id AND OVERLAPS(e1.active_period, e2.active_period) = TRUE + +-- Find entities active during a specific period +SELECT e FROM Entity e +WHERE OVERLAPS(e.active_period, DATERANGE('2023-01-01', '2023-12-31')) = TRUE + +-- Find entities with prices in a specific range +SELECT e FROM Entity e +WHERE OVERLAPS(e.price_range, NUMRANGE(100, 500)) = TRUE + +-- Complex date queries +-- Find entities created in the last 30 days +SELECT e FROM Entity e +WHERE CONTAINS(DATERANGE(DATE_SUBTRACT(CURRENT_DATE, 30), CURRENT_DATE), e.created_at) = TRUE + +-- Find entities with overlapping business hours +SELECT e FROM Entity e +WHERE OVERLAPS(e.business_hours, TSRANGE('09:00:00', '17:00:00')) = TRUE + +-- Group by date ranges +SELECT DATERANGE(DATE_BIN('1 month', e.created_at, '2023-01-01'), + DATE_ADD(DATE_BIN('1 month', e.created_at, '2023-01-01'), 30)) as month_range, + COUNT(*) as entity_count +FROM Entity e +GROUP BY month_range +ORDER BY month_range + +-- Find gaps in date ranges +SELECT e1.end_date, e2.start_date, + DATERANGE(e1.end_date, e2.start_date) as gap_range +FROM Entity e1, Entity e2 +WHERE e1.end_date < e2.start_date + AND NOT EXISTS ( + SELECT 1 FROM Entity e3 + WHERE OVERLAPS(DATERANGE(e1.end_date, e2.start_date), + DATERANGE(e3.start_date, e3.end_date)) = TRUE + ) +``` + +**πŸ“ Range Type Notes:** + +### Range Bounds +PostgreSQL ranges support different bound types: +- `'[)'` - Lower bound inclusive, upper bound exclusive (default) +- `'()'` - Both bounds exclusive +- `'[]'` - Both bounds inclusive +- `'(]'` - Lower bound exclusive, upper bound inclusive + +### Range Types Available +- **daterange**: Date ranges (without time) +- **tsrange**: Timestamp ranges (without timezone) +- **tstzrange**: Timestamp ranges (with timezone) +- **int4range**: 32-bit integer ranges +- **int8range**: 64-bit integer ranges +- **numrange**: Numeric ranges (decimal/float) + +### Empty and Infinite Ranges +- Empty ranges: `DATERANGE(NULL, NULL)` +- Infinite ranges: Use `NULL` for unbounded sides +- Example: `DATERANGE('2023-01-01', NULL)` represents "from 2023-01-01 onwards" + +**πŸ’‘ Tips for Usage:** +1. **Range operators** should be used with `= TRUE` or `= FALSE` in DQL +2. **Date functions** work with PostgreSQL's rich date/time types +3. **Range types** provide efficient storage and querying for value ranges +4. **Overlaps testing** is optimized with proper indexes on range columns +5. **Date extraction** supports many field types: YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, DOW (day of week), DOY (day of year) +6. **Range bounds** default to `[)` (inclusive lower, exclusive upper) if not specified diff --git a/docs/INTEGRATING-WITH-DOCTRINE.md b/docs/INTEGRATING-WITH-DOCTRINE.md index 1db7d947..a30892e8 100644 --- a/docs/INTEGRATING-WITH-DOCTRINE.md +++ b/docs/INTEGRATING-WITH-DOCTRINE.md @@ -47,8 +47,12 @@ Type::addType('tstzrange', "MartinGeorgiev\\Doctrine\\DBAL\\Types\\TstzRange"); *Register the functions you'll use in your DQL queries* - -Full set of the available functions and extra operators can be found [here](AVAILABLE-FUNCTIONS-AND-OPERATORS.md). +Full set of the available functions and extra operators can be found in the [Available Functions and Operators](AVAILABLE-FUNCTIONS-AND-OPERATORS.md) documentation and its specialized sub-pages: +- [Array and JSON Functions](ARRAY-AND-JSON-FUNCTIONS.md) +- [PostGIS Spatial Functions](SPATIAL-FUNCTIONS-AND-OPERATORS.md) +- [Text and Pattern Functions](TEXT-AND-PATTERN-FUNCTIONS.md) +- [Date and Range Functions](DATE-AND-RANGE-FUNCTIONS.md) +- [Mathematical Functions](MATHEMATICAL-FUNCTIONS.md) ```php MartinGeorgiev\Doctrine\DBAL\Types\Jsonb::class, 'jsonb[]' => MartinGeorgiev\Doctrine\DBAL\Types\JsonbArray::class, - 'cnet' => MartinGeorgiev\Doctrine\DBAL\Types\Cnet::class, - 'cnet[]' => MartinGeorgiev\Doctrine\DBAL\Types\CnetArray::class, + 'cidr' => MartinGeorgiev\Doctrine\DBAL\Types\Cidr::class, + 'cidr[]' => MartinGeorgiev\Doctrine\DBAL\Types\CidrArray::class, 'inet' => MartinGeorgiev\Doctrine\DBAL\Types\Inet::class, 'inet[]' => MartinGeorgiev\Doctrine\DBAL\Types\InetArray::class, 'macaddr' => MartinGeorgiev\Doctrine\DBAL\Types\Macaddr::class, @@ -94,7 +94,12 @@ return [ *Register the functions you'll use in your DQL queries* -Full set of the available functions and extra operators can be found [here](AVAILABLE-FUNCTIONS-AND-OPERATORS.md). +Full set of the available functions and extra operators can be found in the [Available Functions and Operators](AVAILABLE-FUNCTIONS-AND-OPERATORS.md) documentation and its specialized sub-pages: +- [Array and JSON Functions](ARRAY-AND-JSON-FUNCTIONS.md) +- [PostGIS Spatial Functions](SPATIAL-FUNCTIONS-AND-OPERATORS.md) +- [Text and Pattern Functions](TEXT-AND-PATTERN-FUNCTIONS.md) +- [Date and Range Functions](DATE-AND-RANGE-FUNCTIONS.md) +- [Mathematical Functions](MATHEMATICAL-FUNCTIONS.md) ```php 0 + +-- Calculate cube root +SELECT e, CBRT(e.volume) as side_length FROM Entity e + +-- Round values +SELECT e, ROUND(e.price, 2) as rounded_price FROM Entity e + +-- Ceiling and floor +SELECT e, CEIL(e.rating) as rating_ceiling, FLOOR(e.rating) as rating_floor FROM Entity e + +-- Truncate decimal places +SELECT e, TRUNC(e.value, 2) as truncated_value FROM Entity e + +-- Find maximum and minimum values +SELECT e, GREATEST(e.value1, e.value2, e.value3) as max_value, + LEAST(e.value1, e.value2, e.value3) as min_value FROM Entity e + +-- Logarithmic functions +SELECT e, LN(e.value) as natural_log, LOG(10, e.value) as log_base_10 FROM Entity e +WHERE e.value > 0 + +-- Exponential function +SELECT e, EXP(e.exponent) as exponential_value FROM Entity e + +-- Trigonometric conversions +SELECT e, DEGREES(e.radians) as degrees, RADIANS(e.degrees) as radians FROM Entity e + +-- Sign function +SELECT e, SIGN(e.balance) as balance_sign FROM Entity e + +-- Random values +SELECT e, RANDOM() as random_value FROM Entity e + +-- Pi constant +SELECT e, PI() * e.radius * e.radius as circle_area FROM Entity e + +-- Width bucket for histograms +SELECT WIDTH_BUCKET(e.score, 0, 100, 10) as bucket, COUNT(*) as count +FROM Entity e GROUP BY bucket ORDER BY bucket + +-- Type conversion and formatting +-- Cast values to different types +SELECT e, CAST(e.text_number as INTEGER) as number_value FROM Entity e + +-- Format numbers as text +SELECT e, TO_CHAR(e.amount, '999,999.99') as formatted_amount FROM Entity e + +-- Parse text as numbers +SELECT e, TO_NUMBER(e.price_text, '999.99') as price_number FROM Entity e + +-- Format dates +SELECT e, TO_CHAR(e.created_at, 'YYYY-MM-DD HH24:MI:SS') as formatted_date FROM Entity e + +-- Utility functions +-- Get any value from a group (useful for aggregation) +SELECT e.category, ANY_VALUE(e.description) as sample_description +FROM Entity e GROUP BY e.category + +-- Create row values +SELECT e, ROW(e.x, e.y, e.z) as coordinates FROM Entity e + +-- Convert row to JSON +SELECT e, ROW_TO_JSON(ROW(e.name, e.value, e.category)) as entity_json FROM Entity e + +-- XML aggregation +SELECT e.category, XML_AGG(e.name) as names_xml FROM Entity e GROUP BY e.category + +-- Complex mathematical calculations +-- Calculate compound interest +SELECT e, e.principal * POWER(1 + e.rate, e.years) as compound_amount FROM Entity e + +-- Calculate distance using Pythagorean theorem +SELECT e, POWER(POWER(e.x2 - e.x1, 2) + POWER(e.y2 - e.y1, 2), 0.5) as distance FROM Entity e + +-- Normalize values to 0-1 range +SELECT e, (e.value - e.min_value) / (e.max_value - e.min_value) as normalized FROM Entity e +WHERE e.max_value != e.min_value + +-- Calculate percentiles using width_bucket +SELECT e.score, + WIDTH_BUCKET(e.score, 0, 100, 100) as percentile +FROM Entity e ORDER BY e.score + +-- Statistical calculations +SELECT e.category, + COUNT(*) as count, + ROUND(AVG(e.value), 2) as avg_value, + GREATEST(MAX(e.value), 0) as max_value, + LEAST(MIN(e.value), 0) as min_value +FROM Entity e GROUP BY e.category + +-- Random sampling +SELECT e FROM Entity e WHERE RANDOM() < 0.1 ORDER BY RANDOM() LIMIT 100 + +-- Bucketing for analytics +SELECT WIDTH_BUCKET(e.age, 0, 100, 10) as age_bucket, + COUNT(*) as count, + ROUND(AVG(e.income), 2) as avg_income +FROM Entity e +GROUP BY age_bucket +ORDER BY age_bucket + +-- Format currency +SELECT e, TO_CHAR(e.price, 'L999,999.99') as formatted_price FROM Entity e + +-- Parse formatted numbers +SELECT e, TO_NUMBER(e.formatted_value, '999,999.99') as parsed_value FROM Entity e +``` + +**πŸ“ Function Categories:** + +### **Mathematical Functions** +- **Basic Math**: CEIL, FLOOR, ROUND, TRUNC for rounding operations +- **Power Functions**: POWER, CBRT, EXP for exponential calculations +- **Logarithmic**: LN, LOG for logarithmic operations +- **Trigonometric**: DEGREES, RADIANS for angle conversions +- **Comparison**: GREATEST, LEAST for finding extremes +- **Utility**: SIGN, RANDOM, PI for various mathematical needs + +### **Statistical Functions** +- **WIDTH_BUCKET**: Creates histogram buckets for data analysis +- **ANY_VALUE**: Returns any value from a group (useful with GROUP BY) + +### **Formatting Functions** +- **TO_CHAR**: Converts numbers and dates to formatted strings +- **TO_NUMBER**: Parses formatted text as numbers +- **CAST**: General type conversion + +### **Data Structure Functions** +- **ROW**: Creates row values +- **ROW_TO_JSON**: Converts rows to JSON format +- **XML_AGG**: Aggregates values into XML format + +**πŸ’‘ Tips for Usage:** +1. **Mathematical functions** work with numeric types and return appropriate precision +2. **Formatting functions** support PostgreSQL's rich formatting patterns +3. **WIDTH_BUCKET** is excellent for creating histograms and analytics +4. **RANDOM()** generates values between 0 and 1 +5. **GREATEST/LEAST** can take multiple arguments and handle NULL values +6. **Type conversion** with CAST supports all PostgreSQL data types +7. **Logarithmic functions** require positive input values diff --git a/docs/SPATIAL-FUNCTIONS-AND-OPERATORS.md b/docs/SPATIAL-FUNCTIONS-AND-OPERATORS.md new file mode 100644 index 00000000..aeb4df40 --- /dev/null +++ b/docs/SPATIAL-FUNCTIONS-AND-OPERATORS.md @@ -0,0 +1,259 @@ +# PostGIS Spatial Functions and Operators + +This document covers PostGIS spatial functions and operators available in this library for working with geometry and geography data types. + +## PostGIS Spatial Operators + +**⚠️ Important**: Some operators have dual meanings for different data types. Use the specific DQL function names to avoid conflicts: + +- **`@`**: Use `CONTAINS` for arrays/JSON, `SPATIAL_CONTAINED_BY` for geometry/geography +- **`~`**: Use `REGEXP` for text patterns, `SPATIAL_CONTAINS` for geometry/geography +- **`&&`**: Use `OVERLAPS` for arrays/JSON, spatial overlaps work automatically with geometry/geography + +**πŸ“ Compatibility Notes**: +- Most bounding box operators work primarily with **geometry** types +- **Geography** types have limited operator support (mainly `&&`, `<->`, `<@>`) +- **3D/n-dimensional operators** may require explicit type casting: `ST_GeomFromText('POINT Z(0 0 0)')` +- Some advanced operators (`&&&`, `<<#>>`) may not be available in all PostGIS versions + +### Bounding Box Operators + +These operators work with geometry and geography bounding boxes. All return boolean values and **should be used with `= TRUE` or `= FALSE` in DQL**. + +| PostgreSQL operator | Register for DQL as | Description | Implemented by | +|---|---|---|---| +| &< | OVERLAPS_LEFT | Returns TRUE if A's bounding box overlaps or is to the left of B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\OverlapsLeft` | +| &> | OVERLAPS_RIGHT | Returns TRUE if A's bounding box overlaps or is to the right of B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\OverlapsRight` | +| << | STRICTLY_LEFT | Returns TRUE if A's bounding box is strictly to the left of B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\StrictlyLeft` | +| >> | STRICTLY_RIGHT | Returns TRUE if A's bounding box is strictly to the right of B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\StrictlyRight` | +| @ | SPATIAL_CONTAINED_BY | Returns TRUE if A's bounding box is contained by B's (**spatial version**) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\SpatialContainedBy` | +| ~ | SPATIAL_CONTAINS | Returns TRUE if A's bounding box contains B's (**spatial version**) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\SpatialContains` | +| ~= | SPATIAL_SAME | Returns TRUE if A's bounding box is the same as B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\SpatialSame` | +| \|&> | OVERLAPS_ABOVE | Returns TRUE if A's bounding box overlaps or is above B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\OverlapsAbove` | +| \|>> | STRICTLY_ABOVE | Returns TRUE if A's bounding box is strictly above B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\StrictlyAbove` | +| &<\| | OVERLAPS_BELOW | Returns TRUE if A's bounding box overlaps or is below B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\OverlapsBelow` | +| <<\| | STRICTLY_BELOW | Returns TRUE if A's bounding box is strictly below B's | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\StrictlyBelow` | +| &&& | ND_OVERLAPS | Returns TRUE if A's n-D bounding box intersects B's n-D bounding box | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\NDimensionalOverlaps` | + +### Distance Operators + +These operators calculate distances between geometries. All return numeric values. + +| PostgreSQL operator | Register for DQL as | Description | Implemented by | +|---|---|---|---| +| <-> | GEOMETRY_DISTANCE | Returns the 2D distance between A and B geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\GeometryDistance` | +| <@> | DISTANCE | Returns distance between points (legacy operator) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Distance` | +| \|=\| | TRAJECTORY_DISTANCE | Returns distance between trajectories at closest point of approach | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\TrajectoryDistance` | +| <#> | BOUNDING_BOX_DISTANCE | Returns the 2D distance between A and B bounding boxes | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\BoundingBoxDistance` | +| <<->> | ND_CENTROID_DISTANCE | Returns n-D distance between centroids of bounding boxes | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\NDimensionalCentroidDistance` | +| <<#>> | ND_BOUNDING_BOX_DISTANCE | Returns the n-D distance between A and B bounding boxes | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\NDimensionalBoundingBoxDistance` | + +## PostGIS Spatial Relationship Functions + +These functions determine spatial relationships between geometries. Most return boolean values and **should be used with `= TRUE` or `= FALSE` in DQL**, but there are exceptions: `ST_Relate(geom, geom)` returns text (intersection matrix) and `ST_LineCrossingDirection` returns integer (crossing behavior). + +| PostgreSQL functions | Register for DQL as | Description | Implemented by | +|---|---|---|---| +| ST_3DDWithin | ST_3DDWITHIN | Tests if two 3D geometries are within a given 3D distance | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DDWithin` | +| ST_3DDFullyWithin | ST_3DDFULLYWITHIN | Tests if two 3D geometries are entirely within a given 3D distance | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DDFullyWithin` | +| ST_3DIntersects | ST_3DINTERSECTS | Tests if two geometries spatially intersect in 3D | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DIntersects` | +| ST_Contains | ST_CONTAINS | Tests if every point of B lies in A, and their interiors have a point in common | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Contains` | +| ST_ContainsProperly | ST_CONTAINSPROPERLY | Tests if every point of B lies in the interior of A | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_ContainsProperly` | +| ST_CoveredBy | ST_COVEREDBY | Tests if every point of A lies in B | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_CoveredBy` | +| ST_Covers | ST_COVERS | Tests if every point of B lies in A | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Covers` | +| ST_Crosses | ST_CROSSES | Tests if two geometries have some, but not all, interior points in common | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Crosses` | +| ST_DFullyWithin | ST_DFULLYWITHIN | Tests if a geometry is entirely inside a distance of another | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_DFullyWithin` | +| ST_Disjoint | ST_DISJOINT | Tests if two geometries have no points in common | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Disjoint` | +| ST_DWithin | ST_DWITHIN | Tests if two geometries are within a given distance | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_DWithin` | +| ST_Equals | ST_EQUALS | Tests if two geometries include the same set of points | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Equals` | +| ST_Intersects | ST_INTERSECTS | Tests if two geometries intersect (they have at least one point in common) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Intersects` | +| ST_LineCrossingDirection | ST_LINECROSSINGDIRECTION | Returns a number indicating the crossing behavior of two LineStrings | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_LineCrossingDirection` | +| ST_OrderingEquals | ST_ORDERINGEQUALS | Tests if two geometries represent the same geometry and have points in the same directional order | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_OrderingEquals` | +| ST_Overlaps | ST_OVERLAPS | Tests if two geometries have the same dimension and intersect, but each has at least one point not in the other | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Overlaps` | +| ST_PointInsideCircle | ST_POINTINSIDECIRCLE | Tests if a point geometry is inside a circle defined by a center and radius | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_PointInsideCircle` | +| ST_Relate | ST_RELATE | Tests if two geometries have a topological relationship matching an Intersection Matrix pattern, or computes their Intersection Matrix | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Relate` | +| ST_RelateMatch | ST_RELATEMATCH | Tests if a DE-9IM Intersection Matrix matches an Intersection Matrix pattern | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_RelateMatch` | +| ST_Touches | ST_TOUCHES | Tests if two geometries have at least one point in common, but their interiors do not intersect | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Touches` | +| ST_Within | ST_WITHIN | Tests if every point of A lies in B, and their interiors have a point in common | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Within` | + +## PostGIS Measurement Functions + +These functions calculate various measurements of geometries including lengths, areas, distances, and angles. + +| PostgreSQL functions | Register for DQL as | Description | Implemented by | +|---|---|---|---| +| ST_Length | ST_LENGTH | Returns the 2D length of LineString/MultiLineString or perimeter of areal geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Length` | +| ST_Length2D | ST_LENGTH2D | Returns the 2D length, ignoring Z coordinates | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Length2D` | +| ST_3DLength | ST_3DLENGTH | Returns the 3D length of LineString/MultiLineString or 3D perimeter of areal geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DLength` | +| ST_Area | ST_AREA | Returns the area of polygon/multi-polygon geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Area` | +| ST_Perimeter | ST_PERIMETER | Returns the 2D perimeter of polygon/multi-polygon geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Perimeter` | +| ST_3DPerimeter | ST_3DPERIMETER | Returns the 3D perimeter of polygon/multi-polygon geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DPerimeter` | +| ST_Distance | ST_DISTANCE | Returns the 2D distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Distance` | +| ST_3DDistance | ST_3DDISTANCE | Returns the 3D distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_3DDistance` | +| ST_Centroid | ST_CENTROID | Returns the geometric center of a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Centroid` | +| ST_MaxDistance | ST_MAXDISTANCE | Returns the maximum distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_MaxDistance` | +| ST_HausdorffDistance | ST_HAUSDORFFDISTANCE | Returns the Hausdorff distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_HausdorffDistance` | +| ST_FrechetDistance | ST_FRECHETDISTANCE | Returns the FrΓ©chet distance between two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_FrechetDistance` | +| ST_Azimuth | ST_AZIMUTH | Returns the azimuth between two points in radians | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Azimuth` | +| ST_Project | ST_PROJECT | Projects a point along a geodesic by distance and azimuth | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Project` | + +## PostGIS Overlay Functions + +These functions perform geometric operations between geometries including intersection, union, difference, and splitting. + +| PostgreSQL functions | Register for DQL as | Description | Implemented by | +|---|---|---|---| +| ST_Intersection | ST_INTERSECTION | Returns the point set intersection of two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Intersection` | +| ST_Union | ST_UNION | Returns the point set union of two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Union` | +| ST_UnaryUnion | ST_UNARYUNION | Performs unary union on a geometry (dissolves internal boundaries) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_UnaryUnion` | +| ST_Difference | ST_DIFFERENCE | Returns the point set difference (A - B) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Difference` | +| ST_SymDifference | ST_SYMDIFFERENCE | Returns the symmetric difference of two geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_SymDifference` | +| ST_Split | ST_SPLIT | Splits a geometry by another geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Split` | +| ST_Subdivide | ST_SUBDIVIDE | Subdivides a geometry into smaller parts | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Subdivide` | +| ST_ClipByBox2D | ST_CLIPBYBOX2D | Clips a geometry by a 2D box | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_ClipByBox2D` | +| ST_Collect | ST_COLLECT | Collects geometries into a geometry collection | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Collect` | +| ST_CollectionExtract | ST_COLLECTIONEXTRACT | Extracts a specific type from a geometry collection | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_CollectionExtract` | +| ST_CollectionHomogenize | ST_COLLECTIONHOMOGENIZE | Homogenizes a geometry collection | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_CollectionHomogenize` | + +## PostGIS Geometry Processing Functions + +These functions modify and transform geometries including buffering, simplification, coordinate system changes, and geometric transformations. + +| PostgreSQL functions | Register for DQL as | Description | Implemented by | +|---|---|---|---| +| ST_Buffer | ST_BUFFER | Creates a buffer around a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Buffer` | +| ST_Simplify | ST_SIMPLIFY | Simplifies geometry using Douglas-Peucker algorithm | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Simplify` | +| ST_SimplifyPreserveTopology | ST_SIMPLIFYPRESERVETOPOLOGY | Simplifies geometry while preserving topology | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_SimplifyPreserveTopology` | +| ST_SimplifyVW | ST_SIMPLIFYVW | Simplifies geometry using Visvalingam-Whyatt algorithm | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_SimplifyVW` | +| ST_ConvexHull | ST_CONVEXHULL | Returns the convex hull of a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_ConvexHull` | +| ST_Envelope | ST_ENVELOPE | Returns the bounding box as a polygon | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Envelope` | +| ST_Boundary | ST_BOUNDARY | Returns the boundary of a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Boundary` | +| ST_Transform | ST_TRANSFORM | Transforms geometry to different coordinate system | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Transform` | +| ST_Reverse | ST_REVERSE | Reverses the order of points in a geometry | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Reverse` | +| ST_Force2D | ST_FORCE2D | Forces geometry to 2D by removing Z/M coordinates | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Force2D` | +| ST_Force3D | ST_FORCE3D | Forces geometry to 3D by adding Z coordinate if needed | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Force3D` | +| ST_Force4D | ST_FORCE4D | Forces geometry to 4D by adding Z and M coordinates if needed | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Force4D` | +| ST_CurveToLine | ST_CURVETOLINE | Converts curved geometries to linear geometries | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_CurveToLine` | +| ST_LineToCurve | ST_LINETOCURVE | Converts linear geometries to curved geometries where possible | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_LineToCurve` | +| ST_Scale | ST_SCALE | Scales a geometry by given factors | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Scale` | +| ST_Rotate | ST_ROTATE | Rotates a geometry by given angle | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Rotate` | +| ST_Translate | ST_TRANSLATE | Translates a geometry by given offsets | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\PostGIS\ST_Translate` | + +## Usage Examples + +```sql +-- Bounding box operations +-- Find geometries to the left of a reference point +SELECT e FROM Entity e WHERE STRICTLY_LEFT(e.geometry, 'POINT(0 0)') = TRUE + +-- Find overlapping polygons +SELECT e FROM Entity e WHERE SPATIAL_CONTAINS(e.polygon, e.point) = TRUE + +-- 3D spatial relationships +SELECT e FROM Entity e WHERE ND_OVERLAPS(e.geometry3d, 'POLYGON Z((0 0 0, 1 1 1, 2 2 2, 0 0 0))') = TRUE + +-- Distance calculations +-- Find nearest geometries +SELECT e, GEOMETRY_DISTANCE(e.geometry, 'POINT(0 0)') as distance +FROM Entity e ORDER BY distance LIMIT 10 + +-- Bounding box distance for index optimization +SELECT e FROM Entity e WHERE BOUNDING_BOX_DISTANCE(e.geometry, 'POINT(0 0)') < 1000 + +-- 3D distance calculations +SELECT ND_CENTROID_DISTANCE(e.geometry3d1, e.geometry3d2) as distance FROM Entity e + +-- Spatial relationship tests +-- Test if geometries intersect +SELECT e FROM Entity e WHERE ST_Intersects(e.geometry, 'POINT(0 0)') = TRUE + +-- Test if one geometry contains another +SELECT e FROM Entity e WHERE ST_Contains(e.polygon, e.point) = TRUE + +-- Test if geometries are within a distance +SELECT e FROM Entity e WHERE ST_DWithin(e.geometry, 'POINT(0 0)', 1000) = TRUE + +-- Test topological relationships with intersection matrix +SELECT e FROM Entity e WHERE ST_Relate(e.geometry1, e.geometry2, 'T*T***T**') = TRUE + +-- Get intersection matrix between two geometries +SELECT e, ST_Relate(e.geometry1, e.geometry2) as matrix FROM Entity e + +-- Test if point is inside circle +SELECT e FROM Entity e WHERE ST_PointInsideCircle(e.point, 0, 0, 1000) = TRUE + +-- Analyze line crossing behavior +SELECT e, ST_LineCrossingDirection(e.line1, e.line2) as crossing FROM Entity e +WHERE ST_LineCrossingDirection(e.line1, e.line2) != 0 + +-- Measurements +-- Calculate areas and perimeters +SELECT e, ST_Area(e.polygon) as area, ST_Perimeter(e.polygon) as perimeter +FROM Entity e WHERE e.polygon IS NOT NULL + +-- Calculate 3D measurements +SELECT e, ST_3DLength(e.line3d) as length3d, ST_3DPerimeter(e.polygon3d) as perimeter3d +FROM Entity e WHERE e.line3d IS NOT NULL OR e.polygon3d IS NOT NULL + +-- Find geometries within distance +SELECT e, ST_Distance(e.geometry, 'POINT(0 0)') as distance +FROM Entity e ORDER BY distance LIMIT 10 + +-- Calculate azimuth between points +SELECT e, ST_Azimuth(e.point1, e.point2) as azimuth_radians, + DEGREES(ST_Azimuth(e.point1, e.point2)) as azimuth_degrees +FROM Entity e WHERE e.point1 IS NOT NULL AND e.point2 IS NOT NULL + +-- Geometric operations and transformations +-- Create buffer around geometry +SELECT e, ST_Buffer(e.geometry, 100) as buffered_geometry FROM Entity e + +-- Simplify complex geometries +SELECT e, ST_Simplify(e.complex_geometry, 0.5) as simplified_geometry FROM Entity e + +-- Transform coordinate systems +SELECT e, ST_Transform(e.geometry, 4326) as wgs84_geometry FROM Entity e + +-- Get convex hull +SELECT e, ST_ConvexHull(e.geometry) as convex_hull FROM Entity e + +-- Scale and rotate geometries +SELECT e, ST_Scale(ST_Rotate(e.geometry, PI()/4), 2, 2) as scaled_rotated_geometry FROM Entity e +``` + +**πŸ“ Notes:** +- `ST_Relate` is a variadic function that accepts 2 or 3 arguments: + - With 2 arguments: returns text (intersection matrix) + - With 3 arguments: returns boolean (relationship test) +- `ST_LineCrossingDirection` returns an integer (0, 1, -1, or 2) indicating crossing behavior: + - `0`: No crossing + - `1`: Left to right crossing + - `-1`: Right to left crossing + - `2`: Multiple crossings +- All other functions return boolean values and should be used with `= TRUE` or `= FALSE` in DQL + +**πŸ” DE-9IM Intersection Matrix Patterns for ST_Relate:** + +The DE-9IM (Dimensionally Extended 9-Intersection Model) uses a 9-character pattern where each character represents the intersection between: +- Interior (I), Boundary (B), and Exterior (E) of geometry A +- Interior (I), Boundary (B), and Exterior (E) of geometry B + +Common patterns: +- `FF*FF****` = Disjoint (no intersection) +- `T*****FF*` = Contains (A contains B) +- `T*T***T**` = Intersects (geometries intersect) +- `FT*******` = Touches (boundary intersection only) +- `F**T*****` = Within (A is within B) +- `T*T***T**` = Overlaps (partial overlap) + +**πŸ“Š Function Return Types:** +- **Boolean functions**: Use with `= TRUE` or `= FALSE` in DQL +- **Numeric functions**: Return values for calculations and ordering +- **Geometry functions**: Return new geometries for further operations +- **Text functions**: Return strings for pattern matching and display + +**πŸ’‘ Tips for Usage:** +1. **Boolean functions** should be used with `= TRUE` or `= FALSE` in DQL +2. **Spatial functions** work best with proper geometry types and indexes +3. **3D functions** require geometries with Z coordinates +4. **Geography types** have limited operator support compared to geometry types diff --git a/docs/TEXT-AND-PATTERN-FUNCTIONS.md b/docs/TEXT-AND-PATTERN-FUNCTIONS.md new file mode 100644 index 00000000..18a04285 --- /dev/null +++ b/docs/TEXT-AND-PATTERN-FUNCTIONS.md @@ -0,0 +1,125 @@ +# Text and Pattern Functions and Operators + +This document covers PostgreSQL text processing, pattern matching, and regular expression functions and operators available in this library. + +## Text and Pattern Operators + +**⚠️ Important**: Some PostgreSQL operators have multiple meanings depending on the data types involved. This library provides specific DQL function names to avoid conflicts: + +| Operator | Array/JSON Usage | Spatial Usage | Text/Pattern Usage | +|---|---|---|---| +| `~` | N/A | `SPATIAL_CONTAINS` (bounding box contains) | `REGEXP` (text pattern matching) | + +**Usage Guidelines:** +- **Text**: Use `REGEXP`, `IREGEXP` for pattern matching +- **Boolean operators**: All operators return boolean values and **should be used with `= TRUE` or `= FALSE` in DQL** + +### Text and Pattern Operators + +| PostgreSQL operator | Register for DQL as | Implemented by | +|---|---|---| +| ilike | ILIKE ([Usage note](USE-CASES-AND-EXAMPLES.md)) | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Ilike` | +| similar to | SIMILAR_TO | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\SimilarTo` | +| not similar to | NOT_SIMILAR_TO | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\NotSimilarTo` | +| ~ | REGEXP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Regexp` | +| ~* | IREGEXP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\IRegexp` | +| !~ | NOT_REGEXP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\NotRegexp` | +| !~* | NOT_IREGEXP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\NotIRegexp` | +| @@ | TSMATCH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Tsmatch` | +| \|\| | STRCONCAT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StrConcat` | + +## Regular Expression Functions + +| PostgreSQL functions | Register for DQL as | Implemented by | +|---|---|---| +| regexp_count | REGEXP_COUNT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpCount` | +| regexp_instr | REGEXP_INSTR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpInstr` | +| regexp_like | REGEXP_LIKE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpLike` | +| regexp_match | REGEXP_MATCH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpMatch` | +| regexp_replace | REGEXP_REPLACE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpReplace` | +| regexp_substr | REGEXP_SUBSTR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpSubstr` | + +## Text Processing Functions + +| PostgreSQL functions | Register for DQL as | Implemented by | +|---|---|---| +| split_part | SPLIT_PART | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\SplitPart` | +| starts_with | STARTS_WITH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StartsWith` | +| string_agg | STRING_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StringAgg` | +| string_to_array | STRING_TO_ARRAY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StringToArray` | +| unaccent | UNACCENT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Unaccent` | + +## Full-Text Search Functions + +| PostgreSQL functions | Register for DQL as | Implemented by | +|---|---|---| +| to_tsquery | TO_TSQUERY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTsquery` | +| to_tsvector | TO_TSVECTOR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTsvector` | + +## Usage Examples + +```sql +-- Text and pattern matching +-- Case-insensitive pattern matching +SELECT e FROM Entity e WHERE IREGEXP(e.name, '^admin.*') = TRUE + +-- Extract text using regex +SELECT e, REGEXP_SUBSTR(e.description, 'version [0-9.]+') as version FROM Entity e + +-- Replace text patterns +SELECT e, REGEXP_REPLACE(e.content, 'old_pattern', 'new_pattern') as updated_content FROM Entity e + +-- Check if text starts with specific string +SELECT e FROM Entity e WHERE STARTS_WITH(e.name, 'user_') = TRUE + +-- Full-text search +SELECT e FROM Entity e WHERE TSMATCH(e.search_vector, 'query & terms') = TRUE + +-- Count pattern occurrences +SELECT e, REGEXP_COUNT(e.text, '[0-9]+') as number_count FROM Entity e + +-- Find position of pattern +SELECT e, REGEXP_INSTR(e.text, 'error') as error_position FROM Entity e + +-- Test if pattern matches +SELECT e FROM Entity e WHERE REGEXP_LIKE(e.email, '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$') = TRUE + +-- Extract first match +SELECT e, REGEXP_MATCH(e.text, '([0-9]{4})-([0-9]{2})-([0-9]{2})') as date_parts FROM Entity e + +-- Split text into parts +SELECT e, SPLIT_PART(e.full_name, ' ', 1) as first_name, + SPLIT_PART(e.full_name, ' ', 2) as last_name FROM Entity e + +-- String concatenation +SELECT e, STRCONCAT(e.first_name, ' ', e.last_name) as full_name FROM Entity e + +-- Remove accents from text +SELECT e, UNACCENT(e.name) as normalized_name FROM Entity e + +-- Aggregate strings +SELECT e.category, STRING_AGG(e.name, ', ') as names FROM Entity e GROUP BY e.category + +-- Convert string to array +SELECT e, STRING_TO_ARRAY(e.tags, ',') as tag_array FROM Entity e + +-- Case-insensitive LIKE +SELECT e FROM Entity e WHERE ILIKE(e.name, '%admin%') = TRUE + +-- SQL pattern matching +SELECT e FROM Entity e WHERE SIMILAR_TO(e.code, '[A-Z]{2}[0-9]{4}') = TRUE + +-- Negated pattern matching +SELECT e FROM Entity e WHERE NOT_SIMILAR_TO(e.code, '[A-Z]{2}[0-9]{4}') = TRUE + +-- Full-text search setup +SELECT e FROM Entity e WHERE TSMATCH(TO_TSVECTOR(e.content), TO_TSQUERY('search & terms')) = TRUE +``` + +**πŸ’‘ Tips for Usage:** +1. **Boolean operators** should be used with `= TRUE` or `= FALSE` in DQL +2. **Regular expressions** use PostgreSQL's POSIX regular expression syntax +3. **Full-text search** requires proper text search configuration and indexes +4. **ILIKE** provides case-insensitive pattern matching similar to LIKE +5. **UNACCENT** requires the unaccent extension to be installed in PostgreSQL +6. **String aggregation** with STRING_AGG allows custom separators and ordering From c4ce574e98bafdd1c192486259dcf2bbd7889033 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 4 Sep 2025 01:11:54 +0300 Subject: [PATCH 26/27] no message --- docs/ARRAY-AND-JSON-FUNCTIONS.md | 2 ++ docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md | 30 +++++++++++------------ docs/DATE-AND-RANGE-FUNCTIONS.md | 2 ++ docs/GEOMETRY-ARRAYS.md | 2 ++ docs/MATHEMATICAL-FUNCTIONS.md | 2 ++ docs/RANGE-TYPES.md | 2 ++ docs/SPATIAL-FUNCTIONS-AND-OPERATORS.md | 2 ++ docs/SPATIAL-TYPES.md | 2 ++ docs/TEXT-AND-PATTERN-FUNCTIONS.md | 2 ++ docs/USE-CASES-AND-EXAMPLES.md | 12 +++++++++ 10 files changed, 43 insertions(+), 15 deletions(-) diff --git a/docs/ARRAY-AND-JSON-FUNCTIONS.md b/docs/ARRAY-AND-JSON-FUNCTIONS.md index 448d826c..17034195 100644 --- a/docs/ARRAY-AND-JSON-FUNCTIONS.md +++ b/docs/ARRAY-AND-JSON-FUNCTIONS.md @@ -2,6 +2,8 @@ This document covers PostgreSQL array and JSON/JSONB operators and functions available in this library. +> πŸ“– **See also**: [Common Use Cases and Examples](USE-CASES-AND-EXAMPLES.md) for practical JSON and array usage examples + ## Array and JSON Operators **⚠️ Important**: Some PostgreSQL operators have multiple meanings depending on the data types involved. This library provides specific DQL function names to avoid conflicts: diff --git a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md index 5f1e039d..f0cdb2f2 100644 --- a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md +++ b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md @@ -15,9 +15,9 @@ This document provides an overview of PostgreSQL functions and operators availab | `&&` | `OVERLAPS` (arrays/ranges overlap) | Works automatically with geometry/geography | N/A | **Usage Guidelines:** -- **Arrays/JSON**: Use `CONTAINS`, `IS_CONTAINED_BY`, `OVERLAPS` for array and JSON operations -- **Spatial**: Use `SPATIAL_CONTAINS`, `SPATIAL_CONTAINED_BY` for explicit spatial bounding box operations -- **Text**: Use `REGEXP`, `IREGEXP` for pattern matching +- **Arrays/JSON**: Use `CONTAINS`, `IS_CONTAINED_BY`, `OVERLAPS` for array and JSON operations β†’ [Array and JSON Functions](ARRAY-AND-JSON-FUNCTIONS.md) +- **Spatial**: Use `SPATIAL_CONTAINS`, `SPATIAL_CONTAINED_BY` for explicit spatial bounding box operations β†’ [PostGIS Spatial Functions](SPATIAL-FUNCTIONS-AND-OPERATORS.md) +- **Text**: Use `REGEXP`, `IREGEXP` for pattern matching β†’ [Text and Pattern Functions](TEXT-AND-PATTERN-FUNCTIONS.md) - **Boolean operators**: All spatial operators return boolean values and **should be used with `= TRUE` or `= FALSE` in DQL** ## πŸ“š Function and Operator Categories @@ -53,32 +53,32 @@ Complete documentation for mathematical operations and utility functions. ### Most Commonly Used Functions -**Array Operations:** +**Array Operations:** ([Complete documentation](ARRAY-AND-JSON-FUNCTIONS.md)) - `CONTAINS` (`@>`) - Test if array/range contains elements - `OVERLAPS` (`&&`) - Test if arrays/ranges overlap - `ARRAY_AGG` - Aggregate values into arrays -**JSON Operations:** +**JSON Operations:** ([Complete documentation](ARRAY-AND-JSON-FUNCTIONS.md)) - `JSON_GET_FIELD_AS_TEXT` (`->>`) - Extract JSON field as text - `JSON_BUILD_OBJECT` - Build JSON objects - `JSONB_PATH_EXISTS` - Test JSON path existence -**Spatial Operations:** +**Spatial Operations:** ([Complete documentation](SPATIAL-FUNCTIONS-AND-OPERATORS.md)) - `ST_INTERSECTS` - Test if geometries intersect - `ST_DISTANCE` - Calculate distance between geometries - `ST_CONTAINS` - Test spatial containment -**Text Operations:** +**Text Operations:** ([Complete documentation](TEXT-AND-PATTERN-FUNCTIONS.md)) - `ILIKE` - Case-insensitive pattern matching - `REGEXP` (`~`) - Regular expression matching - `STARTS_WITH` - Test if text starts with substring -**Date/Range Operations:** +**Date/Range Operations:** ([Complete documentation](DATE-AND-RANGE-FUNCTIONS.md)) - `DATE_ADD` - Add interval to date - `DATE_EXTRACT` - Extract date components - `DATERANGE` - Create date ranges -**Mathematical Operations:** +**Mathematical Operations:** ([Complete documentation](MATHEMATICAL-FUNCTIONS.md)) - `GREATEST`/`LEAST` - Find maximum/minimum values - `ROUND` - Round numeric values - `RANDOM` - Generate random numbers @@ -119,12 +119,12 @@ Complete documentation for mathematical operations and utility functions. --- **πŸ’‘ Tips for Usage:** -1. **Boolean functions** should be used with `= TRUE` or `= FALSE` in DQL -2. **Spatial functions** work best with proper geometry types and indexes -3. **Array functions** provide efficient PostgreSQL array operations -4. **JSON functions** support both JSON and JSONB data types -5. **Range functions** provide efficient storage and querying for value ranges -6. **Mathematical functions** work with numeric types and return appropriate precision +1. **Boolean functions** should be used with `= TRUE` or `= FALSE` in DQL β†’ [Common Use Cases and Examples](USE-CASES-AND-EXAMPLES.md) +2. **Spatial functions** work best with proper geometry types and indexes β†’ [Spatial Types](SPATIAL-TYPES.md) +3. **Array functions** provide efficient PostgreSQL array operations β†’ [Array and JSON Functions](ARRAY-AND-JSON-FUNCTIONS.md) +4. **JSON functions** support both JSON and JSONB data types β†’ [Array and JSON Functions](ARRAY-AND-JSON-FUNCTIONS.md) +5. **Range functions** provide efficient storage and querying for value ranges β†’ [Range Types](RANGE-TYPES.md) +6. **Mathematical functions** work with numeric types and return appropriate precision β†’ [Mathematical Functions](MATHEMATICAL-FUNCTIONS.md) --- diff --git a/docs/DATE-AND-RANGE-FUNCTIONS.md b/docs/DATE-AND-RANGE-FUNCTIONS.md index 575f5b60..84dc3634 100644 --- a/docs/DATE-AND-RANGE-FUNCTIONS.md +++ b/docs/DATE-AND-RANGE-FUNCTIONS.md @@ -2,6 +2,8 @@ This document covers PostgreSQL date, time, and range functions available in this library. +> πŸ“– **See also**: [Range Types](RANGE-TYPES.md) for range value objects and [Common Use Cases and Examples](USE-CASES-AND-EXAMPLES.md) for practical date and range examples + ## Date and Time Functions | PostgreSQL functions | Register for DQL as | Implemented by | diff --git a/docs/GEOMETRY-ARRAYS.md b/docs/GEOMETRY-ARRAYS.md index ba6d2ace..3b67358d 100644 --- a/docs/GEOMETRY-ARRAYS.md +++ b/docs/GEOMETRY-ARRAYS.md @@ -2,6 +2,8 @@ This document explains the usage, limitations, and workarounds for PostgreSQL `geometry` and `geography` array types in Doctrine DBAL. +> πŸ“– **See also**: [PostGIS Spatial Functions and Operators](SPATIAL-FUNCTIONS-AND-OPERATORS.md) for spatial functions that work with geometry and geography data + ## Overview The `GeometryArray` and `GeographyArray` types provide support for PostgreSQL's `GEOMETRY[]` and `GEOGRAPHY[]` array types, allowing you to store collections of spatial data in a single database column. The use of these types currently has several limitations due to Doctrine DBAL's parameter binding behavior. Workarounds are provided for multi-item arrays in [USE-CASES-AND-EXAMPLES.md](./USE-CASES-AND-EXAMPLES.md). diff --git a/docs/MATHEMATICAL-FUNCTIONS.md b/docs/MATHEMATICAL-FUNCTIONS.md index 088799eb..dc7c4872 100644 --- a/docs/MATHEMATICAL-FUNCTIONS.md +++ b/docs/MATHEMATICAL-FUNCTIONS.md @@ -2,6 +2,8 @@ This document covers PostgreSQL mathematical, utility, and miscellaneous functions available in this library. +> πŸ“– **See also**: [Common Use Cases and Examples](USE-CASES-AND-EXAMPLES.md) for practical mathematical function examples + ## Mathematical Functions | PostgreSQL functions | Register for DQL as | Implemented by | diff --git a/docs/RANGE-TYPES.md b/docs/RANGE-TYPES.md index 8656370a..9d9409f4 100644 --- a/docs/RANGE-TYPES.md +++ b/docs/RANGE-TYPES.md @@ -2,6 +2,8 @@ PostgreSQL range types represent ranges of values of some element type (called the range's subtype). This library provides support for all PostgreSQL built-in range types. +> πŸ“– **See also**: [Date and Range Functions](DATE-AND-RANGE-FUNCTIONS.md) for range functions and operators in DQL queries + ## Available Range Types | Range Type | PostgreSQL Type | Value Type | Description | diff --git a/docs/SPATIAL-FUNCTIONS-AND-OPERATORS.md b/docs/SPATIAL-FUNCTIONS-AND-OPERATORS.md index aeb4df40..0a429e0a 100644 --- a/docs/SPATIAL-FUNCTIONS-AND-OPERATORS.md +++ b/docs/SPATIAL-FUNCTIONS-AND-OPERATORS.md @@ -2,6 +2,8 @@ This document covers PostGIS spatial functions and operators available in this library for working with geometry and geography data types. +> πŸ“– **See also**: [Spatial Types](SPATIAL-TYPES.md) for geometry/geography types and [Common Use Cases and Examples](USE-CASES-AND-EXAMPLES.md) for practical spatial query examples + ## PostGIS Spatial Operators **⚠️ Important**: Some operators have dual meanings for different data types. Use the specific DQL function names to avoid conflicts: diff --git a/docs/SPATIAL-TYPES.md b/docs/SPATIAL-TYPES.md index 3766bf37..7a958d2d 100644 --- a/docs/SPATIAL-TYPES.md +++ b/docs/SPATIAL-TYPES.md @@ -17,6 +17,8 @@ This document describes the core primitives used by the spatial DBAL types: pars Parsing outputs a list of `WktSpatialData` value objects that Doctrine DBAL can bind. +> πŸ“– **See also**: [PostGIS Spatial Functions and Operators](SPATIAL-FUNCTIONS-AND-OPERATORS.md) for working with spatial data in queries + ## Enum-driven patterns Two enums drive normalization so the code and docs remain consistent: diff --git a/docs/TEXT-AND-PATTERN-FUNCTIONS.md b/docs/TEXT-AND-PATTERN-FUNCTIONS.md index 18a04285..c92744fc 100644 --- a/docs/TEXT-AND-PATTERN-FUNCTIONS.md +++ b/docs/TEXT-AND-PATTERN-FUNCTIONS.md @@ -2,6 +2,8 @@ This document covers PostgreSQL text processing, pattern matching, and regular expression functions and operators available in this library. +> πŸ“– **See also**: [Common Use Cases and Examples](USE-CASES-AND-EXAMPLES.md) for practical text processing and regular expression examples + ## Text and Pattern Operators **⚠️ Important**: Some PostgreSQL operators have multiple meanings depending on the data types involved. This library provides specific DQL function names to avoid conflicts: diff --git a/docs/USE-CASES-AND-EXAMPLES.md b/docs/USE-CASES-AND-EXAMPLES.md index 358f196e..83e42dec 100644 --- a/docs/USE-CASES-AND-EXAMPLES.md +++ b/docs/USE-CASES-AND-EXAMPLES.md @@ -28,6 +28,8 @@ Using JSON_BUILD_OBJECT and JSONB_BUILD_OBJECT These functions currently only support string literals and object references as arguments. Here are some valid examples: +> πŸ“– **See also**: [Array and JSON Functions](ARRAY-AND-JSON-FUNCTIONS.md) for complete JSON/JSONB function documentation + ```sql -- Basic usage with string literals and entity properties SELECT JSON_BUILD_OBJECT('name', e.userName, 'email', e.userEmail) FROM User e @@ -47,6 +49,8 @@ Using JSON Path Functions PostgreSQL 14+ introduced JSON path functions that provide a powerful way to query JSON data. Here are some examples: +> πŸ“– **See also**: [Array and JSON Functions](ARRAY-AND-JSON-FUNCTIONS.md) for complete JSONB path function documentation + ```sql -- Check if a JSON path exists with a condition SELECT e FROM Entity e WHERE JSONB_PATH_EXISTS(e.jsonData, '$.items[*] ? (@.price > 100)') = TRUE @@ -69,6 +73,8 @@ Using Regular Expression Functions PostgreSQL 15+ introduced additional regular expression functions that provide more flexibility when working with text data: +> πŸ“– **See also**: [Text and Pattern Functions](TEXT-AND-PATTERN-FUNCTIONS.md) for complete regular expression and text processing documentation + ```sql -- Count occurrences of a pattern SELECT e.id, REGEXP_COUNT(e.text, '\d{3}-\d{2}-\d{4}') as ssn_count FROM Entity e @@ -85,6 +91,8 @@ Using Date Functions PostgreSQL 14+ introduced additional date functions that provide more flexibility when working with dates and timestamps: +> πŸ“– **See also**: [Date and Range Functions](DATE-AND-RANGE-FUNCTIONS.md) for complete date/time and range function documentation + ```sql -- Bin timestamps into 15-minute intervals SELECT DATE_BIN('15 minutes', e.createdAt, '2001-01-01') FROM Entity e @@ -103,6 +111,8 @@ Using Range Types PostgreSQL range types allow you to work with ranges of values efficiently. Here are practical examples: +> πŸ“– **See also**: [Range Types](RANGE-TYPES.md) for complete range value object documentation and [Date and Range Functions](DATE-AND-RANGE-FUNCTIONS.md) for range functions + ```php // Entity with range fields #[ORM\Entity] @@ -182,6 +192,8 @@ POINT Z (1 2 3) => POINT Z(1 2 3) PostGIS spatial operators allow you to perform spatial queries using bounding box relationships and distance calculations. **Important**: All spatial operators return boolean values and shall be used with `= TRUE` or `= FALSE` in DQL. +> πŸ“– **See also**: [PostGIS Spatial Functions and Operators](SPATIAL-FUNCTIONS-AND-OPERATORS.md) for complete spatial function documentation + #### Bounding Box Spatial Relationships ```sql From 2d01b146649b5c44131e4887e91c6e1adcfbe6b9 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 4 Sep 2025 01:18:15 +0300 Subject: [PATCH 27/27] no message --- .../Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php index a1c5053b..d7b4a1af 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/PostGIS/ST_ProjectTest.php @@ -28,7 +28,7 @@ public function projects_point_with_literal_values(): void WHERE g.id = 1'; $result = $this->executeDqlQuery($dql); - $this->assertEqualsWithDelta(0, $result[0]['result'], 0.001, 'geometries with opposite directions should return to approximately the same point'); + $this->assertEqualsWithDelta(0, $result[0]['result'], 0.01, 'geometries with opposite directions should return to approximately the same point'); } #[Test]