[SCM] PostGIS branch master updated. 3.6.0rc2-158-g0848c6ab1

git at osgeo.org git at osgeo.org
Thu Oct 23 18:22:47 PDT 2025


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  0848c6ab155041008cdd432042669448878f05cc (commit)
      from  385a9ff4e608bb5486b6146d43f51fdd534d884e (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 0848c6ab155041008cdd432042669448878f05cc
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Fri Oct 24 05:22:33 2025 +0400

    ST_AsGeoJSON warns about duplicate property keys
    
    Closes #4798

diff --git a/NEWS b/NEWS
index 376a414cb..6fc642459 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,7 @@ xxxx/xx/xx
  - #2858, ST_MMin and ST_MMax (Paul Ramsey)
  - #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)
 
 
 PostGIS 3.6.0
diff --git a/doc/reference_output.xml b/doc/reference_output.xml
index a5f2e95da..7d08befec 100644
--- a/doc/reference_output.xml
+++ b/doc/reference_output.xml
@@ -1,4 +1,3 @@
-<!-- Converted by db4-upgrade version 1.1 -->
 <section xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="Geometry_Outputs">
     <title>Geometry Output</title><info>
       <abstract>
@@ -706,6 +705,8 @@ Conversely, passing the parameter will save column type lookups.
 
             <para>The <varname>id_column</varname> parameter is used to set the "id" member of the returned GeoJSON features. As per GeoJSON RFC, this SHOULD be used whenever a feature has a commonly used identifier, such as a primary key. When not specified, the produced features will not get an "id" member and any columns other than the geometry, including any potential keys, will just end up inside the feature’s "properties" member.</para>
 
+            <para>When the input record includes duplicate column names, the resulting "properties" object will contain repeated keys. <function>ST_AsGeoJSON</function> raises a warning in that case because PostgreSQL <type>jsonb</type> keeps only the last value for duplicate keys.</para>
+
             <para>The GeoJSON specification states that polygons are oriented using the Right-Hand Rule,
             and some clients require this orientation.
             This can be ensured by using <xref linkend="ST_ForcePolygonCCW"/>.
@@ -730,7 +731,8 @@ Conversely, passing the parameter will save column type lookups.
       <para role="changed" conformance="2.0.0">Changed: 2.0.0 support default args and named args.</para>
       <para role="changed" conformance="3.0.0">Changed: 3.0.0 support records as input</para>
       <para role="changed" conformance="3.0.0">Changed: 3.0.0 output SRID if not EPSG:4326.</para>
-            <para role="changed" conformance="3.5.0">Changed: 3.5.0 allow specifying the column containing the feature id</para>
+      <para role="changed" conformance="3.5.0">Changed: 3.5.0 allow specifying the column containing the feature id</para>
+      <para role="changed" conformance="3.7.0">Changed: 3.7.0 added warning about duplicate keys</para>
       <para>&Z_support;</para>
     </refsection>
 
diff --git a/postgis/lwgeom_out_geojson.c b/postgis/lwgeom_out_geojson.c
index 0338cdf18..1454343e8 100644
--- a/postgis/lwgeom_out_geojson.c
+++ b/postgis/lwgeom_out_geojson.c
@@ -19,6 +19,7 @@
 #include "utils/datetime.h"
 #include "utils/lsyscache.h"
 #include "utils/json.h"
+#include "utils/hsearch.h"
 #if POSTGIS_PGSQL_VERSION < 130
 #include "utils/jsonapi.h"
 #else
@@ -47,10 +48,20 @@ typedef enum					/* type categories for datum_to_json */
 	JSONTYPE_OTHER				/* all else */
 } JsonTypeCategory;
 
-static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
-				  Datum *vals, bool *nulls, int *valcount,
-				  JsonTypeCategory tcategory, Oid outfuncoid,
-				  bool use_line_feeds);
+typedef struct GeoJsonPropKey {
+	char key[NAMEDATALEN];
+} GeoJsonPropKey;
+
+static void array_dim_to_json(StringInfo result,
+			      int dim,
+			      int ndims,
+			      int *dims,
+			      Datum *vals,
+			      bool *nulls,
+			      int *valcount,
+			      JsonTypeCategory tcategory,
+			      Oid outfuncoid,
+			      bool use_line_feeds);
 static void array_to_json_internal(Datum array, StringInfo result,
 								   bool use_line_feeds);
 static void composite_to_geojson(FunctionCallInfo fcinfo,
@@ -127,18 +138,24 @@ composite_to_geojson(FunctionCallInfo fcinfo,
 		     Oid geog_oid)
 {
 	HeapTupleHeader td;
-	Oid			tupType;
-	int32		tupTypmod;
-	TupleDesc	tupdesc;
-	HeapTupleData tmptup,
-			   *tuple;
-	int			i;
-	bool		needsep = false;
+	Oid tupType;
+	int32 tupTypmod;
+	TupleDesc tupdesc;
+	HeapTupleData tmptup, *tuple;
+	int i;
+	bool needsep = false;
 	const char *sep;
-	StringInfo	props = makeStringInfo();
-	StringInfo	id = makeStringInfo();
-	bool		geom_column_found = false;
-	bool		id_column_found = false;
+	StringInfo props = makeStringInfo();
+	StringInfo id = makeStringInfo();
+	bool geom_column_found = false;
+	bool id_column_found = false;
+	HTAB *prop_keys = NULL;
+	HASHCTL ctl;
+
+	MemSet(&ctl, 0, sizeof(ctl));
+	ctl.keysize = NAMEDATALEN;
+	ctl.entrysize = sizeof(GeoJsonPropKey);
+	ctl.hcxt = CurrentMemoryContext;
 
 	sep = use_line_feeds ? ",\n " : ", ";
 
@@ -149,6 +166,15 @@ composite_to_geojson(FunctionCallInfo fcinfo,
 	tupTypmod = HeapTupleHeaderGetTypMod(td);
 	tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
 
+	/*
+	 * Keep track of property names for this feature so that we can warn
+	 * when SQL supplies duplicate aliases.  GeoJSON accepts repeated keys,
+	 * yet downstream PostgreSQL jsonb casts retain only the last value, so
+	 * surfacing the issue here prevents silent information loss.
+	 */
+	prop_keys =
+	    hash_create("GeoJSON property keys", Max(tupdesc->natts, 8), &ctl, HASH_ELEM | HASH_CONTEXT | HASH_STRINGS);
+
 	/* Build a temporary HeapTuple control structure */
 	tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
 	tmptup.t_data = td;
@@ -158,14 +184,14 @@ composite_to_geojson(FunctionCallInfo fcinfo,
 
 	for (i = 0; i < tupdesc->natts; i++)
 	{
-		Datum		val;
-		bool		isnull;
-		char	   *attname;
+		Datum val;
+		bool isnull;
+		char *attname;
 		JsonTypeCategory tcategory;
-		Oid			outfuncoid;
+		Oid outfuncoid;
 		Form_pg_attribute att = TupleDescAttr(tupdesc, i);
-		bool        is_geom_column = false;
-		bool        is_id_column = false;
+		bool is_geom_column = false;
+		bool is_id_column = false;
 
 		if (att->attisdropped)
 			continue;
@@ -220,10 +246,22 @@ composite_to_geojson(FunctionCallInfo fcinfo,
 		}
 		else
 		{
+			bool found;
+
 			if (needsep)
 				appendStringInfoString(props, sep);
 			needsep = true;
 
+			(void)hash_search(prop_keys, attname, HASH_ENTER, &found);
+			if (found)
+			{
+				ereport(
+				    WARNING,
+				    (errmsg("duplicate key \"%s\" encountered while building GeoJSON properties",
+					    attname),
+				     errhint("Only the last value for each key is preserved when casting to JSONB.")));
+			}
+
 			escape_json(props, attname);
 			appendStringInfoString(props, ": ");
 
@@ -242,16 +280,14 @@ composite_to_geojson(FunctionCallInfo fcinfo,
 	}
 
 	if (!geom_column_found)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("geometry column is missing")));
+		ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("geometry column is missing")));
 
 	if (id_column_name)
 	{
 		if (!id_column_found)
 			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-					 errmsg("Specified id column \"%s\" is missing", id_column_name)));
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("Specified id column \"%s\" is missing", id_column_name)));
 
 		appendStringInfoString(result, ", \"id\": ");
 		appendStringInfo(result, "%s", id->data);
@@ -261,6 +297,7 @@ composite_to_geojson(FunctionCallInfo fcinfo,
 	appendStringInfo(result, "%s", props->data);
 
 	appendStringInfoString(result, "}}");
+	hash_destroy(prop_keys);
 	ReleaseTupleDesc(tupdesc);
 }
 
diff --git a/regress/core/tickets.sql b/regress/core/tickets.sql
index 0b34a3028..6fd77a19d 100644
--- a/regress/core/tickets.sql
+++ b/regress/core/tickets.sql
@@ -1442,6 +1442,18 @@ FROM
         ST_SetSRID(ST_MakePoint(7, 51), 4326) geom
     ) data;
 
+-- https://trac.osgeo.org/postgis/ticket/4798
+SET client_min_messages TO WARNING;
+SELECT
+    '#4798', ST_AsGeoJSON(data.*)
+FROM
+    (SELECT
+        1 AS column,
+        2 AS column,
+        ST_SetSRID(ST_MakePoint(7, 51), 4326) AS geom
+    ) AS data;
+RESET client_min_messages;
+
 
 -- https://trac.osgeo.org/postgis/ticket/5008
 SELECT
diff --git a/regress/core/tickets_expected b/regress/core/tickets_expected
index fbf04a8c1..c39981696 100644
--- a/regress/core/tickets_expected
+++ b/regress/core/tickets_expected
@@ -460,6 +460,8 @@ ERROR:  LWGEOM_addpoint: Invalid offset
 #4770.c|POINT(0 0)|300
 #4770.c|MULTIPOINT((0 0),(1 1))|602
 #4799|{"type": "Feature", "geometry": {"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:25832"}},"coordinates":[359667,5651729]}, "properties": {"id": 1, "geom1": {"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:3035"}},"coordinates":[4110471,3103061]}}}
+WARNING:  duplicate key "column" encountered while building GeoJSON properties
+#4798|{"type": "Feature", "geometry": {"type":"Point","coordinates":[7,51]}, "properties": {"column": 1, "column": 2}}
 #5008|f|f
 ERROR:  Line has no points
 #5350|M 0 0 l 1 -1

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

Summary of changes:
 NEWS                          |  1 +
 doc/reference_output.xml      |  6 ++-
 postgis/lwgeom_out_geojson.c  | 89 ++++++++++++++++++++++++++++++-------------
 regress/core/tickets.sql      | 12 ++++++
 regress/core/tickets_expected |  2 +
 5 files changed, 82 insertions(+), 28 deletions(-)


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list