[SCM] PostGIS branch master updated. 3.6.0rc2-687-g4e470f153

git at osgeo.org git at osgeo.org
Tue Jun 30 13:50:51 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  4e470f1534795ae4a4c52ad5ad35b8744af9d708 (commit)
       via  d7754dccc14914701e4a913473225bdefe5ce88a (commit)
       via  4fcff9df99ea140f0062047b91dc694a5647c624 (commit)
       via  356914a6311a2606de1e9089fc2ced8c4064e3f3 (commit)
       via  b006ec7715a91c4f6dc9ffb50e608b31b2a3087b (commit)
       via  99c75c3f5ad5e7ab66f17a3ec23e46b87d12a82b (commit)
       via  0e07c23d019a731a2e95c1a366510a4cdade4bd7 (commit)
       via  6db6b531f34aa23a775548f98b761ea624930b82 (commit)
       via  a8683ba6ead9f81c08a13cb54fc770fbe452c5d3 (commit)
      from  57fd420a7e9843c925b241c726ba436ad9f2aeb9 (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 4e470f1534795ae4a4c52ad5ad35b8744af9d708
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jun 30 01:33:41 2026 +0400

    ci: keep Winnie SFCGAL runs optional
    
    (cherry picked from commit e06ab2f4f307f80a489d56a9c6ae4c642d480537)

diff --git a/ci/winnie/regress_postgis.sh b/ci/winnie/regress_postgis.sh
index 8ae8cea39..deba4fdb1 100644
--- a/ci/winnie/regress_postgis.sh
+++ b/ci/winnie/regress_postgis.sh
@@ -77,6 +77,11 @@ fi
 
 #LDFLAGS="-Wl,--enable-auto-import -L${PGPATH}/lib -L${PROJECTS}/gdal/rel-${GDAL_VER}w${OS_BUILD}${GCC_TYPE}/lib -L${PROJECTS}/rel-libiconv-${ICON_VER}w${OS_BUILD}${GCC_TYPE}/lib" \
 
+SFCGAL_CONFIGURE_ARG=""
+if [ -n "${SFCGAL_VER:-}" ]; then
+  SFCGAL_CONFIGURE_ARG="--with-sfcgal=${PROJECTS}/CGAL/rel-sfcgal-${SFCGAL_VER}w${OS_BUILD}${GCC_TYPE}/bin/sfcgal-config"
+fi
+
 CPPFLAGS="-I${PGPATH}/include -I${PROJECTS}/rel-libiconv-${ICON_VER}w${OS_BUILD}${GCC_TYPE}/include" \
 LDFLAGS="-Wl,--enable-auto-import -L${PGPATH}/lib -L${LZ4_PATH}/lib -L${PROJECTS}/rel-libiconv-${ICON_VER}w${OS_BUILD}${GCC_TYPE}/lib -L${PROJECTS}/zlib/rel-zlib-${ZLIB_VER}w${OS_BUILD}${GCC_TYPE}/lib" \
 ./configure \
@@ -85,7 +90,7 @@ LDFLAGS="-Wl,--enable-auto-import -L${PGPATH}/lib -L${LZ4_PATH}/lib -L${PROJECTS
   --with-geosconfig=${PROJECTS}/geos/rel-${GEOS_VER}w${OS_BUILD}${GCC_TYPE}/bin/geos-config \
   --with-libiconv=${PROJECTS}/rel-libiconv-${ICON_VER}w${OS_BUILD}${GCC_TYPE} \
   --with-gui --with-gettext=no \
-  --with-sfcgal=${PROJECTS}/CGAL/rel-sfcgal-${SFCGAL_VER}w${OS_BUILD}${GCC_TYPE}/bin/sfcgal-config \
+  ${SFCGAL_CONFIGURE_ARG} \
   --prefix=${PROJECTS}/postgis/liblwgeom-${POSTGIS_VER}w${OS_BUILD}${GCC_TYPE}  \
   ${EXTRA_CONFIGURE_ARGS}
 
@@ -117,7 +122,9 @@ if [ "${MAKE_EXTENSION:-0}" == "1" ]; then
     cp -r topology/*.dll ${PGPATHEDB}/lib
  fi
  cp postgis/postgis*.dll ${PGPATHEDB}/lib
- cp sfcgal/*.dll ${PGPATHEDB}/lib
+ if [ -n "${SFCGAL_VER:-}" ]; then
+    cp sfcgal/*.dll ${PGPATHEDB}/lib
+ fi
 
  if [ "${REGRESS_WITHOUT_RASTER:-0}" == "0" ]; then
     cp raster/rt_pg/postgis_raster-*.dll ${PGPATHEDB}/lib
@@ -135,7 +142,10 @@ value=${value//UPGRADEABLE_VERSIONS = /}
 #echo $value
 export UPGRADEABLE_VERSIONS=$value
 export WIN_RELEASED_VERSIONS="2.0.0 2.0.1 2.0.3 2.0.4 2.0.6 2.1.4 2.1.7 2.1.8 2.2.0 2.2.3 2.3.0 2.3.7 2.4.0 2.4.4"
-export extensions_to_install="postgis postgis_sfcgal"
+export extensions_to_install="postgis"
+if [ -n "${SFCGAL_VER:-}" ]; then
+  extensions_to_install="${extensions_to_install} postgis_sfcgal"
+fi
 
 if [ "${REGRESS_WITHOUT_TOPOLOGY:-0}" == "0" ]; then
   extensions_to_install="${extensions_to_install} postgis_topology"

commit d7754dccc14914701e4a913473225bdefe5ce88a
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jun 16 23:10:59 2026 +0400

    ci: harden utility scripts and doc version list
    
    (cherry picked from commit cd61527547061a91866fdac44b4c48bc9bdafb62)

diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml
index 62eb71c43..6292eeb96 100644
--- a/.github/workflows/codespell.yml
+++ b/.github/workflows/codespell.yml
@@ -25,13 +25,16 @@ jobs:
         sudo apt-get install -y --no-install-recommends \
           autoconf automake libtool pkg-config \
           libgeos-dev libproj-dev libxml2-dev \
-          libxml2-utils docbook-xml docbook-xsl docbook-xsl-ns xsltproc
+          libxml2-utils docbook-xml docbook-xsl docbook-xsl-ns xsltproc \
+          python3-venv
         # Ubuntu's packaged codespell is too old for the skip globs in .codespellrc,
         # which makes the job scan generated/vendor trees and fail noisily.
-        sudo pip install codespell
+        python3 -m venv "${RUNNER_TEMP}/codespell-venv"
+        "${RUNNER_TEMP}/codespell-venv/bin/python" -m pip install 'codespell==2.4.1'
 
     - name: 'Build & Test'
       run: |
+        export PATH="${RUNNER_TEMP}/codespell-venv/bin:${PATH}"
         codespell --version
         ./autogen.sh
         ./configure --without-raster --without-topology --without-sfcgal --without-json --without-protobuf
diff --git a/ci/winnie/regress_postgis.sh b/ci/winnie/regress_postgis.sh
index 241a261f3..8ae8cea39 100644
--- a/ci/winnie/regress_postgis.sh
+++ b/ci/winnie/regress_postgis.sh
@@ -60,15 +60,15 @@ sh autogen.sh
 # excluding topology cause it's erroring out on some tests
 #EXTRA_CONFIGURE_ARGS="${EXTRA_CONFIGURE_ARGS} --without-topology"
 
-if [ $INCLUDE_MINOR_LIB == "1" ]; then
+if [ "${INCLUDE_MINOR_LIB:-0}" == "1" ]; then
   EXTRA_CONFIGURE_ARGS="${EXTRA_CONFIGURE_ARGS} --with-library-minor-version"
 fi
 
-if [ $REGRESS_WITHOUT_TOPOLOGY == "1" ]; then
+if [ "${REGRESS_WITHOUT_TOPOLOGY:-0}" == "1" ]; then
    EXTRA_CONFIGURE_ARGS="${EXTRA_CONFIGURE_ARGS} --without-topology"
 fi
 
-if [ $REGRESS_WITHOUT_RASTER == "1" ]; then
+if [ "${REGRESS_WITHOUT_RASTER:-0}" == "1" ]; then
    EXTRA_CONFIGURE_ARGS="${EXTRA_CONFIGURE_ARGS} --without-raster"
 fi
 
@@ -99,12 +99,12 @@ make -j 2
 make install
 
 # don't run tests twice. Only run regular if extension test is not asked for
-if [ "$MAKE_EXTENSION" == "0" ]; then
+if [ "${MAKE_EXTENSION:-0}" == "0" ]; then
   make check RUNTESTFLAGS=-v
 fi
 
 
-if [ "$MAKE_EXTENSION" == "1" ]; then
+if [ "${MAKE_EXTENSION:-0}" == "1" ]; then
  export PGUSER=postgres
  #need to copy install files to EDB install (since not done by make install
  cd ${POSTGIS_SRC}
@@ -113,13 +113,13 @@ if [ "$MAKE_EXTENSION" == "1" ]; then
  #strip raster/rt_pg/postgis_raster-*.dll
  #strip sfcgal/*.dll
 
- if [ $REGRESS_WITHOUT_TOPOLOGY == "0" ]; then
+ if [ "${REGRESS_WITHOUT_TOPOLOGY:-0}" == "0" ]; then
     cp -r topology/*.dll ${PGPATHEDB}/lib
  fi
  cp postgis/postgis*.dll ${PGPATHEDB}/lib
  cp sfcgal/*.dll ${PGPATHEDB}/lib
 
- if [ $REGRESS_WITHOUT_RASTER == "0" ]; then
+ if [ "${REGRESS_WITHOUT_RASTER:-0}" == "0" ]; then
     cp raster/rt_pg/postgis_raster-*.dll ${PGPATHEDB}/lib
  fi
 
@@ -137,11 +137,11 @@ export UPGRADEABLE_VERSIONS=$value
 export WIN_RELEASED_VERSIONS="2.0.0 2.0.1 2.0.3 2.0.4 2.0.6 2.1.4 2.1.7 2.1.8 2.2.0 2.2.3 2.3.0 2.3.7 2.4.0 2.4.4"
 export extensions_to_install="postgis postgis_sfcgal"
 
-if [ $REGRESS_WITHOUT_TOPOLOGY == "0" ]; then
+if [ "${REGRESS_WITHOUT_TOPOLOGY:-0}" == "0" ]; then
   extensions_to_install="${extensions_to_install} postgis_topology"
 fi
 
-if [ $REGRESS_WITHOUT_RASTER == "0" ]; then
+if [ "${REGRESS_WITHOUT_RASTER:-0}" == "0" ]; then
   extensions_to_install="${extensions_to_install} postgis_raster"
 fi
 
@@ -165,14 +165,14 @@ cp -r extensions/*/*.control ${PGPATHEDB}/share/extension
 
 make check RUNTESTFLAGS="--extension -v"
 
-if [ "$UPGRADE_TEST" == "1" ]; then
+if [ "${UPGRADE_TEST:-0}" == "1" ]; then
   export CURRENTVERSION=${POSTGIS_MAJOR_VERSION}.${POSTGIS_MINOR_VERSION}.${POSTGIS_MICRO_VERSION}
   RUNTESTFLAGS='--extension' ${POSTGIS_SRC}/utils/check_all_upgrades.sh -s "${CURRENTVERSION}" --skip "unpackaged"
 fi
 
 fi
 
-if [ "$DUMP_RESTORE" == "1" ]; then
+if [ "${DUMP_RESTORE:-0}" == "1" ]; then
  echo "Dump restore test"
  make install
  make check RUNTESTFLAGS="-v --dumprestore"
@@ -181,7 +181,7 @@ if [ "$DUMP_RESTORE" == "1" ]; then
  fi
 fi
 
-if [ "$MAKE_GARDEN" == "1" ]; then
+if [ "${MAKE_GARDEN:-0}" == "1" ]; then
  export PGUSER=postgres
  make garden
 fi
diff --git a/doc/xsl-config.xml b/doc/xsl-config.xml
index 686de356e..69a876d56 100644
--- a/doc/xsl-config.xml
+++ b/doc/xsl-config.xml
@@ -23,7 +23,6 @@
         <postgis_versions>
             <v>3.7</v>
             <v>3.6</v>
-            <v>3.6</v>
             <v>3.5</v>
             <v>3.4</v>
             <v>3.3</v>
diff --git a/utils/check_all_upgrades.sh b/utils/check_all_upgrades.sh
index 5fac212ea..8e02e21a1 100755
--- a/utils/check_all_upgrades.sh
+++ b/utils/check_all_upgrades.sh
@@ -3,7 +3,7 @@
 EXIT_ON_FIRST_FAILURE=0
 EXTDIR=`pg_config --sharedir`/extension/
 CTBDIR=`pg_config --sharedir`/contrib/
-TMPDIR=/tmp/check_all_upgrades-$$-tmp
+TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/check_all_upgrades.XXXXXX") || exit 1
 PGVER=`pg_config --version | awk '{print $2}'`
 PGVER_MAJOR=$(echo "${PGVER}" | sed 's/\.[^\.]*//' | sed 's/\(alpha\|beta\|rc\).*//' )
 SKIP_LABEL_REGEXP=
@@ -51,11 +51,10 @@ if test -z "$to_version_param"; then
   exit 1
 fi
 
-mkdir -p ${TMPDIR}
 cleanup()
 {
   echo "Cleaning up"
-  rm -rf ${TMPDIR}
+  rm -rf "${TMPDIR}"
 }
 
 trap 'cleanup' EXIT
@@ -182,23 +181,32 @@ compatible_upgrade()
   fi
 }
 
+run_check()
+{
+  if command -v unbuffer >/dev/null 2>&1; then
+    unbuffer ${MAKE} -C "${REGDIR}" check "TESTS=${SRCDIR}/regress/core/regress.sql" ${MAKE_ARGS}
+  else
+    ${MAKE} -C "${REGDIR}" check "TESTS=${SRCDIR}/regress/core/regress.sql" ${MAKE_ARGS}
+  fi
+}
+
 check_downgrade()
 {
   RUNTESTFLAGS="-v --extension --upgrade-path=${UPGRADE_PATH} ${USERTESTFLAGS}" \
-  unbuffer ${MAKE} -C ${REGDIR} check "TESTS=${SRCDIR}/regress/core/regress.sql" ${MAKE_ARGS} > ${TMPDIR}/log 2>&1
+  run_check > "${TMPDIR}/log" 2>&1
   if test $? = 0; then
     echo "FAIL: ${test_label} did not error out:"
-    tail ${TMPDIR}/log
+    tail "${TMPDIR}/log"
     failed
   else
-    ERR=$( grep 'ERROR:.*Downgrade .* forbidden' ${TMPDIR}/log )
-    test -n "$ERR" && {
+    ERR=$( grep 'ERROR:.*Downgrade .* forbidden' "${TMPDIR}/log" )
+    if test -n "$ERR"; then
       echo "PASS: ${test_label} gave $ERR"
-    } || {
+    else
       echo "FAIL: ${test_label} gave some other error:"
-      tail ${TMPDIR}/log
+      tail "${TMPDIR}/log"
       failed
-    }
+    fi
   fi
 }
 

commit 4fcff9df99ea140f0062047b91dc694a5647c624
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Fri Jun 19 14:29:33 2026 +0400

    postgis: test BRIN mergeability support procs
    
    (cherry picked from commit 564e99eb99bb4f37687f46d62862c0b5b85a1efe)

diff --git a/regress/core/regress_brin_index_3d.sql b/regress/core/regress_brin_index_3d.sql
index ccfc87403..cd0fb6081 100644
--- a/regress/core/regress_brin_index_3d.sql
+++ b/regress/core/regress_brin_index_3d.sql
@@ -28,6 +28,16 @@ $$;
 
 -- BRIN indexes
 
+SELECT 'brin_proc_12', opc.opcname, amp.amprocnum, pro.proname
+FROM pg_catalog.pg_opclass AS opc
+JOIN pg_catalog.pg_amproc AS amp
+  ON amp.amprocfamily = opc.opcfamily
+JOIN pg_catalog.pg_proc AS pro
+  ON pro.oid = amp.amproc
+WHERE opc.opcname IN ('brin_geometry_inclusion_ops_3d', 'brin_geometry_inclusion_ops_4d')
+  AND amp.amprocnum = 12
+ORDER BY opc.opcname;
+
 -- 2D
 CREATE INDEX brin_2d on test using brin (the_geom) WITH (pages_per_range = 10);
 
diff --git a/regress/core/regress_brin_index_3d_expected b/regress/core/regress_brin_index_3d_expected
index b80bee3ec..f3874881b 100644
--- a/regress/core/regress_brin_index_3d_expected
+++ b/regress/core/regress_brin_index_3d_expected
@@ -1,3 +1,5 @@
+brin_proc_12|brin_geometry_inclusion_ops_3d|12|geom3d_brin_inclusion_mergeable
+brin_proc_12|brin_geometry_inclusion_ops_4d|12|geom4d_brin_inclusion_mergeable
 scan_seq|Seq Scan
 1250|POINT Z (125 125 125)
 1251|POINT Z (125.1 125.1 125.1)
diff --git a/regress/core/regress_brin_index_geography.sql b/regress/core/regress_brin_index_geography.sql
index 6ba5e1f41..1eacba73b 100644
--- a/regress/core/regress_brin_index_geography.sql
+++ b/regress/core/regress_brin_index_geography.sql
@@ -27,6 +27,15 @@ $$;
 
 -- BRIN indexes
 
+SELECT 'brin_proc_12', opc.opcname, amp.amprocnum, pro.proname
+FROM pg_catalog.pg_opclass AS opc
+JOIN pg_catalog.pg_amproc AS amp
+  ON amp.amprocfamily = opc.opcfamily
+JOIN pg_catalog.pg_proc AS pro
+  ON pro.oid = amp.amproc
+WHERE opc.opcname = 'brin_geography_inclusion_ops'
+  AND amp.amprocnum = 12;
+
 -- 2D
 CREATE INDEX brin_geog on test using brin (the_geog) WITH (pages_per_range = 10);
 
diff --git a/regress/core/regress_brin_index_geography_expected b/regress/core/regress_brin_index_geography_expected
index c83d7b9c9..90420fcf6 100644
--- a/regress/core/regress_brin_index_geography_expected
+++ b/regress/core/regress_brin_index_geography_expected
@@ -1,3 +1,4 @@
+brin_proc_12|brin_geography_inclusion_ops|12|geog_brin_inclusion_mergeable
 scan_seq|Seq Scan
 42.01|POINT Z (42.01 42.01 42.01)
 42.03|POINT Z (42.03 42.03 42.03)

commit 356914a6311a2606de1e9089fc2ced8c4064e3f3
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Fri Jun 19 01:15:13 2026 +0400

    postgis: upgrade BRIN mergeability support procs
    
    (cherry picked from commit 77c6842375bc693f5a7844b15374847355177085)

diff --git a/NEWS b/NEWS
index 8c00aab71..a636077cb 100644
--- a/NEWS
+++ b/NEWS
@@ -142,6 +142,8 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
           detoasting them (Darafei Praliaskouski)
  - GH-889, ST_Relate pattern inversion avoids out-of-bounds access on
           short DE-9IM patterns (Darafei Praliaskouski)
+ - GH-890, Avoid GIDX BRIN summary merges across mismatched dimensions
+          (Darafei Praliaskouski)
  - Build PostgreSQL extension modules with `-fno-semantic-interposition` when
           available so LTO can optimize same-DSO calls to exported PostGIS
           entry points directly instead of routing through the module PLT; in
diff --git a/postgis/postgis_after_upgrade.sql b/postgis/postgis_after_upgrade.sql
index 7d86c9a5b..4a867234b 100644
--- a/postgis/postgis_after_upgrade.sql
+++ b/postgis/postgis_after_upgrade.sql
@@ -341,6 +341,58 @@ BEGIN
         END;
     END IF;
 
+    -- Check if the mergeability function is already associated with any operator class
+    IF NOT EXISTS (
+        SELECT 1
+        FROM pg_catalog.pg_amproc
+        WHERE amproc::text = 'geom3d_brin_inclusion_mergeable'
+    ) THEN
+        BEGIN
+            -- Create a temporary operator class with the new function
+            CREATE OPERATOR CLASS brin_geometry_inclusion_ops_3d_temp
+            FOR TYPE geometry USING brin AS
+                FUNCTION 12 geom3d_brin_inclusion_mergeable(internal, internal);
+
+            -- Change the pg_amproc association to the existing opclass
+             WITH a AS (
+                SELECT n.oid AS oid_nfamily, o.oid AS oid_ofamily
+                FROM pg_catalog.pg_opfamily AS n
+                CROSS JOIN (
+                    SELECT oid FROM pg_catalog.pg_opfamily
+                    WHERE opfname = 'brin_geometry_inclusion_ops_3d_temp'
+                ) AS o
+                WHERE n.opfname = 'brin_geometry_inclusion_ops_3d'
+            ), amupdate AS (
+                UPDATE pg_catalog.pg_amproc AS amp
+                SET amprocfamily = a.oid_nfamily
+                FROM a
+                WHERE
+                    amp.amprocfamily = a.oid_ofamily
+                    AND amp.amproc::text = 'geom3d_brin_inclusion_mergeable'
+                RETURNING amp.*
+            )
+            -- Change the opclass dependency to the existing one
+            UPDATE pg_depend AS d
+            SET refobjid = (
+                SELECT n.oid
+                FROM pg_catalog.pg_opclass AS n
+                WHERE opcname = 'brin_geometry_inclusion_ops_3d'
+            )
+            FROM amupdate
+            WHERE d.objid = amupdate.oid AND
+                  refobjid = (
+                    SELECT o.oid
+                    FROM pg_catalog.pg_opclass AS o
+                    WHERE opcname = 'brin_geometry_inclusion_ops_3d_temp'
+                );
+
+            -- Dropping the autogenerated temporary family which cascades to temporary class
+            DROP OPERATOR FAMILY brin_geometry_inclusion_ops_3d_temp USING brin;
+        EXCEPTION WHEN OTHERS THEN
+            RAISE EXCEPTION 'Could not add geom3d_brin_inclusion_mergeable to brin_geometry_inclusion_ops_3d class: %', SQLERRM;
+        END;
+    END IF;
+
     -- Check if the function is already associated with any operator class
     IF NOT EXISTS (
         SELECT 1
@@ -393,6 +445,60 @@ BEGIN
         END;
     END IF;
 
+    -- Check if the mergeability function is already associated with any operator class
+    IF NOT EXISTS (
+        SELECT 1
+        FROM pg_catalog.pg_amproc
+        WHERE amproc::text = 'geom4d_brin_inclusion_mergeable'
+    ) THEN
+        BEGIN
+            -- Create a temporary operator class with the new function
+            CREATE OPERATOR CLASS brin_geometry_inclusion_ops_4d_temp
+            FOR TYPE geometry USING brin AS
+                FUNCTION 12 geom4d_brin_inclusion_mergeable(internal, internal);
+
+            -- Change the pg_amproc association to the existing opclass
+             WITH a AS (
+                SELECT n.oid AS oid_nfamily, o.oid AS oid_ofamily
+                FROM pg_catalog.pg_opfamily AS n
+                CROSS JOIN (
+                    SELECT oid FROM pg_catalog.pg_opfamily
+                    WHERE opfname = 'brin_geometry_inclusion_ops_4d_temp'
+                ) AS o
+                WHERE n.opfname = 'brin_geometry_inclusion_ops_4d'
+            ) , amupdate AS (
+                UPDATE pg_catalog.pg_amproc AS amp
+                SET amprocfamily = a.oid_nfamily
+                FROM a
+                WHERE
+                    amp.amprocfamily = a.oid_ofamily
+                    AND amp.amproc::text = 'geom4d_brin_inclusion_mergeable'
+                RETURNING amp.*
+            )
+            -- Change the opclass dependency to the existing one
+            UPDATE pg_depend AS d
+            SET refobjid = (
+                SELECT n.oid
+                FROM pg_catalog.pg_opclass AS n
+                WHERE opcname = 'brin_geometry_inclusion_ops_4d'
+            )
+            FROM amupdate
+            WHERE d.objid = amupdate.oid AND
+                  refobjid = (
+                    SELECT o.oid
+                    FROM pg_catalog.pg_opclass AS o
+                    WHERE opcname = 'brin_geometry_inclusion_ops_4d_temp'
+                );
+
+            -- Dropping the autogenerated temporary family also drops the temporary class
+            DROP OPERATOR FAMILY brin_geometry_inclusion_ops_4d_temp USING brin;
+        EXCEPTION WHEN OTHERS THEN
+            RAISE EXCEPTION
+                'Could not add geom4d_brin_inclusion_mergeable to brin_geometry_inclusion_ops_4d class: %',
+                SQLERRM;
+        END;
+    END IF;
+
     -- Check if the function is already associated with any operator class
     IF NOT EXISTS (
         SELECT 1
@@ -503,6 +609,63 @@ BEGIN
                 SQLERRM;
         END;
     END IF;
+
+-- geography brin mergeability
+    -- Check if the function 'geog_brin_inclusion_mergeable' already exists
+    IF NOT EXISTS (
+        SELECT 1
+        FROM pg_catalog.pg_amproc
+        WHERE amproc::text = 'geog_brin_inclusion_mergeable'
+    ) THEN
+        BEGIN
+            -- Create a temporary operator class for 'brin_geography_inclusion_ops_temp'
+            CREATE OPERATOR CLASS brin_geography_inclusion_ops_temp
+                FOR TYPE geography USING brin AS
+                    FUNCTION 12 geog_brin_inclusion_mergeable(internal, internal);
+
+            -- find current and new operator family OIDs
+            WITH a AS (
+                SELECT n.oid AS oid_nfamily, o.oid AS oid_ofamily
+                FROM pg_catalog.pg_opfamily AS n
+                CROSS JOIN (
+                    SELECT oid
+                    FROM pg_catalog.pg_opfamily
+                    WHERE opfname = 'brin_geography_inclusion_ops_temp'
+                ) AS o
+                WHERE n.opfname = 'brin_geography_inclusion_ops'
+            ),
+            -- Update the amprocfamily in pg_amproc
+            amupdate AS (
+                UPDATE pg_catalog.pg_amproc AS amp
+                SET amprocfamily = a.oid_nfamily
+                FROM a
+                WHERE amp.amprocfamily = a.oid_ofamily AND
+                      amp.amproc::text = 'geog_brin_inclusion_mergeable'
+                RETURNING amp.*
+            )
+            -- Update dependencies in pg_depend to the existing operator class
+            UPDATE pg_depend AS d
+            SET refobjid = (
+                SELECT n.oid
+                FROM pg_catalog.pg_opclass AS n
+                WHERE opcname = 'brin_geography_inclusion_ops'
+            )
+            FROM amupdate
+            WHERE d.objid = amupdate.oid AND
+                  refobjid = (
+                      SELECT o.oid
+                      FROM pg_catalog.pg_opclass AS o
+                      WHERE opcname = 'brin_geography_inclusion_ops_temp'
+                  );
+
+            -- Drop the temporary operator family after use
+            DROP OPERATOR FAMILY brin_geography_inclusion_ops_temp USING brin;
+        EXCEPTION WHEN OTHERS THEN
+            RAISE EXCEPTION
+                'Could not add geog_brin_inclusion_mergeable to brin_geography_inclusion_ops class: %',
+                SQLERRM;
+        END;
+    END IF;
 END;
 
 $$;

commit b006ec7715a91c4f6dc9ffb50e608b31b2a3087b
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Fri Jun 19 01:15:13 2026 +0400

    Guard GIDX BRIN merges by dimension
    
    (cherry picked from commit 9fea3b87cdb8dc1cddedcc140687e5285a7292c7)

diff --git a/postgis/brin_nd.c b/postgis/brin_nd.c
index 246be78de..2cfb79ea4 100644
--- a/postgis/brin_nd.c
+++ b/postgis/brin_nd.c
@@ -17,6 +17,7 @@ static Datum gidx_brin_inclusion_add_value(
 
 static GIDX * gidx_brin_inclusion_merge(
 	GIDX *gidx_key, GIDX *gidx_geom);
+static bool gidx_brin_inclusion_mergeable(GIDX *gidx_key, GIDX *gidx_geom);
 
 /*
  * As for the GiST case, geographies are converted into GIDX before
@@ -195,6 +196,9 @@ gidx_brin_inclusion_add_value(__attribute__((__unused__)) BrinDesc *bdesc,
 static GIDX *
 gidx_brin_inclusion_merge(GIDX *gidx_key, GIDX *gidx_geom)
 {
+	if (!gidx_brin_inclusion_mergeable(gidx_key, gidx_geom))
+		return gidx_key;
+
 	if (!gidx_contains(gidx_key, gidx_geom))
 	{
 		for (uint32_t i = 0; i < GIDX_NDIMS(gidx_key); i++)
@@ -211,6 +215,12 @@ gidx_brin_inclusion_merge(GIDX *gidx_key, GIDX *gidx_geom)
 	return gidx_key;
 }
 
+static bool
+gidx_brin_inclusion_mergeable(GIDX *gidx_key, GIDX *gidx_geom)
+{
+	return GIDX_NDIMS(gidx_key) == GIDX_NDIMS(gidx_geom);
+}
+
 PG_FUNCTION_INFO_V1(geog_brin_inclusion_merge);
 Datum geog_brin_inclusion_merge(PG_FUNCTION_ARGS)
 {
@@ -220,6 +230,16 @@ Datum geog_brin_inclusion_merge(PG_FUNCTION_ARGS)
 	PG_RETURN_POINTER(gidx_brin_inclusion_merge(key, geom));
 }
 
+PG_FUNCTION_INFO_V1(geog_brin_inclusion_mergeable);
+Datum
+geog_brin_inclusion_mergeable(PG_FUNCTION_ARGS)
+{
+	GIDX *key = (GIDX *)PG_GETARG_POINTER(0);
+	GIDX *geom = (GIDX *)PG_GETARG_POINTER(1);
+
+	PG_RETURN_BOOL(gidx_brin_inclusion_mergeable(key, geom));
+}
+
 PG_FUNCTION_INFO_V1(geom3d_brin_inclusion_merge);
 Datum geom3d_brin_inclusion_merge(PG_FUNCTION_ARGS)
 {
@@ -229,6 +249,16 @@ Datum geom3d_brin_inclusion_merge(PG_FUNCTION_ARGS)
 	PG_RETURN_POINTER(gidx_brin_inclusion_merge(key, geom));
 }
 
+PG_FUNCTION_INFO_V1(geom3d_brin_inclusion_mergeable);
+Datum
+geom3d_brin_inclusion_mergeable(PG_FUNCTION_ARGS)
+{
+	GIDX *key = (GIDX *)PG_GETARG_POINTER(0);
+	GIDX *geom = (GIDX *)PG_GETARG_POINTER(1);
+
+	PG_RETURN_BOOL(gidx_brin_inclusion_mergeable(key, geom));
+}
+
 PG_FUNCTION_INFO_V1(geom4d_brin_inclusion_merge);
 Datum geom4d_brin_inclusion_merge(PG_FUNCTION_ARGS)
 {
@@ -238,4 +268,12 @@ Datum geom4d_brin_inclusion_merge(PG_FUNCTION_ARGS)
 	PG_RETURN_POINTER(gidx_brin_inclusion_merge(key, geom));
 }
 
+PG_FUNCTION_INFO_V1(geom4d_brin_inclusion_mergeable);
+Datum
+geom4d_brin_inclusion_mergeable(PG_FUNCTION_ARGS)
+{
+	GIDX *key = (GIDX *)PG_GETARG_POINTER(0);
+	GIDX *geom = (GIDX *)PG_GETARG_POINTER(1);
 
+	PG_RETURN_BOOL(gidx_brin_inclusion_mergeable(key, geom));
+}
diff --git a/postgis/geography_brin.sql.in b/postgis/geography_brin.sql.in
index 694e0aa92..f9760ed37 100644
--- a/postgis/geography_brin.sql.in
+++ b/postgis/geography_brin.sql.in
@@ -65,6 +65,12 @@ RETURNS internal
         AS 'MODULE_PATHNAME','geog_brin_inclusion_merge'
         LANGUAGE 'c' PARALLEL SAFE;
 
+-- Availability: 3.7.0
+CREATE OR REPLACE FUNCTION geog_brin_inclusion_mergeable(internal, internal)
+RETURNS boolean
+        AS 'MODULE_PATHNAME','geog_brin_inclusion_mergeable'
+        LANGUAGE 'c' PARALLEL SAFE;
+
 -- Availability: 2.3.0
 CREATE OPERATOR CLASS brin_geography_inclusion_ops
   DEFAULT FOR TYPE geography
@@ -74,6 +80,7 @@ CREATE OPERATOR CLASS brin_geography_inclusion_ops
     FUNCTION      3        brin_inclusion_consistent(internal, internal, internal),
     FUNCTION      4        brin_inclusion_union(internal, internal, internal),
     FUNCTION      11       geog_brin_inclusion_merge(internal, internal),
+    FUNCTION      12       geog_brin_inclusion_mergeable(internal, internal),
     OPERATOR      3        &&(geography, geography),
     OPERATOR      3        &&(geography, gidx),
     OPERATOR      3        &&(gidx, geography),
diff --git a/postgis/postgis_brin.sql.in b/postgis/postgis_brin.sql.in
index 9009a7420..73a4f469c 100644
--- a/postgis/postgis_brin.sql.in
+++ b/postgis/postgis_brin.sql.in
@@ -210,6 +210,12 @@ RETURNS internal
 AS 'MODULE_PATHNAME','geom3d_brin_inclusion_merge'
 LANGUAGE 'c' PARALLEL SAFE _COST_DEFAULT;
 
+-- Availability: 3.7.0
+CREATE OR REPLACE FUNCTION geom3d_brin_inclusion_mergeable(internal, internal)
+RETURNS boolean
+AS 'MODULE_PATHNAME','geom3d_brin_inclusion_mergeable'
+LANGUAGE 'c' PARALLEL SAFE _COST_DEFAULT;
+
 -- Availability: 2.3.0
 CREATE OR REPLACE FUNCTION geom4d_brin_inclusion_add_value(internal, internal, internal, internal)
 RETURNS boolean
@@ -222,6 +228,12 @@ RETURNS internal
 AS 'MODULE_PATHNAME','geom4d_brin_inclusion_merge'
 LANGUAGE 'c' PARALLEL SAFE _COST_DEFAULT;
 
+-- Availability: 3.7.0
+CREATE OR REPLACE FUNCTION geom4d_brin_inclusion_mergeable(internal, internal)
+RETURNS boolean
+AS 'MODULE_PATHNAME','geom4d_brin_inclusion_mergeable'
+LANGUAGE 'c' PARALLEL SAFE _COST_DEFAULT;
+
 -- Availability: 2.3.0
 CREATE OPERATOR CLASS brin_geometry_inclusion_ops_2d
   DEFAULT FOR TYPE geometry
@@ -259,6 +271,7 @@ CREATE OPERATOR CLASS brin_geometry_inclusion_ops_3d
     FUNCTION      3        brin_inclusion_consistent(internal, internal, internal),
     FUNCTION      4        brin_inclusion_union(internal, internal, internal),
     FUNCTION      11       geom3d_brin_inclusion_merge(internal, internal),
+    FUNCTION      12       geom3d_brin_inclusion_mergeable(internal, internal),
     OPERATOR      3        &&&(geometry, geometry),
     OPERATOR      3        &&&(geometry, gidx),
     OPERATOR      3        &&&(gidx, geometry),
@@ -278,6 +291,7 @@ CREATE OPERATOR CLASS brin_geometry_inclusion_ops_4d
     FUNCTION      3        brin_inclusion_consistent(internal, internal, internal),
     FUNCTION      4        brin_inclusion_union(internal, internal, internal),
     FUNCTION      11       geom4d_brin_inclusion_merge(internal, internal),
+    FUNCTION      12       geom4d_brin_inclusion_mergeable(internal, internal),
     OPERATOR      3        &&&(geometry, geometry),
     OPERATOR      3        &&&(geometry, gidx),
     OPERATOR      3        &&&(gidx, geometry),

commit 99c75c3f5ad5e7ab66f17a3ec23e46b87d12a82b
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jun 16 17:16:28 2026 +0400

    Guard ST_Relate pattern inversion
    
    Short DE-9IM patterns can reach the prepared ST_Relate inversion path. Make the inversion length-aware so each matrix-position swap is guarded by the available pattern length instead of assuming a nine-character string.
    
    Cache the pattern length once, and normalize pattern bytes through unsigned-char toupper() to avoid signed-char undefined behavior.
    
    Closes https://github.com/postgis/postgis/pull/889
    
    (cherry picked from commit 073f45c60c59efde4607fdb59eee410dc55116ee)

diff --git a/NEWS b/NEWS
index 3ea346e04..8c00aab71 100644
--- a/NEWS
+++ b/NEWS
@@ -140,6 +140,8 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
           MinGW builds (Darafei Praliaskouski)
  - GH-886, ST_ClusterRelateWin validates NULL relate matrices before
           detoasting them (Darafei Praliaskouski)
+ - GH-889, ST_Relate pattern inversion avoids out-of-bounds access on
+          short DE-9IM patterns (Darafei Praliaskouski)
  - Build PostgreSQL extension modules with `-fno-semantic-interposition` when
           available so LTO can optimize same-DSO calls to exported PostGIS
           entry points directly instead of routing through the module PLT; in
diff --git a/postgis/lwgeom_geos_predicates.c b/postgis/lwgeom_geos_predicates.c
index 2a283e17c..c400ebca7 100644
--- a/postgis/lwgeom_geos_predicates.c
+++ b/postgis/lwgeom_geos_predicates.c
@@ -22,6 +22,7 @@
  * Copyright 2009-2014 Sandro Santilli <strk at kbt.io>
  * Copyright 2008 Paul Ramsey <pramsey at cleverelephant.ca>
  * Copyright 2001-2003 Refractions Research Inc.
+ * Copyright 2026 Darafei Praliaskouski <me at komzpa.net>
  *
  **********************************************************************/
 
@@ -951,14 +952,33 @@ Datum coveredby(PG_FUNCTION_ARGS)
  *  Flip the DE9IM matrix around the diagonal to
  *  account for flipping the order of the operands
  *  to the GEOSPreparedRelatePattern function.
+ *
+ *  Short patterns are invalid for GEOS, but they can still reach this helper
+ *  before GEOS reports the error, so every swapped position must be guarded by
+ *  the actual pattern length.
  */
 static void
-imInvert(char *im)
+imInvert(char *im, size_t len)
 {
 	char t;
-	t = im[1]; im[1] = im[3]; im[3] = t;
-	t = im[2]; im[2] = im[6]; im[6] = t;
-	t = im[5]; im[5] = im[7]; im[7] = t;
+	if (len > 3)
+	{
+		t = im[1];
+		im[1] = im[3];
+		im[3] = t;
+	}
+	if (len > 6)
+	{
+		t = im[2];
+		im[2] = im[6];
+		im[6] = t;
+	}
+	if (len > 7)
+	{
+		t = im[5];
+		im[5] = im[7];
+		im[7] = t;
+	}
 }
 #endif
 
@@ -975,7 +995,8 @@ Datum relate_pattern(PG_FUNCTION_ARGS)
 		PG_GETARG_DATUM(2), Int32GetDatum(9)));
 	char *im = text_to_cstring(imPtr);
 	int8_t result;
-	uint32_t i;
+	size_t im_len = strlen(im);
+	size_t i;
 #if POSTGIS_GEOS_VERSION >= 31300
 	PrepGeomCache *prep_cache;
 #endif
@@ -985,8 +1006,8 @@ Datum relate_pattern(PG_FUNCTION_ARGS)
 	/*
 	** Need to make sure 't' and 'f' are upper-case before handing to GEOS
 	*/
-	for (i = 0; i < strlen(im); i++)
-		im[i] = toupper(im[i]);
+	for (i = 0; i < im_len; i++)
+		im[i] = toupper((unsigned char)im[i]);
 
 	initGEOS(lwpgnotice, lwgeom_geos_error);
 
@@ -1002,7 +1023,7 @@ Datum relate_pattern(PG_FUNCTION_ARGS)
 		else
 		{
 			g = POSTGIS2GEOS(geom1);
-			imInvert(im);
+			imInvert(im, im_len);
 		}
 
 		if (!g) HANDLE_GEOS_ERROR("Geometry could not be converted to GEOS");
diff --git a/regress/core/relate.sql b/regress/core/relate.sql
index a7172a40d..107e91fe3 100644
--- a/regress/core/relate.sql
+++ b/regress/core/relate.sql
@@ -553,3 +553,9 @@ SELECT '552', ST_Relate('MULTIPOINT(20 20,80 70,140 120,200 170)','MULTIPOINT(80
 SELECT '553', ST_Relate('MULTIPOINT(80 70,20 20,200 170,140 120)','MULTIPOINT(140 120,80 70)');
 SELECT '554', ST_Relate('MULTIPOINT(80 70,20 20,200 170,140 120)','MULTIPOINT(80 170,140 120,200 80)');
 SELECT '555', ST_Relate('MULTIPOINT(80 70,20 20,200 170,140 120)','MULTIPOINT(80 170,140 120,200 80,80 70)');
+
+SELECT 'relate_short_pattern_prepared', id, ST_Relate(g, 'POINT(0 0)'::geometry, 'T')
+FROM (VALUES
+	(1, 'POINT(0 0)'::geometry),
+	(2, 'POINT(1 1)'::geometry)
+) AS v(id, g);
diff --git a/regress/core/relate_expected b/regress/core/relate_expected
index b48ef45fc..52e64ff3b 100644
--- a/regress/core/relate_expected
+++ b/regress/core/relate_expected
@@ -553,3 +553,4 @@
 553|0F0FFFFF2
 554|0F0FFF0F2
 555|0F0FFF0F2
+ERROR:  GEOSRelatePattern: IllegalArgumentException: IllegalArgumentException: Should be length 9, is [T] instead

commit 0e07c23d019a731a2e95c1a366510a4cdade4bd7
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Wed Jun 17 01:26:40 2026 +0400

    raster: align IntersectionFractions test with GEOS float output
    
    (cherry picked from commit c02ab2f5a15e81b9db6c51618b9a0f3295290be8)

diff --git a/raster/rt_core/rt_spatial_relationship.c b/raster/rt_core/rt_spatial_relationship.c
index e799bc921..a71ce179e 100644
--- a/raster/rt_core/rt_spatial_relationship.c
+++ b/raster/rt_core/rt_spatial_relationship.c
@@ -1334,10 +1334,10 @@ rt_raster_intersection_fractions(
 
 	/* Shallow clone a new raster with no bands */
 	rast_out = rt_raster_clone(rast_in, 0);
-	/* Add a float8 band because GEOSGridIntersectionFractions writes doubles */
+	/* Add a float4 band matching GEOSGridIntersectionFractions' float buffer */
 	band_num = rt_raster_generate_new_band(
 		rast_out, /* rast */
-		PT_64BF, /* pixel type */
+		PT_32BF, /* pixel type */
 		0.0, /* initial value */
 		0, 0.0, /* hasnodata, nodataval */
 		0); /* index */
@@ -1388,4 +1388,3 @@ rt_raster_intersection_fractions(
 	return rast_out; /* Return the newly created raster and band*/
 }
 #endif /* POSTGIS_GEOS_VERSION >= 31400 */
-
diff --git a/raster/test/regress/rt_intersection_fractions_expected b/raster/test/regress/rt_intersection_fractions_expected
index 575021720..601ee1f9c 100644
--- a/raster/test/regress/rt_intersection_fractions_expected
+++ b/raster/test/regress/rt_intersection_fractions_expected
@@ -1,4 +1,4 @@
-pixeltype|64BF
+pixeltype|32BF
 polygon|2x2 raster covering 0,0 to 10,10|{{0.5,0.5},{0.5,0.5}}
 polygon|10x10 raster covering 0,0 to 10,10|{{0,0,0,0,0.5,0.5,0,0,0,0},{0,0,0,0.5,1,1,0.5,0,0,0},{0,0,0.5,1,1,1,1,0.5,0,0},{0,0.5,1,1,1,1,1,1,0.5,0},{0.5,1,1,1,1,1,1,1,1,0.5},{0.5,1,1,1,1,1,1,1,1,0.5},{0,0.5,1,1,1,1,1,1,0.5,0},{0,0,0.5,1,1,1,1,0.5,0,0},{0,0,0,0.5,1,1,0.5,0,0,0},{0,0,0,0,0.5,0.5,0,0,0,0}}
 polygon|2x2 raster covering -10,-10 to 0,0|{{0,0},{0,0}}

commit 6db6b531f34aa23a775548f98b761ea624930b82
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Tue Jun 16 17:15:29 2026 +0400

    Fix IntersectionFractions raster band size
    
    (cherry picked from commit 23b9d0e074d4eb40dc04aaeadae16fd4332fcc45)

diff --git a/raster/rt_core/rt_spatial_relationship.c b/raster/rt_core/rt_spatial_relationship.c
index 374dd07f2..e799bc921 100644
--- a/raster/rt_core/rt_spatial_relationship.c
+++ b/raster/rt_core/rt_spatial_relationship.c
@@ -1334,10 +1334,10 @@ rt_raster_intersection_fractions(
 
 	/* Shallow clone a new raster with no bands */
 	rast_out = rt_raster_clone(rast_in, 0);
-	/* Add a float4 band to the empty raster */
+	/* Add a float8 band because GEOSGridIntersectionFractions writes doubles */
 	band_num = rt_raster_generate_new_band(
 		rast_out, /* rast */
-		PT_32BF, /* pixel type */
+		PT_64BF, /* pixel type */
 		0.0, /* initial value */
 		0, 0.0, /* hasnodata, nodataval */
 		0); /* index */
diff --git a/raster/test/regress/rt_intersection_fractions.sql b/raster/test/regress/rt_intersection_fractions.sql
index f3ea96d16..662025f7a 100644
--- a/raster/test/regress/rt_intersection_fractions.sql
+++ b/raster/test/regress/rt_intersection_fractions.sql
@@ -48,6 +48,14 @@ INSERT INTO raster_proportions_rast (name, rast) VALUES (
 	));
 
 SET extra_float_digits = -8;
+
+SELECT 'pixeltype', ST_BandPixelType(
+	ST_IntersectionFractions(
+		rast,
+		'POLYGON((5 0, 0 5, 5 10, 10 5, 5 0))'::geometry), 1)
+FROM raster_proportions_rast
+WHERE name = '2x2 raster covering 0,0 to 10,10';
+
 SELECT 'polygon', name, ST_DumpValues(
 	ST_IntersectionFractions(
 		rast,
diff --git a/raster/test/regress/rt_intersection_fractions_expected b/raster/test/regress/rt_intersection_fractions_expected
index 7318eb76f..575021720 100644
--- a/raster/test/regress/rt_intersection_fractions_expected
+++ b/raster/test/regress/rt_intersection_fractions_expected
@@ -1,3 +1,4 @@
+pixeltype|64BF
 polygon|2x2 raster covering 0,0 to 10,10|{{0.5,0.5},{0.5,0.5}}
 polygon|10x10 raster covering 0,0 to 10,10|{{0,0,0,0,0.5,0.5,0,0,0,0},{0,0,0,0.5,1,1,0.5,0,0,0},{0,0,0.5,1,1,1,1,0.5,0,0},{0,0.5,1,1,1,1,1,1,0.5,0},{0.5,1,1,1,1,1,1,1,1,0.5},{0.5,1,1,1,1,1,1,1,1,0.5},{0,0.5,1,1,1,1,1,1,0.5,0},{0,0,0.5,1,1,1,1,0.5,0,0},{0,0,0,0.5,1,1,0.5,0,0,0},{0,0,0,0,0.5,0.5,0,0,0,0}}
 polygon|2x2 raster covering -10,-10 to 0,0|{{0,0},{0,0}}

commit a8683ba6ead9f81c08a13cb54fc770fbe452c5d3
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Fri Jun 19 02:07:05 2026 +0400

    Fix ST_ClusterRelateWin NULL matrix handling
    
    Validate the window relate matrix before detoasting it so NULL input reports the existing invalid-matrix error instead of dereferencing a NULL text datum. Add GEOS 3.13-gated regression coverage for the window function path.
    
    Closes https://github.com/postgis/postgis/pull/886
    
    (cherry picked from commit 9234e0417d493a59888180c6a8238a5161962f7c)

diff --git a/NEWS b/NEWS
index 5ff5a916a..3ea346e04 100644
--- a/NEWS
+++ b/NEWS
@@ -138,6 +138,8 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
           dropping the tail (Darafei Praliaskouski)
  - #6003, Avoid size_t formats in liblwgeom logging/error wrappers for
           MinGW builds (Darafei Praliaskouski)
+ - GH-886, ST_ClusterRelateWin validates NULL relate matrices before
+          detoasting them (Darafei Praliaskouski)
  - Build PostgreSQL extension modules with `-fno-semantic-interposition` when
           available so LTO can optimize same-DSO calls to exported PostGIS
           entry points directly instead of routing through the module PLT; in
diff --git a/postgis/lwgeom_window.c b/postgis/lwgeom_window.c
index 9b0e6e976..32f5b7a7d 100644
--- a/postgis/lwgeom_window.c
+++ b/postgis/lwgeom_window.c
@@ -20,6 +20,7 @@
  *
  * Copyright 2016 Paul Ramsey <pramsey at cleverelephant.ca>
  * Copyright 2016 Daniel Baston <dbaston at gmail.com>
+ * Copyright 2026 Darafei Praliaskouski <me at komzpa.net>
  *
  **********************************************************************/
 
@@ -375,11 +376,22 @@ Datum ST_ClusterRelateWin(PG_FUNCTION_ARGS)
 		GEOSGeometry** geoms = palloc0(ngeoms * sizeof(GEOSGeometry*));
 		UNIONFIND* uf = UF_create(ngeoms);
 		char *matrix;
-		text *txtIm = DatumGetTextP(WinGetFuncArgCurrent(win_obj, 1, &matrix_is_null));
-		if (matrix_is_null || VARSIZE_ANY_EXHDR(txtIm) != 9)
+		/*
+		 * Window arguments must be checked for NULL before detoasting.
+		 * DatumGetTextP() is not allowed to inspect a NULL Datum.
+		 */
+		Datum matrix_datum = WinGetFuncArgCurrent(win_obj, 1, &matrix_is_null);
+		text *txtIm;
+
+		if (matrix_is_null)
 		{
-			elog(ERROR,"Invalid relate matrix provided");
-			PG_RETURN_NULL();
+			elog(ERROR, "Invalid relate matrix provided");
+		}
+
+		txtIm = DatumGetTextP(matrix_datum);
+		if (VARSIZE_ANY_EXHDR(txtIm) != 9)
+		{
+			elog(ERROR, "Invalid relate matrix provided");
 		}
 		matrix = text_to_cstring(txtIm);
 
diff --git a/regress/core/cluster_geos313.sql b/regress/core/cluster_geos313.sql
new file mode 100644
index 000000000..64e677767
--- /dev/null
+++ b/regress/core/cluster_geos313.sql
@@ -0,0 +1,8 @@
+CREATE TEMPORARY TABLE cluster_geos313_inputs (id int, geom geometry);
+INSERT INTO cluster_geos313_inputs VALUES
+(1, 'LINESTRING (0 0, 1 1)'),
+(2, 'LINESTRING (5 5, 4 4)');
+
+SELECT 'cluster_relate_null_matrix', ST_ClusterRelateWin(geom, NULL) OVER ()
+FROM cluster_geos313_inputs
+LIMIT 1;
diff --git a/regress/core/cluster_geos313_expected b/regress/core/cluster_geos313_expected
new file mode 100644
index 000000000..823cdd774
--- /dev/null
+++ b/regress/core/cluster_geos313_expected
@@ -0,0 +1 @@
+ERROR:  Invalid relate matrix provided
diff --git a/regress/core/tests.mk.in b/regress/core/tests.mk.in
index 73426ab2a..1b14fbeeb 100644
--- a/regress/core/tests.mk.in
+++ b/regress/core/tests.mk.in
@@ -172,6 +172,11 @@ else
 		$(top_srcdir)/regress/core/concave_hull
 endif
 
+ifeq ($(shell expr "$(POSTGIS_GEOS_VERSION)" ">=" 31300),1)
+	TESTS += \
+		$(top_srcdir)/regress/core/cluster_geos313
+endif
+
 ifeq ($(shell expr "$(POSTGIS_GEOS_VERSION)" ">=" 31500),1)
 	TESTS += \
 		$(top_srcdir)/regress/core/geos315

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

Summary of changes:
 .github/workflows/codespell.yml                    |   7 +-
 NEWS                                               |   6 +
 ci/winnie/regress_postgis.sh                       |  40 +++--
 doc/xsl-config.xml                                 |   1 -
 postgis/brin_nd.c                                  |  38 +++++
 postgis/geography_brin.sql.in                      |   7 +
 postgis/lwgeom_geos_predicates.c                   |  37 ++++-
 postgis/lwgeom_window.c                            |  20 ++-
 postgis/postgis_after_upgrade.sql                  | 163 +++++++++++++++++++++
 postgis/postgis_brin.sql.in                        |  14 ++
 raster/rt_core/rt_spatial_relationship.c           |   3 +-
 raster/test/regress/rt_intersection_fractions.sql  |   8 +
 .../regress/rt_intersection_fractions_expected     |   1 +
 regress/core/cluster_geos313.sql                   |   8 +
 regress/core/cluster_geos313_expected              |   1 +
 regress/core/regress_brin_index_3d.sql             |  10 ++
 regress/core/regress_brin_index_3d_expected        |   2 +
 regress/core/regress_brin_index_geography.sql      |   9 ++
 regress/core/regress_brin_index_geography_expected |   1 +
 regress/core/relate.sql                            |   6 +
 regress/core/relate_expected                       |   1 +
 regress/core/tests.mk.in                           |   5 +
 utils/check_all_upgrades.sh                        |  28 ++--
 23 files changed, 374 insertions(+), 42 deletions(-)
 create mode 100644 regress/core/cluster_geos313.sql
 create mode 100644 regress/core/cluster_geos313_expected


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list