Skip to content

in 1.5.3, COPY TO (FORMAT GDAL, DRIVER 'MapML'/'GeoRSS') crashes with basic_string: construction from null is not valid #818

Description

@JonAnCla

This is another geospatial extension crash I found when trying to get ibis working with duckdb 1.5.3 and came across a crash in duckdb that is triggered by some of the ibis geospatial unit tests. I reported the other crash here: duckdb/duckdb-python#475

Crash happens using duckdb==1.5.3, spatial extension b68b309

I have had copilot generate the following minimal reproducer from the failing unit test:

import duckdb, tempfile, os
con = duckdb.connect()
con.load_extension("spatial")
with tempfile.TemporaryDirectory() as tmp:
    con.execute(f"COPY (SELECT ST_POINT(0.0, 0.0) AS geom) TO '{tmp}/out.mapml' (FORMAT GDAL, DRIVER 'MapML')")

The GeoRSS driver is also affected. Some additional insight from copilot and extended reproducer script is below

"""
Minimal reproducer for duckdb-spatial regression:
  COPY TO with DRIVER 'MapML' or DRIVER 'GEORSS' raises
  "Error: basic_string: construction from null is not valid"
  instead of either succeeding or returning a clear error message.

This is a C++ null-pointer dereference inside duckdb-spatial's GDAL COPY
write path: a GDAL API call returns NULL (e.g. a layer name, SRS string, or
driver capability string), and the spatial extension passes it directly to a
std::string constructor without checking for null first.

Affected formats (confirmed):
  - MapML   (GDAL driver short name: MapML)
  - GeoRSS  (GDAL driver short name: GeoRSS / GEORSS)

Both drivers appear in ST_DRIVERS() with can_open=True and can_copy=True, so
duckdb-spatial accepts their names in COPY TO without raising an early error.

Versions tested:
  duckdb          1.5.3
  duckdb-spatial  b68b309 (installed from repository)
  Python          3.13.13

Expected behaviour: either the COPY TO succeeds, or a clear GDAL/IO error is
returned (e.g. "Driver does not support writing" or a GDAL error string).

Note: only GPKG and NGW show can_copy=True in ST_DRIVERS(); the remaining
drivers that accept COPY TO (including MapML and GeoRSS) go through a
separate code path inside duckdb-spatial that does not guard against null.
"""

import os
import tempfile

import duckdb

print(f"duckdb version: {duckdb.__version__}")

con = duckdb.connect()
con.load_extension("spatial")

# Confirm both drivers are known to duckdb-spatial
for short_name in ("MapML", "GeoRSS"):
    row = con.execute(
        f"SELECT short_name, can_create, can_open, can_copy "
        f"FROM st_drivers() WHERE short_name ILIKE '{short_name}'"
    ).fetchone()
    print(f"{short_name} in st_drivers(): {row}")

print()

with tempfile.TemporaryDirectory() as tmp:
    cases = [
        ("MapML", os.path.join(tmp, "out.mapml")),
        ("GeoRSS", os.path.join(tmp, "out.georss")),
    ]

    for driver, path in cases:
        # Smallest possible query: single point row, geometry column only
        sql = f"""
            COPY (SELECT ST_POINT(0.0, 0.0) AS geom)
            TO '{path}' (FORMAT GDAL, DRIVER '{driver}')
        """
        try:
            con.execute(sql)
            print(f"{driver}: OK — wrote {os.path.getsize(path)} bytes")
        except duckdb.Error as e:
            first_line = str(e).splitlines()[0]
            print(f"{driver}: REGRESSION CONFIRMED — {type(e).__name__}: {first_line}")

Many thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions