Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/gdalalg_vector_edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,19 @@ bool GDALVectorEditAlgorithm::RunStep(GDALPipelineStepRunContext &)
ret = (poSrcLayer != nullptr);
if (ret)
{
// Capture errors from VRT layers such as WFS
// (see issue GH # https://github.com/OSGeo/gdal/issues/14826)
const auto errorCount{CPLGetErrorCounter()};
outDS->AddLayer(*poSrcLayer,
std::make_unique<GDALVectorEditAlgorithmLayer>(
*poSrcLayer, m_activeLayer, m_outputLayerName,
bChangeGeomType, eType, m_overrideCrs,
m_layerMetadata, m_unsetLayerMetadata,
m_unsetFID));
if (CPLGetErrorCounter() != errorCount)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this convert warnings into failures? It looks like CPLErrorV increments the error counter whenever it is called, regardless of the error type?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I missed during review that GDALVectorEditAlgorithmLayer() constrructor emits warnings. We should indeed restrict the before/after check just around oSrcLayer.GetLayerDefn()

@dbaston dbaston Jun 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a static std::unique_ptr<GDALVectorEditAlgorithmLayer>::Create that would return a nullptr on construction failure, allowing us to use conventional return code error propagation?

Or GetLayerDefn() returning nullptr on failure?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or GetLayerDefn() returning nullptr on failure?

Actually that was my first attempt but after a conversation with @rouault we decided that GDAL source code is not ready for GetLayerDefn() returning nullptr on failure.

I'll check the alternatives.

{
ret = false;
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions apps/ogr2ogr_lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4519,7 +4519,17 @@ SetupTargetLayer::Setup(OGRLayer *poSrcLayer, const char *pszNewLayerName,
/* -------------------------------------------------------------------- */
/* Get other info. */
/* -------------------------------------------------------------------- */

// Capture errors from VRT layers such as WFS
// (see issue GH # https://github.com/OSGeo/gdal/issues/14826)
const GUInt32 errors{CPLGetErrorCounter()};
const OGRFeatureDefn *poSrcFDefn = poSrcLayer->GetLayerDefn();
if (CPLGetErrorCounter() != errors)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Error retrieving the source layer definition");
return nullptr;
}

/* -------------------------------------------------------------------- */
/* Find requested geometry fields. */
Expand Down
10 changes: 9 additions & 1 deletion autotest/pymod/gdaltest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1995,7 +1995,12 @@ def _read_in_thread(f, q):


def runexternal_out_and_err(
cmd, check_memleak=True, encoding="ascii", stdin=None, close_stdin=False
cmd,
check_memleak=True,
encoding="ascii",
stdin=None,
close_stdin=False,
append_returncode_to_stderr=False,
):
# pylint: disable=unused-argument
if sys.platform == "win32":
Expand Down Expand Up @@ -2034,6 +2039,9 @@ def runexternal_out_and_err(
if waitcode != 0:
ret_stderr = f"{ret_stderr}\nERROR ret code = {waitcode}"

if append_returncode_to_stderr:
ret_stderr = f"{ret_stderr}\nReturn code = {p.returncode}"

return (ret_stdout, ret_stderr)


Expand Down
22 changes: 22 additions & 0 deletions autotest/utilities/test_gdalalg_vector_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -1178,3 +1178,25 @@ def test_gdalalg_vector_pipeline_read_wkt_invalid(tmp_vsimem, wkt):

with pytest.raises(Exception, match="No such file or directory"):
gdal.alg.vector.pipeline(f'read "{wkt}" ! write {tmp_vsimem}/out.shp')


@pytest.mark.require_driver("CSV")
@pytest.mark.require_driver("WFS")
@pytest.mark.require_driver("VRT")
def test_gdalalg_vector_pipeline_wfs_invalid_vrt(tmp_path):

out_filename = str(tmp_path / "out.csv")
vrt_filename = str(tmp_path / "out.vrt")
with open(vrt_filename, "wt") as f:
f.write("""<OGRVRTDataSource>
<OGRVRTLayer name="layer">
<SrcDataSource>WFS:http://this-is-an-unreachable.url</SrcDataSource>
</OGRVRTLayer>
</OGRVRTDataSource>""")

with pytest.raises(
Exception, match="Error returned by server : Could not resolve host"
):
gdal.alg.vector.pipeline(
f"read {vrt_filename} ! edit ! write {out_filename} --overwrite"
)
27 changes: 27 additions & 0 deletions autotest/utilities/test_ogr2ogr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2312,3 +2312,30 @@ def test_ogr2ogr_parquet_dataset_limit(ogr2ogr_path, tmp_path):

ds = ogr.Open(out_filename)
assert ds.GetLayer(0).GetFeatureCount() == 1


###############################################################################
# Test https://github.com/OSGeo/gdal/issues/14826


@pytest.mark.require_driver("CSV")
@pytest.mark.require_driver("WFS")
@pytest.mark.require_driver("VRT")
def test_ogr2ogr_invalid_wfs_vrt(ogr2ogr_path, tmp_path):

out_filename = str(tmp_path / "out.csv")
vrt_filename = str(tmp_path / "out.vrt")
with open(vrt_filename, "wt") as f:
f.write("""<OGRVRTDataSource>
<OGRVRTLayer name="layer">
<SrcDataSource>WFS:http://this-is-an-unreachable.url</SrcDataSource>
</OGRVRTLayer>
</OGRVRTDataSource>""")

ret, err = gdaltest.runexternal_out_and_err(
ogr2ogr_path + f" {out_filename} {vrt_filename}",
append_returncode_to_stderr=True,
)
assert "Return code = 0" not in err
assert "Could not resolve host:" in err
assert "Error retrieving the source layer definition" in err
Loading