[SCM] PostGIS branch master updated. 3.7.0alpha1-75-gebf8cdcb2

git at osgeo.org git at osgeo.org
Tue Jul 7 10:45:23 PDT 2026


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "PostGIS".

The branch, master has been updated
       via  ebf8cdcb257bd46e782954066edc604f9a6572d5 (commit)
       via  978efdc1d7acf9f13cbb2e833ebe97fcce140cdc (commit)
       via  59b7d32d252997a2504d6e165e3e2d98d67b3b74 (commit)
       via  64d353f1838f323ce48ce9e54e8ed30fe3a65ad0 (commit)
       via  2dce9483add1d9e79675e8c28450d105ec1a92ba (commit)
       via  7447449ee2a733b11d53f11d74eda6c19ae54223 (commit)
       via  8428350a14b3d5b87a99ea1ae38b2542f385c28d (commit)
       via  d49f1c98c574bb6a1dee95cacfcab91b279f8c8d (commit)
       via  53fade5b3947f8d1e9e248e6c78c11828a2ec7d7 (commit)
       via  2a703a9f9eddfb3ecd7013a7cf4aece30e640928 (commit)
       via  1c6c69b2dd62b0e4c0b839fdf7fd2f43f00c25ec (commit)
       via  ee22ae6aeeac7fbdf18e79871ebef2f3c0cc57df (commit)
       via  1a5b83202e66768141a4f85126a0f873cce0f930 (commit)
       via  9c3aa65f054c2db944d1916504e75f12037f69c7 (commit)
       via  bdfcf652a6ea03ea4e8db632ff6841d6ea19d2c7 (commit)
       via  3b1b7f262663ad99c4daa1623e4a60381bd2860e (commit)
       via  5de0fe91928943acc8dcef99884782bf453e715d (commit)
       via  d45d6e66d16b6c1e6d160895269db0ef0d7d9855 (commit)
       via  f1fb51cedf07759de2f3a189e14064a4c27a7b45 (commit)
       via  1dcbdacde703bfbd404aa7d4805d588a8367f474 (commit)
       via  55ea7230fb35f394051929d53c5f4c58ef393cd7 (commit)
       via  3b4aeb707e696e9d92d6da4d3fc89a3fd60f2b45 (commit)
       via  e48cae0ad147d669c2688b21ce1d0df219a431cf (commit)
       via  63fc924b351d61a5911235e081c7ad7fb1b8f0e5 (commit)
       via  91233df1d245b30edda27a99841bd6b2fe96c89c (commit)
       via  e94600a0847e77a3ad30fb85986b609f096d170a (commit)
       via  2045b9774fec76e87c758276459c0b1d94632c81 (commit)
       via  8aa9623d7faf05cd175e08646863811ee9ce36c0 (commit)
       via  5e3fb509b91046edda39d2202b045550dd81ff35 (commit)
       via  aafa0ec1d4a629b4c8977ba7a9cf6fc36ee04881 (commit)
       via  62db0d6afd6ef91940ab05cffa3299caa754be11 (commit)
       via  054d7b8f105b81dc6cf2ab193dc6b42c7e2b1f92 (commit)
       via  1441d4769078d22574372e2c83448b7831d68262 (commit)
       via  f4130f5ef8c5ea7e34a9f9bacc47ead14fd1079c (commit)
       via  1382c85d2510098e4917b133c0e0d9030e59e844 (commit)
       via  a55fb44f7f7cd2033c734042558a26f0d0aa1103 (commit)
       via  06dfb605f3184c6c23c352e0ed37fb785a2da343 (commit)
      from  7a39dab9d1099ace6e35069bd0df87fd5f64b84f (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit ebf8cdcb257bd46e782954066edc604f9a6572d5
Merge: 978efdc1d 63fc924b3
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 21:39:48 2026 +0400

    Merge PR #930: use expanded DocBook input for EPUB

diff --cc .github/workflows/ci-macos.yml
index 507695a23,c16ad1c0e..67c0cc1d4
--- a/.github/workflows/ci-macos.yml
+++ b/.github/workflows/ci-macos.yml
@@@ -39,14 -39,14 +39,8 @@@ jobs
              gdal geos icu4c json-c libpq libxml2 \
              proj protobuf-c sfcgal cunit \
              docbook docbook-xsl \
--            gettext
++            postgresql@${PG} gettext
            brew link --force gettext
--          HOMEBREW_GITHUB_ACTIONS=1 brew install "postgresql@${PG}"
--          pg_version="$(brew info --json=v2 "postgresql@${PG}" | python3 -c 'import json,sys; data=json.load(sys.stdin); print(data["formulae"][0]["installed"][0]["version"])')"
--          ln -sfn "${HOMEBREW_PREFIX}/Cellar/postgresql@${PG}/${pg_version}/share/postgresql@${PG}" "${HOMEBREW_PREFIX}/share/postgresql@${PG}"
--          if [ ! -f "${HOMEBREW_PREFIX}/var/postgresql@${PG}/PG_VERSION" ]; then
--            "${HOMEBREW_PREFIX}/opt/postgresql@${PG}/bin/initdb" --locale=en_US.UTF-8 -E UTF-8 "${HOMEBREW_PREFIX}/var/postgresql@${PG}"
--          fi
            ccache --max-size="${CCACHE_MAXSIZE}"
            sudo ln -sfn "${HOMEBREW_PREFIX}/opt/postgresql@${PG}/bin/postgres" /usr/local/bin/postgres
  
diff --cc NEWS
index 7f2d43cdf,aa7605e8b..4fa4d414c
--- a/NEWS
+++ b/NEWS
@@@ -7,10 -7,6 +7,13 @@@ To take advantage of all postgis_sfcga
  
  * Bug Fixes *
  
 + - #6093, Fix SQL generation on ppc64el when NURBS comments are
 +          preprocessed with AltiVec vector macros (Darafei Praliaskouski)
 + - #6094, [upgrade] Avoid legacy string literal warnings in downgrade checks
 +          with standard_conforming_strings off (Darafei Praliaskouski)
++ - #5423, Use a DTD-free expanded DocBook input for EPUB builds so
++          out-of-tree builds do not need dbtoepub entity path support
++          (Darafei Praliaskouski)
   - GH-898, Fix ST_DFullyWithin indexed plans and large-coordinate
            ring-orientation precision regressions (Darafei Praliaskouski)
   - GH-1143, [topology] Clean up TopoRingIsCCW early returns
@@@ -26,34 -22,10 +29,35 @@@
            (Darafei Praliaskouski)
   - GH-1153, [topology] Preserve large ids from edge wrappers
            (Darafei Praliaskouski)
 + - GH-1139, [topology] Preserve large edge ids from vertex-segment
 +          proximity helper (Darafei Praliaskouski)
 + - GH-1140, [liblwgeom] Cap NURBS WKT knot multiplicity
 +          (Darafei Praliaskouski)
 + - GH-1141, [liblwgeom] Reject overflowing NURBS linearization
 +          segment counts (Darafei Praliaskouski)
 + - GH-1151, [topology] Preserve exact zero tolerance in
 +          TopoGeometry construction (Darafei Praliaskouski)
 + - GH-1152, [topology] Enforce max edge limits from reported
 +          TopoGeo_AddLinestring edge creation (Darafei Praliaskouski)
 + - GH-1154, [topology] Quote TotalTopologySize catalog regclasses
 +          safely (Darafei Praliaskouski)
 + - #6092, [fuzzingengine] fuzzingengine doesn't use hardening CPPFLAGS
 +            (Bas Couwenberg)
 +
 +* Enhancements *
 +
 + - #4749, Use point-in-polygon predicate fast paths for point-only
 +          GeometryCollections, including ST_Disjoint (Darafei Praliaskouski)
- 
- * Enhancements *
- 
 + - #3676, Mark geometry and geography btree comparison support functions
 +          as leakproof (Darafei Praliaskouski)
++ - GH-875, Allow ST_OrderingEquals joins to use hash and merge join plans
++          (Darafei Praliaskouski)
++ - #2557, #2805, Document ST_MapAlgebra callback argument
++          signatures and return semantics (Darafei Praliaskouski)
  
  
 -PostGIS 3.7.0alpha1
 -2026/07/05
 +PostGIS 3.7.0dev
 +2026/xx/xx
  
  This version requires GEOS 3.10+, PostgreSQL 14-19beta1, Proj 6.1+, libgmp.
  To take advantage of all features postgis extension features, GEOS 3.15+ is needed.

commit 978efdc1d7acf9f13cbb2e833ebe97fcce140cdc
Merge: 59b7d32d2 55ea7230f
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 21:39:43 2026 +0400

    Merge PR #911: raster: restore ST_Intersects wrapper inlining


commit 59b7d32d252997a2504d6e165e3e2d98d67b3b74
Merge: 64d353f18 91233df1d
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 21:39:43 2026 +0400

    Merge PR #917: Document map algebra and MVT geometry behavior


commit 64d353f1838f323ce48ce9e54e8ed30fe3a65ad0
Merge: 2dce9483a 1382c85d2
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 21:39:43 2026 +0400

    Merge PR #878: Clarify standards support in introduction


commit 2dce9483add1d9e79675e8c28450d105ec1a92ba
Merge: 7447449ee f4130f5ef
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 21:39:43 2026 +0400

    Merge PR #875: Expose ST_OrderingEquals as planner equality


commit 7447449ee2a733b11d53f11d74eda6c19ae54223
Merge: 8428350a1 06dfb605f
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 21:39:43 2026 +0400

    Merge PR #1112: doc: explain repeated ring point validity


commit 8428350a14b3d5b87a99ea1ae38b2542f385c28d
Merge: d49f1c98c e48cae0ad
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 21:39:43 2026 +0400

    Merge PR #1110: postgis: mark btree comparisons leakproof

diff --cc NEWS
index c4cfbf162,86af0e68a..54d2ae111
--- a/NEWS
+++ b/NEWS
@@@ -26,29 -22,15 +26,34 @@@ To take advantage of all postgis_sfcga
            (Darafei Praliaskouski)
   - GH-1153, [topology] Preserve large ids from edge wrappers
            (Darafei Praliaskouski)
 + - GH-1139, [topology] Preserve large edge ids from vertex-segment
 +          proximity helper (Darafei Praliaskouski)
 + - GH-1140, [liblwgeom] Cap NURBS WKT knot multiplicity
 +          (Darafei Praliaskouski)
 + - GH-1141, [liblwgeom] Reject overflowing NURBS linearization
 +          segment counts (Darafei Praliaskouski)
 + - GH-1151, [topology] Preserve exact zero tolerance in
 +          TopoGeometry construction (Darafei Praliaskouski)
 + - GH-1152, [topology] Enforce max edge limits from reported
 +          TopoGeo_AddLinestring edge creation (Darafei Praliaskouski)
 + - GH-1154, [topology] Quote TotalTopologySize catalog regclasses
 +          safely (Darafei Praliaskouski)
 + - #6092, [fuzzingengine] fuzzingengine doesn't use hardening CPPFLAGS
 +            (Bas Couwenberg)
 +
 +* Enhancements *
 +
 + - #4749, Use point-in-polygon predicate fast paths for point-only
 +          GeometryCollections, including ST_Disjoint (Darafei Praliaskouski)
  
+ * Enhancements *
+ 
+  - #3676, Mark geometry and geography btree comparison support functions
+           as leakproof (Darafei Praliaskouski)
+ 
  
 -PostGIS 3.7.0alpha1
 -2026/07/05
 +PostGIS 3.7.0dev
 +2026/xx/xx
  
  This version requires GEOS 3.10+, PostgreSQL 14-19beta1, Proj 6.1+, libgmp.
  To take advantage of all features postgis extension features, GEOS 3.15+ is needed.

commit d49f1c98c574bb6a1dee95cacfcab91b279f8c8d
Merge: 53fade5b3 3b4aeb707
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 21:39:43 2026 +0400

    Merge PR #1074: postgis: fast-path point collection ST_Disjoint

diff --cc NEWS
index 7d0d51c8b,678b9140f..c4cfbf162
--- a/NEWS
+++ b/NEWS
@@@ -26,24 -22,15 +26,29 @@@ To take advantage of all postgis_sfcga
            (Darafei Praliaskouski)
   - GH-1153, [topology] Preserve large ids from edge wrappers
            (Darafei Praliaskouski)
 + - GH-1139, [topology] Preserve large edge ids from vertex-segment
 +          proximity helper (Darafei Praliaskouski)
 + - GH-1140, [liblwgeom] Cap NURBS WKT knot multiplicity
 +          (Darafei Praliaskouski)
 + - GH-1141, [liblwgeom] Reject overflowing NURBS linearization
 +          segment counts (Darafei Praliaskouski)
 + - GH-1151, [topology] Preserve exact zero tolerance in
 +          TopoGeometry construction (Darafei Praliaskouski)
 + - GH-1152, [topology] Enforce max edge limits from reported
 +          TopoGeo_AddLinestring edge creation (Darafei Praliaskouski)
 + - GH-1154, [topology] Quote TotalTopologySize catalog regclasses
 +          safely (Darafei Praliaskouski)
 + - #6092, [fuzzingengine] fuzzingengine doesn't use hardening CPPFLAGS
 +            (Bas Couwenberg)
  
+ * Enhancements *
+ 
+  - #4749, Use point-in-polygon predicate fast paths for point-only
+           GeometryCollections, including ST_Disjoint (Darafei Praliaskouski)
+ 
  
 -PostGIS 3.7.0alpha1
 -2026/07/05
 +PostGIS 3.7.0dev
 +2026/xx/xx
  
  This version requires GEOS 3.10+, PostgreSQL 14-19beta1, Proj 6.1+, libgmp.
  To take advantage of all features postgis extension features, GEOS 3.15+ is needed.

commit 53fade5b3947f8d1e9e248e6c78c11828a2ec7d7
Merge: 2a703a9f9 1a5b83202
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 21:39:43 2026 +0400

    Merge PR #1142: liblwgeom: cap NURBS curve linearization

diff --cc liblwgeom/lwstroke.c
index a6395a2fd,1d3243339..a2518e691
--- a/liblwgeom/lwstroke.c
+++ b/liblwgeom/lwstroke.c
@@@ -933,11 -936,7 +936,8 @@@ lwnurbscurve_linearize(const LWNURBSCUR
  		double height = 0.0;
  		double depth = 0.0;
  		double diagonal = 0.0;
 +		double segments_required;
  
- 		if (!curve->points || curve->points->npoints == 0)
- 			return lwnurbscurve_to_linestring(curve, NURBS_MIN_LINEARIZE_SEGMENTS);
- 
  		if (ptarray_calculate_gbox_cartesian(curve->points, &box) == LW_SUCCESS)
  		{
  			width = box.xmax - box.xmin;

commit 2a703a9f9eddfb3ecd7013a7cf4aece30e640928
Merge: 1c6c69b2d ee22ae6ae
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 21:39:43 2026 +0400

    Merge PR #1144: liblwgeom: keep TIN dimension cheap


commit 1c6c69b2dd62b0e4c0b839fdf7fd2f43f00c25ec
Merge: 7a39dab9d d45d6e66d
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 21:39:42 2026 +0400

    Merge PR #1145: topology: avoid rebuilding unsafe repair indexes


commit ee22ae6aeeac7fbdf18e79871ebef2f3c0cc57df
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 18:11:27 2026 +0400

    liblwgeom: keep 2D solid TIN dimension bounded

diff --git a/doc/reference_accessor.xml b/doc/reference_accessor.xml
index f430278ab..c72ba1b4f 100644
--- a/doc/reference_accessor.xml
+++ b/doc/reference_accessor.xml
@@ -349,7 +349,7 @@ SELECT ST_X(ST_StartPoint(ST_BoundingDiagonal(
 			A <varname>TIN</varname>, including a closed TIN surface, is a
 			surface geometry and has dimension 2.  Use
 			<xref linkend="CG_MakeSolid"/> to make the solid representation
-			explicit; the resulting solid has dimension 3.  Solid geometry
+			explicit; the resulting 3D solid has dimension 3.  Solid geometry
 			semantics, such as volume, are handled separately by the SQL/MM 3D
 			solid functions (SQL-MM IEC 13249-3: 9.1).
 		  </para>
diff --git a/liblwgeom/cunit/cu_surface.c b/liblwgeom/cunit/cu_surface.c
index 57a458fd8..e85f6b6e7 100644
--- a/liblwgeom/cunit/cu_surface.c
+++ b/liblwgeom/cunit/cu_surface.c
@@ -449,6 +449,7 @@ surface_dimension(void)
 	check_solid_st_dimension(
 	    "TIN(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))",
 	    3);
+	check_solid_st_dimension("TIN(((0 0,0 1,1 0,0 0)))", 2);
 }
 
 
diff --git a/liblwgeom/lwgeom.c b/liblwgeom/lwgeom.c
index 9d461c730..4d3b272b1 100644
--- a/liblwgeom/lwgeom.c
+++ b/liblwgeom/lwgeom.c
@@ -1636,8 +1636,8 @@ int lwgeom_dimension(const LWGEOM *geom)
 	case MULTIPOLYGONTYPE:
 		return 2;
 	case TINTYPE:
-		/* A TIN surface is dimension 2 unless it carries an explicit solid flag. */
-		return FLAGS_GET_SOLID(geom->flags) ? 3 : 2;
+		/* A 3D TIN surface is dimension 3 only when explicitly marked solid. */
+		return (FLAGS_GET_SOLID(geom->flags) && FLAGS_GET_Z(geom->flags)) ? 3 : 2;
 	case POLYHEDRALSURFACETYPE:
 	{
 		/* A closed polyhedral surface contains a volume. */
diff --git a/sfcgal/regress/regress_sfcgal.sql b/sfcgal/regress/regress_sfcgal.sql
index 75e058558..bc3af1e57 100644
--- a/sfcgal/regress/regress_sfcgal.sql
+++ b/sfcgal/regress/regress_sfcgal.sql
@@ -112,10 +112,12 @@ WITH geom AS (
         'TIN Z (((0 0 0,0 0 1,0 1 0,0 0 0)),
                 ((0 0 0,0 1 0,1 0 0,0 0 0)),
                 ((0 0 0,1 0 0,0 0 1,0 0 0)),
-                ((1 0 0,0 1 0,0 0 1,1 0 0)))'::geometry AS tin
+                ((1 0 0,0 1 0,0 0 1,1 0 0)))'::geometry AS tin_z,
+        'TIN (((0 0,0 1,1 0,0 0)))'::geometry AS tin
 )
 SELECT
     'CG_MakeSolid TIN ST_Dimension',
-    ST_Dimension(tin),
+    ST_Dimension(tin_z),
+    ST_Dimension(CG_MakeSolid(tin_z)),
     ST_Dimension(CG_MakeSolid(tin))
 FROM geom;
diff --git a/sfcgal/regress/regress_sfcgal_expected b/sfcgal/regress/regress_sfcgal_expected
index ad9c11a50..d5c3005a2 100644
--- a/sfcgal/regress/regress_sfcgal_expected
+++ b/sfcgal/regress/regress_sfcgal_expected
@@ -34,4 +34,4 @@ CG_Difference|MULTILINESTRING((0 0,0.5 0),(0.7 0,1 0))
 CG_Union|LINESTRING(-1 0,0.5 0,1 0)
 CG_Triangulate|TIN(((0.8 0.2,0.2 0.2,1 0,0.8 0.2)),((0.2 0.2,0 0,1 0,0.2 0.2)),((1 1,0.8 0.8,0.8 0.2,1 1)),((0 1,0 0,0.2 0.2,0 1)),((0 1,0.2 0.8,1 1,0 1)),((0 1,0.2 0.2,0.2 0.8,0 1)),((0.2 0.8,0.8 0.8,1 1,0.2 0.8)),((0.2 0.8,0.2 0.2,0.8 0.2,0.2 0.8)),((1 1,0.8 0.2,1 0,1 1)),((0.8 0.8,0.2 0.8,0.8 0.2,0.8 0.8)))
 CG_IsSolid|t
-CG_MakeSolid TIN ST_Dimension|2|3
+CG_MakeSolid TIN ST_Dimension|2|3|2

commit 1a5b83202e66768141a4f85126a0f873cce0f930
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 18:11:01 2026 +0400

    liblwgeom: preserve empty NURBS linearization

diff --git a/liblwgeom/lwstroke.c b/liblwgeom/lwstroke.c
index 6ff6e5b43..1d3243339 100644
--- a/liblwgeom/lwstroke.c
+++ b/liblwgeom/lwstroke.c
@@ -911,6 +911,9 @@ lwnurbscurve_linearize(const LWNURBSCURVE *curve, double tol,
 		return NULL;
 	}
 
+	if (!curve->points || curve->points->npoints == 0)
+		return lwnurbscurve_to_linestring(curve, NURBS_MIN_LINEARIZE_SEGMENTS);
+
 	switch (tolerance_type)
 	{
 	case LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD:
@@ -934,9 +937,6 @@ lwnurbscurve_linearize(const LWNURBSCURVE *curve, double tol,
 		double depth = 0.0;
 		double diagonal = 0.0;
 
-		if (!curve->points || curve->points->npoints == 0)
-			return lwnurbscurve_to_linestring(curve, NURBS_MIN_LINEARIZE_SEGMENTS);
-
 		if (ptarray_calculate_gbox_cartesian(curve->points, &box) == LW_SUCCESS)
 		{
 			width = box.xmax - box.xmin;
diff --git a/regress/core/nurbs_eval.sql b/regress/core/nurbs_eval.sql
index c74691e1b..975217282 100644
--- a/regress/core/nurbs_eval.sql
+++ b/regress/core/nurbs_eval.sql
@@ -176,6 +176,11 @@ SELECT 'NURBS_CURVETOLINE_3',
     ST_Zmflag(ST_CurveToLine('SRID=4326;NURBSCURVE Z EMPTY'::geometry)),
     ST_SRID(ST_CurveToLine('SRID=4326;NURBSCURVE Z EMPTY'::geometry));
 
+SELECT 'NURBS_CURVETOLINE_4',
+    ST_AsEWKT(ST_CurveToLine('SRID=4326;NURBSCURVE Z EMPTY'::geometry, 0.0001, 2)),
+    ST_Zmflag(ST_CurveToLine('SRID=4326;NURBSCURVE Z EMPTY'::geometry, 0.0001, 2)),
+    ST_SRID(ST_CurveToLine('SRID=4326;NURBSCURVE Z EMPTY'::geometry, 0.0001, 2));
+
 SELECT 'NURBS_LENGTH_1', ST_Length('MULTICURVE(NURBSCURVE(2, (0 0, 1 1, 2 0)))'::geometry) > 0;
 
 SELECT 'NURBS_CURVEPOLY_1', ST_Contains(
diff --git a/regress/core/nurbs_eval_expected b/regress/core/nurbs_eval_expected
index 627438a5b..f26ce3ef6 100644
--- a/regress/core/nurbs_eval_expected
+++ b/regress/core/nurbs_eval_expected
@@ -28,6 +28,7 @@ ERROR:  lwnurbscurve_linearize: segs-per-quad is too large, got 2501
 ERROR:  lwnurbscurve_linearize: requested too many segments, got 22361
 ERROR:  lwnurbscurve_linearize: requested too many segments, got 62832
 NURBS_CURVETOLINE_3|SRID=4326;LINESTRING EMPTY|2|4326
+NURBS_CURVETOLINE_4|SRID=4326;LINESTRING EMPTY|2|4326
 NURBS_LENGTH_1|t
 NURBS_CURVEPOLY_1|t
 NURBS_CURVEPOLY_2|ST_Polygon

commit 9c3aa65f054c2db944d1916504e75f12037f69c7
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 17:53:32 2026 +0400

    regress: cover solid TIN dimension semantics

diff --git a/doc/reference_accessor.xml b/doc/reference_accessor.xml
index 36af0640f..f430278ab 100644
--- a/doc/reference_accessor.xml
+++ b/doc/reference_accessor.xml
@@ -346,9 +346,8 @@ SELECT ST_X(ST_StartPoint(ST_BoundingDiagonal(
 		<para>&sqlmm_compliant; SQL-MM 3: 5.1.2</para>
 		<note>
 		  <para>
-			<xref linkend="ST_Dimension"/> follows the SQL-MM topological dimension
-			contract.  A <varname>TIN</varname>, including a closed TIN surface,
-			is a surface geometry and has dimension 2.  Use
+			A <varname>TIN</varname>, including a closed TIN surface, is a
+			surface geometry and has dimension 2.  Use
 			<xref linkend="CG_MakeSolid"/> to make the solid representation
 			explicit; the resulting solid has dimension 3.  Solid geometry
 			semantics, such as volume, are handled separately by the SQL/MM 3D
diff --git a/sfcgal/regress/regress_sfcgal.sql b/sfcgal/regress/regress_sfcgal.sql
index 5d35443c2..75e058558 100644
--- a/sfcgal/regress/regress_sfcgal.sql
+++ b/sfcgal/regress/regress_sfcgal.sql
@@ -107,3 +107,15 @@ SELECT
     'CG_IsSolid',
     CG_IsSolid(geom)
 FROM solid;
+WITH geom AS (
+    SELECT
+        'TIN Z (((0 0 0,0 0 1,0 1 0,0 0 0)),
+                ((0 0 0,0 1 0,1 0 0,0 0 0)),
+                ((0 0 0,1 0 0,0 0 1,0 0 0)),
+                ((1 0 0,0 1 0,0 0 1,1 0 0)))'::geometry AS tin
+)
+SELECT
+    'CG_MakeSolid TIN ST_Dimension',
+    ST_Dimension(tin),
+    ST_Dimension(CG_MakeSolid(tin))
+FROM geom;
diff --git a/sfcgal/regress/regress_sfcgal_expected b/sfcgal/regress/regress_sfcgal_expected
index 261cb63d3..ad9c11a50 100644
--- a/sfcgal/regress/regress_sfcgal_expected
+++ b/sfcgal/regress/regress_sfcgal_expected
@@ -34,3 +34,4 @@ CG_Difference|MULTILINESTRING((0 0,0.5 0),(0.7 0,1 0))
 CG_Union|LINESTRING(-1 0,0.5 0,1 0)
 CG_Triangulate|TIN(((0.8 0.2,0.2 0.2,1 0,0.8 0.2)),((0.2 0.2,0 0,1 0,0.2 0.2)),((1 1,0.8 0.8,0.8 0.2,1 1)),((0 1,0 0,0.2 0.2,0 1)),((0 1,0.2 0.8,1 1,0 1)),((0 1,0.2 0.2,0.2 0.8,0 1)),((0.2 0.8,0.8 0.8,1 1,0.2 0.8)),((0.2 0.8,0.2 0.2,0.8 0.2,0.2 0.8)),((1 1,0.8 0.2,1 0,1 1)),((0.8 0.8,0.2 0.8,0.8 0.2,0.8 0.8)))
 CG_IsSolid|t
+CG_MakeSolid TIN ST_Dimension|2|3

commit bdfcf652a6ea03ea4e8db632ff6841d6ea19d2c7
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 17:30:49 2026 +0400

    liblwgeom: preserve explicit solid TIN dimension

diff --git a/doc/reference_accessor.xml b/doc/reference_accessor.xml
index 03c2d88bd..36af0640f 100644
--- a/doc/reference_accessor.xml
+++ b/doc/reference_accessor.xml
@@ -348,10 +348,11 @@ SELECT ST_X(ST_StartPoint(ST_BoundingDiagonal(
 		  <para>
 			<xref linkend="ST_Dimension"/> follows the SQL-MM topological dimension
 			contract.  A <varname>TIN</varname>, including a closed TIN surface,
-			is a surface geometry and has dimension 2.  Solid geometry semantics,
-			such as volume, are handled separately by the SQL/MM 3D solid
-			functions (SQL-MM IEC 13249-3: 9.1) and by
-			<xref linkend="CG_MakeSolid"/>.
+			is a surface geometry and has dimension 2.  Use
+			<xref linkend="CG_MakeSolid"/> to make the solid representation
+			explicit; the resulting solid has dimension 3.  Solid geometry
+			semantics, such as volume, are handled separately by the SQL/MM 3D
+			solid functions (SQL-MM IEC 13249-3: 9.1).
 		  </para>
 		</note>
 		<para role="enhanced" conformance="2.0.0">Enhanced: 2.0.0 support for Polyhedral surfaces and TINs was introduced. No longer throws an exception if given empty geometry.</para>
diff --git a/doc/reference_sfcgal.xml b/doc/reference_sfcgal.xml
index 1f4dfe117..7f00975ee 100644
--- a/doc/reference_sfcgal.xml
+++ b/doc/reference_sfcgal.xml
@@ -319,7 +319,7 @@
     <refnamediv>
         <refname>CG_Volume</refname>
 
-        <refpurpose>Computes the volume of a 3D solid. If applied to surface geometries, even closed surfaces, returns 0.</refpurpose>
+        <refpurpose>Computes the volume of a 3D geometry. Closed surfaces can have non-zero volume.</refpurpose>
     </refnamediv>
 
     <refsynopsisdiv>
@@ -340,15 +340,14 @@
         <para>&P_support;</para>
         <para>&T_support;</para>
         <para>&sqlmm_compliant; SQL-MM IEC 13249-3: 9.1 (same as CG_3DVolume)</para>
-        <para>Closed surface geometries are still surfaces by default. Use
-        <xref linkend="CG_MakeSolid"/> to make the solid representation explicit
-        before computing volume.</para>
+        <para>Closed surface geometries can have non-zero volume. Use
+        <xref linkend="CG_MakeSolid"/> when the geometry value itself must carry
+        the solid flag.</para>
     </refsection>
 
     <refsection>
         <title>Example</title>
-        <para>A closed polyhedral surface can be converted to a solid with
-        <xref linkend="CG_MakeSolid"/> before computing volume.</para>
+        <para>A closed polyhedral surface has volume.  <xref linkend="CG_MakeSolid"/> makes the solid representation explicit.</para>
     <programlisting>SELECT CG_Volume(geom) As cube_surface_vol,
     CG_Volume(CG_MakeSolid(geom)) As solid_surface_vol
     FROM (SELECT 'POLYHEDRALSURFACE( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),
@@ -597,7 +596,7 @@
     <refnamediv>
         <refname>ST_Volume</refname>
 
-        <refpurpose>Computes the volume of a 3D solid. If applied to surface geometries, even closed surfaces, returns 0.</refpurpose>
+        <refpurpose>Computes the volume of a 3D geometry. Closed surfaces can have non-zero volume.</refpurpose>
     </refnamediv>
 
     <refsynopsisdiv>
@@ -624,15 +623,14 @@
         <para>&P_support;</para>
         <para>&T_support;</para>
         <para>&sqlmm_compliant; SQL-MM IEC 13249-3: 9.1 (same as ST_3DVolume)</para>
-        <para>Closed surface geometries are still surfaces by default. Use
-        <xref linkend="ST_MakeSolid"/> to make the solid representation explicit
-        before computing volume.</para>
+        <para>Closed surface geometries can have non-zero volume. Use
+        <xref linkend="ST_MakeSolid"/> when the geometry value itself must carry
+        the solid flag.</para>
     </refsection>
 
     <refsection>
         <title>Example</title>
-        <para>A closed polyhedral surface can be converted to a solid with
-        <xref linkend="ST_MakeSolid"/> before computing volume.</para>
+        <para>A closed polyhedral surface has volume.  <xref linkend="ST_MakeSolid"/> makes the solid representation explicit.</para>
     <programlisting>SELECT ST_Volume(geom) As cube_surface_vol,
     ST_Volume(ST_MakeSolid(geom)) As solid_surface_vol
     FROM (SELECT 'POLYHEDRALSURFACE( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),
diff --git a/liblwgeom/cunit/cu_surface.c b/liblwgeom/cunit/cu_surface.c
index 985eb63db..57a458fd8 100644
--- a/liblwgeom/cunit/cu_surface.c
+++ b/liblwgeom/cunit/cu_surface.c
@@ -404,6 +404,29 @@ check_dimension(char *ewkt, int dim)
 	lwgeom_free(geom);
 }
 
+static void
+check_st_dimension(char *ewkt, int dim)
+{
+	LWGEOM *geom;
+
+	geom = lwgeom_from_wkt(ewkt, LW_PARSER_CHECK_NONE);
+	CU_ASSERT_EQUAL(strlen(cu_error_msg), 0);
+	CU_ASSERT_EQUAL(lwgeom_dimension(geom), dim);
+	lwgeom_free(geom);
+}
+
+static void
+check_solid_st_dimension(char *ewkt, int dim)
+{
+	LWGEOM *geom;
+
+	geom = lwgeom_from_wkt(ewkt, LW_PARSER_CHECK_NONE);
+	CU_ASSERT_EQUAL(strlen(cu_error_msg), 0);
+	FLAGS_SET_SOLID(geom->flags, 1);
+	CU_ASSERT_EQUAL(lwgeom_dimension(geom), dim);
+	lwgeom_free(geom);
+}
+
 void
 surface_dimension(void)
 {
@@ -418,6 +441,14 @@ surface_dimension(void)
 	/* Tetrahedron */
 	check_dimension("POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))", 3);
 	check_dimension("TIN(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))", 3);
+
+	/* ST_Dimension follows surface/solid type semantics. */
+	check_st_dimension(
+	    "TIN(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))",
+	    2);
+	check_solid_st_dimension(
+	    "TIN(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))",
+	    3);
 }
 
 
diff --git a/liblwgeom/lwgeom.c b/liblwgeom/lwgeom.c
index 4d547c494..9d461c730 100644
--- a/liblwgeom/lwgeom.c
+++ b/liblwgeom/lwgeom.c
@@ -1636,8 +1636,8 @@ int lwgeom_dimension(const LWGEOM *geom)
 	case MULTIPOLYGONTYPE:
 		return 2;
 	case TINTYPE:
-		/* A closed TIN surface is not a solid unless cast with MakeSolid. */
-		return 2;
+		/* A TIN surface is dimension 2 unless it carries an explicit solid flag. */
+		return FLAGS_GET_SOLID(geom->flags) ? 3 : 2;
 	case POLYHEDRALSURFACETYPE:
 	{
 		/* A closed polyhedral surface contains a volume. */

commit 3b1b7f262663ad99c4daa1623e4a60381bd2860e
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 17:21:29 2026 +0400

    doc: clarify TIN dimension and solid semantics

diff --git a/doc/reference_accessor.xml b/doc/reference_accessor.xml
index b021b9bf9..03c2d88bd 100644
--- a/doc/reference_accessor.xml
+++ b/doc/reference_accessor.xml
@@ -344,6 +344,16 @@ SELECT ST_X(ST_StartPoint(ST_BoundingDiagonal(
 		</para>
 
 		<para>&sqlmm_compliant; SQL-MM 3: 5.1.2</para>
+		<note>
+		  <para>
+			<xref linkend="ST_Dimension"/> follows the SQL-MM topological dimension
+			contract.  A <varname>TIN</varname>, including a closed TIN surface,
+			is a surface geometry and has dimension 2.  Solid geometry semantics,
+			such as volume, are handled separately by the SQL/MM 3D solid
+			functions (SQL-MM IEC 13249-3: 9.1) and by
+			<xref linkend="CG_MakeSolid"/>.
+		  </para>
+		</note>
 		<para role="enhanced" conformance="2.0.0">Enhanced: 2.0.0 support for Polyhedral surfaces and TINs was introduced. No longer throws an exception if given empty geometry.</para>
 		<note> <para>Prior to 2.0.0, this function throws an exception if used with empty geometry. </para></note>
 		<para>&P_support;</para>
@@ -355,11 +365,13 @@ SELECT ST_X(ST_StartPoint(ST_BoundingDiagonal(
 
 		 <programlisting>SELECT ST_Dimension('GEOMETRYCOLLECTION(LINESTRING(1 1,0 0),POINT(0 0))');</programlisting>
 <screen>1</screen>
+		 <programlisting>SELECT ST_Dimension('TIN Z (((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))');</programlisting>
+<screen>2</screen>
 		</refsection>
 		<refsection>
 			<title>See Also</title>
 
-			<para><xref linkend="ST_NDims"/></para>
+			<para><xref linkend="ST_NDims"/>, <xref linkend="CG_MakeSolid"/>, <xref linkend="CG_Volume"/></para>
 		</refsection>
 	</refentry>
 
diff --git a/doc/reference_sfcgal.xml b/doc/reference_sfcgal.xml
index fedb49dd0..1f4dfe117 100644
--- a/doc/reference_sfcgal.xml
+++ b/doc/reference_sfcgal.xml
@@ -319,7 +319,7 @@
     <refnamediv>
         <refname>CG_Volume</refname>
 
-        <refpurpose>Computes the volume of a 3D geometry. Closed surfaces can have non-zero volume.</refpurpose>
+        <refpurpose>Computes the volume of a 3D solid. If applied to surface geometries, even closed surfaces, returns 0.</refpurpose>
     </refnamediv>
 
     <refsynopsisdiv>
@@ -340,11 +340,15 @@
         <para>&P_support;</para>
         <para>&T_support;</para>
         <para>&sqlmm_compliant; SQL-MM IEC 13249-3: 9.1 (same as CG_3DVolume)</para>
+        <para>Closed surface geometries are still surfaces by default. Use
+        <xref linkend="CG_MakeSolid"/> to make the solid representation explicit
+        before computing volume.</para>
     </refsection>
 
     <refsection>
         <title>Example</title>
-        <para>A closed polyhedral surface has volume.  <xref linkend="CG_MakeSolid"/> makes the solid representation explicit.</para>
+        <para>A closed polyhedral surface can be converted to a solid with
+        <xref linkend="CG_MakeSolid"/> before computing volume.</para>
     <programlisting>SELECT CG_Volume(geom) As cube_surface_vol,
     CG_Volume(CG_MakeSolid(geom)) As solid_surface_vol
     FROM (SELECT 'POLYHEDRALSURFACE( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),
@@ -593,7 +597,7 @@
     <refnamediv>
         <refname>ST_Volume</refname>
 
-        <refpurpose>Computes the volume of a 3D geometry. Closed surfaces can have non-zero volume.</refpurpose>
+        <refpurpose>Computes the volume of a 3D solid. If applied to surface geometries, even closed surfaces, returns 0.</refpurpose>
     </refnamediv>
 
     <refsynopsisdiv>
@@ -620,11 +624,15 @@
         <para>&P_support;</para>
         <para>&T_support;</para>
         <para>&sqlmm_compliant; SQL-MM IEC 13249-3: 9.1 (same as ST_3DVolume)</para>
+        <para>Closed surface geometries are still surfaces by default. Use
+        <xref linkend="ST_MakeSolid"/> to make the solid representation explicit
+        before computing volume.</para>
     </refsection>
 
     <refsection>
         <title>Example</title>
-        <para>A closed polyhedral surface has volume.  <xref linkend="ST_MakeSolid"/> makes the solid representation explicit.</para>
+        <para>A closed polyhedral surface can be converted to a solid with
+        <xref linkend="ST_MakeSolid"/> before computing volume.</para>
     <programlisting>SELECT ST_Volume(geom) As cube_surface_vol,
     ST_Volume(ST_MakeSolid(geom)) As solid_surface_vol
     FROM (SELECT 'POLYHEDRALSURFACE( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),

commit 5de0fe91928943acc8dcef99884782bf453e715d
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jul 7 17:18:08 2026 +0400

    liblwgeom: document TIN dimension semantics

diff --git a/liblwgeom/lwgeom.c b/liblwgeom/lwgeom.c
index 1add20cb3..4d547c494 100644
--- a/liblwgeom/lwgeom.c
+++ b/liblwgeom/lwgeom.c
@@ -1636,6 +1636,7 @@ int lwgeom_dimension(const LWGEOM *geom)
 	case MULTIPOLYGONTYPE:
 		return 2;
 	case TINTYPE:
+		/* A closed TIN surface is not a solid unless cast with MakeSolid. */
 		return 2;
 	case POLYHEDRALSURFACETYPE:
 	{
@@ -1742,11 +1743,17 @@ static int lwcollection_dimensionality(const LWCOLLECTION *col)
  * - 0 for points and multipoints,
  * - 1 for linear/curve types (lines, circular strings, compound/multi-curves, NURBS),
  * - 2 for polygonal/surface types,
- * - 3 for polyhedral surfaces or TINs that are closed (otherwise 2).
+ * Closure-aware dimensionality used by internal validity/repair checks.
+ *
+ * This is intentionally not the same contract as lwgeom_dimension(), which
+ * backs ST_Dimension.  Closed polyhedral surfaces and TINs are reported as 3
+ * here so callers that reason about solid-like inputs keep their legacy
+ * behavior.
+ *
  * For COLLECTIONTYPE the result is the maximum dimensionality of its members.
  *
  * @param geom Geometry to inspect (must be non-NULL).
- * @return The topological dimension (0..3). On unsupported types the function logs an error and returns 0.
+ * @return The closure-aware dimensionality (0..3). On unsupported types the function logs an error and returns 0.
  */
 extern int lwgeom_dimensionality(const LWGEOM *geom)
 {
diff --git a/regress/core/triangulatedsurface.sql b/regress/core/triangulatedsurface.sql
index fe6fab17c..4619ff73b 100644
--- a/regress/core/triangulatedsurface.sql
+++ b/regress/core/triangulatedsurface.sql
@@ -2,11 +2,11 @@
 SELECT 'dimension_01', ST_Dimension('TIN(((0 0,1 1,0 1,0 0)))'::geometry);
 SELECT 'dimension_02', ST_Dimension('GEOMETRYCOLLECTION(TIN(((0 0,1 1,0 1,0 0))))'::geometry);
 
--- ST_Dimension on 3D: closed
+-- ST_Dimension on 3D: closed surface, not solid
 SELECT 'dimension_03', ST_Dimension('TIN(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))'::geometry);
 SELECT 'dimension_04', ST_Dimension('GEOMETRYCOLLECTION(TIN(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0))))'::geometry);
 
--- ST_Dimension on 4D: closed
+-- ST_Dimension on 4D: closed surface, not solid
 SELECT 'dimension_05', ST_Dimension('TIN(((0 0 0 0,0 0 1 0,0 1 0 2,0 0 0 0)),((0 0 0 0,0 1 0 0,1 0 0 4,0 0 0 0)),((0 0 0 0,1 0 0 0,0 0 1 6,0 0 0 0)),((1 0 0 0,0 1 0 0,0 0 1 0,1 0 0 0)))'::geometry);
 SELECT 'dimension_06', ST_Dimension('GEOMETRYCOLLECTION(TIN(((0 0 0 0,0 0 1 0,0 1 0 2,0 0 0 0)),((0 0 0 0,0 1 0 0,1 0 0 4,0 0 0 0)),((0 0 0 0,1 0 0 0,0 0 1 6,0 0 0 0)),((1 0 0 0,0 1 0 0,0 0 1 0,1 0 0 0))))'::geometry);
 

commit d45d6e66d16b6c1e6d160895269db0ef0d7d9855
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Jul 6 11:37:12 2026 +0400

    topology: avoid rebuilding unsafe repair indexes

diff --git a/doc/extras_topology.xml b/doc/extras_topology.xml
index f059d3d7d..875cc95e0 100644
--- a/doc/extras_topology.xml
+++ b/doc/extras_topology.xml
@@ -563,6 +563,7 @@ Rename a topology from <varname>topo_stage</varname> to <varname>topo_prod</varn
 When upgrading from PostGIS topology <3.6.0 to version >3.6.0+, the topogeometry column definition was changed.
 This caused corruption in topogeometries created before the upgrade. This function fixes this corruption in affected tables.
                 </para>
+                <para>The function refuses to process tables with expression or partial indexes; drop or rebuild those indexes manually around the repair.</para>
 
                 <!-- use this format if new function -->
                 <para role="availability" conformance="3.6.1">Availability: 3.6.1</para>
diff --git a/topology/sql/manage/FixCorruptTopoGeometryColumn.sql.in b/topology/sql/manage/FixCorruptTopoGeometryColumn.sql.in
index b385d4686..203318379 100644
--- a/topology/sql/manage/FixCorruptTopoGeometryColumn.sql.in
+++ b/topology/sql/manage/FixCorruptTopoGeometryColumn.sql.in
@@ -21,6 +21,33 @@ $$
 DECLARE var_sql text; var_row_count bigint; result text; var_create_index_sql text; var_drop_index_sql text;
 BEGIN
     result = '';
+    EXECUTE format('LOCK TABLE %1$I.%2$I IN SHARE ROW EXCLUSIVE MODE', layerSchema, layerTable);
+
+    IF EXISTS (
+        SELECT 1
+        FROM pg_catalog.pg_inherits inherited
+        JOIN pg_catalog.pg_class tbl ON tbl.oid = inherited.inhparent
+        JOIN pg_catalog.pg_namespace nsp ON nsp.oid = tbl.relnamespace
+        WHERE nsp.nspname = layerSchema
+          AND tbl.relname = layerTable
+    ) THEN
+        RAISE EXCEPTION 'Cannot safely repair %.% while inheritance or partition child tables exist; repair each table separately',
+            layerSchema, layerTable;
+    END IF;
+
+    IF EXISTS (
+        SELECT 1
+        FROM pg_catalog.pg_index i
+        JOIN pg_catalog.pg_class tbl ON tbl.oid = i.indrelid
+        JOIN pg_catalog.pg_namespace nsp ON nsp.oid = tbl.relnamespace
+        WHERE nsp.nspname = layerSchema
+          AND tbl.relname = layerTable
+          AND (i.indexprs IS NOT NULL OR i.indpred IS NOT NULL)
+    ) THEN
+        RAISE EXCEPTION 'Cannot safely repair %.% while expression or partial indexes exist; drop or rebuild those indexes manually',
+            layerSchema, layerTable;
+    END IF;
+
 	-- if topogeometry is bigint, then fix damaged integer, need to upgrade to bigint
 	IF EXISTS ( SELECT 1
 		FROM  pg_catalog.pg_type AS pg_type
@@ -31,12 +58,27 @@ WHERE pg_type.typname = 'topogeometry' AND pga.attname = 'id'
 			AND
 		pg_type.typnamespace::regnamespace::text = 'topology' AND  pga.atttypid::regtype::text = 'bigint' ) THEN
 
-    -- generate index scripts to create and drop indexes that are based on the column
-    IF EXISTS( SELECT 1 FROM pg_indexes WHERE schemaname = layerSchema AND tablename = layerTable AND indexdef LIKE '%(' || layerColumn || ')%' ) THEN
-        SELECT string_agg(indexdef, ';'),  string_agg('DROP INDEX ' || quote_ident(schemaname) || '.' || quote_ident(indexname), ';')  INTO var_create_index_sql, var_drop_index_sql
-            FROM pg_indexes
-             WHERE schemaname = layerSchema
-                AND tablename = layerTable AND indexdef LIKE ('%(' || layerColumn || ')%');
+    -- generate scripts for plain indexes that directly include the column
+    IF EXISTS (
+        SELECT 1
+        FROM pg_catalog.pg_index i
+        JOIN pg_catalog.pg_class tbl ON tbl.oid = i.indrelid
+        JOIN pg_catalog.pg_namespace nsp ON nsp.oid = tbl.relnamespace
+        JOIN pg_catalog.pg_attribute att ON att.attrelid = tbl.oid AND att.attnum = ANY(string_to_array(i.indkey::text, ' ')::int2[])
+        WHERE nsp.nspname = layerSchema
+          AND tbl.relname = layerTable
+          AND att.attname = layerColumn
+    ) THEN
+        SELECT string_agg(pg_catalog.pg_get_indexdef(i.indexrelid), ';'),
+               string_agg('DROP INDEX ' || i.indexrelid::regclass::text, ';')
+        INTO var_create_index_sql, var_drop_index_sql
+        FROM pg_catalog.pg_index i
+        JOIN pg_catalog.pg_class tbl ON tbl.oid = i.indrelid
+        JOIN pg_catalog.pg_namespace nsp ON nsp.oid = tbl.relnamespace
+        JOIN pg_catalog.pg_attribute att ON att.attrelid = tbl.oid AND att.attnum = ANY(string_to_array(i.indkey::text, ' ')::int2[])
+        WHERE nsp.nspname = layerSchema
+          AND tbl.relname = layerTable
+          AND att.attname = layerColumn;
     END IF;
 
     IF var_drop_index_sql > '' THEN
@@ -44,7 +86,7 @@ WHERE pg_type.typname = 'topogeometry' AND pga.attname = 'id'
     END IF;
 
     -- correct any corrupt topogeometries and fix
-	 var_sql =  format('UPDATE %1$I.%2$I
+	 var_sql =  format('UPDATE ONLY %1$I.%2$I
 	 	SET
 	   %3$I = (
 	     (%3$I).topology_id,
@@ -63,7 +105,7 @@ WHERE pg_type.typname = 'topogeometry' AND pga.attname = 'id'
     END IF;
 	result = result || E'\n' || format('%s rows updated for %s.%s.%s column to bigint id type', var_row_count, layerSchema, layerTable, layerColumn);
   ELSE --we are coming from bigint and going back to integer
-  	var_sql =  format('UPDATE %1$I.%2$I
+	var_sql =  format('UPDATE ONLY %1$I.%2$I
 	 	SET
 	   %3$I = (
 	     (%3$I).topology_id,
diff --git a/topology/test/regress/hooks/hook-after-upgrade-topology.sql b/topology/test/regress/hooks/hook-after-upgrade-topology.sql
index c9b25bb51..3030dc731 100644
--- a/topology/test/regress/hooks/hook-after-upgrade-topology.sql
+++ b/topology/test/regress/hooks/hook-after-upgrade-topology.sql
@@ -1,6 +1,7 @@
 SELECT * FROM topology.layer;
 \d upgrade_test.feature
 -- https://trac.osgeo.org/postgis/ticket/5983
+DROP INDEX upgrade_test.upgrade_test_feature_tg_id_idx;
 SELECT topology.FixCorruptTopoGeometryColumn(schema_name, table_name, feature_column)
     FROM topology.layer;
 
@@ -22,4 +23,3 @@ INSERT INTO upgrade_test.domain_test values (
 
 SELECT topology.DropTopology('upgrade_test');
 SELECT topology.DropTopology('upgrade_test_copy');
-
diff --git a/topology/test/regress/hooks/hook-before-upgrade-topology.sql b/topology/test/regress/hooks/hook-before-upgrade-topology.sql
index fc2ffb1e8..ca6e94cee 100644
--- a/topology/test/regress/hooks/hook-before-upgrade-topology.sql
+++ b/topology/test/regress/hooks/hook-before-upgrade-topology.sql
@@ -4,7 +4,7 @@ SELECT topology.createTopology('upgrade_test');
 CREATE TABLE upgrade_test.feature(id serial primary key);
 SELECT topology.AddTopoGeometryColumn('upgrade_test', 'upgrade_test', 'feature', 'tg', 'linear');
 INSERT INTO upgrade_test.feature(tg) SELECT topology.toTopoGeom('LINESTRING(0 0, 10 0)', 'upgrade_test', 1);
-CREATE INDEX ON upgrade_test.feature ( id(tg) );
+CREATE INDEX upgrade_test_feature_tg_id_idx ON upgrade_test.feature ( id(tg) );
 
 -- Create some TopoGeometry data
 CREATE TABLE upgrade_test.domain_test(a topology.topoelement, b topology.topoelementarray);
@@ -12,4 +12,3 @@ INSERT INTO upgrade_test.domain_test values (
   '{1,2}'::topology.topoelement,
   '{{2,3}}'::topology.topoelementarray
 );
-

commit f1fb51cedf07759de2f3a189e14064a4c27a7b45
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Jul 6 11:36:20 2026 +0400

    liblwgeom: keep TIN dimension cheap

diff --git a/liblwgeom/lwgeom.c b/liblwgeom/lwgeom.c
index 158508a73..1add20cb3 100644
--- a/liblwgeom/lwgeom.c
+++ b/liblwgeom/lwgeom.c
@@ -1636,11 +1636,7 @@ int lwgeom_dimension(const LWGEOM *geom)
 	case MULTIPOLYGONTYPE:
 		return 2;
 	case TINTYPE:
-	{
-		/* A closed tin surface contains a volume. */
-		int closed = lwtin_is_closed((LWTIN*)geom);
-		return ( closed ? 3 : 2 );
-	}
+		return 2;
 	case POLYHEDRALSURFACETYPE:
 	{
 		/* A closed polyhedral surface contains a volume. */
diff --git a/regress/core/triangulatedsurface_expected b/regress/core/triangulatedsurface_expected
index 5c8722d2e..e09a72230 100644
--- a/regress/core/triangulatedsurface_expected
+++ b/regress/core/triangulatedsurface_expected
@@ -1,9 +1,9 @@
 dimension_01|2
 dimension_02|2
-dimension_03|3
-dimension_04|3
-dimension_05|3
-dimension_06|3
+dimension_03|2
+dimension_04|2
+dimension_05|2
+dimension_06|2
 dimension_07|2
 dimension_08|2
 numpatches_01|0

commit 1dcbdacde703bfbd404aa7d4805d588a8367f474
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Jul 6 11:35:31 2026 +0400

    liblwgeom: cap NURBS curve linearization

diff --git a/liblwgeom/lwstroke.c b/liblwgeom/lwstroke.c
index 1e7cf6618..6ff6e5b43 100644
--- a/liblwgeom/lwstroke.c
+++ b/liblwgeom/lwstroke.c
@@ -48,7 +48,7 @@ LWGEOM* lwgeom_unstroke(const LWGEOM *geom);
 static LWLINE* lwnurbscurve_linearize(const LWNURBSCURVE *curve, double tol, LW_LINEARIZE_TOLERANCE_TYPE tolerance_type, int flags);
 
 #define NURBS_MIN_LINEARIZE_SEGMENTS 8
-
+#define NURBS_MAX_LINEARIZE_SEGMENTS 10000
 
 /*
  * Determines (recursively in the case of collections) whether the geometry
@@ -919,7 +919,7 @@ lwnurbscurve_linearize(const LWNURBSCURVE *curve, double tol,
 			lwerror("%s: segs-per-quad must be an integer, got %.15g", __func__, tol);
 			return NULL;
 		}
-		if (tol > ((double)UINT32_MAX / 4.0))
+		if (tol > ((double)NURBS_MAX_LINEARIZE_SEGMENTS / 4.0))
 		{
 			lwerror("%s: segs-per-quad is too large, got %.15g", __func__, tol);
 			return NULL;
@@ -969,6 +969,11 @@ lwnurbscurve_linearize(const LWNURBSCURVE *curve, double tol,
 
 	if (num_segments < NURBS_MIN_LINEARIZE_SEGMENTS)
 		num_segments = NURBS_MIN_LINEARIZE_SEGMENTS;
+	if (num_segments > NURBS_MAX_LINEARIZE_SEGMENTS)
+	{
+		lwerror("%s: requested too many segments, got %u", __func__, (unsigned int)num_segments);
+		return NULL;
+	}
 
 	(void)flags; /* Currently unused for NURBS linearization */
 
diff --git a/regress/core/nurbs_eval.sql b/regress/core/nurbs_eval.sql
index df430b3b7..c74691e1b 100644
--- a/regress/core/nurbs_eval.sql
+++ b/regress/core/nurbs_eval.sql
@@ -162,6 +162,15 @@ SELECT 'NURBS_CURVETOLINE_TOLERANCE_2',
     ST_GeometryType(ST_CurveToLine('NURBSCURVE(2, (0 0, 1 1, 2 0))'::geometry, 0.01, 2)),
     ST_NPoints(ST_CurveToLine('NURBSCURVE(2, (0 0, 1 1, 2 0))'::geometry, 0.01, 2));
 
+SELECT 'NURBS_CURVETOLINE_SEGMENT_CAP',
+    ST_CurveToLine('NURBSCURVE(2, (0 0, 1 1, 2 0))'::geometry, 2501, 0);
+
+SELECT 'NURBS_CURVETOLINE_MAX_DEVIATION_CAP',
+    ST_CurveToLine('NURBSCURVE(2, (0 0, 1 1, 2 0))'::geometry, 0.0001, 1);
+
+SELECT 'NURBS_CURVETOLINE_MAX_ANGLE_CAP',
+    ST_CurveToLine('NURBSCURVE(2, (0 0, 1 1, 2 0))'::geometry, 0.0001, 2);
+
 SELECT 'NURBS_CURVETOLINE_3',
     ST_AsEWKT(ST_CurveToLine('SRID=4326;NURBSCURVE Z EMPTY'::geometry)),
     ST_Zmflag(ST_CurveToLine('SRID=4326;NURBSCURVE Z EMPTY'::geometry)),
diff --git a/regress/core/nurbs_eval_expected b/regress/core/nurbs_eval_expected
index 636a1a825..627438a5b 100644
--- a/regress/core/nurbs_eval_expected
+++ b/regress/core/nurbs_eval_expected
@@ -24,6 +24,9 @@ NURBS_CURVETOLINE_1|ST_LineString
 NURBS_CURVETOLINE_2|ST_MultiLineString
 NURBS_CURVETOLINE_TOLERANCE_1|ST_LineString|225
 NURBS_CURVETOLINE_TOLERANCE_2|ST_LineString|630
+ERROR:  lwnurbscurve_linearize: segs-per-quad is too large, got 2501
+ERROR:  lwnurbscurve_linearize: requested too many segments, got 22361
+ERROR:  lwnurbscurve_linearize: requested too many segments, got 62832
 NURBS_CURVETOLINE_3|SRID=4326;LINESTRING EMPTY|2|4326
 NURBS_LENGTH_1|t
 NURBS_CURVEPOLY_1|t

commit 55ea7230fb35f394051929d53c5f4c58ef393cd7
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Jul 6 17:51:25 2026 +0400

    NEWS: target entry to 3.7.0beta1

diff --git a/NEWS b/NEWS
index a72ae6492..70a3dc001 100644
--- a/NEWS
+++ b/NEWS
@@ -24,7 +24,7 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
           (Darafei Praliaskouski)
 
 
-PostGIS 3.7.0beta1
+PostGIS 3.7.0alpha1
 2026/07/05
 
 This version requires GEOS 3.10+, PostgreSQL 14-19beta1, Proj 6.1+, libgmp.

commit 3b4aeb707e696e9d92d6da4d3fc89a3fd60f2b45
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Jul 6 17:46:32 2026 +0400

    NEWS: move entry to 3.7.0beta1

diff --git a/NEWS b/NEWS
index af5ae11da..678b9140f 100644
--- a/NEWS
+++ b/NEWS
@@ -23,8 +23,13 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
  - GH-1153, [topology] Preserve large ids from edge wrappers
           (Darafei Praliaskouski)
 
+* Enhancements *
 
-PostGIS 3.7.0beta1
+ - #4749, Use point-in-polygon predicate fast paths for point-only
+          GeometryCollections, including ST_Disjoint (Darafei Praliaskouski)
+
+
+PostGIS 3.7.0alpha1
 2026/07/05
 
 This version requires GEOS 3.10+, PostgreSQL 14-19beta1, Proj 6.1+, libgmp.
@@ -112,7 +117,7 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
  - #2137, [raster] Reuse matching source raster overviews in raster2pgsql -l
           (Darafei Praliaskouski)
  - #4749, Use point-in-polygon predicate fast paths for point-only
-          GeometryCollections, including ST_Disjoint (Darafei Praliaskouski)
+          GeometryCollections (Darafei Praliaskouski)
  - #5532, Validate the manual against DocBook XMLSchema in check-xml
           (Darafei Praliaskouski)
  - #6087, Add a repository-owned CI status checker and static dashboard

commit e48cae0ad147d669c2688b21ce1d0df219a431cf
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Jul 6 17:45:56 2026 +0400

    NEWS: move entry to 3.7.0beta1

diff --git a/NEWS b/NEWS
index bc002d3da..86af0e68a 100644
--- a/NEWS
+++ b/NEWS
@@ -23,8 +23,13 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
  - GH-1153, [topology] Preserve large ids from edge wrappers
           (Darafei Praliaskouski)
 
+* Enhancements *
 
-PostGIS 3.7.0beta1
+ - #3676, Mark geometry and geography btree comparison support functions
+          as leakproof (Darafei Praliaskouski)
+
+
+PostGIS 3.7.0alpha1
 2026/07/05
 
 This version requires GEOS 3.10+, PostgreSQL 14-19beta1, Proj 6.1+, libgmp.
@@ -98,8 +103,6 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
           public liblwgeom header (Darafei Praliaskouski)
  - #4678, [raster] Move st_sum4ma neighborhood callback to C
           (Darafei Praliaskouski)
- - #3676, Mark geometry and geography btree comparison support functions
-          as leakproof (Darafei Praliaskouski)
  - #2116, [raster] Add ST_Value nearest-neighbor boundary options
           (Darafei Praliaskouski)
  - #2804, #4315, [raster] Support two-argument ST_MapAlgebra callbacks

commit 63fc924b351d61a5911235e081c7ad7fb1b8f0e5
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Jul 6 16:03:23 2026 +0400

    ci: avoid Homebrew PostgreSQL postinstall initdb

diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml
index c4ae069c1..c16ad1c0e 100644
--- a/.github/workflows/ci-macos.yml
+++ b/.github/workflows/ci-macos.yml
@@ -39,8 +39,14 @@ jobs:
             gdal geos icu4c json-c libpq libxml2 \
             proj protobuf-c sfcgal cunit \
             docbook docbook-xsl \
-            postgresql@${PG} gettext
+            gettext
           brew link --force gettext
+          HOMEBREW_GITHUB_ACTIONS=1 brew install "postgresql@${PG}"
+          pg_version="$(brew info --json=v2 "postgresql@${PG}" | python3 -c 'import json,sys; data=json.load(sys.stdin); print(data["formulae"][0]["installed"][0]["version"])')"
+          ln -sfn "${HOMEBREW_PREFIX}/Cellar/postgresql@${PG}/${pg_version}/share/postgresql@${PG}" "${HOMEBREW_PREFIX}/share/postgresql@${PG}"
+          if [ ! -f "${HOMEBREW_PREFIX}/var/postgresql@${PG}/PG_VERSION" ]; then
+            "${HOMEBREW_PREFIX}/opt/postgresql@${PG}/bin/initdb" --locale=en_US.UTF-8 -E UTF-8 "${HOMEBREW_PREFIX}/var/postgresql@${PG}"
+          fi
           ccache --max-size="${CCACHE_MAXSIZE}"
           sudo ln -sfn "${HOMEBREW_PREFIX}/opt/postgresql@${PG}/bin/postgres" /usr/local/bin/postgres
 

commit 91233df1d245b30edda27a99841bd6b2fe96c89c
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Jul 6 16:03:07 2026 +0400

    ci: avoid Homebrew PostgreSQL postinstall initdb

diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml
index c4ae069c1..c16ad1c0e 100644
--- a/.github/workflows/ci-macos.yml
+++ b/.github/workflows/ci-macos.yml
@@ -39,8 +39,14 @@ jobs:
             gdal geos icu4c json-c libpq libxml2 \
             proj protobuf-c sfcgal cunit \
             docbook docbook-xsl \
-            postgresql@${PG} gettext
+            gettext
           brew link --force gettext
+          HOMEBREW_GITHUB_ACTIONS=1 brew install "postgresql@${PG}"
+          pg_version="$(brew info --json=v2 "postgresql@${PG}" | python3 -c 'import json,sys; data=json.load(sys.stdin); print(data["formulae"][0]["installed"][0]["version"])')"
+          ln -sfn "${HOMEBREW_PREFIX}/Cellar/postgresql@${PG}/${pg_version}/share/postgresql@${PG}" "${HOMEBREW_PREFIX}/share/postgresql@${PG}"
+          if [ ! -f "${HOMEBREW_PREFIX}/var/postgresql@${PG}/PG_VERSION" ]; then
+            "${HOMEBREW_PREFIX}/opt/postgresql@${PG}/bin/initdb" --locale=en_US.UTF-8 -E UTF-8 "${HOMEBREW_PREFIX}/var/postgresql@${PG}"
+          fi
           ccache --max-size="${CCACHE_MAXSIZE}"
           sudo ln -sfn "${HOMEBREW_PREFIX}/opt/postgresql@${PG}/bin/postgres" /usr/local/bin/postgres
 

commit e94600a0847e77a3ad30fb85986b609f096d170a
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Jul 6 16:02:40 2026 +0400

    postgis: maintenance retargets

diff --git a/NEWS b/NEWS
index 98e588906..bc002d3da 100644
--- a/NEWS
+++ b/NEWS
@@ -24,7 +24,7 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
           (Darafei Praliaskouski)
 
 
-PostGIS 3.7.0alpha1
+PostGIS 3.7.0beta1
 2026/07/05
 
 This version requires GEOS 3.10+, PostgreSQL 14-19beta1, Proj 6.1+, libgmp.

commit 2045b9774fec76e87c758276459c0b1d94632c81
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Jun 21 16:48:07 2026 +0400

    postgis: mark btree comparisons leakproof
    
    The geometry and geography btree comparison support functions use serialized-value comparison and hashing without GEOS or GDAL calls. Mark this audited subset LEAKPROOF so security-barrier relations can use these operators more effectively.
    
    References #3676

diff --git a/NEWS b/NEWS
index 70a3dc001..98e588906 100644
--- a/NEWS
+++ b/NEWS
@@ -98,6 +98,8 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
           public liblwgeom header (Darafei Praliaskouski)
  - #4678, [raster] Move st_sum4ma neighborhood callback to C
           (Darafei Praliaskouski)
+ - #3676, Mark geometry and geography btree comparison support functions
+          as leakproof (Darafei Praliaskouski)
  - #2116, [raster] Add ST_Value nearest-neighbor boundary options
           (Darafei Praliaskouski)
  - #2804, #4315, [raster] Support two-argument ST_MapAlgebra callbacks
diff --git a/postgis/geography.sql.in b/postgis/geography.sql.in
index ced61b8c7..fa4e98b4d 100644
--- a/postgis/geography.sql.in
+++ b/postgis/geography.sql.in
@@ -313,37 +313,37 @@ CREATE OPERATOR CLASS gist_geography_ops
 CREATE OR REPLACE FUNCTION geography_lt(geography, geography)
 	RETURNS bool
 	AS 'MODULE_PATHNAME', 'geography_lt'
-	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE;
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF;
 
 -- Availability: 1.5.0
 CREATE OR REPLACE FUNCTION geography_le(geography, geography)
 	RETURNS bool
 	AS 'MODULE_PATHNAME', 'geography_le'
-	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE;
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF;
 
 -- Availability: 1.5.0
 CREATE OR REPLACE FUNCTION geography_gt(geography, geography)
 	RETURNS bool
 	AS 'MODULE_PATHNAME', 'geography_gt'
-	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE;
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF;
 
 -- Availability: 1.5.0
 CREATE OR REPLACE FUNCTION geography_ge(geography, geography)
 	RETURNS bool
 	AS 'MODULE_PATHNAME', 'geography_ge'
-	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE;
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF;
 
 -- Availability: 1.5.0
 CREATE OR REPLACE FUNCTION geography_eq(geography, geography)
 	RETURNS bool
 	AS 'MODULE_PATHNAME', 'geography_eq'
-	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE;
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF;
 
 -- Availability: 1.5.0
 CREATE OR REPLACE FUNCTION geography_cmp(geography, geography)
 	RETURNS integer
 	AS 'MODULE_PATHNAME', 'geography_cmp'
-	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE;
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF;
 
 --
 -- Sorting operators for Btree
diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in
index d4025cda8..9b19145b9 100644
--- a/postgis/postgis.sql.in
+++ b/postgis/postgis.sql.in
@@ -383,43 +383,43 @@ CREATE TYPE gidx (
 CREATE OR REPLACE FUNCTION geometry_lt(geom1 geometry, geom2 geometry)
 	RETURNS bool
 	AS 'MODULE_PATHNAME', 'lwgeom_lt'
-	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF
 	_COST_DEFAULT;
 
 CREATE OR REPLACE FUNCTION geometry_le(geom1 geometry, geom2 geometry)
 	RETURNS bool
 	AS 'MODULE_PATHNAME', 'lwgeom_le'
-	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF
 	_COST_DEFAULT;
 
 CREATE OR REPLACE FUNCTION geometry_gt(geom1 geometry, geom2 geometry)
 	RETURNS bool
 	AS 'MODULE_PATHNAME', 'lwgeom_gt'
-	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF
 	_COST_DEFAULT;
 
 CREATE OR REPLACE FUNCTION geometry_ge(geom1 geometry, geom2 geometry)
 	RETURNS bool
 	AS 'MODULE_PATHNAME', 'lwgeom_ge'
-	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF
 	_COST_DEFAULT;
 
 CREATE OR REPLACE FUNCTION geometry_eq(geom1 geometry, geom2 geometry)
 	RETURNS bool
 	AS 'MODULE_PATHNAME', 'lwgeom_eq'
-	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF
 	_COST_DEFAULT;
 
 CREATE OR REPLACE FUNCTION geometry_neq(geom1 geometry, geom2 geometry)
 	RETURNS bool
 	AS 'MODULE_PATHNAME', 'lwgeom_neq'
-	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF
 	_COST_DEFAULT;
 
 CREATE OR REPLACE FUNCTION geometry_cmp(geom1 geometry, geom2 geometry)
 	RETURNS integer
 	AS 'MODULE_PATHNAME', 'lwgeom_cmp'
-	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE LEAKPROOF
 	_COST_DEFAULT;
 
 -- Availability: 3.0.0
@@ -494,7 +494,7 @@ CREATE OPERATOR CLASS btree_geometry_ops
 CREATE OR REPLACE FUNCTION geometry_hash(geometry)
 	RETURNS integer
 	AS 'MODULE_PATHNAME','lwgeom_hash'
-	LANGUAGE 'c' STRICT IMMUTABLE PARALLEL SAFE;
+	LANGUAGE 'c' STRICT IMMUTABLE PARALLEL SAFE LEAKPROOF;
 
 -- Availability: 2.5.0
 CREATE OPERATOR CLASS hash_geometry_ops
diff --git a/regress/core/regress.sql b/regress/core/regress.sql
index e2a52a7b3..65e13cf5b 100644
--- a/regress/core/regress.sql
+++ b/regress/core/regress.sql
@@ -379,6 +379,26 @@ WHERE (
 AND prosecdef
 ORDER BY oid;
 
+SELECT 'leakproof_compare_support', count(*)
+FROM pg_proc
+WHERE oid IN (
+	'geometry_lt(geometry,geometry)'::regprocedure,
+	'geometry_le(geometry,geometry)'::regprocedure,
+	'geometry_gt(geometry,geometry)'::regprocedure,
+	'geometry_ge(geometry,geometry)'::regprocedure,
+	'geometry_eq(geometry,geometry)'::regprocedure,
+	'geometry_neq(geometry,geometry)'::regprocedure,
+	'geometry_cmp(geometry,geometry)'::regprocedure,
+	'geometry_hash(geometry)'::regprocedure,
+	'geography_lt(geography,geography)'::regprocedure,
+	'geography_le(geography,geography)'::regprocedure,
+	'geography_gt(geography,geography)'::regprocedure,
+	'geography_ge(geography,geography)'::regprocedure,
+	'geography_eq(geography,geography)'::regprocedure,
+	'geography_cmp(geography,geography)'::regprocedure
+)
+AND proleakproof;
+
 SELECT 'UNEXPECTED', postgis_full_version()
 	WHERE postgis_full_version() LIKE '%UNPACKAGED%'
 	   OR postgis_full_version() LIKE '%need upgrade%';
diff --git a/regress/core/regress_expected b/regress/core/regress_expected
index 42a03aaaf..f089a2df3 100644
--- a/regress/core/regress_expected
+++ b/regress/core/regress_expected
@@ -231,3 +231,4 @@ ERROR:  ST_TileEnvelope: Margin must not be less than -50%, margin=-0.510000
 314|676
 315|0
 316|t|LINESTRING(40 8.660254037844387,35 8.660254037844387)
+leakproof_compare_support|14

commit 8aa9623d7faf05cd175e08646863811ee9ce36c0
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Jul 6 16:02:39 2026 +0400

    postgis: move NEWS to 3.7.0beta1

diff --git a/NEWS b/NEWS
index 70a3dc001..a72ae6492 100644
--- a/NEWS
+++ b/NEWS
@@ -24,7 +24,7 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
           (Darafei Praliaskouski)
 
 
-PostGIS 3.7.0alpha1
+PostGIS 3.7.0beta1
 2026/07/05
 
 This version requires GEOS 3.10+, PostgreSQL 14-19beta1, Proj 6.1+, libgmp.

commit 5e3fb509b91046edda39d2202b045550dd81ff35
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Wed Jun 17 02:09:09 2026 +0400

    raster: lower ST_Intersects wrapper cost
    
    Closes #4463

diff --git a/raster/rt_pg/rtpostgis.sql.in b/raster/rt_pg/rtpostgis.sql.in
index aa09a7357..e6fe48407 100644
--- a/raster/rt_pg/rtpostgis.sql.in
+++ b/raster/rt_pg/rtpostgis.sql.in
@@ -164,7 +164,7 @@ CREATE OR REPLACE FUNCTION st_envelope(raster)
 CREATE OR REPLACE FUNCTION st_convexhull(raster)
     RETURNS geometry
     AS 'MODULE_PATHNAME','RASTER_convex_hull'
-    LANGUAGE 'c' IMMUTABLE STRICT
+    LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
     COST 300;
 
 CREATE OR REPLACE FUNCTION st_minconvexhull(
@@ -6032,13 +6032,13 @@ CREATE OR REPLACE FUNCTION st_intersects(rast1 raster, nband1 integer, rast2 ras
 	RETURNS boolean
 	AS $$ SELECT $1 OPERATOR(@extschema at .&&) $3 AND CASE WHEN $2 IS NULL OR $4 IS NULL THEN @extschema at ._st_intersects(@extschema at .st_convexhull($1), @extschema at .st_convexhull($3)) ELSE @extschema at ._st_intersects($1, $2, $3, $4) END $$
 	LANGUAGE 'sql' IMMUTABLE PARALLEL SAFE
-	COST 1000;
+	_COST_DEFAULT;
 
 CREATE OR REPLACE FUNCTION st_intersects(rast1 raster, rast2 raster)
 	RETURNS boolean
 	AS $$ SELECT @extschema at .st_intersects($1, NULL::integer, $2, NULL::integer) $$
 	LANGUAGE 'sql' IMMUTABLE PARALLEL SAFE
-	COST 1000;
+	_COST_DEFAULT;
 
 -----------------------------------------------------------------------
 -- ST_Intersects(geometry, raster)
@@ -6077,7 +6077,7 @@ CREATE OR REPLACE FUNCTION st_intersects(geom geometry, rast raster, nband integ
 	RETURNS boolean AS
 	$$ SELECT $1 OPERATOR(@extschema at .&&) $2::@extschema at .geometry AND @extschema at ._st_intersects($1, $2, $3); $$
 	LANGUAGE 'sql' IMMUTABLE PARALLEL SAFE
-	COST 1000;
+	_COST_DEFAULT;
 
 -----------------------------------------------------------------------
 -- ST_Intersects(raster, geometry)
@@ -6087,13 +6087,13 @@ CREATE OR REPLACE FUNCTION st_intersects(rast raster, geom geometry, nband integ
 	RETURNS boolean
 	AS $$ SELECT $1::@extschema at .geometry OPERATOR(@extschema at .&&) $2 AND @extschema at ._st_intersects($2, $1, $3) $$
 	LANGUAGE 'sql' IMMUTABLE PARALLEL SAFE
-	COST 1000;
+	_COST_DEFAULT;
 
 CREATE OR REPLACE FUNCTION st_intersects(rast raster, nband integer, geom geometry)
 	RETURNS boolean
 	AS $$ SELECT $1::@extschema at .geometry OPERATOR(@extschema at .&&) $3 AND @extschema at ._st_intersects($3, $1, $2) $$
 	LANGUAGE 'sql' IMMUTABLE PARALLEL SAFE
-	COST 1000;
+	_COST_DEFAULT;
 
 -----------------------------------------------------------------------
 -- ST_Overlaps(raster, raster)
diff --git a/raster/test/regress/rt_intersects.sql b/raster/test/regress/rt_intersects.sql
index cb0c944cf..fc3f7423e 100644
--- a/raster/test/regress/rt_intersects.sql
+++ b/raster/test/regress/rt_intersects.sql
@@ -431,5 +431,44 @@ FROM raster_intersects_rast r1
 CROSS JOIN raster_intersects_geom g1
 WHERE r1.rid = 2;
 
+CREATE INDEX raster_intersects_geom_idx ON raster_intersects_geom USING gist (geom);
+ANALYZE raster_intersects_rast (rid);
+ANALYZE raster_intersects_geom (geom);
+SET enable_seqscan = off;
+
+CREATE OR REPLACE FUNCTION raster_intersects_plan_has(query text, pattern text)
+	RETURNS boolean AS $$
+	DECLARE
+		exp text;
+	BEGIN
+		IF current_setting('server_version_num')::integer >= 140000 THEN
+			PERFORM set_config('enable_memoize', 'off', true);
+		END IF;
+
+		FOR exp IN EXECUTE 'EXPLAIN (COSTS OFF) ' || query LOOP
+			IF exp ~ pattern THEN
+				RETURN TRUE;
+			END IF;
+		END LOOP;
+		RETURN FALSE;
+	END;
+	$$ LANGUAGE 'plpgsql';
+
+SELECT
+	'#4463.1',
+	raster_intersects_plan_has(
+		'SELECT g1.gid FROM raster_intersects_geom g1 JOIN raster_intersects_rast r1 ON ST_Intersects(g1.geom, r1.rast) WHERE r1.rid = 0',
+		'Index Cond: .*geom && .*r1[.]rast.*geometry'
+	);
+
+SELECT
+	'#4463.2',
+	raster_intersects_plan_has(
+		'SELECT g1.gid FROM raster_intersects_geom g1 JOIN raster_intersects_rast r1 ON ST_Intersects(r1.rast, g1.geom) WHERE r1.rid = 0',
+		'Index Cond: .*geom && .*r1[.]rast.*geometry'
+	);
+
+RESET enable_seqscan;
+DROP FUNCTION raster_intersects_plan_has(text, text);
 DROP TABLE IF EXISTS raster_intersects_rast;
 DROP TABLE IF EXISTS raster_intersects_geom;
diff --git a/raster/test/regress/rt_intersects_expected b/raster/test/regress/rt_intersects_expected
index ae0ac1723..e3c4ca618 100644
--- a/raster/test/regress/rt_intersects_expected
+++ b/raster/test/regress/rt_intersects_expected
@@ -234,3 +234,5 @@
 2.6|2|44|ST_MultiPolygon|t
 2.6|2|45|ST_MultiPolygon|t
 2.6|2|46|ST_MultiPolygon|t
+#4463.1|t
+#4463.2|t

commit aafa0ec1d4a629b4c8977ba7a9cf6fc36ee04881
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Jul 6 16:01:30 2026 +0400

    postgis: maintenance retargets

diff --git a/NEWS b/NEWS
index 3c8b64a0c..af5ae11da 100644
--- a/NEWS
+++ b/NEWS
@@ -24,7 +24,7 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
           (Darafei Praliaskouski)
 
 
-PostGIS 3.7.0alpha1
+PostGIS 3.7.0beta1
 2026/07/05
 
 This version requires GEOS 3.10+, PostgreSQL 14-19beta1, Proj 6.1+, libgmp.

commit 62db0d6afd6ef91940ab05cffa3299caa754be11
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Jun 21 01:06:58 2026 +0400

    postgis: fast-path point collection ST_Disjoint
    
    References #4749

diff --git a/NEWS b/NEWS
index 70a3dc001..3c8b64a0c 100644
--- a/NEWS
+++ b/NEWS
@@ -112,7 +112,7 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
  - #2137, [raster] Reuse matching source raster overviews in raster2pgsql -l
           (Darafei Praliaskouski)
  - #4749, Use point-in-polygon predicate fast paths for point-only
-          GeometryCollections (Darafei Praliaskouski)
+          GeometryCollections, including ST_Disjoint (Darafei Praliaskouski)
  - #5532, Validate the manual against DocBook XMLSchema in check-xml
           (Darafei Praliaskouski)
  - #6087, Add a repository-owned CI status checker and static dashboard
diff --git a/postgis/lwgeom_geos_predicates.c b/postgis/lwgeom_geos_predicates.c
index c400ebca7..670410f4c 100644
--- a/postgis/lwgeom_geos_predicates.c
+++ b/postgis/lwgeom_geos_predicates.c
@@ -378,6 +378,7 @@ Datum disjoint(PG_FUNCTION_ARGS)
 	int8_t result;
 	GBOX box1, box2;
 	PrepGeomCache *prep_cache;
+	bool pip_result;
 
 	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
 
@@ -398,6 +399,21 @@ Datum disjoint(PG_FUNCTION_ARGS)
 		}
 	}
 
+	/*
+	 * Short-circuit 2: if one geometry is polygonal and the other is a
+	 * point or point-only collection, use the IntervalTree PIP fast path.
+	 */
+	if (is_point_or_collection(geom1) && is_poly(geom2) &&
+	    try_itree_pointlike_pip(fcinfo, geom1, shared_geom2, itree_pip_intersects, &pip_result))
+	{
+		PG_RETURN_BOOL(!pip_result);
+	}
+	else if (is_point_or_collection(geom2) && is_poly(geom1) &&
+		 try_itree_pointlike_pip(fcinfo, geom2, shared_geom1, itree_pip_intersects, &pip_result))
+	{
+		PG_RETURN_BOOL(!pip_result);
+	}
+
 	initGEOS(lwpgnotice, lwgeom_geos_error);
 
 	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, shared_geom2);
diff --git a/regress/core/tickets.sql b/regress/core/tickets.sql
index 83f954139..cf80a0d25 100644
--- a/regress/core/tickets.sql
+++ b/regress/core/tickets.sql
@@ -1313,6 +1313,21 @@ SELECT '#4749.intersects',
 	ST_Intersects(poly, pts_outside)
 FROM g;
 
+WITH g AS (
+	SELECT
+		'POLYGON((0 0,0 10,10 10,10 0,0 0))'::geometry AS poly,
+		'GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(5 5)),MULTIPOINT((7 7),(8 8)))'::geometry AS pts_inside,
+		'GEOMETRYCOLLECTION(POINT(5 5),POINT(0 0))'::geometry AS pts_boundary,
+		'GEOMETRYCOLLECTION(POINT(5 5),POINT(11 11))'::geometry AS pts_outside,
+		'GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(-1 -1)),MULTIPOINT((11 11),(12 12)))'::geometry AS pts_disjoint
+)
+SELECT '#4749.disjoint',
+	ST_Disjoint(poly, pts_inside),
+	ST_Disjoint(pts_boundary, poly),
+	ST_Disjoint(poly, pts_outside),
+	ST_Disjoint(pts_disjoint, poly)
+FROM g;
+
 -- #4299
 SELECT '#4299', ST_Disjoint(ST_GeneratePoints(g, 1000), ST_GeneratePoints(g, 1000))
 FROM (SELECT 'POLYGON((0 0,1 0,1 1,0 1,0 0))'::geometry AS g) AS f;
diff --git a/regress/core/tickets_expected b/regress/core/tickets_expected
index bed2d3302..1daac1d93 100644
--- a/regress/core/tickets_expected
+++ b/regress/core/tickets_expected
@@ -392,6 +392,7 @@ equals213|t
 #4749.covers|t|t|f
 #4749.coveredby|t|t|f
 #4749.intersects|t|t|t
+#4749.disjoint|f|f|f|t
 #4299|t
 #4304|t|t|t
 ERROR:  BOX3D_construct: args can not be empty points

commit 054d7b8f105b81dc6cf2ab193dc6b42c7e2b1f92
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Thu Jun 18 04:05:54 2026 +0400

    doc: use expanded DocBook input for EPUB

diff --git a/NEWS b/NEWS
index 70a3dc001..aa7605e8b 100644
--- a/NEWS
+++ b/NEWS
@@ -173,6 +173,11 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
           (Darafei Praliaskouski)
  - GH-885, [topology] Avoid GMP overflow in ring orientation for very large
           finite coordinates (Darafei Praliaskouski)
+ - GH-892, Add OSS-Fuzz coverage for TWKB and serialized raster inputs,
+          including guards for malformed TWKB reads and counts (Darafei Praliaskouski)
+ - #5423, Use a DTD-free expanded DocBook input for EPUB builds so
+          out-of-tree builds do not need dbtoepub entity path support
+          (Darafei Praliaskouski)
  - #6073, #6074, Stabilize geography LRS endpoint interpolation and pole
           centroid regressions on 32-bit systems (Darafei Praliaskouski)
  - #6082, Fix Japanese PDF build by avoiding non-ASCII arrows in SQL
diff --git a/doc/Makefile.in b/doc/Makefile.in
index 5288648c6..ce02ffa98 100644
--- a/doc/Makefile.in
+++ b/doc/Makefile.in
@@ -405,6 +405,9 @@ postgis-out.xml: postgis.xml Makefile $(XML_INPUTS) Makefile
 	$(PERL) -lpe "s'@@LAST_RELEASE_VERSION@@'${POSTGIS_MAJOR_VERSION}.${POSTGIS_MINOR_VERSION}.${POSTGIS_MICRO_VERSION}'g;s'@@POSTGIS_DOWNLOAD_URL@@'${POSTGIS_DOWNLOAD_URL}'g;" $< > $@.in
 	$(XMLLINT) $(XSLTPROC_PATH_OPT) --noent -o $@ $@.in
 
+postgis-out-epub.xml: postgis-out.xml Makefile
+	$(XMLLINT) $(XSLTPROC_PATH_OPT) --dropdtd -o $@ $<
+
 chunked-html: ## Generate multi-file html in html/postgis-en/
 
 chunked-html: $(html_builddir)/postgis$(DOCSUFFIX)/index.html
@@ -465,7 +468,7 @@ pdf-install: postgis-${POSTGIS_MAJOR_VERSION}.${POSTGIS_MINOR_VERSION}.${POSTGIS
 pdf-uninstall:
 	rm -f $(DESTDIR)$(docdir)/postgis-${POSTGIS_MAJOR_VERSION}.${POSTGIS_MINOR_VERSION}.${POSTGIS_MICRO_VERSION}$(DOCSUFFIX).pdf
 
-postgis-${POSTGIS_MAJOR_VERSION}.${POSTGIS_MINOR_VERSION}.${POSTGIS_MICRO_VERSION}$(DOCSUFFIX).epub: postgis-out.xml images
+postgis-${POSTGIS_MAJOR_VERSION}.${POSTGIS_MINOR_VERSION}.${POSTGIS_MICRO_VERSION}$(DOCSUFFIX).epub: postgis-out-epub.xml images
 ifeq ($(DBTOEPUB),)
 	@echo
 	@echo "configure was unable to find the 'dbtoepub' utility program."
@@ -526,6 +529,7 @@ cheatsheet-clean:
 
 clean: images-clean html-clean pdf-clean epub-clean cheatsheet-clean clean-pot ## Remove generated files except comments
 	rm -f images # work around https://trac.osgeo.org/postgis/ticket/5422
+	rm -f postgis-out-epub.xml
 	rm -f $(XML_GENERATED_SOURCES)
 	rm -f postgis-nospecial.xml postgis-nospecial.xml.in
 	rm -f postgis-out.xml postgis-out.xml.in

commit 1441d4769078d22574372e2c83448b7831d68262
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Wed Jun 17 03:13:51 2026 +0400

    Document map algebra and MVT geometry behavior

diff --git a/NEWS b/NEWS
index 70a3dc001..2045e4a8d 100644
--- a/NEWS
+++ b/NEWS
@@ -69,6 +69,8 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
  - #5992, Optimize GiST index for repeated edge subdivision in topology (Darafei Praliaskouski)
  - #5702, Allow the compiler to detect the parallelism -flto=auto (Darafei Praliaskouski)
  - #4798, ST_AsGeoJSON warns about duplicate property keys (Darafei Praliaskouski)
+ - #2557, #2805, Document ST_MapAlgebra callback argument
+          signatures and return semantics (Darafei Praliaskouski)
  - #5950, Document POSTGIS_REGRESS_DB_OWNER for sandboxed regression roles (Darafei Praliaskouski)
  - #4332, Clarify the scope of several Function Reference categories (Darafei Praliaskouski)
  - #4222, [raster] ST_DumpAsPolygons honours PostgreSQL interrupts (Darafei Praliaskouski)
diff --git a/doc/reference_raster.xml b/doc/reference_raster.xml
index 0992d30f9..c46f28add 100644
--- a/doc/reference_raster.xml
+++ b/doc/reference_raster.xml
@@ -9886,7 +9886,7 @@ FROM (
                                     The <varname>callbackfunc</varname> parameter must be the name and signature of an SQL or PL/pgSQL function, cast to a regprocedure. An example PL/pgSQL function example is:
                                 </para>
                                     <programlisting>
-CREATE OR REPLACE FUNCTION sample_callbackfunc(value double precision[][][], position integer[][], VARIADIC userargs text[])
+CREATE OR REPLACE FUNCTION sample_callbackfunc(value double precision[][][], pos integer[][], VARIADIC userargs text[])
     RETURNS double precision
     AS $$
     BEGIN
@@ -9899,6 +9899,10 @@ CREATE OR REPLACE FUNCTION sample_callbackfunc(value double precision[][][], pos
                                     The <varname>callbackfunc</varname> must have two or three arguments: a 3-dimension double precision array, a 2-dimension integer array and, optionally, a variadic 1-dimension text array. The first argument <varname>value</varname> is the set of values (as double precision) from all input rasters. The three dimensions (where indexes are 1-based) are: raster #, row y, column x. The second argument <varname>position</varname> is the set of pixel positions from the output raster and input rasters. The outer dimension (where indexes are 0-based) is the raster #.  The position at outer dimension index 0 is the output raster's pixel position.  For each outer dimension, there are two elements in the inner dimension for X and Y.  The optional third argument <varname>userargs</varname> is for passing through any user-specified arguments.
                                 </para>
 
+                                <para>
+                                    The callback return value is used as the value of the output pixel. If the callback returns <varname>NULL</varname>, the output pixel is set to NODATA when the output band has a NODATA value. If the output band has no NODATA value, a <varname>NULL</varname> callback result raises an error.
+                                </para>
+
                                 <para>
                                     Passing a <type>regprocedure</type> argument to a SQL function requires the full function signature to be passed, then cast to a <type>regprocedure</type> type. To pass the above example PL/pgSQL function as an argument, the SQL for the argument is:
                                 </para>
@@ -9906,7 +9910,7 @@ CREATE OR REPLACE FUNCTION sample_callbackfunc(value double precision[][][], pos
                                     <programlisting>'sample_callbackfunc(double precision[], integer[], text[])'::regprocedure</programlisting>
 
                                 <para>
-                                    Note that the argument contains the name of the function, the types of the function arguments, quotes around the name and argument types, and a cast to a <type>regprocedure</type>.
+                                    Note that PostgreSQL array types do not encode dimensions in function signatures, so multidimensional callback arguments are written as <type>double precision[]</type> and <type>integer[]</type> in the <type>regprocedure</type> signature.
                                 </para>
 
                             </listitem>
@@ -10099,8 +10103,7 @@ WITH src AS (
     SELECT 6, ST_AddBand(ST_MakeEmptyRaster(2, 2, 0, -4, 1, -1, 0, 0, 0), 1, '16BUI', 100, 0) AS rast UNION ALL
     SELECT 7, ST_AddBand(ST_MakeEmptyRaster(2, 2, 2, -4, 1, -1, 0, 0, 0), 1, '16BUI', 200, 0) AS rast UNION ALL
     SELECT 8, ST_AddBand(ST_MakeEmptyRaster(2, 2, 4, -4, 1, -1, 0, 0, 0), 1, '16BUI', 300, 0) AS rast
-)
-WITH foo AS (
+), foo AS (
     SELECT
         t1.rid,
         ST_Union(t2.rast) AS rast
@@ -10113,8 +10116,9 @@ WITH foo AS (
 ), bar AS (
     SELECT
         t1.rid,
-        ST_MapAlgebra(ARRAY[ROW(t2.rast, 1)]::rastbandarg[],
-            'raster_nmapalgebra_test(double precision[], int[], text[])'::regprocedure,
+        ST_MapAlgebra(
+            ARRAY[ROW(t2.rast, 1)]::rastbandarg[],
+            'sample_callbackfunc(double precision[], int[], text[])'::regprocedure,
             '32BUI',
             'CUSTOM', t1.rast,
             1, 1

commit f4130f5ef8c5ea7e34a9f9bacc47ead14fd1079c
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Thu Jun 11 05:50:11 2026 +0400

    Let ST_OrderingEquals expose planner equality
    
    ST_OrderingEquals uses the same serialized comparison semantics as the geometry equality operator. Expose that equality to PostgreSQL planning for variable joins so duplicate-detection queries can use ordinary equality join strategies.
    
    Keep constant predicates on the existing spatial index support path, reject volatile argument expressions before simplification, and avoid adding bbox equality as a mandatory conjunct because non-finite boxes can make ~= narrower than exact serialized equality.
    
    Closes https://github.com/postgis/postgis/pull/875

diff --git a/NEWS b/NEWS
index 5f3f75770..3c9d93010 100644
--- a/NEWS
+++ b/NEWS
@@ -72,6 +72,9 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
  - #2804, #4315, [raster] Support two-argument ST_MapAlgebra callbacks
           and pass callback call data as actual arguments (Darafei Praliaskouski)
  - #2898, Document SQL function cost tiers for contributors
+          (Darafei Praliaskouski)
+ - GH-875, Allow ST_OrderingEquals joins to use hash and merge join plans
+          (Darafei Praliaskouski)
  - #6062, [topology] Stop using recursive snapping, for improved robustness (Sandro Santilli)
  - #6065, Improved winding order computation robustness for rings having collapsed elements
           (Sandro Santilli)
diff --git a/postgis/gserialized_supportfn.c b/postgis/gserialized_supportfn.c
index b6fe6dbba..aceb69b9a 100644
--- a/postgis/gserialized_supportfn.c
+++ b/postgis/gserialized_supportfn.c
@@ -34,6 +34,7 @@
 #include "nodes/nodeFuncs.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_oper.h"
 #include "parser/parse_func.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -328,6 +329,55 @@ Datum postgis_index_supportfn(PG_FUNCTION_ARGS)
 	 */
 	postgis_initialize_cache();
 
+	if (IsA(rawreq, SupportRequestSimplify))
+	{
+		SupportRequestSimplify *req = (SupportRequestSimplify *)rawreq;
+		FuncExpr *clause = req->fcall;
+		IndexableFunction idxfn = {NULL, 0, 0, 0, 0};
+
+		if (needsSpatialIndex(clause->funcid, &idxfn) && idxfn.index == ST_ORDERINGEQUALS_IDX &&
+		    list_length(clause->args) == 2)
+		{
+			Node *leftarg = linitial(clause->args);
+			Node *rightarg = lsecond(clause->args);
+			Oid leftdatatype = exprType(leftarg);
+			Oid rightdatatype = exprType(rightarg);
+			Oid eqoproid = InvalidOid;
+			bool hashable = false;
+
+			if (leftdatatype != rightdatatype)
+				PG_RETURN_POINTER((Node *)NULL);
+
+			/* Keep constant predicates on the spatial index support path below. */
+			if (!contain_var_clause(leftarg) || !contain_var_clause(rightarg))
+				PG_RETURN_POINTER((Node *)NULL);
+
+			if (contain_volatile_functions(leftarg) || contain_volatile_functions(rightarg))
+				PG_RETURN_POINTER((Node *)NULL);
+
+			/* ST_OrderingEquals uses the same gserialized_cmp equality as the = operator. */
+			get_sort_group_operators(leftdatatype, false, true, false, NULL, &eqoproid, NULL, &hashable);
+
+			if (!OidIsValid(eqoproid))
+				PG_RETURN_POINTER((Node *)NULL);
+
+			/*
+			 * Do not add a bbox-equality conjunct here. Exact serialized equality can
+			 * match non-finite coordinates that box2df equality rejects, so adding ~=
+			 * would make the simplification narrower than ST_OrderingEquals.
+			 */
+			ret = (Node *)make_opclause(eqoproid,
+						    BOOLOID,
+						    false,
+						    (Expr *)leftarg,
+						    (Expr *)rightarg,
+						    InvalidOid,
+						    clause->inputcollid);
+
+			PG_RETURN_POINTER(ret);
+		}
+	}
+
 	if (IsA(rawreq, SupportRequestSelectivity))
 	{
 		SupportRequestSelectivity *req = (SupportRequestSelectivity *) rawreq;
diff --git a/regress/core/regress_index.sql b/regress/core/regress_index.sql
index 59c62f281..09c71254d 100644
--- a/regress/core/regress_index.sql
+++ b/regress/core/regress_index.sql
@@ -25,6 +25,22 @@ BEGIN
 END;
 $$;
 
+CREATE OR REPLACE FUNCTION qplan_has(q text, pattern text) RETURNS boolean
+LANGUAGE 'plpgsql' AS
+$$
+DECLARE
+  exp TEXT;
+BEGIN
+  FOR exp IN EXECUTE 'EXPLAIN ' || q
+  LOOP
+    IF exp ~ pattern THEN
+      RETURN true;
+    END IF;
+  END LOOP;
+  RETURN false;
+END;
+$$;
+
 -- GiST index
 
 CREATE INDEX quick_gist on test using gist (the_geom);
@@ -113,12 +129,82 @@ SELECT 'expr &&', id, estimate_error(
   'select num from test where st_centroid(the_geom) && ' || box, tol )
   FROM sample_queries ORDER BY id;
 
+SELECT 'st_orderingequals_idx',
+  qnodes('select * from test where ST_OrderingEquals(the_geom, ST_MakePoint(0,0))');
+
+CREATE TABLE st_orderingequals_volatile_calls (num integer);
+INSERT INTO st_orderingequals_volatile_calls VALUES (0);
+
+CREATE OR REPLACE FUNCTION st_orderingequals_volatile_geom(g geometry)
+RETURNS geometry
+LANGUAGE 'plpgsql' VOLATILE AS $$
+BEGIN
+  UPDATE st_orderingequals_volatile_calls SET num = num + 1;
+  RETURN g;
+END;
+$$;
+
+SELECT 'st_orderingequals_volatile_count', count(*)
+FROM (VALUES (ST_MakePoint(0,0))) AS q(g)
+JOIN test t ON ST_OrderingEquals(st_orderingequals_volatile_geom(t.the_geom), q.g);
+
+SELECT 'st_orderingequals_volatile', num FROM st_orderingequals_volatile_calls;
+
+DROP FUNCTION st_orderingequals_volatile_geom(geometry);
+DROP TABLE st_orderingequals_volatile_calls;
+
+CREATE SCHEMA st_orderingequals_search_path;
+CREATE FUNCTION st_orderingequals_search_path.geometry_bbox_false(geometry, geometry)
+RETURNS boolean
+LANGUAGE 'sql' IMMUTABLE AS 'SELECT false';
+CREATE OPERATOR st_orderingequals_search_path.~= (
+  LEFTARG = geometry,
+  RIGHTARG = geometry,
+  PROCEDURE = st_orderingequals_search_path.geometry_bbox_false,
+  COMMUTATOR = '~='
+);
+
+SET search_path = st_orderingequals_search_path, public;
+SELECT 'st_orderingequals_qualified_bbox', count(*)
+FROM (VALUES (ST_MakePoint(0,0))) AS g1(g)
+JOIN (VALUES (ST_MakePoint(0,0))) AS g2(g)
+  ON ST_OrderingEquals(g1.g, g2.g);
+RESET search_path;
+
+DROP OPERATOR st_orderingequals_search_path.~= (geometry, geometry);
+DROP FUNCTION st_orderingequals_search_path.geometry_bbox_false(geometry, geometry);
+DROP SCHEMA st_orderingequals_search_path;
+
+WITH geoms AS (SELECT ST_MakePoint(0, 'NaN'::float8) AS g)
+SELECT 'st_orderingequals_nan_bbox', ST_OrderingEquals(g, g), g = g, g ~= g
+FROM geoms;
+
+SELECT 'st_orderingequals_nan_join', count(*)
+FROM (VALUES (ST_MakePoint(0, 'NaN'::float8))) AS g1(g)
+JOIN (VALUES (ST_MakePoint(0, 'NaN'::float8))) AS g2(g)
+  ON ST_OrderingEquals(g1.g, g2.g);
+
+-- ST_OrderingEquals is exact geometry equality, so the planner should see
+-- the hash/merge-joinable geometry operator instead of only a function filter.
+set enable_nestloop = off;
+SELECT 'st_orderingequals_join',
+  qplan_has(
+    'SELECT t1.num FROM test t1, test t2 WHERE t1.num > t2.num AND ST_OrderingEquals(t1.the_geom, t2.the_geom)',
+    '(Hash|Merge) Join'
+  ),
+  qplan_has(
+    'SELECT t1.num FROM test t1, test t2 WHERE t1.num > t2.num AND ST_OrderingEquals(t1.the_geom, t2.the_geom)',
+    'Nested Loop'
+  );
+set enable_nestloop = on;
+
 DROP TABLE test;
 DROP TABLE test_gist_idx_2d;
 DROP TABLE sample_queries;
 
 DROP FUNCTION estimate_error(text, int);
 
+DROP FUNCTION qplan_has(text, text);
 DROP FUNCTION qnodes(text);
 
 set enable_indexscan = on;
diff --git a/regress/core/regress_index_expected b/regress/core/regress_index_expected
index 69d730748..70952dba5 100644
--- a/regress/core/regress_index_expected
+++ b/regress/core/regress_index_expected
@@ -22,4 +22,11 @@ expr &&|1|5+-5:true
 expr &&|2|912+-60:true
 expr &&|3|12505+-500:true
 expr &&|4|50000+-600:true
+st_orderingequals_idx|Index Scan
+st_orderingequals_volatile_count|0
+st_orderingequals_volatile|50000
+st_orderingequals_qualified_bbox|1
+st_orderingequals_nan_bbox|t|t|f
+st_orderingequals_nan_join|1
+st_orderingequals_join|t|f
 _st_sortablehash|0|768602608280535040|768602608280535040

commit 1382c85d2510098e4917b133c0e0d9030e59e844
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Wed Jun 17 00:18:18 2026 +0400

    doc: clarify standards support scope

diff --git a/doc/introduction.xml b/doc/introduction.xml
index b6da8dfdd..12f92ef9a 100644
--- a/doc/introduction.xml
+++ b/doc/introduction.xml
@@ -19,19 +19,28 @@
   <section xml:id="standards_support">
 	<title>Standards Support</title>
 
-	<para>PostGIS implements parts of the
-	<link xlink:href="https://www.ogc.org/standard/sfa/">OGC Simple Features Access</link>
-	and <link xlink:href="https://www.iso.org/standard/60343.html">SQL/MM Spatial</link>
-	standards.  The geometry type, the Well-Known Text (WKT) and Well-Known
-	Binary (WKB) input/output functions, and many spatial accessors,
-	constructors, predicates, and processing functions follow those standards
-	where the manual marks them as OGC or SQL/MM compliant.</para>
+	<para>This manual marks individual functions as compliant with spatial
+	standards so users can tell which behavior is portable and which behavior is
+	a PostGIS extension.  PostGIS implements the
+	<link xlink:href="https://www.ogc.org/standard/sfs/">OGC Simple Features
+	for SQL</link> model, including the geometry type, Well-Known Text (WKT),
+	Well-Known Binary (WKB), and the common spatial accessors, constructors,
+	predicates, and processing functions.  PostGIS also implements many
+	<link xlink:href="https://www.iso.org/standard/60343.html">SQL/MM Spatial</link>
+	types and functions.</para>
 
-	<para>PostGIS also includes widely used extensions beyond those standards.
-	Examples include Extended WKT (EWKT) and Extended WKB (EWKB), SRID metadata
-	in those extended formats, geography, raster, topology, curved geometry,
-	polyhedral surface, TIN, and many analysis functions.  These extensions are
-	documented individually, but should not be read as OGC or SQL/MM
+	<para>PostGIS supports geometry families defined by later standards,
+	including SQL/MM curved geometry and OGC Simple Features Access 1.2
+	PolyhedralSurface, Triangle, and TIN.  Support for these geometry families
+	is function-specific, and each function page documents the supported input
+	types and conformance level.</para>
+
+	<para>PostGIS also includes widely used extensions and integrations.
+	Examples of PostGIS extensions include Extended WKT (EWKT), Extended WKB
+	(EWKB), SRID metadata in those extended formats, geography, and raster.
+	PostGIS also supports input and output exchange formats defined by other
+	standards, such as GeoJSON, GML, and KML.  These features are documented
+	individually and should not be read as OGC Simple Features or SQL/MM
 	requirements unless the specific function or format says so.</para>
 
 	<para>When the implementation intentionally differs from a standard, or when

commit a55fb44f7f7cd2033c734042558a26f0d0aa1103
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Thu Jun 11 09:03:02 2026 +0400

    docs: clarify standards support

diff --git a/doc/introduction.xml b/doc/introduction.xml
index 20999fd59..b6da8dfdd 100644
--- a/doc/introduction.xml
+++ b/doc/introduction.xml
@@ -16,6 +16,30 @@
   surfaces, networks), data source for desktop user interface tools for viewing and editing
   GIS data, and web-based access tools.</para>
 
+  <section xml:id="standards_support">
+	<title>Standards Support</title>
+
+	<para>PostGIS implements parts of the
+	<link xlink:href="https://www.ogc.org/standard/sfa/">OGC Simple Features Access</link>
+	and <link xlink:href="https://www.iso.org/standard/60343.html">SQL/MM Spatial</link>
+	standards.  The geometry type, the Well-Known Text (WKT) and Well-Known
+	Binary (WKB) input/output functions, and many spatial accessors,
+	constructors, predicates, and processing functions follow those standards
+	where the manual marks them as OGC or SQL/MM compliant.</para>
+
+	<para>PostGIS also includes widely used extensions beyond those standards.
+	Examples include Extended WKT (EWKT) and Extended WKB (EWKB), SRID metadata
+	in those extended formats, geography, raster, topology, curved geometry,
+	polyhedral surface, TIN, and many analysis functions.  These extensions are
+	documented individually, but should not be read as OGC or SQL/MM
+	requirements unless the specific function or format says so.</para>
+
+	<para>When the implementation intentionally differs from a standard, or when
+	a standard leaves behavior ambiguous, the relevant function page describes
+	the PostGIS behavior.  The <xref linkend="PostGIS_Special_Functions_Index"/>
+	chapter summarizes SQL/MM compliance and geometry-type support across
+	functions.</para>
+  </section>
 
   <section xml:id="psc">
 	<title>Project Steering Committee</title>

commit 06dfb605f3184c6c23c352e0ed37fb785a2da343
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Mon Jun 22 00:12:53 2026 +0400

    doc: explain repeated ring point validity
    
    Explain why consecutive repeated coordinates in a polygon boundary ring are treated as redundant vertices under the SFS curve model.
    
    References https://github.com/postgis/postgis/pull/1017#discussion_r3448971304
    
    References #3425

diff --git a/doc/using_postgis_dataman.xml b/doc/using_postgis_dataman.xml
index d5b91fc75..74f6fe1c5 100644
--- a/doc/using_postgis_dataman.xml
+++ b/doc/using_postgis_dataman.xml
@@ -1091,10 +1091,12 @@ SELECT
         </orderedlist>
 
       <para>
-        Consecutive repeated points in a polygon boundary ring are treated as
-        redundant vertices, and do not make the polygon invalid. Repeated points
-        that cause the boundary to self-intersect, self-touch, or collapse
-        still violate the validity rules.
+        Consecutive repeated points in a polygon boundary ring do not make the
+        ring non-simple. Under the SFS curve model, a curve is interpreted by a
+        continuous parameterization; consecutive equal coordinates are
+        topologically indistinguishable from a single vertex. PostGIS therefore
+        treats them as redundant vertices, so they do not make an otherwise valid
+        polygon invalid.
       </para>
 
 	  <informaltable frame="none">

-----------------------------------------------------------------------

Summary of changes:
 NEWS                                               | 14 ++++
 doc/Makefile.in                                    |  6 +-
 doc/extras_topology.xml                            |  1 +
 doc/introduction.xml                               | 33 +++++++++
 doc/reference_accessor.xml                         | 14 +++-
 doc/reference_raster.xml                           | 16 ++--
 doc/reference_sfcgal.xml                           |  6 ++
 doc/using_postgis_dataman.xml                      | 10 ++-
 liblwgeom/cunit/cu_surface.c                       | 32 ++++++++
 liblwgeom/lwgeom.c                                 | 17 +++--
 liblwgeom/lwstroke.c                               | 15 ++--
 postgis/geography.sql.in                           | 12 +--
 postgis/gserialized_supportfn.c                    | 50 +++++++++++++
 postgis/lwgeom_geos_predicates.c                   | 16 ++++
 postgis/postgis.sql.in                             | 16 ++--
 raster/rt_pg/rtpostgis.sql.in                      | 12 +--
 raster/test/regress/rt_intersects.sql              | 39 ++++++++++
 raster/test/regress/rt_intersects_expected         |  2 +
 regress/core/nurbs_eval.sql                        | 14 ++++
 regress/core/nurbs_eval_expected                   |  4 +
 regress/core/regress.sql                           | 20 +++++
 regress/core/regress_expected                      |  1 +
 regress/core/regress_index.sql                     | 86 ++++++++++++++++++++++
 regress/core/regress_index_expected                |  7 ++
 regress/core/tickets.sql                           | 15 ++++
 regress/core/tickets_expected                      |  1 +
 regress/core/triangulatedsurface.sql               |  4 +-
 regress/core/triangulatedsurface_expected          |  8 +-
 sfcgal/regress/regress_sfcgal.sql                  | 14 ++++
 sfcgal/regress/regress_sfcgal_expected             |  1 +
 .../sql/manage/FixCorruptTopoGeometryColumn.sql.in | 58 +++++++++++++--
 .../regress/hooks/hook-after-upgrade-topology.sql  |  2 +-
 .../regress/hooks/hook-before-upgrade-topology.sql |  3 +-
 33 files changed, 488 insertions(+), 61 deletions(-)


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list