From 3aef07a434e135c599b0d64b82cf7c773c5b94fe Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 15 Jan 2025 19:03:37 +0100 Subject: [PATCH] FileGDB: remove native update and creation support; forward it to OpenFileGDB driver --- autotest/ogr/ogr_fgdb.py | 2249 +-------------- autotest/ogr/ogr_fgdb_stress_test.py | 194 -- doc/source/drivers/vector/filegdb.rst | 185 +- ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp | 394 +-- ogr/ogrsf_frmts/filegdb/FGdbDriver.cpp | 622 +---- ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp | 2866 +------------------- ogr/ogrsf_frmts/filegdb/ogr_fgdb.h | 125 +- port/cpl_known_config_options.h | 6 - 8 files changed, 225 insertions(+), 6416 deletions(-) delete mode 100755 autotest/ogr/ogr_fgdb_stress_test.py diff --git a/autotest/ogr/ogr_fgdb.py b/autotest/ogr/ogr_fgdb.py index ae63385d6cfa..f61a690ba3fa 100755 --- a/autotest/ogr/ogr_fgdb.py +++ b/autotest/ogr/ogr_fgdb.py @@ -14,7 +14,6 @@ import os import shutil -import sys import gdaltest import ogrtest @@ -83,31 +82,6 @@ def fgdb_drv(openfilegdb_drv): ############################################################################### -@pytest.fixture() -def fgdb_sdk_1_4_or_later(fgdb_drv): - fgdb_is_sdk_1_4 = False - - try: - shutil.rmtree("tmp/ogr_fgdb_is_sdk_1_4_or_later.gdb") - except OSError: - pass - - ds = fgdb_drv.CreateDataSource("tmp/ogr_fgdb_is_sdk_1_4_or_later.gdb") - srs = osr.SpatialReference() - srs.ImportFromProj4("+proj=tmerc +datum=WGS84 +no_defs") - with gdal.quiet_errors(): - lyr = ds.CreateLayer("test", srs=srs, geom_type=ogr.wkbPoint) - if lyr is not None: - fgdb_is_sdk_1_4 = True - ds = None - shutil.rmtree("tmp/ogr_fgdb_is_sdk_1_4_or_later.gdb") - if not fgdb_is_sdk_1_4: - pytest.skip("SDK 1.4 required") - - -############################################################################### - - @pytest.fixture() def ogrsf_path(): import test_cli_utilities @@ -191,11 +165,11 @@ def test_gdb_datalist(): @pytest.fixture() -def test_gdb(fgdb_drv, tmp_path, test_gdb_datalist): +def test_gdb(openfilegdb_drv, tmp_path, test_gdb_datalist): srs = osr.SpatialReference() srs.SetFromUserInput("WGS84") - ds = fgdb_drv.CreateDataSource(tmp_path / "test.gdb") + ds = openfilegdb_drv.CreateDataSource(tmp_path / "test.gdb") options = [ "COLUMN_TYPES=smallint=esriFieldTypeSmallInteger,float=esriFieldTypeSingle,guid=esriFieldTypeGUID,xml=esriFieldTypeXML" @@ -251,6 +225,8 @@ def test_gdb(fgdb_drv, tmp_path, test_gdb_datalist): feat.SetField("float2", 1.5) lyr.CreateFeature(feat) + del ds + yield tmp_path / "test.gdb" @@ -286,7 +262,7 @@ def test_ogr_fgdb_1(test_gdb, test_gdb_datalist): or feat.GetField("int") != 123 or feat.GetField("float") != 1.5 or feat.GetField("real") != 4.56 - or feat.GetField("adate") != "2013/12/26 12:34:56" + or feat.GetField("adate") != "2013/12/26 12:34:56+00" or feat.GetField("guid") != "{12345678-9ABC-DEF0-1234-567890ABCDEF}" or feat.GetField("xml") != "" or feat.GetField("binary") != "00FF7F" @@ -321,103 +297,6 @@ def test_ogr_fgdb_1(test_gdb, test_gdb_datalist): ds = None -############################################################################### -# Test DeleteField() - - -def test_ogr_fgdb_DeleteField(test_gdb): - - ds = ogr.Open(test_gdb, update=1) - lyr = ds.GetLayerByIndex(0) - assert lyr.GetDataset().GetDescription() == ds.GetDescription() - assert lyr.GetDataset().GetDriver().GetDescription() == "FileGDB" - - assert ( - lyr.GetLayerDefn() - .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("smallint")) - .GetSubType() - == ogr.OFSTInt16 - ) - assert ( - lyr.GetLayerDefn() - .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("smallint2")) - .GetSubType() - == ogr.OFSTInt16 - ) - assert ( - lyr.GetLayerDefn() - .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("float")) - .GetSubType() - == ogr.OFSTFloat32 - ) - assert ( - lyr.GetLayerDefn() - .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("float2")) - .GetSubType() - == ogr.OFSTFloat32 - ) - - assert ( - lyr.GetLayerDefn() - .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("smallint")) - .GetWidth() - == 0 - ) - assert ( - lyr.GetLayerDefn() - .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("str")) - .GetWidth() - == 0 - ) - assert lyr.DeleteField(lyr.GetLayerDefn().GetFieldIndex("str")) == 0 - - # Needed since FileGDB v1.4, otherwise crash/error ... - if True: # pylint: disable=using-constant-test - ds = ogr.Open(test_gdb, update=1) - lyr = ds.GetLayerByIndex(0) - - fld_defn = ogr.FieldDefn("str2", ogr.OFTString) - fld_defn.SetWidth(80) - lyr.CreateField(fld_defn) - feat = lyr.GetNextFeature() - feat.SetField("str2", "foo2_\xc3\xa9") - lyr.SetFeature(feat) - - # Test updating non-existing feature - feat.SetFID(-10) - with gdaltest.disable_exceptions(): - assert ( - lyr.SetFeature(feat) == ogr.OGRERR_NON_EXISTING_FEATURE - ), "Expected failure of SetFeature()." - - # Test deleting non-existing feature - assert ( - lyr.DeleteFeature(-10) == ogr.OGRERR_NON_EXISTING_FEATURE - ), "Expected failure of DeleteFeature()." - - sql_lyr = ds.ExecuteSQL("REPACK") - assert sql_lyr - f = sql_lyr.GetNextFeature() - assert f[0] == "true" - ds.ReleaseResultSet(sql_lyr) - - feat = None - ds = None - - ds = ogr.Open(test_gdb) - lyr = ds.GetLayerByIndex(0) - assert ( - lyr.GetLayerDefn() - .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("str2")) - .GetWidth() - == 80 - ) - assert lyr.GetLayerDefn().GetFieldIndex("str") == -1 - feat = lyr.GetNextFeature() - assert feat.GetFieldAsString("str2") == "foo2_\xc3\xa9" - ds = None - - ############################################################################### # Run test_ogrsf @@ -498,43 +377,6 @@ def test_ogr_fgdb_sql(poly_gdb): ds = None -############################################################################### -# Test delete layer - - -def test_ogr_fgdb_4(test_gdb): - - for j in range(2): - - # Create a layer - ds = ogr.Open(test_gdb, update=1) - srs = osr.SpatialReference() - srs.SetFromUserInput("WGS84") - lyr = ds.CreateLayer("layer_to_remove", geom_type=ogr.wkbPoint, srs=srs) - lyr.CreateField(ogr.FieldDefn("str", ogr.OFTString)) - feat = ogr.Feature(lyr.GetLayerDefn()) - feat.SetGeometry(ogr.CreateGeometryFromWkt("POINT(2 49)")) - feat.SetField("str", "foo") - feat = None - lyr = None - - if j == 1: - ds = None - ds = ogr.Open(test_gdb, update=1) - - # Delete it - for i in range(ds.GetLayerCount()): - if ds.GetLayer(i).GetName() == "layer_to_remove": - ds.DeleteLayer(i) - break - - # Check it no longer exists - lyr = ds.GetLayerByName("layer_to_remove") - ds = None - - assert lyr is None, "failed at iteration %d" % j - - ############################################################################### # Test DeleteDataSource() @@ -546,320 +388,6 @@ def test_ogr_fgdb_5(fgdb_drv, poly_gdb): assert not os.path.exists(poly_gdb) -############################################################################### -# Test adding a layer to an existing feature dataset - - -def test_ogr_fgdb_6(fgdb_drv, tmp_path): - - srs = osr.SpatialReference() - srs.SetFromUserInput("WGS84") - - with fgdb_drv.CreateDataSource(tmp_path / "test.gdb") as ds: - lyr = ds.CreateLayer( - "layer1", - srs=srs, - geom_type=ogr.wkbPoint, - options=["FEATURE_DATASET=featuredataset"], - ) - assert lyr.GetDataset().GetDescription() == ds.GetDescription() - assert lyr.GetDataset().GetDriver().GetDescription() == "FileGDB" - ds.CreateLayer( - "layer2", - srs=srs, - geom_type=ogr.wkbPoint, - options=["FEATURE_DATASET=featuredataset"], - ) - - with ogr.Open(tmp_path / "test.gdb") as ds: - assert ds.GetLayerCount() == 2 - - -############################################################################### -# Test bulk loading (#4420) - - -def test_ogr_fgdb_7(fgdb_drv, tmp_path): - - srs = osr.SpatialReference() - srs.SetFromUserInput("WGS84") - - with fgdb_drv.CreateDataSource(tmp_path / "test.gdb") as ds: - lyr = ds.CreateLayer("test", srs=srs, geom_type=ogr.wkbPoint) - lyr.CreateField(ogr.FieldDefn("id", ogr.OFTInteger)) - with gdal.config_option("FGDB_BULK_LOAD", "YES"): - for i in range(1000): - feat = ogr.Feature(lyr.GetLayerDefn()) - feat.SetField(0, i) - geom = ogr.CreateGeometryFromWkt("POINT(0 1)") - feat.SetGeometry(geom) - lyr.CreateFeature(feat) - feat = None - - lyr.ResetReading() - feat = lyr.GetNextFeature() - assert feat.GetField(0) == 0 - - -############################################################################### -# Test field name laundering (#4458) - - -def test_ogr_fgdb_8(fgdb_drv, tmp_path): - - srs = osr.SpatialReference() - srs.SetFromUserInput("WGS84") - - ds = fgdb_drv.CreateDataSource(tmp_path / "test.gdb") - lyr = ds.CreateLayer("test", srs=srs, geom_type=ogr.wkbPoint) - with gdal.quiet_errors(): - lyr.CreateField(ogr.FieldDefn("FROM", ogr.OFTInteger)) # reserved keyword - lyr.CreateField( - ogr.FieldDefn("1NUMBER", ogr.OFTInteger) - ) # starting with a number - lyr.CreateField( - ogr.FieldDefn("WITH SPACE AND !$*!- special characters", ogr.OFTInteger) - ) # unallowed characters - lyr.CreateField(ogr.FieldDefn("é" * 64, ogr.OFTInteger)) # OK - lyr.CreateField( - ogr.FieldDefn( - "A123456789012345678901234567890123456789012345678901234567890123", - ogr.OFTInteger, - ) - ) # 64 characters : ok - lyr.CreateField( - ogr.FieldDefn( - "A1234567890123456789012345678901234567890123456789012345678901234", - ogr.OFTInteger, - ) - ) # 65 characters : nok - lyr.CreateField( - ogr.FieldDefn( - "A12345678901234567890123456789012345678901234567890123456789012345", - ogr.OFTInteger, - ) - ) # 66 characters : nok - - lyr_defn = lyr.GetLayerDefn() - expected_names = [ - "FROM_", - "_1NUMBER", - "WITH_SPACE_AND_______special_characters", - "é" * 64, - "A123456789012345678901234567890123456789012345678901234567890123", - "A1234567890123456789012345678901234567890123456789012345678901_1", - "A1234567890123456789012345678901234567890123456789012345678901_2", - ] - for i in range(5): - assert lyr_defn.GetFieldIndex(expected_names[i]) == i, ( - "did not find %s" % expected_names[i] - ) - - -############################################################################### -# Test layer name laundering (#4466) - - -def test_ogr_fgdb_9(fgdb_drv, tmp_path): - - srs = osr.SpatialReference() - srs.SetFromUserInput("WGS84") - - _160char = "A123456789" * 16 - - in_names = [ - "FROM", # reserved keyword - "1NUMBER", # starting with a number - "WITH SPACE AND !$*!- special characters", # banned characters - "sde_foo", # reserved prefixes - _160char, # OK - _160char + "A", # too long - _160char + "B", # still too long - ] - - ds = fgdb_drv.CreateDataSource(tmp_path / "test.gdb") - with gdal.quiet_errors(): - for in_name in in_names: - lyr = ds.CreateLayer(in_name, srs=srs, geom_type=ogr.wkbPoint) - - lyr.GetLayerDefn() - expected_names = [ - "FROM_", - "_1NUMBER", - "WITH_SPACE_AND_______special_characters", - "_sde_foo", - _160char, - _160char[0:158] + "_1", - _160char[0:158] + "_2", - ] - for i, exp_name in enumerate(expected_names): - assert ds.GetLayerByIndex(i).GetName() == exp_name, "did not find %s" % exp_name - - -############################################################################### -# Test SRS support - - -def test_ogr_fgdb_10(fgdb_drv, tmp_path): - - srs_exact_4326 = osr.SpatialReference() - srs_exact_4326.ImportFromEPSG(4326) - - srs_approx_4326 = srs_exact_4326.Clone() - srs_approx_4326.MorphToESRI() - srs_approx_4326.ImportFromWkt(srs_approx_4326.ExportToWkt()) - - srs_exact_2193 = osr.SpatialReference() - srs_exact_2193.ImportFromEPSG(2193) - - srs_approx_2193 = srs_exact_2193.Clone() - srs_approx_2193.MorphToESRI() - srs_approx_2193.ImportFromWkt(srs_approx_2193.ExportToWkt()) - - srs_not_in_db = osr.SpatialReference( - """PROJCS["foo", - GEOGCS["foo", - DATUM["foo", - SPHEROID["foo",6000000,300]], - PRIMEM["Greenwich",0], - UNIT["Degree",0.017453292519943295]], - PROJECTION["Transverse_Mercator"], - PARAMETER["latitude_of_origin",0], - PARAMETER["central_meridian",0], - PARAMETER["scale_factor",1], - PARAMETER["false_easting",500000], - PARAMETER["false_northing",0], - UNIT["Meter",1]]""" - ) - - srs_exact_4230 = osr.SpatialReference() - srs_exact_4230.ImportFromEPSG(4230) - srs_approx_4230 = srs_exact_4230.Clone() - srs_approx_4230.MorphToESRI() - srs_approx_4230.ImportFromWkt(srs_approx_4230.ExportToWkt()) - - srs_approx_intl = osr.SpatialReference() - srs_approx_intl.ImportFromProj4("+proj=longlat +ellps=intl +no_defs") - - srs_exact_4233 = osr.SpatialReference() - srs_exact_4233.ImportFromEPSG(4233) - - ds = fgdb_drv.CreateDataSource(tmp_path / "test.gdb") - lyr = ds.CreateLayer("srs_exact_4326", srs=srs_exact_4326, geom_type=ogr.wkbPoint) - lyr = ds.CreateLayer("srs_approx_4326", srs=srs_approx_4326, geom_type=ogr.wkbPoint) - lyr = ds.CreateLayer("srs_exact_2193", srs=srs_exact_2193, geom_type=ogr.wkbPoint) - lyr = ds.CreateLayer("srs_approx_2193", srs=srs_approx_2193, geom_type=ogr.wkbPoint) - - lyr = ds.CreateLayer("srs_approx_4230", srs=srs_approx_4230, geom_type=ogr.wkbPoint) - - # will fail - with gdal.quiet_errors(): - lyr = ds.CreateLayer( - "srs_approx_intl", srs=srs_approx_intl, geom_type=ogr.wkbPoint - ) - - # will fail: 4233 doesn't exist in DB - with gdal.quiet_errors(): - lyr = ds.CreateLayer( - "srs_exact_4233", srs=srs_exact_4233, geom_type=ogr.wkbPoint - ) - - # will fail - with gdal.quiet_errors(): - lyr = ds.CreateLayer("srs_not_in_db", srs=srs_not_in_db, geom_type=ogr.wkbPoint) - - ds = None - - ds = ogr.Open(tmp_path / "test.gdb") - lyr = ds.GetLayerByName("srs_exact_4326") - assert lyr.GetSpatialRef().ExportToWkt().find("4326") != -1 - lyr = ds.GetLayerByName("srs_approx_4326") - assert lyr.GetSpatialRef().ExportToWkt().find("4326") != -1 - lyr = ds.GetLayerByName("srs_exact_2193") - assert lyr.GetSpatialRef().ExportToWkt().find("2193") != -1 - lyr = ds.GetLayerByName("srs_approx_2193") - assert lyr.GetSpatialRef().ExportToWkt().find("2193") != -1 - lyr = ds.GetLayerByName("srs_approx_4230") - assert lyr.GetSpatialRef().ExportToWkt().find("4230") != -1 - ds = None - - -############################################################################### -# Test all data types - - -def test_ogr_fgdb_11(fgdb_drv, tmp_path): - - f = open("data/filegdb/test_filegdb_field_types.xml", "rt") - xml_def = f.read() - f.close() - - ds = fgdb_drv.CreateDataSource(tmp_path / "test.gdb") - lyr = ds.CreateLayer( - "test", geom_type=ogr.wkbNone, options=["XML_DEFINITION=%s" % xml_def] - ) - feat = ogr.Feature(lyr.GetLayerDefn()) - feat.SetField("esriFieldTypeSmallInteger", 12) - feat.SetField("esriFieldTypeInteger", 3456) - feat.SetField("esriFieldTypeSingle", 78.9) - feat.SetField("esriFieldTypeDouble", 1.23) - feat.SetField("esriFieldTypeDate", "2012/12/31 12:34:56") - feat.SetField("esriFieldTypeString", "astr") - feat.SetField( - "esriFieldTypeGlobalID", "{12345678-9ABC-DEF0-1234-567890ABCDEF}" - ) # This is ignored and value is generated by FileGDB SDK itself - feat.SetField("esriFieldTypeGUID", "{12345678-9abc-DEF0-1234-567890ABCDEF}") - lyr.CreateFeature(feat) - feat = None - - feat = ogr.Feature(lyr.GetLayerDefn()) - lyr.CreateFeature(feat) - feat = None - - # Create a esriFieldTypeGlobalID field - lyr = ds.CreateLayer( - "test2", - geom_type=ogr.wkbNone, - options=["COLUMN_TYPES=global_id=esriFieldTypeGlobalID"], - ) - lyr.CreateField(ogr.FieldDefn("global_id", ogr.OFTString)) - feat = ogr.Feature(lyr.GetLayerDefn()) - lyr.CreateFeature(feat) - feat = None - - ds = None - - ds = ogr.Open(tmp_path / "test.gdb") - lyr = ds.GetLayerByName("test") - feat = lyr.GetNextFeature() - if ( - feat.GetField("esriFieldTypeSmallInteger") != 12 - or feat.GetField("esriFieldTypeInteger") != 3456 - or feat.GetField("esriFieldTypeSingle") != pytest.approx(78.9, abs=1e-2) - or feat.GetField("esriFieldTypeDouble") != 1.23 - or feat.GetField("esriFieldTypeDate") != "2012/12/31 12:34:56" - or feat.GetField("esriFieldTypeString") != "astr" - or feat.GetField("esriFieldTypeGUID") - != "{12345678-9ABC-DEF0-1234-567890ABCDEF}" - or (not feat.IsFieldSet("esriFieldTypeGlobalID")) - ): - feat.DumpReadable() - pytest.fail() - - feat = lyr.GetNextFeature() - if not feat.IsFieldSet("esriFieldTypeGlobalID"): - feat.DumpReadable() - pytest.fail() - - lyr = ds.GetLayerByName("test2") - feat = lyr.GetNextFeature() - if not feat.IsFieldSet("global_id"): - feat.DumpReadable() - pytest.fail() - - ds = None - - ############################################################################### # Test failed Open() @@ -891,1400 +419,132 @@ def test_ogr_fgdb_12_ter(tmp_path): assert ds is None -############################################################################### -# Test failed CreateDataSource() and DeleteDataSource() - - -def test_ogr_fgdb_13(fgdb_drv, tmp_path): - - with gdal.quiet_errors(): - ds = fgdb_drv.CreateDataSource(tmp_path / "foo") - assert ds is None - - -def test_ogr_fgdb_13_bis(fgdb_drv, tmp_path): - - f = open(tmp_path / "dummy.gdb", "wb") - f.close() - - with gdal.quiet_errors(): - ds = fgdb_drv.CreateDataSource(tmp_path / "dummy.gdb") - assert ds is None - - -def test_ogr_fgdb_13_ter(fgdb_drv, tmp_path): - - if sys.platform == "win32": - name = "/nonexistingdrive:/nonexistingdir/dummy.gdb" - else: - name = "/proc/dummy.gdb" - - with gdal.quiet_errors(): - ds = fgdb_drv.CreateDataSource(name) - assert ds is None - - with gdal.quiet_errors(): - ret = fgdb_drv.DeleteDataSource(name) - assert ret != 0 - - ############################################################################### # Test interleaved opening and closing of databases (#4270) -def test_ogr_fgdb_14(poly_gdb): - - for _ in range(3): - ds1 = ogr.Open(poly_gdb) - assert ds1 is not None - ds2 = ogr.Open(poly_gdb) - assert ds2 is not None - ds2 = None - ds1 = None - - -############################################################################### -# Test opening a FGDB with both SRID and LatestSRID set (#5638) - - -def test_ogr_fgdb_15(tmp_path): - - gdaltest.unzip(tmp_path, "data/filegdb/test3005.gdb.zip") - ds = ogr.Open(tmp_path / "test3005.gdb") - lyr = ds.GetLayer(0) - got_wkt = lyr.GetSpatialRef().ExportToWkt() - sr = osr.SpatialReference() - sr.ImportFromEPSG(3005) - expected_wkt = sr.ExportToWkt() - assert got_wkt == expected_wkt - ds = None - - -############################################################################### -# Test fix for #5674 - - -def test_ogr_fgdb_16(openfilegdb_drv, fgdb_drv): - if fgdb_drv is None or openfilegdb_drv is None: - pytest.skip() - - try: - gdaltest.unzip("tmp/cache", "data/filegdb/ESSENCE_NAIPF_ORI_PROV_sub93.gdb.zip") - except OSError: - pass - try: - os.stat("tmp/cache/ESSENCE_NAIPF_ORI_PROV_sub93.gdb") - except OSError: - pytest.skip() - - fgdb_drv.Deregister() - - # Force FileGDB first - fgdb_drv.Register() - openfilegdb_drv.Register() - - try: - ds = ogr.Open("tmp/cache/ESSENCE_NAIPF_ORI_PROV_sub93.gdb") - assert ds is not None - finally: - # Deregister OpenFileGDB again - openfilegdb_drv.Deregister() - - shutil.rmtree("tmp/cache/ESSENCE_NAIPF_ORI_PROV_sub93.gdb") - - -############################################################################### -# Test not nullable fields - - -def test_ogr_fgdb_17(fgdb_drv, tmp_path): - - ds = fgdb_drv.CreateDataSource(tmp_path / "test.gdb") - sr = osr.SpatialReference() - sr.ImportFromEPSG(4326) - lyr = ds.CreateLayer( - "test", geom_type=ogr.wkbPoint, srs=sr, options=["GEOMETRY_NULLABLE=NO"] - ) - assert lyr.GetLayerDefn().GetGeomFieldDefn(0).IsNullable() == 0 - field_defn = ogr.FieldDefn("field_not_nullable", ogr.OFTString) - field_defn.SetNullable(0) - lyr.CreateField(field_defn) - field_defn = ogr.FieldDefn("field_nullable", ogr.OFTString) - lyr.CreateField(field_defn) - - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetField("field_not_nullable", "not_null") - f.SetGeometryDirectly(ogr.CreateGeometryFromWkt("POINT(0 0)")) - ret = lyr.CreateFeature(f) - assert ret == 0 - f = None - - # Error case: missing geometry - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetField("field_not_nullable", "not_null") - with gdal.quiet_errors(): - ret = lyr.CreateFeature(f) - assert ret != 0 - f = None - - # Error case: missing non-nullable field - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetGeometryDirectly(ogr.CreateGeometryFromWkt("POINT(0 0)")) - with gdal.quiet_errors(): - ret = lyr.CreateFeature(f) - assert ret != 0 - f = None - - ds = None - - ds = ogr.Open(tmp_path / "test.gdb", update=1) - lyr = ds.GetLayerByName("test") - assert ( - lyr.GetLayerDefn() - .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("field_not_nullable")) - .IsNullable() - == 0 - ) - assert ( - lyr.GetLayerDefn() - .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("field_nullable")) - .IsNullable() - == 1 - ) - assert lyr.GetLayerDefn().GetGeomFieldDefn(0).IsNullable() == 0 - - ds = None - - -############################################################################### -# Test default values - - -def test_ogr_fgdb_18(openfilegdb_drv, fgdb_drv, tmp_path): - - ds = fgdb_drv.CreateDataSource(tmp_path / "test.gdb") - lyr = ds.CreateLayer("test", geom_type=ogr.wkbNone) - - field_defn = ogr.FieldDefn("field_string", ogr.OFTString) - field_defn.SetDefault("'a''b'") - lyr.CreateField(field_defn) - - field_defn = ogr.FieldDefn("field_int", ogr.OFTInteger) - field_defn.SetDefault("123") - lyr.CreateField(field_defn) - - field_defn = ogr.FieldDefn("field_real", ogr.OFTReal) - field_defn.SetDefault("1.23") - lyr.CreateField(field_defn) - - field_defn = ogr.FieldDefn("field_nodefault", ogr.OFTInteger) - lyr.CreateField(field_defn) - - field_defn = ogr.FieldDefn("field_datetime", ogr.OFTDateTime) - field_defn.SetDefault("CURRENT_TIMESTAMP") - lyr.CreateField(field_defn) - - field_defn = ogr.FieldDefn("field_datetime2", ogr.OFTDateTime) - field_defn.SetDefault("'2015/06/30 12:34:56'") - lyr.CreateField(field_defn) - - f = ogr.Feature(lyr.GetLayerDefn()) - lyr.CreateFeature(f) - f = None - - ds = None - - if openfilegdb_drv is not None: - openfilegdb_drv.Register() - ogr_fgdb_18_test_results(openfilegdb_drv, tmp_path / "test.gdb") - if openfilegdb_drv is not None: - openfilegdb_drv.Deregister() - - -def ogr_fgdb_18_test_results(openfilegdb_drv, fname): - - ds = ogr.Open(fname, update=1) - lyr = ds.GetLayerByName("test") - assert ( - lyr.GetLayerDefn() - .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("field_string")) - .GetDefault() - == "'a''b'" - ) - if openfilegdb_drv is not None: - assert ( - lyr.GetLayerDefn() - .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("field_int")) - .GetDefault() - == "123" - ) - assert ( - lyr.GetLayerDefn() - .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("field_real")) - .GetDefault() - == "1.23" - ) - assert ( - lyr.GetLayerDefn() - .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("field_nodefault")) - .GetDefault() - is None - ) - # if lyr.GetLayerDefn().GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex('field_datetime')).GetDefault() != 'CURRENT_TIMESTAMP': - # gdaltest.post_reason('fail') - # return 'fail' - # if lyr.GetLayerDefn().GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex('field_datetime2')).GetDefault() != "'2015/06/30 12:34:56'": - # gdaltest.post_reason('fail') - # print(lyr.GetLayerDefn().GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex('field_datetime2')).GetDefault()) - # return 'fail' - f = lyr.GetNextFeature() - if ( - f.GetField("field_string") != "a'b" - or f.GetField("field_int") != 123 - or f.GetField("field_real") != 1.23 - or not f.IsFieldNull("field_nodefault") - or not f.IsFieldSet("field_datetime") - or f.GetField("field_datetime2") != "2015/06/30 12:34:56" - ): - f.DumpReadable() - pytest.fail() - ds = None - - -############################################################################### -# Test transaction support - - -def ogr_fgdb_19_open_update(openfilegdb_drv, fgdb_drv, filename): - - # We need the OpenFileGDB driver for Linux improved StartTransaction() - bPerLayerCopyingForTransaction = False - if openfilegdb_drv is not None: - openfilegdb_drv.Register() - if os.name != "nt": - val = gdal.GetConfigOption("FGDB_PER_LAYER_COPYING_TRANSACTION", "TRUE") - if val == "TRUE" or val == "YES" or val == "ON": - bPerLayerCopyingForTransaction = True - - ds = fgdb_drv.Open(filename, update=1) - - if openfilegdb_drv is not None: - openfilegdb_drv.Deregister() - fgdb_drv.Deregister() - # Force OpenFileGDB first - openfilegdb_drv.Register() - fgdb_drv.Register() - - return (bPerLayerCopyingForTransaction, ds) - - -def test_ogr_fgdb_19(openfilegdb_drv, fgdb_drv, test_gdb): - - # FIXME likely due to too old FileGDB SDK on those targets - # fails with ERROR 1: Failed to open Geodatabase (The system cannot find the file specified.) - # File "ogr_fgdb.py", line 1664, in ogr_fgdb_19 - # if ds.StartTransaction(force=True) != 0: - if ( - gdaltest.is_travis_branch("ubuntu_2204") - or gdaltest.is_travis_branch("ubuntu_2404") - or gdaltest.is_travis_branch("ubuntu_2004") - or gdaltest.is_travis_branch("ubuntu_1804") - or gdaltest.is_travis_branch("ubuntu_1604") - or gdaltest.is_travis_branch("trusty_clang") - or gdaltest.is_travis_branch("python3") - or gdaltest.is_travis_branch("trunk_with_coverage") - ): - pytest.skip() - - # Error case: try in read-only - ds = fgdb_drv.Open(test_gdb) - with gdal.quiet_errors(): - ret = ds.StartTransaction(force=True) - assert ret != 0 - ds = None - - (bPerLayerCopyingForTransaction, ds) = ogr_fgdb_19_open_update( - openfilegdb_drv, fgdb_drv, test_gdb - ) - - assert ds.TestCapability(ogr.ODsCEmulatedTransactions) == 1 - - # Error case: try in non-forced mode - with gdal.quiet_errors(): - ret = ds.StartTransaction(force=False) - assert ret != 0 - - # Error case: try StartTransaction() with a ExecuteSQL layer still active - sql_lyr = ds.ExecuteSQL("SELECT * FROM test") - with gdal.quiet_errors(): - ret = ds.StartTransaction(force=True) - assert ret != 0 - ds.ReleaseResultSet(sql_lyr) - - # Error case: call CommitTransaction() while there is no transaction - with gdal.quiet_errors(): - ret = ds.CommitTransaction() - assert ret != 0 - - # Error case: call RollbackTransaction() while there is no transaction - with gdal.quiet_errors(): - ret = ds.RollbackTransaction() - assert ret != 0 - - # Error case: try StartTransaction() with another active connection - ds2 = fgdb_drv.Open(test_gdb, update=1) - with gdal.quiet_errors(): - ret = ds2.StartTransaction(force=True) - assert ret != 0 - ds2 = None - - # Successful StartTransaction() finally! - lyr = ds.GetLayer(0) - lyr = ds.GetLayer(0) # again - old_count = lyr.GetFeatureCount() - lyr_defn = lyr.GetLayerDefn() - layer_created_before_transaction = ds.CreateLayer( - "layer_created_before_transaction", geom_type=ogr.wkbNone - ) - layer_created_before_transaction_defn = ( - layer_created_before_transaction.GetLayerDefn() - ) - - assert ds.StartTransaction(force=True) == 0 - - assert os.path.exists(f"{test_gdb}.ogredited") - assert not os.path.exists(f"{test_gdb}.ogrtmp") - - ret = lyr.CreateField(ogr.FieldDefn("foobar", ogr.OFTString)) - assert ret == 0 - - ret = lyr.DeleteField(lyr.GetLayerDefn().GetFieldIndex("foobar")) - assert ret == 0 - - with gdal.quiet_errors(): - ret = lyr.CreateGeomField(ogr.GeomFieldDefn("foobar", ogr.wkbPoint)) - assert ret != 0 - - with gdal.quiet_errors(): - ret = lyr.ReorderFields([i for i in range(lyr.GetLayerDefn().GetFieldCount())]) - assert ret != 0 - - with gdal.quiet_errors(): - ret = lyr.AlterFieldDefn(0, ogr.FieldDefn("foo", ogr.OFTString), 0) - assert ret != 0 - - f = ogr.Feature(lyr_defn) - f.SetField("field_string", "foo") - lyr.CreateFeature(f) - lyr.SetFeature(f) - fid = f.GetFID() - assert fid > 0 - lyr.ResetReading() - for i in range(fid): - f = lyr.GetNextFeature() - assert f.GetFID() == fid and f.GetField("field_string") == "foo" - f = lyr.GetFeature(fid) - assert f.GetFID() == fid and f.GetField("field_string") == "foo" - - f = ogr.Feature(layer_created_before_transaction_defn) - layer_created_before_transaction.CreateFeature(f) - - # Error case: call StartTransaction() while there is an active transaction - with gdal.quiet_errors(): - ret = ds.StartTransaction(force=True) - assert ret != 0 - - # Error case: try CommitTransaction() with a ExecuteSQL layer still active - sql_lyr = ds.ExecuteSQL("SELECT * FROM test") - with gdal.quiet_errors(): - ret = ds.CommitTransaction() - assert ret != 0 - ds.ReleaseResultSet(sql_lyr) - - # Error case: try RollbackTransaction() with a ExecuteSQL layer still active - sql_lyr = ds.ExecuteSQL("SELECT * FROM test") - with gdal.quiet_errors(): - ret = ds.RollbackTransaction() - assert ret != 0 - ds.ReleaseResultSet(sql_lyr) - - # Test that CommitTransaction() works - assert ds.CommitTransaction() == 0 - - assert not os.path.exists(f"{test_gdb}.ogredited") - assert not os.path.exists(f"{test_gdb}.ogrtmp") - - lst = gdal.ReadDir(test_gdb) - for filename in lst: - assert ".tmp" not in filename, lst - - lyr_tmp = ds.GetLayer(0) - lyr_tmp = ds.GetLayer(0) - new_count = lyr_tmp.GetFeatureCount() - assert new_count == old_count + 1 - old_count = new_count - - assert layer_created_before_transaction.GetFeatureCount() == 1 - - for i in range(ds.GetLayerCount()): - if ds.GetLayer(i).GetName() == layer_created_before_transaction.GetName(): - ds.DeleteLayer(i) - break - layer_created_before_transaction = None - - # Test suppression of layer within transaction - lyr_count = ds.GetLayerCount() - ds.CreateLayer("layer_tmp", geom_type=ogr.wkbNone) - ret = ds.StartTransaction(force=True) - assert ret == 0 - ds.DeleteLayer(ds.GetLayerCount() - 1) - assert ds.CommitTransaction() == 0 - - new_lyr_count = ds.GetLayerCount() - assert new_lyr_count == lyr_count - - # Test that RollbackTransaction() works - ret = ds.StartTransaction(force=True) - assert ret == 0 - - f = ogr.Feature(lyr_defn) - lyr.CreateFeature(f) - - layer_created_during_transaction = ds.CreateLayer( - "layer_created_during_transaction", geom_type=ogr.wkbNone - ) - layer_created_during_transaction.CreateField(ogr.FieldDefn("foo", ogr.OFTString)) - - assert ds.RollbackTransaction() == 0 - - assert not os.path.exists(f"{test_gdb}.ogredited") - assert not os.path.exists(f"{test_gdb}.ogrtmp") - - assert lyr.GetFeatureCount() == old_count - - # Cannot retrieve the layer any more from fresh - assert ds.GetLayerByName("layer_created_during_transaction") is None - # Pointer is in ghost state - assert layer_created_during_transaction.GetLayerDefn().GetFieldCount() == 0 - - # Simulate an error case where StartTransaction() cannot copy backup files - lyr_count = ds.GetLayerCount() - with gdal.config_option("FGDB_SIMUL_FAIL", "CASE1"), gdaltest.error_handler(): - ret = ds.StartTransaction(force=True) - assert ret != 0 - - assert ds.GetLayerCount() == lyr_count - - # Simulate an error case where StartTransaction() cannot reopen database - with gdal.config_option("FGDB_SIMUL_FAIL", "CASE2"), gdaltest.error_handler(): - ret = ds.StartTransaction(force=True) - assert ret != 0 - - assert ds.GetLayerCount() == 0 - shutil.rmtree(f"{test_gdb}.ogredited") - - # Test method on ghost datasource and layer - ds.GetName() - ds.GetLayerCount() - ds.GetLayer(0) - ds.GetLayerByName("test") - ds.DeleteLayer(0) - ds.TestCapability("foo") - ds.CreateLayer("bar", geom_type=ogr.wkbNone) - ds.CopyLayer(lyr, "baz") - ds.GetStyleTable() - # ds.SetStyleTableDirectly(None) - ds.SetStyleTable(None) - sql_lyr = ds.ExecuteSQL("SELECT * FROM test") - ds.ReleaseResultSet(sql_lyr) - ds.FlushCache() - ds.GetMetadata() - ds.GetMetadataItem("foo") - ds.SetMetadata(None) - ds.SetMetadataItem("foo", None) - - lyr.GetSpatialFilter() - lyr.SetSpatialFilter(None) - lyr.SetSpatialFilterRect(0, 0, 0, 0) - lyr.SetSpatialFilter(0, None) - lyr.SetSpatialFilterRect(0, 0, 0, 0, 0) - lyr.SetAttributeFilter(None) - lyr.ResetReading() - lyr.GetNextFeature() - lyr.SetNextByIndex(0) - lyr.GetFeature(0) - lyr.SetFeature(ogr.Feature(lyr.GetLayerDefn())) - lyr.CreateFeature(ogr.Feature(lyr.GetLayerDefn())) - lyr.DeleteFeature(0) - lyr.GetName() - lyr.GetGeomType() - lyr.GetLayerDefn() - lyr.GetSpatialRef() - lyr.GetFeatureCount() - lyr.GetExtent() - lyr.GetExtent(0) - lyr.TestCapability("foo") - lyr.CreateField(ogr.FieldDefn("foo", ogr.OFTString)) - lyr.DeleteField(0) - lyr.ReorderFields([i for i in range(lyr.GetLayerDefn().GetFieldCount())]) - lyr.AlterFieldDefn(0, ogr.FieldDefn("foo", ogr.OFTString), 0) - lyr.SyncToDisk() - lyr.GetStyleTable() - # lyr.SetStyleTableDirectly(None) - lyr.SetStyleTable(None) - lyr.StartTransaction() - lyr.CommitTransaction() - lyr.RollbackTransaction() - lyr.SetIgnoredFields([]) - lyr.GetMetadata() - lyr.GetMetadataItem("foo") - lyr.SetMetadata(None) - lyr.SetMetadataItem("foo", None) - - ds = None - - if bPerLayerCopyingForTransaction: - - # Test an error case where we simulate a failure of destroying a - # layer destroyed during transaction - (bPerLayerCopyingForTransaction, ds) = ogr_fgdb_19_open_update( - openfilegdb_drv, fgdb_drv, test_gdb - ) - - layer_tmp = ds.CreateLayer("layer_tmp", geom_type=ogr.wkbNone) - layer_tmp.CreateField(ogr.FieldDefn("foo", ogr.OFTString)) - - assert ds.StartTransaction(force=True) == 0 - - ds.DeleteLayer(ds.GetLayerCount() - 1) - - with gdal.config_option("FGDB_SIMUL_FAIL", "CASE1"), gdaltest.error_handler(): - ret = ds.CommitTransaction() - assert ret != 0 - - ds = None - - shutil.rmtree(f"{test_gdb}.ogredited") - - lst = gdal.ReadDir(test_gdb) - for filename in lst: - assert ".tmp" not in filename, lst - - # Test an error case where we simulate a failure in renaming - # a file in original directory - (bPerLayerCopyingForTransaction, ds) = ogr_fgdb_19_open_update( - openfilegdb_drv, fgdb_drv, test_gdb - ) - - for i in range(ds.GetLayerCount()): - if ds.GetLayer(i).GetName() == "layer_tmp": - ds.DeleteLayer(i) - break - - assert ds.StartTransaction(force=True) == 0 - - lyr = ds.GetLayer(0) - f = lyr.GetNextFeature() - lyr.SetFeature(f) - f = None - - with gdal.config_option("FGDB_SIMUL_FAIL", "CASE2"), gdaltest.error_handler(): - ret = ds.CommitTransaction() - assert ret != 0 - - ds = None - - shutil.rmtree(f"{test_gdb}.ogredited") - - lst = gdal.ReadDir(test_gdb) - for filename in lst: - assert ".tmp" not in filename, lst - - # Test an error case where we simulate a failure in moving - # a file into original directory - (bPerLayerCopyingForTransaction, ds) = ogr_fgdb_19_open_update( - openfilegdb_drv, fgdb_drv, test_gdb - ) - - assert ds.StartTransaction(force=True) == 0 - - lyr = ds.GetLayer(0) - f = lyr.GetNextFeature() - lyr.SetFeature(f) - f = None - - with gdal.config_option("FGDB_SIMUL_FAIL", "CASE3"), gdaltest.error_handler(): - ret = ds.CommitTransaction() - assert ret != 0 - - ds = None - - shutil.rmtree(f"{test_gdb}.ogredited") - - # Remove left over .tmp files - lst = gdal.ReadDir(test_gdb) - for filename in lst: - if ".tmp" in filename: - os.remove(f"{test_gdb}/{filename}") - - # Test not critical error in removing a temporary file - for case in ("CASE4", "CASE5"): - (bPerLayerCopyingForTransaction, ds) = ogr_fgdb_19_open_update( - openfilegdb_drv, fgdb_drv, test_gdb - ) - - assert ds.StartTransaction(force=True) == 0 - - lyr = ds.GetLayer(0) - f = lyr.GetNextFeature() - lyr.SetFeature(f) - f = None - - with gdal.config_option("FGDB_SIMUL_FAIL", case), gdaltest.error_handler(): - ret = ds.CommitTransaction() - assert ret == 0, case - - ds = None - - if case == "CASE4": - assert not os.path.exists(f"{test_gdb}.ogredited"), case - else: - shutil.rmtree(f"{test_gdb}.ogredited") - - # Remove left over .tmp files - lst = gdal.ReadDir(test_gdb) - for filename in lst: - if ".tmp" in filename: - os.remove(f"{test_gdb}/{filename}") - - else: - # Test an error case where we simulate a failure of rename from .gdb to .gdb.ogrtmp during commit - (bPerLayerCopyingForTransaction, ds) = ogr_fgdb_19_open_update( - openfilegdb_drv, fgdb_drv, test_gdb - ) - lyr = ds.GetLayer(0) - lyr_defn = lyr.GetLayerDefn() - - assert ds.StartTransaction(force=True) == 0 - - with gdal.config_option("FGDB_SIMUL_FAIL", "CASE1"), gdaltest.error_handler(): - ret = ds.CommitTransaction() - assert ret != 0 - - ds = None - - # Test an error case where we simulate a failure of rename from .gdb.ogredited to .gdb during commit - (bPerLayerCopyingForTransaction, ds) = ogr_fgdb_19_open_update( - openfilegdb_drv, fgdb_drv, test_gdb - ) - lyr = ds.GetLayer(0) - lyr_defn = lyr.GetLayerDefn() - - assert ds.StartTransaction(force=True) == 0 - - with gdal.config_option("FGDB_SIMUL_FAIL", "CASE2"), gdaltest.error_handler(): - ret = ds.CommitTransaction() - assert ret != 0 - - ds = None - os.rename(f"{test_gdb}.ogrtmp", test_gdb) - - # Test an error case where we simulate a failure of removing from .gdb.ogrtmp during commit - (bPerLayerCopyingForTransaction, ds) = ogr_fgdb_19_open_update( - openfilegdb_drv, fgdb_drv, test_gdb - ) - lyr = ds.GetLayer(0) - lyr_defn = lyr.GetLayerDefn() - - assert ds.StartTransaction(force=True) == 0 - - with gdal.config_option("FGDB_SIMUL_FAIL", "CASE3"), gdaltest.error_handler(): - ret = ds.CommitTransaction() - assert ret == 0 - - ds = None - shutil.rmtree(f"{test_gdb}.ogrtmp") - - # Test an error case where we simulate a failure of reopening the committed DB - (bPerLayerCopyingForTransaction, ds) = ogr_fgdb_19_open_update( - openfilegdb_drv, fgdb_drv, test_gdb - ) - lyr = ds.GetLayer(0) - lyr_defn = lyr.GetLayerDefn() - - assert ds.StartTransaction(force=True) == 0 - - with gdal.config_option("FGDB_SIMUL_FAIL", "CASE_REOPEN"), gdaltest.error_handler(): - ret = ds.CommitTransaction() - assert ret != 0 - - assert ds.GetLayerCount() == 0 - - ds = None - - # Test an error case where we simulate a failure of removing from .gdb.ogredited during rollback - (bPerLayerCopyingForTransaction, ds) = ogr_fgdb_19_open_update( - openfilegdb_drv, fgdb_drv, test_gdb - ) - lyr = ds.GetLayer(0) - lyr_defn = lyr.GetLayerDefn() - - assert ds.StartTransaction(force=True) == 0 - - with gdal.config_option("FGDB_SIMUL_FAIL", "CASE1"), gdaltest.error_handler(): - ret = ds.RollbackTransaction() - assert ret != 0 - - ds = None - shutil.rmtree(f"{test_gdb}.ogredited") - - # Test an error case where we simulate a failure of reopening the rollbacked DB - (bPerLayerCopyingForTransaction, ds) = ogr_fgdb_19_open_update( - openfilegdb_drv, fgdb_drv, test_gdb - ) - lyr = ds.GetLayer(0) - lyr_defn = lyr.GetLayerDefn() - - assert ds.StartTransaction(force=True) == 0 - - with gdal.config_option("FGDB_SIMUL_FAIL", "CASE2"), gdaltest.error_handler(): - ret = ds.RollbackTransaction() - assert ret != 0 - - assert ds.GetLayerCount() == 0 - - ds = None - - if openfilegdb_drv is not None: - openfilegdb_drv.Deregister() - - -# Same, but retry without per-layer copying optimization (in the case -# this was what was tested in previous step) - - -def test_ogr_fgdb_19bis(openfilegdb_drv, fgdb_drv, test_gdb): - - if ( - gdaltest.is_travis_branch("ubuntu_2204") - or gdaltest.is_travis_branch("ubuntu_2404") - or gdaltest.is_travis_branch("ubuntu_2004") - or gdaltest.is_travis_branch("ubuntu_1804") - or gdaltest.is_travis_branch("ubuntu_1604") - or gdaltest.is_travis_branch("trusty_clang") - or gdaltest.is_travis_branch("python3") - or gdaltest.is_travis_branch("trunk_with_coverage") - ): - pytest.skip() - - (bPerLayerCopyingForTransaction, ds) = ogr_fgdb_19_open_update( - openfilegdb_drv, fgdb_drv, test_gdb - ) - del ds - if not bPerLayerCopyingForTransaction: - pytest.skip() - - with gdal.config_option("FGDB_PER_LAYER_COPYING_TRANSACTION", "FALSE"): - test_ogr_fgdb_19(openfilegdb_drv, fgdb_drv, test_gdb) - - -############################################################################### -# Test CreateFeature() with user defined FID - - -def test_ogr_fgdb_20(openfilegdb_drv, fgdb_drv, tmp_path): - - if openfilegdb_drv is None: - pytest.skip("No OpenFileGDB driver available") - - if ( - gdaltest.is_travis_branch("ubuntu_2204") - or gdaltest.is_travis_branch("ubuntu_2404") - or gdaltest.is_travis_branch("ubuntu_2004") - or gdaltest.is_travis_branch("ubuntu_1804") - or gdaltest.is_travis_branch("ubuntu_1604") - or gdaltest.is_travis_branch("trusty_clang") - or gdaltest.is_travis_branch("python3") - or gdaltest.is_travis_branch("trunk_with_coverage") - ): - pytest.skip() - - ds = fgdb_drv.CreateDataSource(tmp_path / "test.gdb") - ds = None - - # We need the OpenFileGDB driver for CreateFeature() with user defined FID - openfilegdb_drv.Register() - ds = fgdb_drv.Open(tmp_path / "test.gdb", update=1) - openfilegdb_drv.Deregister() - fgdb_drv.Deregister() - # Force OpenFileGDB first - openfilegdb_drv.Register() - fgdb_drv.Register() - - lyr = ds.CreateLayer("test_2147483647", geom_type=ogr.wkbNone) - lyr.CreateField(ogr.FieldDefn("int", ogr.OFTInteger)) - f = ogr.Feature(lyr.GetLayerDefn()) - fid = 2147483647 - f.SetFID(fid) - f.SetField(0, fid) - lyr.CreateFeature(f) - ds = None - - ds = openfilegdb_drv.Open(tmp_path / "test.gdb") - lyr = ds.GetLayerByName("test_2147483647") - f = lyr.GetNextFeature() - assert f - assert f.GetFID() == 2147483647 - ds = None - - ds = fgdb_drv.Open(tmp_path / "test.gdb", update=1) - lyr = ds.GetLayerByName("test_2147483647") - # GetNextFeature() is excruciatingly slow on such huge FID with the SDK driver - f = lyr.GetFeature(2147483647) - assert f - - lyr = ds.CreateLayer("ogr_fgdb_20", geom_type=ogr.wkbNone) - lyr.CreateField(ogr.FieldDefn("id", ogr.OFTInteger)) - lyr.CreateField(ogr.FieldDefn("str", ogr.OFTString)) - - ds.ExecuteSQL("CREATE INDEX ogr_fgdb_20_id ON ogr_fgdb_20(id)") - - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetField("id", 1) - ret = lyr.CreateFeature(f) - assert ( - ret == 0 - and f.GetFID() == 1 - and lyr.GetMetadataItem("1", "MAP_OGR_FID_TO_FGDB_FID") is None - ) - - # Existing FID - with gdal.quiet_errors(): - ret = lyr.CreateFeature(f) - assert ret != 0 - - for invalid_fid in [-2, 0, 9876543210]: - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(invalid_fid) - with gdal.quiet_errors(): - ret = lyr.CreateFeature(f) - assert ret != 0, invalid_fid - - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(2) - f.SetField("id", 2) - ret = lyr.CreateFeature(f) - if ( - ret != 0 - or f.GetFID() != 2 - or lyr.GetMetadataItem("2", "MAP_OGR_FID_TO_FGDB_FID") is not None - ): - f.DumpReadable() - pytest.fail() - - # OGR FID = 4, FileGDB FID = 3 - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(4) - f.SetField("id", 4) - - # Cannot call CreateFeature() with a set FID when a dataset is opened more than once - ds2 = fgdb_drv.Open(tmp_path / "test.gdb", update=1) - with gdal.quiet_errors(): - ret = lyr.CreateFeature(f) - assert ret != 0 - ds2 = None - - ret = lyr.CreateFeature(f) - if ( - ret != 0 - or f.GetFID() != 4 - or lyr.GetMetadataItem("4", "MAP_OGR_FID_TO_FGDB_FID") != "3" - ): - f.DumpReadable() - pytest.fail(lyr.GetMetadataItem("4", "MAP_OGR_FID_TO_FGDB_FID")) - - # Cannot open geodatabase at the moment since it is in 'FID hack mode' - with gdal.quiet_errors(): - ds2 = fgdb_drv.Open(tmp_path / "test.gdb", update=1) - assert ds2 is None - ds2 = None - - # Existing FID, but only in OGR space - with gdal.quiet_errors(): - ret = lyr.CreateFeature(f) - assert ret != 0 - - # This FID exists as a FGDB ID, but should not be user visible. - f.SetFID(3) - ret = lyr.SetFeature(f) - assert ret == ogr.OGRERR_NON_EXISTING_FEATURE - ret = lyr.DeleteFeature(3) - assert ret == ogr.OGRERR_NON_EXISTING_FEATURE - ret = lyr.GetFeature(3) - assert ret is None - - # Trying to set OGR FID = 3 --> FileGDB FID = 4 - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(3) - f.SetField("id", 3) - - ret = lyr.CreateFeature(f) - if ( - ret != 0 - or f.GetFID() != 3 - or lyr.GetMetadataItem("3", "MAP_OGR_FID_TO_FGDB_FID") != "4" - ): - f.DumpReadable() - pytest.fail() - - lyr.ResetReading() - expected = [(1, None), (2, None), (4, 3), (3, 4)] - for i in range(2): - for (fid, fgdb_fid) in expected: - if i == 0: - f = lyr.GetNextFeature() - else: - f = lyr.GetFeature(fid) - assert f is not None - if f.GetFID() != fid or f.GetField("id") != fid: - f.DumpReadable() - pytest.fail(fid) - got_fgdb_fid = lyr.GetMetadataItem( - str(f.GetFID()), "MAP_OGR_FID_TO_FGDB_FID" - ) - if got_fgdb_fid is None: - assert fgdb_fid is None - elif int(got_fgdb_fid) != fgdb_fid: - print(fgdb_fid) - pytest.fail(got_fgdb_fid) - - for fid in [-9876543210, 0, 100]: - f = lyr.GetFeature(fid) - if f is not None: - f.DumpReadable() - pytest.fail() - - for invalid_fid in [-2, 0, 9876543210]: - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(invalid_fid) - ret = lyr.SetFeature(f) - assert ret == ogr.OGRERR_NON_EXISTING_FEATURE - ret = lyr.DeleteFeature(invalid_fid) - assert ret == ogr.OGRERR_NON_EXISTING_FEATURE - - f = lyr.GetFeature(3) - f.SetField("str", "3") - ret = lyr.SetFeature(f) - assert ret == 0 - - f = lyr.GetFeature(3) - assert f.GetField("str") == "3" - - ret = lyr.DeleteFeature(1) - assert ret == 0 - - ret = lyr.DeleteFeature(3) - assert ret == 0 - - for (fid, fgdb_fid) in [ - (3, 5), - (2049, 6), - (10, 7), - (7, 8), - (9, None), - (8, 10), - (12, 11), - ]: - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(fid) - f.SetField("id", fid) - ret = lyr.CreateFeature(f) - if ( - ret != 0 - or f.GetFID() != fid - or str(lyr.GetMetadataItem(str(fid), "MAP_OGR_FID_TO_FGDB_FID")) - != str(fgdb_fid) - ): - f.DumpReadable() - print(lyr.GetMetadataItem(str(fid), "MAP_OGR_FID_TO_FGDB_FID")) - pytest.fail(fid) - - # Normally 12 should be attributed, but it has already been reserved - f = ogr.Feature(lyr.GetLayerDefn()) - ret = lyr.CreateFeature(f) - if ret != 0 or f.GetFID() != 13: - f.DumpReadable() - pytest.fail() - f.SetField("id", f.GetFID()) - lyr.SetFeature(f) - - lyr.ResetReading() - expected = [ - (2, None), - (4, 3), - (3, 5), - (2049, 6), - (10, 7), - (7, 8), - (9, None), - (8, 10), - ] - for (fid, fgdb_fid) in expected: - f = lyr.GetNextFeature() - assert f is not None - if ( - f.GetFID() != fid - or f.GetField("id") != fid - or str(lyr.GetMetadataItem(str(fid), "MAP_OGR_FID_TO_FGDB_FID")) - != str(fgdb_fid) - ): - f.DumpReadable() - print(lyr.GetMetadataItem(str(fid), "MAP_OGR_FID_TO_FGDB_FID")) - pytest.fail(fid) - - lyr.SetAttributeFilter("id = 3") - lyr.ResetReading() - f = lyr.GetNextFeature() - if f.GetFID() != 3: - f.DumpReadable() - pytest.fail() - - # This will cause a resync of indexes - lyr.SetAttributeFilter("OBJECTID = 3") - lyr.ResetReading() - f = lyr.GetNextFeature() - if f.GetFID() != 3: - f.DumpReadable() - pytest.fail() - - # No sparse pages - lyr = ds.CreateLayer("ogr_fgdb_20_simple", geom_type=ogr.wkbNone) - lyr.CreateField(ogr.FieldDefn("id", ogr.OFTInteger)) - - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(2) - f.SetField("id", 2) - lyr.CreateFeature(f) - - # This will cause a resync of indexes - sql_lyr = ds.ExecuteSQL("SELECT * FROM ogr_fgdb_20_simple") - f = sql_lyr.GetNextFeature() - if f.GetFID() != 2: - f.DumpReadable() - pytest.fail() +def test_ogr_fgdb_14(poly_gdb): - # Do not allow user set FID while a select layer is in progress - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(3) - f.SetField("id", 3) - with gdal.quiet_errors(): - ret = lyr.CreateFeature(f) - assert ret != 0 + for _ in range(3): + ds1 = ogr.Open(poly_gdb) + assert ds1 is not None + ds2 = ogr.Open(poly_gdb) + assert ds2 is not None + ds2 = None + ds1 = None - ds.ReleaseResultSet(sql_lyr) - # Do it in transaction, but this is completely orthogonal - ds.StartTransaction(force=True) +############################################################################### +# Test opening a FGDB with both SRID and LatestSRID set (#5638) - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(3) - f.SetField("id", 3) - lyr.CreateFeature(f) - f = None - ds.CommitTransaction() +def test_ogr_fgdb_15(tmp_path): - # Multi-page indexes - srs = osr.SpatialReference() - srs.ImportFromEPSG(32630) - with gdal.config_option("FGDB_RESYNC_THRESHOLD", "600"): - lyr = ds.CreateLayer("ogr_fgdb_20_indexes", geom_type=ogr.wkbPoint, srs=srs) - lyr.CreateField(ogr.FieldDefn("id", ogr.OFTInteger)) - ds.ExecuteSQL("CREATE INDEX ogr_fgdb_20_indexes_id ON ogr_fgdb_20_indexes(id)") - with gdal.config_option("FGDB_BULK_LOAD", "YES"): - for i in range(1000): - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(i + 2) - f.SetField("id", i + 2) - f.SetGeometry(ogr.CreateGeometryFromWkt("POINT (%d 0)" % i)) - lyr.CreateFeature(f) + gdaltest.unzip(tmp_path, "data/filegdb/test3005.gdb.zip") + ds = ogr.Open(tmp_path / "test3005.gdb") + lyr = ds.GetLayer(0) + got_wkt = lyr.GetSpatialRef().ExportToWkt() + sr = osr.SpatialReference() + sr.ImportFromEPSG(3005) + expected_wkt = sr.ExportToWkt() + assert got_wkt == expected_wkt ds = None - # Check consistency after re-opening - gdal.ErrorReset() - for update in [0, 1]: - ds = fgdb_drv.Open(tmp_path / "test.gdb", update=update) - lyr = ds.GetLayerByName("ogr_fgdb_20") - assert lyr.GetFeatureCount() == 10 - lyr.ResetReading() - expected = [2, 3, 4, 7, 8, 9, 10, 12, 13, 2049] - for fid in expected: - f = lyr.GetNextFeature() - assert gdal.GetLastErrorType() == 0 - assert f is not None, fid - if f.GetFID() != fid or f.GetField("id") != fid: - f.DumpReadable() - pytest.fail(fid) - - for fid in expected: - lyr.SetAttributeFilter("id = %d" % fid) - lyr.ResetReading() - f = lyr.GetNextFeature() - if f.GetFID() != fid or f.GetField("id") != fid: - f.DumpReadable() - pytest.fail(fid) - - lyr = ds.GetLayerByName("ogr_fgdb_20_simple") - f = lyr.GetNextFeature() - assert f.GetFID() == 2 - f = lyr.GetNextFeature() - assert f.GetFID() == 3 - - # Check attribute index - lyr = ds.GetLayerByName("ogr_fgdb_20_indexes") - for i in range(1000): - fid = i + 2 - lyr.SetAttributeFilter("id = %d" % fid) - lyr.ResetReading() - f = lyr.GetNextFeature() - assert f.GetFID() == fid - - # Check spatial index - lyr.SetAttributeFilter(None) - if update == 1: - for i in range(1000): - fid = i + 2 - lyr.SetSpatialFilterRect(i - 0.01, -0.01, i + 0.01, 0.01) - lyr.ResetReading() - f = lyr.GetNextFeature() - assert f.GetFID() == fid - - # Insert new features - ds = fgdb_drv.Open(tmp_path / "test.gdb", update=1) - lyr = ds.GetLayerByName("ogr_fgdb_20") - for (fid, fgdb_fid) in [ - (10000000, 2050), - (10000001, 2051), - (8191, 2052), - (16384, 2053), - ]: - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(fid) - f.SetField("id", fid) - ret = lyr.CreateFeature(f) - if ( - ret != 0 - or f.GetFID() != fid - or str(lyr.GetMetadataItem(str(fid), "MAP_OGR_FID_TO_FGDB_FID")) - != str(fgdb_fid) - ): - f.DumpReadable() - pytest.fail(lyr.GetMetadataItem(str(fid), "MAP_OGR_FID_TO_FGDB_FID")) - ds = None +############################################################################### +# Test fix for #5674 - # Insert a new intermediate FIDs - for (fid, fgdb_fid) in [(1000000, 10000002), (1000001, 10000002)]: - ds = fgdb_drv.Open(tmp_path / "test.gdb", update=1) - lyr = ds.GetLayerByName("ogr_fgdb_20") - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(fid) - f.SetField("id", fid) - ret = lyr.CreateFeature(f) - if ( - ret != 0 - or f.GetFID() != fid - or lyr.GetMetadataItem(str(fid), "MAP_OGR_FID_TO_FGDB_FID") != str(fgdb_fid) - ): - f.DumpReadable() - pytest.fail(lyr.GetMetadataItem(str(fid), "MAP_OGR_FID_TO_FGDB_FID")) - ds = None - - # Check consistency after re-opening - gdal.ErrorReset() - for update in [0, 1]: - ds = fgdb_drv.Open(tmp_path / "test.gdb", update=update) - lyr = ds.GetLayerByName("ogr_fgdb_20") - assert lyr.GetFeatureCount() == 16 - lyr.ResetReading() - expected = [ - 2, - 3, - 4, - 7, - 8, - 9, - 10, - 12, - 13, - 2049, - 8191, - 16384, - 1000000, - 1000001, - 10000000, - 10000001, - ] - for fid in expected: - f = lyr.GetNextFeature() - assert gdal.GetLastErrorType() == 0 - assert f is not None, fid - if f.GetFID() != fid or f.GetField("id") != fid: - f.DumpReadable() - pytest.fail(fid) - - # Simulate different errors when database reopening is done - # to sync ids - for case in ("CASE1", "CASE2", "CASE3"): - try: - shutil.rmtree("tmp/test2.gdb") - except OSError: - pass - - ds = fgdb_drv.CreateDataSource(tmp_path / "test2.gdb") - - lyr = ds.CreateLayer("foo", geom_type=ogr.wkbNone) - lyr.CreateField(ogr.FieldDefn("id", ogr.OFTInteger)) - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(2) - f.SetField("id", 2) - lyr.CreateFeature(f) - - with gdal.quiet_errors(): - with gdal.config_option("FGDB_SIMUL_FAIL_REOPEN", case): - sql_lyr = ds.ExecuteSQL("SELECT * FROM foo") - if case == "CASE3": - assert sql_lyr is not None, case - ds.ReleaseResultSet(sql_lyr) - else: - assert sql_lyr is None, case +def test_ogr_fgdb_16(openfilegdb_drv, fgdb_drv): + if fgdb_drv is None or openfilegdb_drv is None: + pytest.skip() - # Everything will fail, but hopefully without crashing - lyr.ResetReading() - assert lyr.GetNextFeature() is None - assert lyr.GetFeature(1) is None - assert lyr.DeleteFeature(1) != 0 - assert lyr.CreateFeature(f) != 0 - assert lyr.SetFeature(f) != 0 - if case != "CASE3": - assert ds.CreateLayer("bar", geom_type=ogr.wkbNone) is None - assert ds.DeleteLayer(0) != 0 - sql_lyr = ds.ExecuteSQL("SELECT * FROM foo") - assert case == "CASE3" or sql_lyr is None - ds.ReleaseResultSet(sql_lyr) + try: + gdaltest.unzip("tmp/cache", "data/filegdb/ESSENCE_NAIPF_ORI_PROV_sub93.gdb.zip") + except OSError: + pass + try: + os.stat("tmp/cache/ESSENCE_NAIPF_ORI_PROV_sub93.gdb") + except OSError: + pytest.skip() - ds = None + fgdb_drv.Deregister() - openfilegdb_drv.Deregister() - # sys.exit(0) + # Force FileGDB first + fgdb_drv.Register() + openfilegdb_drv.Register() + + try: + ds = ogr.Open("tmp/cache/ESSENCE_NAIPF_ORI_PROV_sub93.gdb") + assert ds is not None + finally: + # Deregister OpenFileGDB again + openfilegdb_drv.Deregister() + + shutil.rmtree("tmp/cache/ESSENCE_NAIPF_ORI_PROV_sub93.gdb") ############################################################################### -# Test M support - - -def test_ogr_fgdb_21(fgdb_drv, fgdb_sdk_1_4_or_later, tmp_path): - # Fails on MULTIPOINT ZM - if ( - gdaltest.is_travis_branch("ubuntu_2204") - or gdaltest.is_travis_branch("ubuntu_2404") - or gdaltest.is_travis_branch("ubuntu_2004") - or gdaltest.is_travis_branch("ubuntu_1804") - or gdaltest.is_travis_branch("ubuntu_1604") - or gdaltest.is_travis_branch("python3") - or gdaltest.is_ci() - ): - pytest.skip() +# Test not nullable fields - ds = fgdb_drv.CreateDataSource(tmp_path / "test.gdb") - datalist = [ - ["pointm", ogr.wkbPointM, "POINT M (1 2 3)"], - ["pointzm", ogr.wkbPointM, "POINT ZM (1 2 3 4)"], - ["multipointm", ogr.wkbMultiPointM, "MULTIPOINT M ((1 2 3),(4 5 6))"], - ["multipointzm", ogr.wkbMultiPointZM, "MULTIPOINT ZM ((1 2 3 4),(5 6 7 8))"], - [ - "linestringm", - ogr.wkbLineStringM, - "LINESTRING M (1 2 3,4 5 6)", - "MULTILINESTRING M ((1 2 3,4 5 6))", - ], - [ - "linestringzm", - ogr.wkbLineStringZM, - "LINESTRING ZM (1 2 3 4,5 6 7 8)", - "MULTILINESTRING ZM ((1 2 3 4,5 6 7 8))", - ], - [ - "multilinestringm", - ogr.wkbMultiLineStringM, - "MULTILINESTRING M ((1 2 3,4 5 6))", - ], - [ - "multilinestringzm", - ogr.wkbMultiLineStringZM, - "MULTILINESTRING ZM ((1 2 3 4,5 6 7 8))", - ], - [ - "polygonm", - ogr.wkbPolygonM, - "POLYGON M ((0 0 1,0 1 2,1 1 3,1 0 4,0 0 1))", - "MULTIPOLYGON M (((0 0 1,0 1 2,1 1 3,1 0 4,0 0 1)))", - ], - [ - "polygonzm", - ogr.wkbPolygonZM, - "POLYGON ZM ((0 0 1 -1,0 1 2 -2,1 1 3 -3,1 0 4 -4,0 0 1 -1))", - "MULTIPOLYGON ZM (((0 0 1 -1,0 1 2 -2,1 1 3 -3,1 0 4 -4,0 0 1 -1)))", - ], - [ - "multipolygonm", - ogr.wkbMultiPolygonM, - "MULTIPOLYGON M (((0 0 1,0 1 2,1 1 3,1 0 4,0 0 1)))", - ], - [ - "multipolygonzm", - ogr.wkbMultiPolygonZM, - "MULTIPOLYGON ZM (((0 0 1 -1,0 1 2 -2,1 1 3 -3,1 0 4 -4,0 0 1 -1)))", - ], - ["empty_polygonm", ogr.wkbPolygonM, "POLYGON M EMPTY", None], - ] +def test_ogr_fgdb_17(openfilegdb_drv, tmp_path): - srs = osr.SpatialReference() - srs.SetFromUserInput("WGS84") + ds = openfilegdb_drv.CreateDataSource(tmp_path / "test.gdb") + sr = osr.SpatialReference() + sr.ImportFromEPSG(4326) + lyr = ds.CreateLayer( + "test", geom_type=ogr.wkbPoint, srs=sr, options=["GEOMETRY_NULLABLE=NO"] + ) + assert lyr.GetLayerDefn().GetGeomFieldDefn(0).IsNullable() == 0 + field_defn = ogr.FieldDefn("field_not_nullable", ogr.OFTString) + field_defn.SetNullable(0) + lyr.CreateField(field_defn) + field_defn = ogr.FieldDefn("field_nullable", ogr.OFTString) + lyr.CreateField(field_defn) - for data in datalist: - lyr = ds.CreateLayer(data[0], geom_type=data[1], srs=srs, options=[]) + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetField("field_not_nullable", "not_null") + f.SetGeometryDirectly(ogr.CreateGeometryFromWkt("POINT(0 0)")) + ret = lyr.CreateFeature(f) + assert ret == 0 + f = None + + # Error case: missing geometry + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetField("field_not_nullable", "not_null") + with gdal.quiet_errors(): + ret = lyr.CreateFeature(f) + assert ret != 0 + f = None - feat = ogr.Feature(lyr.GetLayerDefn()) - # print(data[2]) - feat.SetGeometry(ogr.CreateGeometryFromWkt(data[2])) - lyr.CreateFeature(feat) + # Error case: missing non-nullable field + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometryDirectly(ogr.CreateGeometryFromWkt("POINT(0 0)")) + with gdal.quiet_errors(): + ret = lyr.CreateFeature(f) + assert ret != 0 + f = None ds = None - ds = ogr.Open(tmp_path / "test.gdb") - for data in datalist: - lyr = ds.GetLayerByName(data[0]) - expected_geom_type = data[1] - if expected_geom_type == ogr.wkbLineStringM: - expected_geom_type = ogr.wkbMultiLineStringM - elif expected_geom_type == ogr.wkbLineStringZM: - expected_geom_type = ogr.wkbMultiLineStringZM - elif expected_geom_type == ogr.wkbPolygonM: - expected_geom_type = ogr.wkbMultiPolygonM - elif expected_geom_type == ogr.wkbPolygonZM: - expected_geom_type = ogr.wkbMultiPolygonZM - - assert lyr.GetGeomType() == expected_geom_type, data - feat = lyr.GetNextFeature() - try: - expected_wkt = data[3] - except IndexError: - expected_wkt = data[2] + ds = ogr.Open(tmp_path / "test.gdb") + lyr = ds.GetLayerByName("test") + assert ( + lyr.GetLayerDefn() + .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("field_not_nullable")) + .IsNullable() + == 0 + ) + assert ( + lyr.GetLayerDefn() + .GetFieldDefn(lyr.GetLayerDefn().GetFieldIndex("field_nullable")) + .IsNullable() + == 1 + ) + assert lyr.GetLayerDefn().GetGeomFieldDefn(0).IsNullable() == 0 - ogrtest.check_feature_geometry(feat, expected_wkt) + ds = None ############################################################################### @@ -2388,7 +648,7 @@ def test_ogr_fgdb_25(): @pytest.mark.require_geos -def test_ogr_fgdb_weird_winding_order(fgdb_sdk_1_4_or_later, tmp_path): +def test_ogr_fgdb_weird_winding_order(tmp_path): gdaltest.unzip(tmp_path, "data/filegdb/weird_winding_order_fgdb.zip") @@ -2414,59 +674,6 @@ def test_ogr_fgdb_utc_datetime(): assert f.GetFieldAsString("EditDate") == "2020/06/22 07:49:36+00" -############################################################################### -# Test field alias - - -def test_ogr_fgdb_alias(fgdb_drv, tmp_path): - - srs = osr.SpatialReference() - srs.SetFromUserInput("WGS84") - - ds = fgdb_drv.CreateDataSource(tmp_path / "alias.gdb") - lyr = ds.CreateLayer("test", srs=srs, geom_type=ogr.wkbPoint) - fld_defn = ogr.FieldDefn("short_name", ogr.OFTInteger) - fld_defn.SetAlternativeName("longer name") - lyr.CreateField(fld_defn) - fld_defn = ogr.FieldDefn("regular_name", ogr.OFTInteger) - lyr.CreateField(fld_defn) - ds = None - - ds = ogr.Open(tmp_path / "alias.gdb") - lyr = ds.GetLayer(0) - lyr_defn = lyr.GetLayerDefn() - assert lyr_defn.GetFieldDefn(0).GetAlternativeName() == "longer name" - assert lyr_defn.GetFieldDefn(1).GetAlternativeName() == "" - - -############################################################################### -# Test field alias with ampersand character. Requires OpenFileGDB to be read back - - -@pytest.mark.require_driver("OpenFileGDB") -def test_ogr_fgdb_alias_with_ampersand(fgdb_drv, openfilegdb_drv, tmp_path): - - srs = osr.SpatialReference() - srs.SetFromUserInput("WGS84") - - ds = fgdb_drv.CreateDataSource(tmp_path / "alias.gdb") - lyr = ds.CreateLayer("test", srs=srs, geom_type=ogr.wkbPoint) - fld_defn = ogr.FieldDefn("short_name", ogr.OFTInteger) - fld_defn.SetAlternativeName("longer & name") - lyr.CreateField(fld_defn) - fld_defn = ogr.FieldDefn("regular_name", ogr.OFTInteger) - lyr.CreateField(fld_defn) - ds = None - - openfilegdb_drv.Register() - ds = fgdb_drv.Open(tmp_path / "alias.gdb") - openfilegdb_drv.Deregister() - lyr = ds.GetLayer(0) - lyr_defn = lyr.GetLayerDefn() - assert lyr_defn.GetFieldDefn(0).GetAlternativeName() == "longer & name" - assert lyr_defn.GetFieldDefn(1).GetAlternativeName() == "" - - ############################################################################### # Test reading field domains @@ -2516,46 +723,6 @@ def test_ogr_fgdb_read_domains(): _check_domains(ds) -############################################################################### -# Test writing field domains - - -def test_ogr_fgdb_write_domains(fgdb_drv, tmp_path): - - out_dir = tmp_path / "test_ogr_fgdb_write_domains.gdb" - - ds = gdal.VectorTranslate(out_dir, "data/filegdb/Domains.gdb", options="-f FileGDB") - _check_domains(ds) - - assert ds.TestCapability(ogr.ODsCAddFieldDomain) == 1 - assert ds.TestCapability(ogr.ODsCDeleteFieldDomain) == 1 - assert ds.TestCapability(ogr.ODsCUpdateFieldDomain) == 1 - - with pytest.raises(Exception): - with gdal.ExceptionMgr(): - ds.DeleteFieldDomain("not_existing") - - domain = ogr.CreateCodedFieldDomain( - "unused_domain", "desc", ogr.OFTInteger, ogr.OFSTNone, {1: "one", "2": None} - ) - assert ds.AddFieldDomain(domain) - assert ds.DeleteFieldDomain("unused_domain") - domain = ds.GetFieldDomain("unused_domain") - assert domain is None - - domain = ogr.CreateRangeFieldDomain( - "SpeedLimit", "desc", ogr.OFTInteger, ogr.OFSTNone, 1, True, 2, True - ) - assert ds.UpdateFieldDomain(domain) - - ds = None - - ds = gdal.OpenEx(out_dir, allowed_drivers=["FileGDB"]) - domain = ds.GetFieldDomain("SpeedLimit") - assert domain.GetDescription() == "desc" - ds = None - - ############################################################################### # Test reading layer hierarchy @@ -2616,58 +783,6 @@ def test_ogr_fgdb_read_layer_hierarchy(): assert standalone is not None -############################################################################### -# Test renaming a layer - - -@pytest.mark.parametrize("options", [[], ["FEATURE_DATASET=fd1"]]) -def test_ogr_fgdb_rename_layer(fgdb_drv, options, tmp_path): - - srs4326 = osr.SpatialReference() - srs4326.ImportFromEPSG(4326) - - ds = fgdb_drv.CreateDataSource(tmp_path / "rename.gdb") - ds.CreateLayer("other_layer", geom_type=ogr.wkbNone) - lyr = ds.CreateLayer("foo", geom_type=ogr.wkbPoint, srs=srs4326, options=options) - assert lyr.TestCapability(ogr.OLCRename) == 1 - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetGeometryDirectly(ogr.CreateGeometryFromWkt("POINT (1 2)")) - lyr.CreateFeature(f) - - assert lyr.Rename("bar") == ogr.OGRERR_NONE - assert lyr.GetDescription() == "bar" - assert lyr.GetLayerDefn().GetName() == "bar" - - with gdal.quiet_errors(): - assert lyr.Rename("bar") != ogr.OGRERR_NONE - - with gdal.quiet_errors(): - assert lyr.Rename("other_layer") != ogr.OGRERR_NONE - - # Second renaming - assert lyr.Rename("baz") == ogr.OGRERR_NONE - assert lyr.GetDescription() == "baz" - assert lyr.GetLayerDefn().GetName() == "baz" - - lyr.ResetReading() - f = lyr.GetNextFeature() - assert f.GetGeometryRef() is not None - - ds = None - - ds = ogr.Open(tmp_path / "rename.gdb") - lyr = ds.GetLayerByName("baz") - assert lyr is not None, [ - ds.GetLayer(i).GetName() for i in range(ds.GetLayerCount()) - ] - - lyr.ResetReading() - f = lyr.GetNextFeature() - assert f.GetGeometryRef() is not None - - ds = None - - ############################################################################### # Test that non-spatial tables which are not present in GDB_Items are listed # see https://github.com/OSGeo/gdal/issues/4463 @@ -2952,38 +1067,6 @@ def test_ogr_filegdb_read_relationships(openfilegdb_drv, fgdb_drv): assert rel.GetRelatedTableType() == "media" -############################################################################### -# Test inserting geometries of type incompatible with the layer geometry type - - -@pytest.mark.parametrize( - "layer_geom_type,wkt", - [ - (ogr.wkbLineString, "POLYGON((0 0,0 1,1 1,0 0))"), - (ogr.wkbPolygon, "LINESTRING(0 0,1 1)"), - (ogr.wkbPoint, "MULTIPOINT((0 0))"), - (ogr.wkbMultiPoint, "POINT(0 0)"), - ], -) -def test_ogr_filegdb_incompatible_geometry_types( - fgdb_drv, tmp_path, layer_geom_type, wkt -): - - dirname = tmp_path / "test_ogr_filegdb_incompatible_geometry_types.gdb" - - ds = fgdb_drv.CreateDataSource(dirname) - - srs = osr.SpatialReference() - srs.ImportFromEPSG(4326) - - lyr = ds.CreateLayer("test", srs=srs, geom_type=layer_geom_type) - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetGeometryDirectly(ogr.CreateGeometryFromWkt(wkt)) - with gdal.quiet_errors(): - assert lyr.CreateFeature(f) == ogr.OGRERR_FAILURE - ds = None - - ############################################################################### # Test reading an empty polygon @@ -3025,131 +1108,3 @@ def test_ogr_filegdb_read_cdf(): ds = ogr.Open("data/filegdb/with_cdf.gdb") lyr = ds.GetLayer(0) assert lyr.GetFeatureCount() == 3 - - -############################################################################### -# Test geometry coordinate precision support - - -def test_ogr_filegdb_write_geom_coord_precision(tmp_path): - - filename = str(tmp_path / "test.gdb") - ds = gdal.GetDriverByName("FileGDB").Create(filename, 0, 0, 0, gdal.GDT_Unknown) - geom_fld = ogr.GeomFieldDefn("geometry", ogr.wkbPointZM) - prec = ogr.CreateGeomCoordinatePrecision() - prec.Set(1e-5, 1e-3, 1e-2) - geom_fld.SetCoordinatePrecision(prec) - lyr = ds.CreateLayerFromGeomFieldDefn("test", geom_fld) - geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) - prec = geom_fld.GetCoordinatePrecision() - assert prec.GetXYResolution() == 1e-5 - assert prec.GetZResolution() == 1e-3 - assert prec.GetMResolution() == 1e-2 - assert prec.GetFormats() == ["FileGeodatabase"] - opts = prec.GetFormatSpecificOptions("FileGeodatabase") - for key in opts: - opts[key] = float(opts[key]) - assert opts == { - "MOrigin": -100000.0, - "MScale": 100.0, - "MTolerance": 0.001, - "XOrigin": -2147483647.0, - "XYScale": 100000.0, - "XYTolerance": 1e-06, - "YOrigin": -2147483647.0, - "ZOrigin": -100000.0, - "ZScale": 1000.0, - "ZTolerance": 0.0001, - } - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetGeometry( - ogr.CreateGeometryFromWkt("POINT(1.23456789 2.34567891 9.87654321 -9.87654321)") - ) - lyr.CreateFeature(f) - ds.Close() - - ds = ogr.Open(filename) - lyr = ds.GetLayer(0) - geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) - prec = geom_fld.GetCoordinatePrecision() - assert prec.GetXYResolution() == 1e-5 - assert prec.GetZResolution() == 1e-3 - assert prec.GetMResolution() == 1e-2 - assert prec.GetFormats() == ["FileGeodatabase"] - opts = prec.GetFormatSpecificOptions("FileGeodatabase") - for key in opts: - try: - opts[key] = float(opts[key]) - except ValueError: - pass - assert opts == { - "MOrigin": -100000.0, - "MScale": 100.0, - "MTolerance": 0.001, - "XOrigin": -2147483647.0, - "XYScale": 100000.0, - "XYTolerance": 1e-06, - "YOrigin": -2147483647.0, - "ZOrigin": -100000.0, - "ZScale": 1000.0, - "ZTolerance": 0.0001, - "HighPrecision": "true", - } - f = lyr.GetNextFeature() - g = f.GetGeometryRef() - assert g.GetX(0) == pytest.approx(1.23456789, abs=1e-5) - assert g.GetX(0) != pytest.approx(1.23456789, abs=1e-8) - assert g.GetY(0) == pytest.approx(2.34567891, abs=1e-5) - assert g.GetZ(0) == pytest.approx(9.87654321, abs=1e-3) - assert g.GetZ(0) != pytest.approx(9.87654321, abs=1e-8) - assert g.GetM(0) == pytest.approx(-9.87654321, abs=1e-2) - assert g.GetM(0) != pytest.approx(-9.87654321, abs=1e-8) - ds.Close() - - j = gdal.VectorInfo(filename, format="json") - j_geom_field = j["layers"][0]["geometryFields"][0] - assert j_geom_field["xyCoordinateResolution"] == 1e-5 - assert j_geom_field["zCoordinateResolution"] == 1e-3 - assert j_geom_field["mCoordinateResolution"] == 1e-2 - assert j_geom_field["coordinatePrecisionFormatSpecificOptions"] == { - "FileGeodatabase": { - "XOrigin": -2147483647, - "YOrigin": -2147483647, - "XYScale": 100000, - "ZOrigin": -100000, - "ZScale": 1000, - "MOrigin": -100000, - "MScale": 100, - "XYTolerance": 1e-06, - "ZTolerance": 0.0001, - "MTolerance": 0.001, - "HighPrecision": "true", - } - } - - -############################################################################### -# Test dummy use of CreateLayerFromGeomFieldDefn() with a geometry field -# definition of type wkbNone - - -def test_ogr_filegdb_CreateLayerFromGeomFieldDefn_geom_type_none(tmp_path): - - filename = str(tmp_path / "test.gdb") - ds = gdal.GetDriverByName("FileGDB").Create(filename, 0, 0, 0, gdal.GDT_Unknown) - geom_fld = ogr.GeomFieldDefn("geometry", ogr.wkbNone) - ds.CreateLayerFromGeomFieldDefn("test", geom_fld) - ds.Close() - - ds = ogr.Open(filename) - lyr = ds.GetLayer(0) - assert lyr.GetGeomType() == ogr.wkbNone - ds.Close() - - filename2 = str(tmp_path / "test2.gdb") - gdal.VectorTranslate(filename2, filename, format="FileGDB") - - ds = ogr.Open(filename2) - lyr = ds.GetLayer(0) - assert lyr.GetGeomType() == ogr.wkbNone - ds.Close() diff --git a/autotest/ogr/ogr_fgdb_stress_test.py b/autotest/ogr/ogr_fgdb_stress_test.py deleted file mode 100755 index a5758c19de6f..000000000000 --- a/autotest/ogr/ogr_fgdb_stress_test.py +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/env pytest -# -*- coding: utf-8 -*- -############################################################################### -# -# Project: GDAL/OGR Test Suite -# Purpose: FGDB driver stress testing of CreateFeature() with user set FID -# Author: Even Rouault -# -############################################################################### -# Copyright (c) 2015, Even Rouault -# -# SPDX-License-Identifier: MIT -############################################################################### - - -import random -import shutil - -import ogrtest -import pytest - -from osgeo import gdal, ogr, osr - -############################################################################### -# Test if driver is available - - -@pytest.mark.require_run_on_demand -def test_ogr_fgdb_stress_init(): - - ogrtest.fgdb_drv = None - ogrtest.openfilegdb_drv = None - - ogrtest.fgdb_drv = ogr.GetDriverByName("FileGDB") - ogrtest.reference_drv = ogr.GetDriverByName("GPKG") - ogrtest.reference_ext = "gpkg" - ogrtest.openfilegdb_drv = ogr.GetDriverByName("OpenFileGDB") - - if ogrtest.fgdb_drv is None: - pytest.skip() - if ogrtest.reference_drv is None: - pytest.skip() - if ogrtest.openfilegdb_drv is None: - pytest.skip() - - try: - shutil.rmtree("tmp/test.gdb") - except OSError: - pass - - gdal.Unlink("tmp/test." + ogrtest.reference_ext) - - -############################################################################### -# Generate databases from random operations - - -@pytest.mark.require_run_on_demand -def test_ogr_fgdb_stress_1(): - if ogrtest.fgdb_drv is None: - pytest.skip() - - verbose = False - - ds_test = ogrtest.fgdb_drv.CreateDataSource("tmp/test.gdb") - ds_ref = ogrtest.reference_drv.CreateDataSource("tmp/test." + ogrtest.reference_ext) - sr = osr.SpatialReference() - sr.ImportFromEPSG(4326) - lyr_test = ds_test.CreateLayer("test", geom_type=ogr.wkbPoint, srs=sr) - lyr_ref = ds_ref.CreateLayer("test", geom_type=ogr.wkbPoint, srs=sr) - for lyr in [lyr_test, lyr_ref]: - lyr.CreateField(ogr.FieldDefn("str", ogr.OFTString)) - ds_test.ExecuteSQL("CREATE INDEX idx_test_str ON test(str)") - ds_ref.ExecuteSQL("CREATE INDEX idx_test_str ON test(str)") - random.seed(0) - in_transaction = False - nfeatures_created = 0 - for _ in range(100000): - function = random.randrange(0, 500) - if function == 0: - if not in_transaction: - if verbose: - print("StartTransaction") - ds_test.StartTransaction(force=1) - else: - if verbose: - print("CommitTransaction") - ds_test.CommitTransaction() - in_transaction = not in_transaction - elif function < 500 / 3: - ret = [] - fid = -1 - if random.randrange(0, 2) == 0: - fid = 1 + random.randrange(0, 1000) - wkt = "POINT (%d %d)" % (random.randrange(0, 100), random.randrange(0, 100)) - if verbose: - print("Create(%d)" % fid) - for lyr in [lyr_test, lyr_ref]: - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(fid) - f.SetField(0, "%d" % random.randrange(0, 1000)) - f.SetGeometry(ogr.CreateGeometryFromWkt(wkt)) - with gdal.quiet_errors(): - ret.append(lyr.CreateFeature(f)) - # So to ensure lyr_ref will use the same FID as the tested layer - fid = f.GetFID() - # print("created %d" % fid) - assert ret[0] == ret[1] - if ret[0] == 0: - nfeatures_created += 1 - # For some odd reason, the .spx file is no longer updated when doing - # a SetFeature() before having creating at least 2 features ! - elif function < 500 * 2 / 3 and nfeatures_created >= 2: - ret = [] - fid = 1 + random.randrange(0, 1000) - if verbose: - print("Update(%d)" % fid) - wkt = "POINT (%d %d)" % (random.randrange(0, 100), random.randrange(0, 100)) - for lyr in [lyr_test, lyr_ref]: - f = ogr.Feature(lyr.GetLayerDefn()) - f.SetFID(fid) - f.SetField(0, "%d" % random.randrange(0, 1000)) - f.SetGeometry(ogr.CreateGeometryFromWkt(wkt)) - # gdal.PushErrorHandler() - ret.append(lyr.SetFeature(f)) - # gdal.PopErrorHandler() - assert ret[0] == ret[1] - # Same for DeleteFeature() - elif nfeatures_created >= 2: - ret = [] - fid = 1 + random.randrange(0, 1000) - if verbose: - print("Delete(%d)" % fid) - for lyr in [lyr_test, lyr_ref]: - # gdal.PushErrorHandler() - ret.append(lyr.DeleteFeature(fid)) - # gdal.PopErrorHandler() - assert ret[0] == ret[1] - - if in_transaction: - ds_test.CommitTransaction() - - -############################################################################### -# Compare databases - - -@pytest.mark.require_run_on_demand -def test_ogr_fgdb_stress_2(): - if ogrtest.fgdb_drv is None: - pytest.skip() - - ds_test = ogr.Open("tmp/test.gdb") - ds_ref = ogr.Open("tmp/test." + ogrtest.reference_ext) - - lyr_test = ds_test.GetLayer(0) - lyr_ref = ds_ref.GetLayer(0) - - while True: - f_test = lyr_test.GetNextFeature() - f_ref = lyr_ref.GetNextFeature() - assert not (f_test is None and f_ref is not None) or ( - f_test is not None and f_ref is None - ) - if f_test is None: - break - - assert f_test.GetFID() == f_ref.GetFID() - assert f_test["str"] == f_ref["str"] - ogrtest.check_feature_geometry(f_test, f_ref.GetGeometryRef()) - - for val in range(1000): - lyr_test.SetAttributeFilter("str = '%d'" % val) - lyr_ref.SetAttributeFilter("str = '%d'" % val) - assert lyr_test.GetFeatureCount() == lyr_ref.GetFeatureCount(), val - - # sys.exit(0) - - -############################################################################### -# Cleanup - - -@pytest.mark.require_run_on_demand -def test_ogr_fgdb_stress_cleanup(): - if ogrtest.fgdb_drv is None: - pytest.skip() - - try: - shutil.rmtree("tmp/test.gdb") - except OSError: - pass - - gdal.Unlink("tmp/test." + ogrtest.reference_ext) diff --git a/doc/source/drivers/vector/filegdb.rst b/doc/source/drivers/vector/filegdb.rst index 718000290d68..7c5659bb5fb3 100644 --- a/doc/source/drivers/vector/filegdb.rst +++ b/doc/source/drivers/vector/filegdb.rst @@ -15,6 +15,9 @@ dataset name must be the directory/folder name, and it must end with the Note : the :ref:`OpenFileGDB driver ` driver exists as an alternative built-in (i.e. not depending on a third-party library) driver. +Starting with GDAL 3.11, update and creation support is delegated to the +:ref:`OpenFileGDB driver ` driver. + Driver capabilities ------------------- @@ -29,19 +32,6 @@ Requirements Curve in geometries are supported on reading with GDAL >= 2.2. -Bulk feature loading --------------------- - -The :config:`FGDB_BULK_LOAD` configuration option can be set to YES to speed-up -feature insertion (or sometimes solve problems when inserting a lot of -features (see http://trac.osgeo.org/gdal/ticket/4420). The effect of -this configuration option is to cause a write lock to be taken and a -temporary disabling of the indexes. Those are restored when the -datasource is closed or when a read operation is done. - -Bulk load is enabled by default for newly -created layers (unless otherwise specified). - SQL support ----------- @@ -88,175 +78,6 @@ element or within a feature dataset can be explored using the methods :cpp:func:`GDALGroup::GetGroupNames`, :cpp:func:`GDALGroup::OpenGroup`, :cpp:func:`GDALGroup::GetVectorLayerNames` and :cpp:func:`GDALGroup::OpenVectorLayer` -Transaction support -------------------- - -The FileGDB driver implements transactions at the database level, -through an emulation (as per :ref:`rfc-54`), -since the FileGDB SDK itself does not offer it. This works by backing up -the current state of a geodatabase when StartTransaction(force=TRUE) is -called. If the transaction is committed, the backup copy is destroyed. -If the transaction is rolled back, the backup copy is restored. So this -might be costly when operating on huge geodatabases. - -Starting with GDAL 2.1, on Linux/Unix, instead of a full backup copy -only layers that are modified are backed up. - -Note that this emulation has an unspecified behavior in case of -concurrent updates (with different connections in the same or another -process). - -CreateFeature() support ------------------------ - -The FileGDB SDK API does not allow to create a feature with a FID -specified by the user. Starting with GDAL 2.1, the FileGDB driver -implements a special FID remapping technique to enable the user to -create features at the FID of their choice. - - -Dataset Creation Options ------------------------- - -None. - -Layer Creation Options ----------------------- - -|about-layer-creation-options| -The following layer creation options are supported: - -- .. lco:: FEATURE_DATASET - - When this option is set, the new layer will be - created inside the named FeatureDataset folder. If the folder does - not already exist, it will be created. - -- .. lco:: LAYER_ALIAS - :since: 2.3 - - Set layer name alias. - -- .. lco:: GEOMETRY_NAME - :default: SHAPE - - Set name of geometry column in new layer. - -- .. lco:: GEOMETRY_NULLABLE - :default: YES - :since: 2.0 - - Whether the values of the geometry column can be NULL. - Can be set to NO so that geometry is required. - -- .. lco:: FID - :default: OBJECTID - - Name of the OID column to create. - Note: option was called OID_NAME in releases before GDAL 2 - -- .. lco:: XYTOLERANCE - :default: 0.01 - - Controls (with :lco:`ZTOLERANCE` and :lco:`MTOLERANCE`) the snapping - tolerance used for advanced ArcGIS features like network and topology - rules. They won't effect any OGR operations, but they will by used by - ArcGIS. The units of the parameters are the units of the coordinate - reference system. - - ArcMap 10.0 and OGR defaults for XYTOLERANCE are 0.001m (or - equivalent) for projected coordinate systems, and 0.000000008983153° - for geographic coordinate systems. - ArcMap 10.0 and OGR defaults for ZTOLERANCE and MTOLERANCE are 0.0001. - - .. lco:: ZTOLERANCE - :default: 0.0001 - - .. lco:: MTOLERANCE - :default: 0.0001 - :since: 3.5.1 - -- **XORIGIN, YORIGIN, ZORIGIN, MORIGIN, XYSCALE, ZSCALE, MSCALE**: These parameters - control the `coordinate precision - grid `__ - inside the file geodatabase. The dimensions of the grid are - determined by the origin, and the scale. The origin defines the - location of a reference grid point in space. The scale is the - reciprocal of the resolution. So, to get a grid with an origin at 0 - and a resolution of 0.001 on all axes, you would set all the origins - to 0 and all the scales to 1000. - - *Important*: The domain specified by - ``(xmin=XORIGIN, ymin=YORIGIN, xmax=(XORIGIN + 9E+15 / XYSCALE), ymax=(YORIGIN + 9E+15 / XYSCALE))`` - needs to encompass every possible coordinate value for the feature - class. If features are added with coordinates that fall outside the - domain, errors will occur in ArcGIS with spatial indexing, feature - selection, and exporting data. - - ArcMap 10.0 and OGR defaults: - - - For geographic coordinate systems: XORIGIN=-400, YORIGIN=-400, - XYSCALE=1000000000 - - For projected coordinate systems: XYSCALE=10000 for the default - XYTOLERANCE of 0.001m. XORIGIN and YORIGIN change based on the - coordinate system, but the OGR default of -2147483647 is suitable - with the default XYSCALE for all coordinate systems. - - ZORIGIN and MORIGIN: -100000 - - ZSCALE and MSCALE: 10000 - - .. note:: MORIGIN and MSCALE added in GDAL 3.5.1 - -- .. lco:: XML_DEFINITION - - When this option is set, its - value will be used as the XML definition to create the new table. The - root node of such a XML definition must be a - element conformant to FileGDBAPI.xsd - -- .. lco:: CREATE_MULTIPATCH - :choices: YES, NO - - When this option is set, - geometries of layers of type MultiPolygon will be written as - MultiPatch - -- .. lco:: CONFIGURATION_KEYWORD - :choices: DEFAULTS, TEXT_UTF16, MAX_FILE_SIZE_4GB, MAX_FILE_SIZE_256TB, GEOMETRY_OUTOFLINE, BLOB_OUTOFLINE, GEOMETRY_AND_BLOB_OUTOFLINE - - Customize how data is stored. By default text in UTF-8 and data up to 1TB - -- .. lco:: CREATE_SHAPE_AREA_AND_LENGTH_FIELDS - :choices: YES, NO - :default: NO - :since: 3.6.0 - - When this option is set, - a Shape_Area and Shape_Length special fields will be created for polygonal - layers (Shape_Length only for linear layers). These fields will automatically - be populated with the feature's area or length whenever a new feature is - added to the dataset or an existing feature is amended. - When using ogr2ogr with a source layer that has Shape_Area/Shape_Length - special fields, and this option is not explicitly specified, it will be - automatically set, so that the resulting FileGeodatabase has those fields - properly tagged. - -Configuration options ---------------------- - -|about-config-options| -The following configuration options are available: - -- .. config:: FGDB_BULK_LOAD - :choices: YES, NO - - Can be set to YES to speed-up - feature insertion (or sometimes solve problems when inserting a lot of - features (see http://trac.osgeo.org/gdal/ticket/4420). The effect of - this configuration option is to cause a write lock to be taken and a - temporary disabling of the indexes. Those are restored when the - datasource is closed or when a read operation is done. Bulk load is - enabled by default for newly created layers (unless otherwise specified). - Geometry coordinate precision ----------------------------- diff --git a/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp b/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp index f7f731694d67..d0453b2ab616 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp @@ -102,10 +102,9 @@ FGdbDataSource::FGdbDataSource(bool bUseDriverMutex, FGdbDatabaseConnection *pConnection, bool bUseOpenFileGDB) : m_bUseDriverMutex(bUseDriverMutex), m_pConnection(pConnection), - m_pGeodatabase(nullptr), m_bUpdate(false), m_poOpenFileGDBDrv(nullptr), + m_pGeodatabase(nullptr), m_poOpenFileGDBDrv(nullptr), m_bUseOpenFileGDB(bUseOpenFileGDB) { - bPerLayerCopyingForTransaction = -1; SetDescription(pConnection->m_osName.c_str()); poDriver = GDALDriver::FromHandle(GDALGetDriverByName("FileGDB")); } @@ -119,9 +118,6 @@ FGdbDataSource::~FGdbDataSource() CPLMutexHolderOptionalLockD(m_bUseDriverMutex ? FGdbDriver::hMutex : nullptr); - if (m_pConnection && m_pConnection->IsLocked()) - CommitTransaction(); - // CloseInternal(); size_t count = m_layers.size(); for (size_t i = 0; i < count; ++i) @@ -132,8 +128,6 @@ FGdbDataSource::~FGdbDataSource() } } - FixIndexes(); - if (m_bUseDriverMutex) FGdbDriver::Release(m_osPublicName); @@ -150,89 +144,16 @@ FGdbDataSource::~FGdbDataSource() } } -/************************************************************************/ -/* FixIndexes() */ -/************************************************************************/ - -int FGdbDataSource::FixIndexes() -{ - int bRet = TRUE; - if (m_pConnection && m_pConnection->IsFIDHackInProgress()) - { - m_pConnection->CloseGeodatabase(); - - const char *const apszDrivers[2] = {"OpenFileGDB", nullptr}; - const std::string osSystemCatalog = - CPLFormFilenameSafe(m_osFSName, "a00000001.gdbtable", nullptr); - auto poOpenFileGDBDS = std::unique_ptr( - GDALDataset::Open(osSystemCatalog.c_str(), GDAL_OF_VECTOR, - apszDrivers, nullptr, nullptr)); - if (poOpenFileGDBDS == nullptr || - poOpenFileGDBDS->GetLayer(0) == nullptr) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot open %s with OpenFileGDB driver. " - "Should not happen. Some layers will be corrupted", - osSystemCatalog.c_str()); - bRet = FALSE; - } - else - { - OGRLayer *poLayer = poOpenFileGDBDS->GetLayer(0); - size_t count = m_layers.size(); - for (size_t i = 0; i < count; ++i) - { - FGdbLayer *poFgdbLayer = dynamic_cast(m_layers[i]); - if (!poFgdbLayer) - continue; - - if (poFgdbLayer->m_oMapOGRFIDToFGDBFID.empty()) - continue; - CPLString osFilter = "name = '"; - osFilter += m_layers[i]->GetName(); - osFilter += "'"; - poLayer->SetAttributeFilter(osFilter); - poLayer->ResetReading(); - OGRFeature *poF = poLayer->GetNextFeature(); - if (poF == nullptr) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot find filename for layer %s", - m_layers[i]->GetName()); - bRet = FALSE; - } - else - { - if (!poFgdbLayer->EditIndexesForFIDHack( - CPLFormFilenameSafe( - m_osFSName, - CPLSPrintf("a%08x", (int)poF->GetFID()), - nullptr) - .c_str())) - { - bRet = FALSE; - } - } - delete poF; - } - } - - m_pConnection->SetFIDHackInProgress(FALSE); - } - return bRet; -} - /************************************************************************/ /* Open() */ /************************************************************************/ -int FGdbDataSource::Open(const char *pszNewName, int bUpdate, +int FGdbDataSource::Open(const char *pszNewName, int /* bUpdate */, const char *pszPublicName) { m_osFSName = pszNewName; m_osPublicName = (pszPublicName) ? pszPublicName : pszNewName; m_pGeodatabase = m_pConnection->GetGDB(); - m_bUpdate = CPL_TO_BOOL(bUpdate); m_poOpenFileGDBDrv = (GDALDriver *)GDALGetDriverByName("OpenFileGDB"); return LoadLayers(L"\\"); @@ -253,83 +174,10 @@ int FGdbDataSource::CloseInternal(int bCloseGeodatabase) } } - int bRet = FixIndexes(); if (m_pConnection && bCloseGeodatabase) m_pConnection->CloseGeodatabase(); m_pGeodatabase = nullptr; - return bRet; -} - -/************************************************************************/ -/* ReOpen() */ -/************************************************************************/ - -int FGdbDataSource::ReOpen() -{ - CPLAssert(m_pGeodatabase == nullptr); - - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL_REOPEN", ""), "CASE1") || - !m_pConnection->OpenGeodatabase(m_osFSName)) - { - CPLError(CE_Failure, CPLE_AppDefined, "Cannot reopen %s", - m_osFSName.c_str()); - return FALSE; - } - - FGdbDataSource *pDS = - new FGdbDataSource(m_bUseDriverMutex, m_pConnection, m_bUseOpenFileGDB); - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL_REOPEN", ""), "CASE2") || - !pDS->Open(m_osPublicName, TRUE, m_osFSName)) - { - pDS->m_bUseDriverMutex = false; - delete pDS; - CPLError(CE_Failure, CPLE_AppDefined, "Cannot reopen %s", - m_osFSName.c_str()); - return FALSE; - } - - int bRet = TRUE; - size_t count = m_layers.size(); - for (size_t i = 0; i < count; ++i) - { - FGdbLayer *pNewLayer = - (FGdbLayer *)pDS->GetLayerByName(m_layers[i]->GetName()); - FGdbLayer *poFgdbLayer = dynamic_cast(m_layers[i]); - if (pNewLayer && - !EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL_REOPEN", ""), "CASE3")) - { - if (poFgdbLayer) - { - poFgdbLayer->m_pTable = pNewLayer->m_pTable; - } - pNewLayer->m_pTable = nullptr; - if (poFgdbLayer) - { - poFgdbLayer->m_pEnumRows = pNewLayer->m_pEnumRows; - } - pNewLayer->m_pEnumRows = nullptr; - } - else - { - CPLError(CE_Failure, CPLE_AppDefined, "Cannot reopen %s", - m_layers[i]->GetName()); - bRet = FALSE; - } - if (poFgdbLayer) - { - poFgdbLayer->m_oMapOGRFIDToFGDBFID.clear(); - poFgdbLayer->m_oMapFGDBFIDToOGRFID.clear(); - } - } - - m_pGeodatabase = pDS->m_pGeodatabase; - pDS->m_pGeodatabase = nullptr; - - pDS->m_bUseDriverMutex = false; - pDS->m_pConnection = nullptr; - delete pDS; - - return bRet; + return true; } /************************************************************************/ @@ -597,68 +445,13 @@ bool FGdbDataSource::LoadLayers(const std::wstring &root) return true; } -/************************************************************************/ -/* DeleteLayer() */ -/************************************************************************/ - -OGRErr FGdbDataSource::DeleteLayer(int iLayer) -{ - if (!m_bUpdate || m_pGeodatabase == nullptr) - return OGRERR_FAILURE; - - if (iLayer < 0 || iLayer >= static_cast(m_layers.size())) - return OGRERR_FAILURE; - - FGdbLayer *poBaseLayer = dynamic_cast(m_layers[iLayer]); - if (!poBaseLayer) - return OGRERR_FAILURE; - - // Fetch FGDBAPI Table before deleting OGR layer object - - // Table* pTable = poBaseLayer->GetTable(); - - std::string name = poBaseLayer->GetLayerDefn()->GetName(); - std::wstring strPath = poBaseLayer->GetTablePath(); - std::wstring strType = poBaseLayer->GetType(); - - // delete OGR layer - delete m_layers[iLayer]; - - // pTable = NULL; // OGR Layer had ownership of FGDB Table - - m_layers.erase(m_layers.begin() + iLayer); - - long hr; - - if (FAILED(hr = m_pGeodatabase->Delete(strPath, strType))) - { - CPLError(CE_Warning, CPLE_AppDefined, - "%s was not deleted however it has been closed", name.c_str()); - GDBErr(hr, "Failed deleting dataset"); - return OGRERR_FAILURE; - } - - return OGRERR_NONE; -} - /************************************************************************/ /* TestCapability() */ /************************************************************************/ int FGdbDataSource::TestCapability(const char *pszCap) { - if (EQUAL(pszCap, ODsCCreateLayer)) - return m_bUpdate; - - else if (EQUAL(pszCap, ODsCDeleteLayer)) - return m_bUpdate; - else if (EQUAL(pszCap, ODsCRandomLayerWrite)) - return m_bUpdate; - else if (EQUAL(pszCap, ODsCAddFieldDomain) || - EQUAL(pszCap, ODsCDeleteFieldDomain) || - EQUAL(pszCap, ODsCUpdateFieldDomain)) - return m_bUpdate; - else if (EQUAL(pszCap, ODsCMeasuredGeometries)) + if (EQUAL(pszCap, ODsCMeasuredGeometries)) return TRUE; else if (EQUAL(pszCap, ODsCZGeometries)) return TRUE; @@ -680,32 +473,6 @@ OGRLayer *FGdbDataSource::GetLayer(int iLayer) return m_layers[iLayer]; } -/************************************************************************/ -/* ICreateLayer() */ -/* */ -/* See FGdbLayer::Create for creation options */ -/************************************************************************/ - -OGRLayer * -FGdbDataSource::ICreateLayer(const char *pszLayerName, - const OGRGeomFieldDefn *poSrcGeomFieldDefn, - CSLConstList papszOptions) -{ - if (!m_bUpdate || m_pGeodatabase == nullptr) - return nullptr; - - FGdbLayer *pLayer = new FGdbLayer(); - if (!pLayer->Create(this, pszLayerName, poSrcGeomFieldDefn, papszOptions)) - { - delete pLayer; - return nullptr; - } - - m_layers.push_back(pLayer); - - return pLayer; -} - /************************************************************************/ /* OGRFGdbSingleFeatureLayer */ /************************************************************************/ @@ -792,21 +559,9 @@ OGRLayer *FGdbDataSource::ExecuteSQL(const char *pszSQLCommand, const char *pszDialect) { - if (m_pConnection && m_pConnection->IsFIDHackInProgress()) - { - if (CloseInternal()) - ReOpen(); - } if (m_pGeodatabase == nullptr) return nullptr; - size_t count = m_layers.size(); - for (size_t i = 0; i < count; ++i) - { - if (FGdbLayer *poFgdbLayer = dynamic_cast(m_layers[i])) - poFgdbLayer->EndBulkLoad(); - } - /* -------------------------------------------------------------------- */ /* Use generic implementation for recognized dialects */ /* -------------------------------------------------------------------- */ @@ -935,39 +690,6 @@ void FGdbDataSource::ReleaseResultSet(OGRLayer *poResultsSet) delete poResultsSet; } -/************************************************************************/ -/* HasPerLayerCopyingForTransaction() */ -/************************************************************************/ - -int FGdbDataSource::HasPerLayerCopyingForTransaction() -{ - if (bPerLayerCopyingForTransaction >= 0) - return bPerLayerCopyingForTransaction; -#ifdef _WIN32 - bPerLayerCopyingForTransaction = FALSE; -#else - bPerLayerCopyingForTransaction = - m_poOpenFileGDBDrv != nullptr && - CPLTestBool( - CPLGetConfigOption("FGDB_PER_LAYER_COPYING_TRANSACTION", "TRUE")); -#endif - return bPerLayerCopyingForTransaction; -} - -/************************************************************************/ -/* SetSymlinkFlagOnAllLayers() */ -/************************************************************************/ - -void FGdbDataSource::SetSymlinkFlagOnAllLayers() -{ - size_t count = m_layers.size(); - for (size_t i = 0; i < count; ++i) - { - if (FGdbLayer *poFgdbLayer = dynamic_cast(m_layers[i])) - poFgdbLayer->SetSymlinkFlag(); - } -} - /************************************************************************/ /* GetFieldDomain() */ /************************************************************************/ @@ -1019,114 +741,6 @@ std::vector FGdbDataSource::GetFieldDomainNames(CSLConstList) const return oDomainNamesList; } -/************************************************************************/ -/* AddFieldDomain() */ -/************************************************************************/ - -bool FGdbDataSource::AddFieldDomain(std::unique_ptr &&domain, - std::string &failureReason) -{ - const std::string domainName(domain->GetName()); - if (!m_bUpdate) - { - CPLError(CE_Failure, CPLE_NotSupported, - "AddFieldDomain() not supported on read-only dataset"); - return false; - } - - if (GetFieldDomain(domainName) != nullptr) - { - failureReason = "A domain of identical name already exists"; - return false; - } - - std::string osXML = - BuildXMLFieldDomainDef(domain.get(), true, failureReason); - if (osXML.empty()) - { - return false; - } - - const auto hr = m_pGeodatabase->CreateDomain(osXML); - if (FAILED(hr)) - { - GDBErr(hr, "Failed in CreateDomain()"); - CPLDebug("FileGDB", "XML of domain: %s", osXML.c_str()); - return false; - } - - m_oMapFieldDomains[domainName] = std::move(domain); - - return true; -} - -/************************************************************************/ -/* DeleteFieldDomain() */ -/************************************************************************/ - -bool FGdbDataSource::DeleteFieldDomain(const std::string &name, - std::string & /*failureReason*/) -{ - if (!m_bUpdate) - { - CPLError(CE_Failure, CPLE_NotSupported, - "DeleteFieldDomain() not supported on read-only dataset"); - return false; - } - - const auto hr = m_pGeodatabase->DeleteDomain(StringToWString(name)); - if (FAILED(hr)) - { - GDBErr(hr, "Failed in DeleteDomain()"); - return false; - } - - m_oMapFieldDomains.erase(name); - - return true; -} - -/************************************************************************/ -/* UpdateFieldDomain() */ -/************************************************************************/ - -bool FGdbDataSource::UpdateFieldDomain(std::unique_ptr &&domain, - std::string &failureReason) -{ - const std::string domainName(domain->GetName()); - if (!m_bUpdate) - { - CPLError(CE_Failure, CPLE_NotSupported, - "AddFieldDomain() not supported on read-only dataset"); - return false; - } - - if (GetFieldDomain(domainName) == nullptr) - { - failureReason = "The domain should already exist to be updated"; - return false; - } - - std::string osXML = - BuildXMLFieldDomainDef(domain.get(), true, failureReason); - if (osXML.empty()) - { - return false; - } - - const auto hr = m_pGeodatabase->AlterDomain(osXML); - if (FAILED(hr)) - { - GDBErr(hr, "Failed in AlterDomain()"); - CPLDebug("FileGDB", "XML of domain: %s", osXML.c_str()); - return false; - } - - m_oMapFieldDomains[domainName] = std::move(domain); - - return true; -} - /************************************************************************/ /* GetRelationshipNames() */ /************************************************************************/ diff --git a/ogr/ogrsf_frmts/filegdb/FGdbDriver.cpp b/ogr/ogrsf_frmts/filegdb/FGdbDriver.cpp index ba4ae53c927b..892491bd249f 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbDriver.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbDriver.cpp @@ -25,7 +25,6 @@ extern "C" void RegisterOGRFileGDB(); static std::map *poMapConnections = nullptr; CPLMutex *FGdbDriver::hMutex = nullptr; -FGdbTransactionManager *FGdbDriver::m_poTransactionManager = nullptr; /************************************************************************/ /* OGRFileGDBDriverUnload() */ @@ -39,8 +38,6 @@ static void OGRFileGDBDriverUnload(GDALDriver *) if (FGdbDriver::hMutex != nullptr) CPLDestroyMutex(FGdbDriver::hMutex); FGdbDriver::hMutex = nullptr; - delete FGdbDriver::m_poTransactionManager; - FGdbDriver::m_poTransactionManager = nullptr; delete poMapConnections; poMapConnections = nullptr; } @@ -51,6 +48,9 @@ static void OGRFileGDBDriverUnload(GDALDriver *) static GDALDataset *OGRFileGDBDriverOpen(GDALOpenInfo *poOpenInfo) { + if (poOpenInfo->eAccess == GA_Update) + return nullptr; + const char *pszFilename = poOpenInfo->pszFilename; // @MAY_USE_OPENFILEGDB may be set to NO by the OpenFileGDB driver in its // Open() method when it detects that a dataset includes compressed tables @@ -84,7 +84,6 @@ static GDALDataset *OGRFileGDBDriverOpen(GDALOpenInfo *poOpenInfo) } } - const bool bUpdate = poOpenInfo->eAccess == GA_Update; long hr; CPLMutexHolderD(&FGdbDriver::hMutex); @@ -94,14 +93,6 @@ static GDALDataset *OGRFileGDBDriverOpen(GDALOpenInfo *poOpenInfo) FGdbDatabaseConnection *pConnection = (*poMapConnections)[pszFilename]; if (pConnection != nullptr) { - if (pConnection->IsFIDHackInProgress()) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot open geodatabase at the moment since it is in " - "'FID hack mode'"); - return nullptr; - } - pConnection->m_nRefCount++; CPLDebug("FileGDB", "ref_count of %s = %d now", pszFilename, pConnection->m_nRefCount); @@ -115,8 +106,7 @@ static GDALDataset *OGRFileGDBDriverOpen(GDALOpenInfo *poOpenInfo) { delete pGeoDatabase; - if (OGRGetDriverByName("OpenFileGDB") != nullptr && - bUpdate == FALSE) + if (OGRGetDriverByName("OpenFileGDB") != nullptr) { std::wstring fgdb_error_desc_w; std::string fgdb_error_desc("Unknown error"); @@ -149,7 +139,7 @@ static GDALDataset *OGRFileGDBDriverOpen(GDALOpenInfo *poOpenInfo) pDS = new FGdbDataSource(true, pConnection, bUseOpenFileGDB); - if (!pDS->Open(pszFilename, bUpdate, nullptr)) + if (!pDS->Open(pszFilename, /*bUpdate = */ FALSE, nullptr)) { delete pDS; return nullptr; @@ -158,11 +148,7 @@ static GDALDataset *OGRFileGDBDriverOpen(GDALOpenInfo *poOpenInfo) { auto poMutexedDS = new OGRMutexedDataSource(pDS, TRUE, FGdbDriver::hMutex, TRUE); - if (bUpdate) - return OGRCreateEmulatedTransactionDataSourceWrapper( - poMutexedDS, FGdbDriver::GetTransactionManager(), TRUE, FALSE); - else - return poMutexedDS; + return poMutexedDS; } } @@ -175,582 +161,18 @@ OGRFileGDBDriverCreate(const char *pszName, CPL_UNUSED int nBands, CPL_UNUSED int nXSize, CPL_UNUSED int nYSize, CPL_UNUSED GDALDataType eDT, char **papszOptions) { - long hr; - Geodatabase *pGeodatabase; - std::wstring wconn = StringToWString(pszName); - int bUpdate = TRUE; // If we're creating, we must be writing. - VSIStatBuf stat; - - CPLMutexHolderD(&FGdbDriver::hMutex); - - /* We don't support options yet, so warn if they send us some */ - if (papszOptions) - { - /* TODO: warning, ignoring options */ - } - - /* Only accept names of form "filename.gdb" and */ - /* also .gdb.zip to be able to return FGDB with MapServer OGR output (#4199) - */ - const std::string osExt = CPLGetExtensionSafe(pszName); - if (!(EQUAL(osExt.c_str(), "gdb") || EQUAL(osExt.c_str(), "zip"))) - { - CPLError(CE_Failure, CPLE_AppDefined, - "FGDB data source name must use 'gdb' extension.\n"); - return nullptr; - } - - /* Don't try to create on top of something already there */ - if (CPLStat(pszName, &stat) == 0) - { - CPLError(CE_Failure, CPLE_AppDefined, "%s already exists.\n", pszName); - return nullptr; - } - - /* Try to create the geodatabase */ - pGeodatabase = - new Geodatabase; // Create on heap so we can store it in the Datasource - hr = CreateGeodatabase(wconn, *pGeodatabase); - - /* Handle creation errors */ - if (S_OK != hr) - { - const char *errstr = "Error creating geodatabase (%s).\n"; - if (hr == -2147220653) - errstr = "File already exists (%s).\n"; - delete pGeodatabase; - CPLError(CE_Failure, CPLE_AppDefined, errstr, pszName); - return nullptr; - } - - FGdbDatabaseConnection *pConnection = - new FGdbDatabaseConnection(pszName, pGeodatabase); - - if (poMapConnections == nullptr) - poMapConnections = new std::map(); - (*poMapConnections)[pszName] = pConnection; - - /* Ready to embed the Geodatabase in an OGR Datasource */ - FGdbDataSource *pDS = new FGdbDataSource(true, pConnection, true); - if (!pDS->Open(pszName, bUpdate, nullptr)) + auto poOpenFileGDBDriver = + GetGDALDriverManager()->GetDriverByName("OpenFileGDB"); + if (poOpenFileGDBDriver) { - delete pDS; - return nullptr; + CPLError( + CE_Warning, CPLE_AppDefined, + "FileGDB driver has no longer any direct creation capabilities. " + "Forwarding to OpenFileGDB driver"); + return poOpenFileGDBDriver->Create(pszName, 0, 0, 0, GDT_Unknown, + papszOptions); } - else - return OGRCreateEmulatedTransactionDataSourceWrapper( - new OGRMutexedDataSource(pDS, TRUE, FGdbDriver::hMutex, TRUE), - FGdbDriver::GetTransactionManager(), TRUE, FALSE); -} - -/************************************************************************/ -/* StartTransaction() */ -/************************************************************************/ - -OGRErr FGdbTransactionManager::StartTransaction(GDALDataset *&poDSInOut, - int &bOutHasReopenedDS) -{ - CPLMutexHolderOptionalLockD(FGdbDriver::hMutex); - - bOutHasReopenedDS = FALSE; - - auto poMutexedDS = (OGRMutexedDataSource *)poDSInOut; - FGdbDataSource *poDS = - cpl::down_cast(poMutexedDS->GetBaseDataSource()); - if (!poDS->GetUpdate()) - return OGRERR_FAILURE; - FGdbDatabaseConnection *pConnection = poDS->GetConnection(); - if (pConnection->GetRefCount() != 1) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot start transaction as database is opened in another " - "connection"); - return OGRERR_FAILURE; - } - if (pConnection->IsLocked()) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Transaction is already in progress"); - return OGRERR_FAILURE; - } - - bOutHasReopenedDS = TRUE; - - CPLString osName(poMutexedDS->GetDescription()); - CPLString osNameOri(osName); - if (osName.back() == '/' || osName.back() == '\\') - osName.pop_back(); - -#ifndef _WIN32 - int bPerLayerCopyingForTransaction = - poDS->HasPerLayerCopyingForTransaction(); -#endif - - pConnection->m_nRefCount++; - delete poDSInOut; - poDSInOut = nullptr; - poMutexedDS = nullptr; - poDS = nullptr; - - pConnection->CloseGeodatabase(); - - const CPLString osEditedName(CPLString(osName).append(".ogredited")); - - CPLPushErrorHandler(CPLQuietErrorHandler); - CPL_IGNORE_RET_VAL(CPLUnlinkTree(osEditedName)); - CPLPopErrorHandler(); - - OGRErr eErr = OGRERR_NONE; - - CPLString osDatabaseToReopen; -#ifndef _WIN32 - if (bPerLayerCopyingForTransaction) - { - int bError = FALSE; - - if (VSIMkdir(osEditedName, 0755) != 0) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot create directory '%s'.", osEditedName.c_str()); - bError = TRUE; - } - - // Only copy a0000000X.Y files with X >= 1 && X <= 8, gdb and timestamps - // and symlink others - char **papszFiles = VSIReadDir(osName); - for (char **papszIter = papszFiles; !bError && *papszIter; ++papszIter) - { - if (strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0) - continue; - if (((*papszIter)[0] == 'a' && atoi((*papszIter) + 1) >= 1 && - atoi((*papszIter) + 1) <= 8) || - EQUAL(*papszIter, "gdb") || EQUAL(*papszIter, "timestamps")) - { - if (CPLCopyFile( - CPLFormFilenameSafe(osEditedName, *papszIter, nullptr) - .c_str(), - CPLFormFilenameSafe(osName, *papszIter, nullptr) - .c_str()) != 0) - { - bError = TRUE; - CPLError(CE_Failure, CPLE_AppDefined, "Cannot copy %s", - *papszIter); - } - } - else - { - CPLString osSourceFile; - if (CPLIsFilenameRelative(osName)) - osSourceFile = CPLFormFilenameSafe( - CPLSPrintf("../%s", CPLGetFilename(osName.c_str())), - *papszIter, nullptr); - else - osSourceFile = osName; - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") || - CPLSymlink(osSourceFile, - CPLFormFilenameSafe(osEditedName.c_str(), - *papszIter, nullptr) - .c_str(), - nullptr) != 0) - { - bError = TRUE; - CPLError(CE_Failure, CPLE_AppDefined, "Cannot symlink %s", - *papszIter); - } - } - } - CSLDestroy(papszFiles); - - if (bError) - { - eErr = OGRERR_FAILURE; - osDatabaseToReopen = osName; - } - else - osDatabaseToReopen = osEditedName; - } - else -#endif - { - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") || - CPLCopyTree(osEditedName, osName) != 0) - { - CPLError(CE_Failure, CPLE_AppDefined, "Cannot backup geodatabase"); - eErr = OGRERR_FAILURE; - osDatabaseToReopen = osName; - } - else - osDatabaseToReopen = osEditedName; - } - - pConnection->m_pGeodatabase = new Geodatabase; - long hr = ::OpenGeodatabase(StringToWString(osDatabaseToReopen), - *(pConnection->m_pGeodatabase)); - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") || FAILED(hr)) - { - delete pConnection->m_pGeodatabase; - pConnection->m_pGeodatabase = nullptr; - FGdbDriver::Release(osName); - GDBErr(hr, CPLSPrintf("Failed to open %s. Dataset should be closed", - osDatabaseToReopen.c_str())); - - return OGRERR_FAILURE; - } - - FGdbDataSource *pDS = new FGdbDataSource(true, pConnection, true); - pDS->Open(osDatabaseToReopen, TRUE, osNameOri); - -#ifndef _WIN32 - if (eErr == OGRERR_NONE && bPerLayerCopyingForTransaction) - { - pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction); - pDS->SetSymlinkFlagOnAllLayers(); - } -#endif - - poDSInOut = new OGRMutexedDataSource(pDS, TRUE, FGdbDriver::hMutex, TRUE); - - if (eErr == OGRERR_NONE) - pConnection->SetLocked(TRUE); - return eErr; -} - -/************************************************************************/ -/* CommitTransaction() */ -/************************************************************************/ - -OGRErr FGdbTransactionManager::CommitTransaction(GDALDataset *&poDSInOut, - int &bOutHasReopenedDS) -{ - CPLMutexHolderOptionalLockD(FGdbDriver::hMutex); - - bOutHasReopenedDS = FALSE; - - OGRMutexedDataSource *poMutexedDS = (OGRMutexedDataSource *)poDSInOut; - FGdbDataSource *poDS = (FGdbDataSource *)poMutexedDS->GetBaseDataSource(); - FGdbDatabaseConnection *pConnection = poDS->GetConnection(); - if (!pConnection->IsLocked()) - { - CPLError(CE_Failure, CPLE_NotSupported, "No transaction in progress"); - return OGRERR_FAILURE; - } - - bOutHasReopenedDS = TRUE; - - CPLString osName(poMutexedDS->GetDescription()); - CPLString osNameOri(osName); - if (osName.back() == '/' || osName.back() == '\\') - osName.pop_back(); - -#ifndef _WIN32 - int bPerLayerCopyingForTransaction = - poDS->HasPerLayerCopyingForTransaction(); -#endif - - pConnection->m_nRefCount++; - delete poDSInOut; - poDSInOut = nullptr; - poMutexedDS = nullptr; - poDS = nullptr; - - pConnection->CloseGeodatabase(); - - CPLString osEditedName(osName); - osEditedName += ".ogredited"; - -#ifndef _WIN32 - if (bPerLayerCopyingForTransaction) - { - int bError = FALSE; - char **papszFiles; - std::vector aosTmpFilesToClean; - - // Check for files present in original copy that are not in edited copy - // That is to say deleted layers - papszFiles = VSIReadDir(osName); - for (char **papszIter = papszFiles; !bError && *papszIter; ++papszIter) - { - if (strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0) - continue; - VSIStatBufL sStat; - if ((*papszIter)[0] == 'a' && - VSIStatL(CPLFormFilenameSafe(osEditedName, *papszIter, nullptr) - .c_str(), - &sStat) != 0) - { - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") || - VSIRename(CPLFormFilenameSafe(osName, *papszIter, nullptr) - .c_str(), - CPLFormFilenameSafe(osName, *papszIter, "tmp") - .c_str()) != 0) - { - CPLError( - CE_Failure, CPLE_AppDefined, "Cannot rename %s to %s", - CPLFormFilenameSafe(osName, *papszIter, nullptr) - .c_str(), - CPLFormFilenameSafe(osName, *papszIter, "tmp").c_str()); - bError = TRUE; - } - else - aosTmpFilesToClean.push_back( - CPLFormFilenameSafe(osName, *papszIter, "tmp")); - } - } - CSLDestroy(papszFiles); - - // Move modified files from edited directory to main directory - papszFiles = VSIReadDir(osEditedName); - for (char **papszIter = papszFiles; !bError && *papszIter; ++papszIter) - { - if (strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0) - continue; - struct stat sStat; - if (lstat(CPLFormFilenameSafe(osEditedName, *papszIter, nullptr) - .c_str(), - &sStat) != 0) - { - CPLError(CE_Failure, CPLE_AppDefined, "Cannot stat %s", - CPLFormFilenameSafe(osEditedName, *papszIter, nullptr) - .c_str()); - bError = TRUE; - } - else if (!S_ISLNK(sStat.st_mode)) - { - // If there was such a file in original directory, first rename - // it as a temporary file - if (lstat(CPLFormFilenameSafe(osName, *papszIter, nullptr) - .c_str(), - &sStat) == 0) - { - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), - "CASE2") || - VSIRename( - CPLFormFilenameSafe(osName, *papszIter, nullptr) - .c_str(), - CPLFormFilenameSafe(osName, *papszIter, "tmp") - .c_str()) != 0) - { - CPLError( - CE_Failure, CPLE_AppDefined, - "Cannot rename %s to %s", - CPLFormFilenameSafe(osName, *papszIter, nullptr) - .c_str(), - CPLFormFilenameSafe(osName, *papszIter, "tmp") - .c_str()); - bError = TRUE; - } - else - aosTmpFilesToClean.push_back( - CPLFormFilenameSafe(osName, *papszIter, "tmp")); - } - if (!bError) - { - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), - "CASE3") || - CPLMoveFile( - CPLFormFilenameSafe(osName, *papszIter, nullptr) - .c_str(), - CPLFormFilenameSafe(osEditedName, *papszIter, - nullptr) - .c_str()) != 0) - { - CPLError( - CE_Failure, CPLE_AppDefined, "Cannot move %s to %s", - CPLFormFilenameSafe(osEditedName, *papszIter, - nullptr) - .c_str(), - CPLFormFilenameSafe(osName, *papszIter, nullptr) - .c_str()); - bError = TRUE; - } - else - CPLDebug( - "FileGDB", "Move %s to %s", - CPLFormFilenameSafe(osEditedName, *papszIter, - nullptr) - .c_str(), - CPLFormFilenameSafe(osName, *papszIter, nullptr) - .c_str()); - } - } - } - CSLDestroy(papszFiles); - - if (!bError) - { - for (size_t i = 0; i < aosTmpFilesToClean.size(); i++) - { - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE4") || - VSIUnlink(aosTmpFilesToClean[i]) != 0) - { - CPLError(CE_Warning, CPLE_AppDefined, - "Cannot remove %s. Manual cleanup required", - aosTmpFilesToClean[i].c_str()); - } - } - } - - if (bError) - { - CPLError( - CE_Failure, CPLE_AppDefined, - "An error occurred while moving files from %s back to %s. " - "Manual cleaning must be done and dataset should be closed", - osEditedName.c_str(), osName.c_str()); - pConnection->SetLocked(FALSE); - FGdbDriver::Release(osName); - return OGRERR_FAILURE; - } - else if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE5") || - CPLUnlinkTree(osEditedName) != 0) - { - CPLError(CE_Warning, CPLE_AppDefined, - "Cannot remove %s. Manual cleanup required", - osEditedName.c_str()); - } - } - else -#endif - { - CPLString osTmpName(osName); - osTmpName += ".ogrtmp"; - - /* Install the backup copy as the main database in 3 steps : */ - /* first rename the main directory in .tmp */ - /* then rename the edited copy under regular name */ - /* and finally dispose the .tmp directory */ - /* That way there's no risk definitely losing data */ - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") || - VSIRename(osName, osTmpName) != 0) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot rename %s to %s. Edited database during " - "transaction is in %s" - "Dataset should be closed", - osName.c_str(), osTmpName.c_str(), osEditedName.c_str()); - pConnection->SetLocked(FALSE); - FGdbDriver::Release(osName); - return OGRERR_FAILURE; - } - - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") || - VSIRename(osEditedName, osName) != 0) - { - CPLError( - CE_Failure, CPLE_AppDefined, - "Cannot rename %s to %s. The original geodatabase is in '%s'. " - "Dataset should be closed", - osEditedName.c_str(), osName.c_str(), osTmpName.c_str()); - pConnection->SetLocked(FALSE); - FGdbDriver::Release(osName); - return OGRERR_FAILURE; - } - - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE3") || - CPLUnlinkTree(osTmpName) != 0) - { - CPLError(CE_Warning, CPLE_AppDefined, - "Cannot remove %s. Manual cleanup required", - osTmpName.c_str()); - } - } - - pConnection->m_pGeodatabase = new Geodatabase; - long hr = ::OpenGeodatabase(StringToWString(osName), - *(pConnection->m_pGeodatabase)); - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE_REOPEN") || - FAILED(hr)) - { - delete pConnection->m_pGeodatabase; - pConnection->m_pGeodatabase = nullptr; - pConnection->SetLocked(FALSE); - FGdbDriver::Release(osName); - GDBErr(hr, "Failed to re-open Geodatabase. Dataset should be closed"); - return OGRERR_FAILURE; - } - - FGdbDataSource *pDS = new FGdbDataSource(true, pConnection, true); - pDS->Open(osNameOri, TRUE, nullptr); - // pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction); - poDSInOut = new OGRMutexedDataSource(pDS, TRUE, FGdbDriver::hMutex, TRUE); - - pConnection->SetLocked(FALSE); - - return OGRERR_NONE; -} - -/************************************************************************/ -/* RollbackTransaction() */ -/************************************************************************/ - -OGRErr FGdbTransactionManager::RollbackTransaction(GDALDataset *&poDSInOut, - int &bOutHasReopenedDS) -{ - CPLMutexHolderOptionalLockD(FGdbDriver::hMutex); - - bOutHasReopenedDS = FALSE; - - OGRMutexedDataSource *poMutexedDS = (OGRMutexedDataSource *)poDSInOut; - FGdbDataSource *poDS = (FGdbDataSource *)poMutexedDS->GetBaseDataSource(); - FGdbDatabaseConnection *pConnection = poDS->GetConnection(); - if (!pConnection->IsLocked()) - { - CPLError(CE_Failure, CPLE_NotSupported, "No transaction in progress"); - return OGRERR_FAILURE; - } - - bOutHasReopenedDS = TRUE; - - CPLString osName(poMutexedDS->GetDescription()); - CPLString osNameOri(osName); - if (osName.back() == '/' || osName.back() == '\\') - osName.pop_back(); - - // int bPerLayerCopyingForTransaction = - // poDS->HasPerLayerCopyingForTransaction(); - - pConnection->m_nRefCount++; - delete poDSInOut; - poDSInOut = nullptr; - poMutexedDS = nullptr; - poDS = nullptr; - - pConnection->CloseGeodatabase(); - - CPLString osEditedName(osName); - osEditedName += ".ogredited"; - - OGRErr eErr = OGRERR_NONE; - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") || - CPLUnlinkTree(osEditedName) != 0) - { - CPLError(CE_Warning, CPLE_AppDefined, - "Cannot remove %s. Manual cleanup required", - osEditedName.c_str()); - eErr = OGRERR_FAILURE; - } - - pConnection->m_pGeodatabase = new Geodatabase; - long hr = ::OpenGeodatabase(StringToWString(osName), - *(pConnection->m_pGeodatabase)); - if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") || FAILED(hr)) - { - delete pConnection->m_pGeodatabase; - pConnection->m_pGeodatabase = nullptr; - pConnection->SetLocked(FALSE); - FGdbDriver::Release(osName); - GDBErr(hr, "Failed to re-open Geodatabase. Dataset should be closed"); - return OGRERR_FAILURE; - } - - FGdbDataSource *pDS = new FGdbDataSource(true, pConnection, true); - pDS->Open(osNameOri, TRUE, nullptr); - // pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction); - poDSInOut = new OGRMutexedDataSource(pDS, TRUE, FGdbDriver::hMutex, TRUE); - - pConnection->SetLocked(FALSE); - - return eErr; + return nullptr; } /***********************************************************************/ @@ -779,18 +201,6 @@ void FGdbDriver::Release(const char *pszName) } } -/***********************************************************************/ -/* GetTransactionManager() */ -/***********************************************************************/ - -FGdbTransactionManager *FGdbDriver::GetTransactionManager() -{ - CPLMutexHolderD(&FGdbDriver::hMutex); - if (m_poTransactionManager == nullptr) - m_poTransactionManager = new FGdbTransactionManager(); - return m_poTransactionManager; -} - /***********************************************************************/ /* CloseGeodatabase() */ /***********************************************************************/ diff --git a/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp b/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp index 5b22f0df0ee6..2738d820d32d 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp @@ -25,7 +25,6 @@ #include "filegdb_gdbtoogrfieldtype.h" #include "filegdb_fielddomain.h" #include "filegdb_coordprec_read.h" -#include "filegdb_coordprec_write.h" // See https://github.com/Esri/file-geodatabase-api/issues/46 // On certain FileGDB datasets with binary fields, iterating over a result set @@ -138,19 +137,11 @@ FGdbLayer::FGdbLayer() : m_pDS(nullptr), m_pTable(nullptr), m_wstrSubfields(L"*"), m_bFilterDirty(true), m_bLaunderReservedKeywords(true) { - m_bBulkLoadAllowed = -1; /* uninitialized */ - m_bBulkLoadInProgress = FALSE; m_pEnumRows = new EnumRows; #ifdef EXTENT_WORKAROUND m_bLayerEnvelopeValid = false; - m_bLayerJustCreated = false; #endif - m_papszOptions = nullptr; - m_bCreateMultipatch = FALSE; - m_nResyncThreshold = - atoi(CPLGetConfigOption("FGDB_RESYNC_THRESHOLD", "1000000")); - m_bSymlinkFlag = FALSE; } /************************************************************************/ @@ -164,9 +155,6 @@ FGdbLayer::~FGdbLayer() for (size_t i = 0; i < m_apoByteArrays.size(); i++) delete m_apoByteArrays[i]; m_apoByteArrays.resize(0); - - CSLDestroy(m_papszOptions); - m_papszOptions = nullptr; } /************************************************************************/ @@ -175,8 +163,6 @@ FGdbLayer::~FGdbLayer() void FGdbLayer::CloseGDBObjects() { - EndBulkLoad(); - #ifdef EXTENT_WORKAROUND WorkAroundExtentProblem(); #endif @@ -190,720 +176,6 @@ void FGdbLayer::CloseGDBObjects() FGdbBaseLayer::CloseGDBObjects(); } -/************************************************************************/ -/* EditIndexesForFIDHack() */ -/************************************************************************/ - -int FGdbLayer::EditIndexesForFIDHack(const char *pszRadixTablename) -{ - // Fix FIDs in .gdbtablx, .spx and .atx's - - const CPLString osGDBTablX = - CPLResetExtensionSafe(pszRadixTablename, "gdbtablx"); - const CPLString osNewGDBTablX = - CPLResetExtensionSafe(pszRadixTablename, "gdbtablx.new"); - - if (!EditGDBTablX(osGDBTablX, osNewGDBTablX)) - { - CPLError(CE_Failure, CPLE_AppDefined, "Error occurred when editing %s", - osNewGDBTablX.c_str()); - VSIUnlink(osNewGDBTablX); - return FALSE; - } - - CPLString osDirectory(CPLGetPathSafe(pszRadixTablename)); - char **papszFiles = VSIReadDir(osDirectory); - const CPLString osBasename(CPLGetBasenameSafe(pszRadixTablename)); - int bRet = TRUE; - for (char **papszIter = papszFiles; papszIter && *papszIter; papszIter++) - { - if (strncmp(*papszIter, osBasename.c_str(), osBasename.size()) == 0 && - (EQUAL(CPLGetExtensionSafe(*papszIter).c_str(), "atx") || - EQUAL(CPLGetExtensionSafe(*papszIter).c_str(), "spx"))) - { - const CPLString osIndex( - CPLFormFilenameSafe(osDirectory, *papszIter, nullptr)); - if (!EditATXOrSPX(osIndex)) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Error occurred when editing %s", osIndex.c_str()); - bRet = FALSE; - } - } - } - CSLDestroy(papszFiles); - - CPLString osGDBTablXTmp(CPLSPrintf("%s.tmp", osGDBTablX.c_str())); - int bRet2 = (VSIRename(osGDBTablX, osGDBTablXTmp) == 0 && - VSIRename(osNewGDBTablX, osGDBTablX) == 0); - VSIUnlink(osGDBTablXTmp); - if (!bRet2) - { - CPLError(CE_Failure, CPLE_AppDefined, "Cannot rename %s to %s", - osNewGDBTablX.c_str(), osGDBTablX.c_str()); - bRet = FALSE; - } - - return bRet; -} - -/************************************************************************/ -/* EditATXOrSPX() */ -/************************************************************************/ - -/* See https://github.com/rouault/dump_gdbtable/wiki/FGDB-Spec */ -int FGdbLayer::EditATXOrSPX(const CPLString &osIndex) -{ - VSILFILE *fp = VSIFOpenL(osIndex, "rb+"); - if (fp == nullptr) - { - CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", osIndex.c_str()); - return FALSE; - } - VSIFSeekL(fp, 0, SEEK_END); - vsi_l_offset nPos = VSIFTellL(fp); - int bRet = FALSE; - int bInvalidateIndex = FALSE; - if (nPos > 22) - { - VSIFSeekL(fp, nPos - 22, SEEK_SET); - GByte nSizeIndexedValue; - if (VSIFReadL(&nSizeIndexedValue, 1, 1, fp) == 1 && - nSizeIndexedValue > 0) - { - GByte abyIndexedValue[255]; - VSIFSeekL(fp, nPos - 22 + 6, SEEK_SET); - int nDepth; - if (VSIFReadL(&nDepth, 1, 4, fp) == 4) - { - CPL_LSBPTR32(&nDepth); - - int bIndexedValueIsValid = FALSE; - int nFirstIndexAtThisValue = -1; - std::vector anPagesAtThisValue; - int bSortThisValue = FALSE; - int nLastPageVisited = 0; - bRet = EditATXOrSPX(fp, 1, nLastPageVisited, nDepth, - nSizeIndexedValue, abyIndexedValue, - bIndexedValueIsValid, - nFirstIndexAtThisValue, anPagesAtThisValue, - bSortThisValue, bInvalidateIndex); - } - } - } - VSIFCloseL(fp); - if (bInvalidateIndex) - { - // CPLDebug("FGDB", "Invalidate %s", osIndex.c_str()); - CPLError(CE_Warning, CPLE_AppDefined, "Invalidate %s", osIndex.c_str()); - VSIUnlink(osIndex); - } - return bRet; -} - -static int FGdbLayerSortATX(const void *_pa, const void *_pb) -{ - int a = CPL_LSBWORD32(*(int *)_pa); - int b = CPL_LSBWORD32(*(int *)_pb); - if (a < b) - return -1; - else if (a > b) - return 1; - CPLAssert(false); - return 0; -} - -int FGdbLayer::EditATXOrSPX(VSILFILE *fp, int nThisPage, int &nLastPageVisited, - int nDepth, int nSizeIndexedValue, - GByte *pabyLastIndexedValue, - int &bIndexedValueIsValid, - int &nFirstIndexAtThisValue, - std::vector &anPagesAtThisValue, - int &bSortThisValue, int &bInvalidateIndex) -{ - GByte abyBuffer[4096]; - - VSIFSeekL(fp, (nThisPage - 1) * 4096, SEEK_SET); - - if (nDepth == 1) - { - if (nThisPage == nLastPageVisited) - return TRUE; - - /* This page directly references features */ - int bRewritePage = FALSE; - if (VSIFReadL(abyBuffer, 1, 4096, fp) != 4096) - return FALSE; - int nNextPageID; - memcpy(&nNextPageID, abyBuffer, 4); - int nFeatures; - memcpy(&nFeatures, abyBuffer + 4, 4); - CPL_LSBPTR32(&nFeatures); - - // if( nLastPageVisited == 0 ) - // printf("nFeatures = %d\n", nFeatures); - - const int nMaxPerPages = (4096 - 12) / (4 + nSizeIndexedValue); - const int nOffsetFirstValInPage = 12 + nMaxPerPages * 4; - if (nFeatures > nMaxPerPages) - return FALSE; - for (int i = 0; i < nFeatures; i++) - { - int bNewVal = (!bIndexedValueIsValid || - memcmp(pabyLastIndexedValue, - abyBuffer + nOffsetFirstValInPage + - i * nSizeIndexedValue, - nSizeIndexedValue) != 0); - - int nFID; - memcpy(&nFID, abyBuffer + 12 + 4 * i, 4); - CPL_LSBPTR32(&nFID); - int nOGRFID = m_oMapFGDBFIDToOGRFID[nFID]; - if (nOGRFID) - { - nFID = nOGRFID; - CPL_LSBPTR32(&nOGRFID); - memcpy(abyBuffer + 12 + 4 * i, &nOGRFID, 4); - bRewritePage = TRUE; - - if (bIndexedValueIsValid && i == nFeatures - 1 && - nNextPageID == 0) - bSortThisValue = TRUE; - } - - // We must make sure that features with same indexed values are - // sorted by increasing FID, even when that spans over several - // pages - if (bSortThisValue && - (bNewVal || (i == nFeatures - 1 && nNextPageID == 0))) - { - if (anPagesAtThisValue[0] == nThisPage) - { - CPLAssert(anPagesAtThisValue.size() == 1); - int nFeaturesToSortThisPage = i - nFirstIndexAtThisValue; - if (!bNewVal && i == nFeatures - 1 && nNextPageID == 0) - nFeaturesToSortThisPage++; - CPLAssert(nFeaturesToSortThisPage > 0); - - bRewritePage = TRUE; - qsort(abyBuffer + 12 + 4 * nFirstIndexAtThisValue, - nFeaturesToSortThisPage, 4, FGdbLayerSortATX); - } - else - { - std::vector anValues; - int nFeaturesToSort = 0; - anValues.resize(anPagesAtThisValue.size() * nMaxPerPages); - - int nFeaturesToSortLastPage = i; - if (!bNewVal && i == nFeatures - 1 && nNextPageID == 0) - nFeaturesToSortLastPage++; - - for (size_t j = 0; j < anPagesAtThisValue.size(); j++) - { - int nFeaturesPrevPage; - VSIFSeekL(fp, (anPagesAtThisValue[j] - 1) * 4096 + 4, - SEEK_SET); - VSIFReadL(&nFeaturesPrevPage, 1, 4, fp); - CPL_LSBPTR32(&nFeaturesPrevPage); - if (j == 0) - { - VSIFSeekL(fp, - (anPagesAtThisValue[j] - 1) * 4096 + 12 + - 4 * nFirstIndexAtThisValue, - SEEK_SET); - VSIFReadL( - &anValues[nFeaturesToSort], 4, - nFeaturesPrevPage - nFirstIndexAtThisValue, fp); - nFeaturesToSort += - nFeaturesPrevPage - nFirstIndexAtThisValue; - } - else if (j == anPagesAtThisValue.size() - 1 && - anPagesAtThisValue[j] == nThisPage) - { - bRewritePage = TRUE; - memcpy(&anValues[nFeaturesToSort], abyBuffer + 12, - nFeaturesToSortLastPage * 4); - nFeaturesToSort += nFeaturesToSortLastPage; - } - else - { - VSIFSeekL(fp, - (anPagesAtThisValue[j] - 1) * 4096 + 12, - SEEK_SET); - VSIFReadL(&anValues[nFeaturesToSort], 4, - nFeaturesPrevPage, fp); - nFeaturesToSort += nFeaturesPrevPage; - } - } - - qsort(&anValues[0], nFeaturesToSort, 4, FGdbLayerSortATX); - - nFeaturesToSort = 0; - for (size_t j = 0; j < anPagesAtThisValue.size(); j++) - { - int nFeaturesPrevPage; - VSIFSeekL(fp, (anPagesAtThisValue[j] - 1) * 4096 + 4, - SEEK_SET); - VSIFReadL(&nFeaturesPrevPage, 1, 4, fp); - CPL_LSBPTR32(&nFeaturesPrevPage); - if (j == 0) - { - VSIFSeekL(fp, - (anPagesAtThisValue[j] - 1) * 4096 + 12 + - 4 * nFirstIndexAtThisValue, - SEEK_SET); - VSIFWriteL( - &anValues[nFeaturesToSort], 4, - nFeaturesPrevPage - nFirstIndexAtThisValue, fp); - nFeaturesToSort += - nFeaturesPrevPage - nFirstIndexAtThisValue; - } - else if (j == anPagesAtThisValue.size() - 1 && - anPagesAtThisValue[j] == nThisPage) - { - memcpy(abyBuffer + 12, &anValues[nFeaturesToSort], - nFeaturesToSortLastPage * 4); - nFeaturesToSort += nFeaturesToSortLastPage; - } - else - { - VSIFSeekL(fp, - (anPagesAtThisValue[j] - 1) * 4096 + 12, - SEEK_SET); - VSIFWriteL(&anValues[nFeaturesToSort], 4, - nFeaturesPrevPage, fp); - nFeaturesToSort += nFeaturesPrevPage; - } - } - } - } - - if (bNewVal) - { - nFirstIndexAtThisValue = i; - anPagesAtThisValue.clear(); - anPagesAtThisValue.push_back(nThisPage); - - memcpy(pabyLastIndexedValue, - abyBuffer + nOffsetFirstValInPage + - i * nSizeIndexedValue, - nSizeIndexedValue); - bSortThisValue = FALSE; - } - else if (i == 0) - { - if (anPagesAtThisValue.size() > 100000) - { - bInvalidateIndex = TRUE; - return FALSE; - } - else - { - anPagesAtThisValue.push_back(nThisPage); - } - } - - if (nOGRFID) - bSortThisValue = TRUE; - - bIndexedValueIsValid = TRUE; - } - - if (bRewritePage) - { - VSIFSeekL(fp, (nThisPage - 1) * 4096, SEEK_SET); - if (VSIFWriteL(abyBuffer, 1, 4096, fp) != 4096) - return FALSE; - } - - nLastPageVisited = nThisPage; - - return TRUE; - } - else - { - /* This page references other pages */ - if (VSIFReadL(abyBuffer, 1, 4096, fp) != 4096) - return FALSE; - int nSubPages; - memcpy(&nSubPages, abyBuffer + 4, 4); - CPL_LSBPTR32(&nSubPages); - nSubPages++; - if (nSubPages > (4096 - 8) / 4) - return FALSE; - for (int i = 0; i < nSubPages; i++) - { - int nSubPageID; - memcpy(&nSubPageID, abyBuffer + 8 + 4 * i, 4); - CPL_LSBPTR32(&nSubPageID); - if (nSubPageID < 1) - return FALSE; - if (!EditATXOrSPX(fp, nSubPageID, nLastPageVisited, nDepth - 1, - nSizeIndexedValue, pabyLastIndexedValue, - bIndexedValueIsValid, nFirstIndexAtThisValue, - anPagesAtThisValue, bSortThisValue, - bInvalidateIndex)) - { - return FALSE; - } - } - - return TRUE; - } -} - -/************************************************************************/ -/* GetInt32() */ -/************************************************************************/ - -static GInt32 GetInt32(const GByte *pBaseAddr, int iOffset) -{ - GInt32 nVal; - memcpy(&nVal, pBaseAddr + sizeof(nVal) * iOffset, sizeof(nVal)); - CPL_LSBPTR32(&nVal); - return nVal; -} - -/************************************************************************/ -/* UpdateNextOGRFIDAndFGDBFID() */ -/************************************************************************/ - -static CPL_INLINE void UpdateNextOGRFIDAndFGDBFID( - int i, const std::map &oMapOGRFIDToFGDBFID, - std::map::iterator &oIterO2F, int &nNextOGRFID, - const std::map &oMapFGDBFIDToOGRFID, - std::map::iterator &oIterF2O, int &nNextFGDBFID) -{ - while (nNextOGRFID > 0 && i > nNextOGRFID) - { - ++oIterO2F; - if (oIterO2F == oMapOGRFIDToFGDBFID.end()) - nNextOGRFID = -1; - else - nNextOGRFID = oIterO2F->first; - } - - while (nNextFGDBFID > 0 && i > nNextFGDBFID) - { - ++oIterF2O; - if (oIterF2O == oMapFGDBFIDToOGRFID.end()) - nNextFGDBFID = -1; - else - nNextFGDBFID = oIterF2O->first; - } -} - -/************************************************************************/ -/* EditGDBTablX() */ -/************************************************************************/ - -#define TEST_BIT(ar, bit) (ar[(bit) / 8] & (1 << ((bit) % 8))) -#define SET_BIT(ar, bit) ar[(bit) / 8] |= (1 << ((bit) % 8)) -#define BIT_ARRAY_SIZE_IN_BYTES(bitsize) (((bitsize) + 7) / 8) - -/* See https://github.com/rouault/dump_gdbtable/wiki/FGDB-Spec */ -int FGdbLayer::EditGDBTablX(const CPLString &osGDBTablX, - const CPLString &osNewGDBTablX) -{ - VSILFILE *fp = VSIFOpenL(osGDBTablX, "rb"); - if (fp == nullptr) - { - CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", osGDBTablX.c_str()); - return FALSE; - } - VSILFILE *fpNew = VSIFOpenL(osNewGDBTablX, "wb"); - if (fpNew == nullptr) - { - CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", - osNewGDBTablX.c_str()); - VSIFCloseL(fp); - return FALSE; - } - GByte abyBuffer[16]; - VSIFReadL(abyBuffer, 1, 16, fp); - int n1024Blocks = GetInt32(abyBuffer, 1); - int nInMaxFID = GetInt32(abyBuffer, 2); -#ifdef DEBUG - const int nInMaxFIDOri = nInMaxFID; -#endif - int nRecordSize = GetInt32(abyBuffer, 3); - CPLAssert(nRecordSize >= 4 && nRecordSize <= 6); - - std::map::iterator oIterO2F = m_oMapOGRFIDToFGDBFID.begin(); - int nMaxOGRFID = 0; - for (; oIterO2F != m_oMapOGRFIDToFGDBFID.end(); ++oIterO2F) - nMaxOGRFID = oIterO2F->first; - // printf("nInMaxFID = %d\n", nInMaxFID); - // printf("nMaxOGRFID = %d\n", nMaxOGRFID); - int nOutMaxFID = MAX(nInMaxFID, nMaxOGRFID); - - // Optimization: If the feature ids at the end of the file all map to a OGR - // fid then they don't need to be included in the final file - for (int i = nInMaxFID; i > nMaxOGRFID; i--) - { - if (m_oMapFGDBFIDToOGRFID.find(i) != m_oMapFGDBFIDToOGRFID.end()) - { - nOutMaxFID--; - nInMaxFID--; - } - else - break; - } - - // printf("nInMaxFID = %d\n", nInMaxFID); - // printf("nOutMaxFID = %d\n", nOutMaxFID); - - int n1024BlocksOut = (int)(((GIntBig)nOutMaxFID + 1023) / 1024); - int nTmp; - - nTmp = CPL_LSBWORD32(n1024BlocksOut); - memcpy(abyBuffer + 4, &nTmp, 4); - - nTmp = CPL_LSBWORD32(nOutMaxFID); - memcpy(abyBuffer + 8, &nTmp, 4); - VSIFWriteL(abyBuffer, 1, 16, fpNew); - - VSIFSeekL(fp, 1024 * n1024Blocks * nRecordSize, SEEK_CUR); - VSIFReadL(abyBuffer, 1, 16, fp); - int nBitmapInt32Words = GetInt32(abyBuffer, 0); - int n1024BlocksTotal = GetInt32(abyBuffer, 1); - CPLAssert(n1024BlocksTotal == (int)(((GIntBig)nInMaxFIDOri + 1023) / 1024)); - GByte *pabyBlockMap = nullptr; - if (nBitmapInt32Words != 0) - { - int nSizeInBytes = BIT_ARRAY_SIZE_IN_BYTES(n1024BlocksTotal); - pabyBlockMap = (GByte *)CPLMalloc(nSizeInBytes); - VSIFReadL(pabyBlockMap, nSizeInBytes, 1, fp); - } - int nSizeInBytesOut = BIT_ARRAY_SIZE_IN_BYTES(n1024BlocksOut); - /* Round to the next multiple of 128 bytes (32 int4 words) */ - nSizeInBytesOut = ((nSizeInBytesOut + 127) / 128) * 128; - GByte *pabyBlockMapOut = (GByte *)VSI_CALLOC_VERBOSE(1, nSizeInBytesOut); - GByte *pabyPage = (GByte *)VSI_MALLOC_VERBOSE(1024 * nRecordSize); - if (pabyBlockMapOut == nullptr || pabyPage == nullptr) - { - VSIFree(pabyBlockMapOut); - VSIFree(pabyPage); - VSIFCloseL(fp); - return FALSE; - } - GByte abyEmptyOffset[6]; - memset(abyEmptyOffset, 0, 6); - int nNonEmptyPages = 0; - int nOffsetInPage = 0, nLastWrittenOffset = 0; - int bDisableSparsePages = - CPLTestBool(CPLGetConfigOption("FILEGDB_DISABLE_SPARSE_PAGES", "NO")); - - oIterO2F = m_oMapOGRFIDToFGDBFID.begin(); - int nNextOGRFID = oIterO2F->first; - std::map::iterator oIterF2O = m_oMapFGDBFIDToOGRFID.begin(); - int nNextFGDBFID = oIterF2O->first; - - int nCountBlocksBeforeIBlockIdx = 0; - int nCountBlocksBeforeIBlockValue = 0; - - int bRet = TRUE; - int i = 0; - for (unsigned iUnsigned = 1; iUnsigned <= static_cast(nOutMaxFID); - iUnsigned = static_cast(i) + 1, nOffsetInPage += nRecordSize) - { - i = static_cast(iUnsigned); - if (nOffsetInPage == 1024 * nRecordSize) - { - if (nLastWrittenOffset > 0 || bDisableSparsePages) - { - SET_BIT(pabyBlockMapOut, (i - 2) / 1024); - nNonEmptyPages++; - if (nLastWrittenOffset < nOffsetInPage) - memset(pabyPage + nLastWrittenOffset, 0, - nOffsetInPage - nLastWrittenOffset); - if (VSIFWriteL(pabyPage, 1024 * nRecordSize, 1, fpNew) != 1) - { - bRet = FALSE; - goto end; - } - } - nOffsetInPage = 0; - nLastWrittenOffset = 0; - - // A few optimizations : - if (!bDisableSparsePages && i > nInMaxFID && nNextOGRFID > 0 && - i < nNextOGRFID - 1024) - { - // If we created a OGR FID far away from the latest FGDB FID - // then skip to it - i = ((nNextOGRFID - 1) / 1024) * 1024 + 1; - } - // coverity[negative_shift] - else if (!bDisableSparsePages && pabyBlockMap != nullptr && - i <= nInMaxFID && - TEST_BIT(pabyBlockMap, (i - 1) / 1024) == 0) - { - // Skip empty pages - UpdateNextOGRFIDAndFGDBFID(i, m_oMapOGRFIDToFGDBFID, oIterO2F, - nNextOGRFID, m_oMapFGDBFIDToOGRFID, - oIterF2O, nNextFGDBFID); - if ((nNextOGRFID < 0 || i < nNextOGRFID - 1024) && - (nNextFGDBFID < 0 || i < nNextFGDBFID - 1024)) - { - if (i > INT_MAX - 1024) - break; - i += 1023; - nOffsetInPage += 1023 * nRecordSize; - continue; - } - } - } - - UpdateNextOGRFIDAndFGDBFID(i, m_oMapOGRFIDToFGDBFID, oIterO2F, - nNextOGRFID, m_oMapFGDBFIDToOGRFID, oIterF2O, - nNextFGDBFID); - - int nSrcFID; - if (i == nNextOGRFID) - { - // This FID matches a user defined OGR FID, then find the - // corresponding FGDB record - nSrcFID = oIterO2F->second; - // printf("(1) i = %d, nSrcFID = %d\n", i, nSrcFID); - } - else if (i == nNextFGDBFID || i > nInMaxFID) - { - // This record is a temporary one (will be moved to a user-define - // FID) or we are out of the validity zone of input records - // printf("(2) i = %d, nNextFGDBFID = %d, nInMaxFID = %d\n", i, - // nNextFGDBFID, nInMaxFID); - continue; - } - else - { - // Regular record, not overloaded by user defined FID - nSrcFID = i; - // printf("(3) i = %d, nSrcFID = %d\n", i, nSrcFID); - } - - if (pabyBlockMap != nullptr) - { - int iBlock = (nSrcFID - 1) / 1024; - - // Check if the block is not empty - // coverity[negative_shift] - if (TEST_BIT(pabyBlockMap, iBlock)) - { - int nCountBlocksBefore; - if (iBlock >= nCountBlocksBeforeIBlockIdx) - { - nCountBlocksBefore = nCountBlocksBeforeIBlockValue; - for (int j = nCountBlocksBeforeIBlockIdx; j < iBlock; j++) - { - // coverity[negative_shift] - nCountBlocksBefore += TEST_BIT(pabyBlockMap, j) != 0; - } - } - else - { - nCountBlocksBefore = 0; - for (int j = 0; j < iBlock; j++) - { - // coverity[negative_shift] - nCountBlocksBefore += TEST_BIT(pabyBlockMap, j) != 0; - } - } - nCountBlocksBeforeIBlockIdx = iBlock; - nCountBlocksBeforeIBlockValue = nCountBlocksBefore; - int iCorrectedRow = - nCountBlocksBefore * 1024 + ((nSrcFID - 1) % 1024); - VSIFSeekL(fp, 16 + nRecordSize * iCorrectedRow, SEEK_SET); - VSIFReadL(abyBuffer, 1, nRecordSize, fp); - if (memcmp(abyBuffer, abyEmptyOffset, nRecordSize) != 0) - { - if (nLastWrittenOffset < nOffsetInPage) - memset(pabyPage + nLastWrittenOffset, 0, - nOffsetInPage - nLastWrittenOffset); - memcpy(pabyPage + nOffsetInPage, abyBuffer, nRecordSize); - nLastWrittenOffset = nOffsetInPage + nRecordSize; - } - } - } - else - { - VSIFSeekL(fp, 16 + nRecordSize * (nSrcFID - 1), SEEK_SET); - VSIFReadL(abyBuffer, 1, nRecordSize, fp); - if (memcmp(abyBuffer, abyEmptyOffset, nRecordSize) != 0) - { - if (nLastWrittenOffset < nOffsetInPage) - memset(pabyPage + nLastWrittenOffset, 0, - nOffsetInPage - nLastWrittenOffset); - memcpy(pabyPage + nOffsetInPage, abyBuffer, nRecordSize); - nLastWrittenOffset = nOffsetInPage + nRecordSize; - } - } - } - // printf("nLastWrittenOffset = %d\n", nLastWrittenOffset); - if (nLastWrittenOffset > 0 || bDisableSparsePages) - { - assert(nOutMaxFID >= 1); - SET_BIT(pabyBlockMapOut, (nOutMaxFID - 1) / 1024); - nNonEmptyPages++; - if (nLastWrittenOffset < 1024 * nRecordSize) - memset(pabyPage + nLastWrittenOffset, 0, - 1024 * nRecordSize - nLastWrittenOffset); - if (VSIFWriteL(pabyPage, 1024 * nRecordSize, 1, fpNew) != 1) - { - bRet = FALSE; - goto end; - } - } - - memset(abyBuffer, 0, 16); - - /* Number of total blocks, including omitted ones */ - nTmp = CPL_LSBWORD32(n1024BlocksOut); - memcpy(abyBuffer + 4, &nTmp, 4); - - nTmp = CPL_LSBWORD32(nNonEmptyPages); - memcpy(abyBuffer + 8, &nTmp, 4); - - if (nNonEmptyPages < n1024BlocksOut) - { - /* Number of int4 words for the bitmap (rounded to the next multiple of - * 32) */ - nTmp = CPL_LSBWORD32(nSizeInBytesOut / 4); - memcpy(abyBuffer + 0, &nTmp, 4); - - /* Number of int4 words in the bitmap where there's at least a non-zero - * bit */ - /* Seems to be unused */ - nTmp = CPL_LSBWORD32(((nOutMaxFID - 1) / 1024 + 31) / 32); - memcpy(abyBuffer + 12, &nTmp, 4); - } - - if (VSIFWriteL(abyBuffer, 1, 16, fpNew) != 16) - { - bRet = FALSE; - goto end; - } - - if (nNonEmptyPages < n1024BlocksOut) - { - VSIFWriteL(pabyBlockMapOut, 1, nSizeInBytesOut, fpNew); - - VSIFSeekL(fpNew, 4, SEEK_SET); - nTmp = CPL_LSBWORD32(nNonEmptyPages); - VSIFWriteL(&nTmp, 1, 4, fpNew); - } - -end: - CPLFree(pabyBlockMap); - CPLFree(pabyBlockMapOut); - CPLFree(pabyPage); - VSIFCloseL(fpNew); - VSIFCloseL(fp); - - return bRet; -} - #ifdef EXTENT_WORKAROUND /************************************************************************/ @@ -960,9 +232,8 @@ bool FGdbLayer::UpdateRowWithGeometry(Row &row, OGRGeometry *poGeom) void FGdbLayer::WorkAroundExtentProblem() { - if (!m_bLayerJustCreated || !m_bLayerEnvelopeValid) + if (!m_bLayerEnvelopeValid) return; - m_bLayerJustCreated = FALSE; OGREnvelope sEnvelope; if (FGdbLayer::GetExtent(&sEnvelope, TRUE) != OGRERR_NONE) @@ -984,1800 +255,139 @@ void FGdbLayer::WorkAroundExtentProblem() { long hr; Row row; - EnumRows enumRows; - - if (FAILED(hr = m_pTable->Search(StringToWString("*"), - StringToWString(""), true, enumRows))) - return; - - if (FAILED(hr = enumRows.Next(row))) - return; - - if (hr != S_OK) - return; - - /* Backup original shape buffer */ - ShapeBuffer originalGdbGeometry; - if (FAILED(hr = row.GetGeometry(originalGdbGeometry))) - return; - - OGRGeometry *pOGRGeo = nullptr; - if ((!GDBGeometryToOGRGeometry(m_forceMulti, &originalGdbGeometry, - m_pSRS, &pOGRGeo)) || - pOGRGeo == nullptr) - { - delete pOGRGeo; - return; - } - - OGRwkbGeometryType eType = wkbFlatten(pOGRGeo->getGeometryType()); - - delete pOGRGeo; - pOGRGeo = nullptr; - - OGRPoint oP1(floor(sLayerEnvelope.MinX), floor(sLayerEnvelope.MinY)); - OGRPoint oP2(ceil(sLayerEnvelope.MaxX), ceil(sLayerEnvelope.MaxY)); - - OGRLinearRing oLR; - oLR.addPoint(&oP1); - oLR.addPoint(&oP2); - oLR.addPoint(&oP1); - - if (eType == wkbPoint) - { - UpdateRowWithGeometry(row, &oP1); - UpdateRowWithGeometry(row, &oP2); - } - else if (eType == wkbLineString) - { - UpdateRowWithGeometry(row, &oLR); - } - else if (eType == wkbPolygon) - { - OGRPolygon oPoly; - oPoly.addRing(&oLR); - - UpdateRowWithGeometry(row, &oPoly); - } - else if (eType == wkbMultiPoint) - { - OGRMultiPoint oColl; - oColl.addGeometry(&oP1); - oColl.addGeometry(&oP2); - - UpdateRowWithGeometry(row, &oColl); - } - else if (eType == wkbMultiLineString) - { - OGRMultiLineString oColl; - oColl.addGeometry(&oLR); - - UpdateRowWithGeometry(row, &oColl); - } - else if (eType == wkbMultiPolygon) - { - OGRMultiPolygon oColl; - OGRPolygon oPoly; - oPoly.addRing(&oLR); - oColl.addGeometry(&oPoly); - - UpdateRowWithGeometry(row, &oColl); - } - else - return; - - /* Restore original ShapeBuffer */ - hr = row.SetGeometry(originalGdbGeometry); - if (FAILED(hr)) - return; - - /* Update Row */ - hr = m_pTable->Update(row); - if (FAILED(hr)) - return; - - CPLDebug("FGDB", - "Workaround extent problem with Linux 64bit FGDB SDK 1.1"); - } -} -#endif // EXTENT_WORKAROUND - -/************************************************************************/ -/* ICreateFeature() */ -/* Create an FGDB Row and populate it from an OGRFeature. */ -/* */ -/************************************************************************/ - -OGRErr FGdbLayer::ICreateFeature(OGRFeature *poFeature) -{ - Row fgdb_row; - fgdbError hr; - - if (!m_pDS->GetUpdate() || m_pTable == nullptr) - return OGRERR_FAILURE; - - GIntBig nFID = poFeature->GetFID(); - if (nFID < -1 || nFID == 0 || !CPL_INT64_FITS_ON_INT32(nFID)) - { - CPLError(CE_Failure, CPLE_NotSupported, - "Only 32 bit positive integers FID supported by FileGDB"); - return OGRERR_FAILURE; - } - - if (nFID > 0) - { - if (m_pDS->GetOpenFileGDBDrv() == nullptr) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot call CreateFeature() with a set FID when " - "OpenFileGDB driver not available"); - return OGRERR_FAILURE; - } - - if (m_pDS->HasSelectLayers()) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot call CreateFeature() with a set FID when a layer " - "resulting from ExecuteSQL() is still opened"); - return OGRERR_FAILURE; - } - - if (m_pDS->GetConnection()->GetRefCount() > 1) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot call CreateFeature() with a set FID when a " - "dataset is opened more than once"); - return OGRERR_FAILURE; - } - - if (m_oMapOGRFIDToFGDBFID.find((int)poFeature->GetFID()) != - m_oMapOGRFIDToFGDBFID.end()) - { - CPLError(CE_Failure, CPLE_AppDefined, - "A feature with same FID already exists"); - return OGRERR_FAILURE; - } - - if (m_oMapFGDBFIDToOGRFID.find((int)poFeature->GetFID()) == - m_oMapFGDBFIDToOGRFID.end()) - { - EnumRows enumRows; - Row row; - if (GetRow(enumRows, row, (int)poFeature->GetFID()) == OGRERR_NONE) - { - CPLError(CE_Failure, CPLE_AppDefined, - "A feature with same FID already exists"); - return OGRERR_FAILURE; - } - } - - if ((int)m_oMapOGRFIDToFGDBFID.size() == m_nResyncThreshold) - ResyncIDs(); - } - - if (m_bSymlinkFlag && !CreateRealCopy()) - return OGRERR_FAILURE; - - if (m_bBulkLoadAllowed < 0) - m_bBulkLoadAllowed = - CPLTestBool(CPLGetConfigOption("FGDB_BULK_LOAD", "NO")); - - if (m_bBulkLoadAllowed && !m_bBulkLoadInProgress) - StartBulkLoad(); - - hr = m_pTable->CreateRowObject(fgdb_row); - - /* Check the status of the Row create */ - if (FAILED(hr)) - { - GDBErr(hr, "Failed at creating Row in CreateFeature."); - return OGRERR_FAILURE; - } - - /* As we have issues with fixed values for dates, or CURRENT_xxxx isn't */ - /* handled anyway, let's fill ourselves all unset fields with their default - */ - poFeature->FillUnsetWithDefault(FALSE, nullptr); - - /* Populate the row with the feature content */ - if (PopulateRowWithFeature(fgdb_row, poFeature) != OGRERR_NONE) - return OGRERR_FAILURE; - - /* Cannot write to FID field - it is managed by GDB*/ - // std::wstring wfield_name = StringToWString(m_strOIDFieldName); - // hr = fgdb_row.SetInteger(wfield_name, poFeature->GetFID()); - - /* Write the row to the table */ - hr = m_pTable->Insert(fgdb_row); - if (FAILED(hr)) - { - GDBErr(hr, "Failed at writing Row to Table in CreateFeature."); - return OGRERR_FAILURE; - } - - int32 oid = -1; - if (!FAILED(hr = fgdb_row.GetOID(oid))) - { - if (poFeature->GetFID() < 0) - { - // Avoid colliding with a user set FID - while (m_oMapOGRFIDToFGDBFID.find(oid) != - m_oMapOGRFIDToFGDBFID.end()) - { - EndBulkLoad(); - - CPLDebug("FGDB", "Collision with user set FID %d", oid); - if (FAILED(hr = m_pTable->Delete(fgdb_row))) - { - GDBErr(hr, "Failed deleting row "); - return OGRERR_FAILURE; - } - hr = m_pTable->Insert(fgdb_row); - if (FAILED(hr)) - { - GDBErr(hr, - "Failed at writing Row to Table in CreateFeature."); - return OGRERR_FAILURE; - } - if (FAILED(hr = fgdb_row.GetOID(oid))) - { - return OGRERR_FAILURE; - } - } - poFeature->SetFID(oid); - } - else if ((int)poFeature->GetFID() != oid) - { - m_pDS->GetConnection()->SetFIDHackInProgress(TRUE); - m_oMapOGRFIDToFGDBFID[(int)poFeature->GetFID()] = oid; - m_oMapFGDBFIDToOGRFID[oid] = (int)poFeature->GetFID(); - } - } - -#ifdef EXTENT_WORKAROUND - /* For WorkAroundExtentProblem() needs */ - OGRGeometry *poGeom = poFeature->GetGeometryRef(); - if (m_bLayerJustCreated && poGeom != nullptr && !poGeom->IsEmpty()) - { - OGREnvelope sFeatureGeomEnvelope; - poGeom->getEnvelope(&sFeatureGeomEnvelope); - if (!m_bLayerEnvelopeValid) - { - sLayerEnvelope = sFeatureGeomEnvelope; - m_bLayerEnvelopeValid = true; - } - else - { - sLayerEnvelope.Merge(sFeatureGeomEnvelope); - } - } -#endif - - return OGRERR_NONE; -} - -/************************************************************************/ -/* PopulateRowWithFeature() */ -/* */ -/************************************************************************/ - -OGRErr FGdbLayer::PopulateRowWithFeature(Row &fgdb_row, OGRFeature *poFeature) -{ - ShapeBuffer shape; - fgdbError hr; - - OGRFeatureDefn *poFeatureDefn = m_pFeatureDefn; - int nFieldCount = poFeatureDefn->GetFieldCount(); - - /* Copy the OGR visible fields (everything except geometry and FID) */ - int nCountBinaryField = 0; - for (int i = 0; i < nFieldCount; i++) - { - std::string field_name = poFeatureDefn->GetFieldDefn(i)->GetNameRef(); - std::wstring wfield_name = StringToWString(field_name); - const std::string &strFieldType = m_vOGRFieldToESRIFieldType[i]; - - /* Set empty fields to NULL */ - if (!poFeature->IsFieldSetAndNotNull(i)) - { - if (strFieldType == "esriFieldTypeGlobalID") - continue; - - if (FAILED(hr = fgdb_row.SetNull(wfield_name))) - { - GDBErr(hr, "Failed setting field to NULL."); - return OGRERR_FAILURE; - } - continue; - } - - /* Set the information using the appropriate FGDB function */ - int nOGRFieldType = poFeatureDefn->GetFieldDefn(i)->GetType(); - - if (nOGRFieldType == OFTInteger) - { - int fldvalue = poFeature->GetFieldAsInteger(i); - if (strFieldType == "esriFieldTypeInteger") - hr = fgdb_row.SetInteger(wfield_name, fldvalue); - else - { - if (fldvalue < -32768 || fldvalue > 32767) - { - CPLErrorOnce(CE_Warning, CPLE_NotSupported, - "Value %d for field %s does not fit into a " - "short and will be clamped. " - "This warning will not be emitted any more", - fldvalue, field_name.c_str()); - if (fldvalue < -32768) - fldvalue = -32768; - else - fldvalue = 32767; - } - hr = fgdb_row.SetShort(wfield_name, (short)fldvalue); - } - } - else if (nOGRFieldType == OFTReal || nOGRFieldType == OFTInteger64) - { - /* Doubles (we don't handle FGDB Floats) */ - double fldvalue = poFeature->GetFieldAsDouble(i); - if (strFieldType == "esriFieldTypeDouble") - hr = fgdb_row.SetDouble(wfield_name, fldvalue); - else - hr = fgdb_row.SetFloat(wfield_name, (float)fldvalue); - } - else if (nOGRFieldType == OFTString) - { - /* Strings we convert to wstring */ - std::string fldvalue = poFeature->GetFieldAsString(i); - if (strFieldType == "esriFieldTypeString") - { - std::wstring wfldvalue = StringToWString(fldvalue); - hr = fgdb_row.SetString(wfield_name, wfldvalue); - } - // Apparently, esriFieldTypeGlobalID can not be set, but is - // initialized by the FileGDB SDK itself. - else if( strFieldType == "esriFieldTypeGUID" /*|| - strFieldType == "esriFieldTypeGlobalID" */ ) - { - Guid guid; - std::wstring wfldvalue = StringToWString(fldvalue); - if (FAILED(hr = guid.FromString(wfldvalue))) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot parse GUID value %s for field %s.", - fldvalue.c_str(), field_name.c_str()); - } - else - { - hr = fgdb_row.SetGUID(wfield_name, guid); - } - } - else if (strFieldType == "esriFieldTypeXML") - { - hr = fgdb_row.SetXML(wfield_name, fldvalue); - } - else - hr = 0; - } - else if (nOGRFieldType == OFTDateTime || nOGRFieldType == OFTDate) - { - /* Dates we need to coerce a little */ - struct tm val; - poFeature->GetFieldAsDateTime( - i, &(val.tm_year), &(val.tm_mon), &(val.tm_mday), - &(val.tm_hour), &(val.tm_min), &(val.tm_sec), nullptr); - val.tm_year -= 1900; - val.tm_mon = val.tm_mon - 1; /* OGR months go 1-12, FGDB go 0-11 */ - hr = fgdb_row.SetDate(wfield_name, val); - } - else if (nOGRFieldType == OFTBinary) - { - /* Binary data */ - int bytesize; - GByte *bytes = poFeature->GetFieldAsBinary(i, &bytesize); - if (bytesize) - { - /* This is annoying but SetBinary() doesn't keep the binary */ - /* content. The ByteArray object must still be alive at */ - /* the time Insert() is called */ - m_apoByteArrays[nCountBinaryField]->Allocate(bytesize); - memcpy(m_apoByteArrays[nCountBinaryField]->byteArray, bytes, - bytesize); - m_apoByteArrays[nCountBinaryField]->inUseLength = bytesize; - hr = fgdb_row.SetBinary(wfield_name, - *(m_apoByteArrays[nCountBinaryField])); - } - else - { - hr = fgdb_row.SetNull(wfield_name); - } - nCountBinaryField++; - } - else - { - /* We can't handle this type */ - CPLError(CE_Failure, CPLE_AppDefined, - "FGDB driver does not support OGR type."); - return OGRERR_FAILURE; - } - - if (FAILED(hr)) - { - CPLError(CE_Warning, CPLE_AppDefined, - "Cannot set value for field %s", field_name.c_str()); - } - } - - const auto eFlatLayerGeomType = wkbFlatten(m_pFeatureDefn->GetGeomType()); - if (eFlatLayerGeomType != wkbNone) - { - /* Done with attribute fields, now do geometry */ - OGRGeometry *poGeom = poFeature->GetGeometryRef(); - - if (poGeom == nullptr || poGeom->IsEmpty()) - { - /* EMPTY geometries should be treated as NULL, see #4832 */ - hr = fgdb_row.SetNull(StringToWString(m_strShapeFieldName)); - if (FAILED(hr)) - { - GDBErr(hr, "Failed at writing EMPTY Geometry to Row in " - "CreateFeature."); - return OGRERR_FAILURE; - } - } - else - { - /* Write geometry to a buffer */ - GByte *pabyShape = nullptr; - int nShapeSize = 0; - OGRErr err; - - const OGRwkbGeometryType eType = - wkbFlatten(poGeom->getGeometryType()); - if (m_bCreateMultipatch && - (eType == wkbMultiPolygon || eType == wkbMultiSurface || - eType == wkbTIN || eType == wkbPolyhedralSurface || - eType == wkbGeometryCollection)) - { - err = OGRWriteMultiPatchToShapeBin(poGeom, &pabyShape, - &nShapeSize); - if (err == OGRERR_UNSUPPORTED_OPERATION) - err = OGRWriteToShapeBin(poGeom, &pabyShape, &nShapeSize); - } - else - { - if (((eFlatLayerGeomType == wkbLineString || - eFlatLayerGeomType == wkbMultiLineString) && - (eType != wkbLineString && eType != wkbMultiLineString)) || - ((eFlatLayerGeomType == wkbPolygon || - eFlatLayerGeomType == wkbMultiPolygon) && - (eType != wkbPolygon && eType != wkbMultiPolygon)) || - ((eFlatLayerGeomType == wkbPoint || - eFlatLayerGeomType == wkbMultiPoint) && - eType != eFlatLayerGeomType)) - { - // Otherwise crash in the SDK... - CPLError( - CE_Failure, CPLE_NotSupported, - "Geometry type %s not supported in layer of type %s", - OGRToOGCGeomType(eType), - OGRToOGCGeomType(eFlatLayerGeomType)); - return OGRERR_FAILURE; - } - - err = OGRWriteToShapeBin(poGeom, &pabyShape, &nShapeSize); - } - if (err != OGRERR_NONE) - { - CPLFree(pabyShape); - return err; - } - - /* Copy it into a ShapeBuffer */ - if (nShapeSize > 0) - { - shape.Allocate(nShapeSize); - memcpy(shape.shapeBuffer, pabyShape, nShapeSize); - shape.inUseLength = nShapeSize; - } - - /* Free the shape buffer */ - CPLFree(pabyShape); - - /* Write ShapeBuffer into the Row */ - hr = fgdb_row.SetGeometry(shape); - if (FAILED(hr)) - { - GDBErr(hr, - "Failed at writing Geometry to Row in CreateFeature."); - return OGRERR_FAILURE; - } - } - } - - return OGRERR_NONE; -} - -/************************************************************************/ -/* GetRow() */ -/************************************************************************/ - -OGRErr FGdbLayer::GetRow(EnumRows &enumRows, Row &row, GIntBig nFID) -{ - long hr; - CPLString osQuery; - - /* Querying a 64bit FID causes a runtime exception in FileGDB... */ - if (!CPL_INT64_FITS_ON_INT32(nFID)) - { - return OGRERR_FAILURE; - } - - osQuery.Printf("%s = " CPL_FRMT_GIB, m_strOIDFieldName.c_str(), nFID); - - if (FAILED(hr = m_pTable->Search(m_wstrSubfields, - StringToWString(osQuery.c_str()), true, - enumRows))) - { - GDBErr(hr, "Failed fetching row "); - return OGRERR_FAILURE; - } - - if (FAILED(hr = enumRows.Next(row))) - { - GDBErr(hr, "Failed fetching row "); - return OGRERR_FAILURE; - } - - if (hr != S_OK) - return OGRERR_NON_EXISTING_FEATURE; // none found - but no failure - - return OGRERR_NONE; -} - -/************************************************************************/ -/* DeleteFeature() */ -/************************************************************************/ - -OGRErr FGdbLayer::DeleteFeature(GIntBig nFID) - -{ - long hr; - EnumRows enumRows; - Row row; - - if (!m_pDS->GetUpdate() || m_pTable == nullptr) - return OGRERR_FAILURE; - if (!CPL_INT64_FITS_ON_INT32(nFID)) - return OGRERR_NON_EXISTING_FEATURE; - - if (m_bSymlinkFlag && !CreateRealCopy()) - return OGRERR_FAILURE; - - int nFID32 = (int)nFID; - std::map::iterator oIter = m_oMapOGRFIDToFGDBFID.find(nFID32); - if (oIter != m_oMapOGRFIDToFGDBFID.end()) - { - nFID32 = oIter->second; - m_oMapFGDBFIDToOGRFID.erase(nFID32); - m_oMapOGRFIDToFGDBFID.erase(oIter); - } - else if (m_oMapFGDBFIDToOGRFID.find(nFID32) != m_oMapFGDBFIDToOGRFID.end()) - return OGRERR_NON_EXISTING_FEATURE; - - EndBulkLoad(); - - OGRErr eErr = GetRow(enumRows, row, nFID32); - if (eErr != OGRERR_NONE) - return eErr; - - if (FAILED(hr = m_pTable->Delete(row))) - { - GDBErr(hr, "Failed deleting row "); - return OGRERR_FAILURE; - } - - return OGRERR_NONE; -} - -/************************************************************************/ -/* ISetFeature() */ -/************************************************************************/ - -OGRErr FGdbLayer::ISetFeature(OGRFeature *poFeature) - -{ - long hr; - EnumRows enumRows; - Row row; - - if (!m_pDS->GetUpdate() || m_pTable == nullptr) - return OGRERR_FAILURE; - - GIntBig nFID64 = poFeature->GetFID(); - if (nFID64 == OGRNullFID) - { - CPLError(CE_Failure, CPLE_AppDefined, - "SetFeature() with unset FID fails."); - return OGRERR_FAILURE; - } - if (!CPL_INT64_FITS_ON_INT32(nFID64)) - return OGRERR_NON_EXISTING_FEATURE; - - EndBulkLoad(); - - if (m_bSymlinkFlag && !CreateRealCopy()) - return OGRERR_FAILURE; - - int nFID = (int)nFID64; - std::map::iterator oIter = m_oMapOGRFIDToFGDBFID.find(nFID); - if (oIter != m_oMapOGRFIDToFGDBFID.end()) - nFID = oIter->second; - else if (m_oMapFGDBFIDToOGRFID.find((int)poFeature->GetFID()) != - m_oMapFGDBFIDToOGRFID.end()) - return OGRERR_NON_EXISTING_FEATURE; - - OGRErr eErr = GetRow(enumRows, row, nFID); - if (eErr != OGRERR_NONE) - return eErr; - - /* Populate the row with the feature content */ - if (PopulateRowWithFeature(row, poFeature) != OGRERR_NONE) - return OGRERR_FAILURE; - - if (FAILED(hr = m_pTable->Update(row))) - { - GDBErr(hr, "Failed updating row "); - return OGRERR_FAILURE; - } - - return OGRERR_NONE; -} - -/************************************************************************/ -/* CreateFieldDefn() */ -/************************************************************************/ - -char *FGdbLayer::CreateFieldDefn(OGRFieldDefn &oField, int bApproxOK, - std::string &fieldname_clean, - std::string &gdbFieldType) -{ - std::string fieldname = oField.GetNameRef(); - // std::string fidname = std::string(GetFIDColumn()); - std::string nullable = (oField.IsNullable()) ? "true" : "false"; - - /* Try to map the OGR type to an ESRI type */ - OGRFieldType fldtype = oField.GetType(); - if (!OGRToGDBFieldType(fldtype, oField.GetSubType(), &gdbFieldType)) - { - GDBErr(-1, "Failed converting field type."); - return nullptr; - } - - if (oField.GetType() == OFTInteger64 && !bApproxOK) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Integer64 not supported in FileGDB"); - return nullptr; - } - - const char *pszColumnTypes = - CSLFetchNameValue(m_papszOptions, "COLUMN_TYPES"); - if (pszColumnTypes != nullptr) - { - char **papszTokens = CSLTokenizeString2(pszColumnTypes, ",", 0); - const char *pszFieldType = - CSLFetchNameValue(papszTokens, fieldname.c_str()); - if (pszFieldType != nullptr) - { - OGRFieldType fldtypeCheck; - OGRFieldSubType eSubType; - if (GDBToOGRFieldType(pszFieldType, &fldtypeCheck, &eSubType)) - { - if (fldtypeCheck != fldtype) - { - CPLError(CE_Warning, CPLE_AppDefined, - "Ignoring COLUMN_TYPES=%s=%s : %s not consistent " - "with OGR data type", - fieldname.c_str(), pszFieldType, pszFieldType); - } - else - gdbFieldType = pszFieldType; - } - else - CPLError(CE_Warning, CPLE_AppDefined, - "Ignoring COLUMN_TYPES=%s=%s : %s not recognized", - fieldname.c_str(), pszFieldType, pszFieldType); - } - CSLDestroy(papszTokens); - } - - if (!fieldname_clean.empty()) - { - oField.SetName(fieldname_clean.c_str()); - } - else - { - /* Clean field names */ - std::wstring wfieldname_clean = - FGDBLaunderName(StringToWString(fieldname)); - - if (m_bLaunderReservedKeywords) - wfieldname_clean = FGDBEscapeReservedKeywords(wfieldname_clean); - - /* Truncate to 64 characters */ - constexpr size_t FIELD_NAME_MAX_SIZE = 64; - if (wfieldname_clean.size() > FIELD_NAME_MAX_SIZE) - wfieldname_clean.resize(FIELD_NAME_MAX_SIZE); - - /* Ensures uniqueness of field name */ - int numRenames = 1; - while ((m_pFeatureDefn->GetFieldIndex( - WStringToString(wfieldname_clean).c_str()) >= 0) && - (numRenames < 10)) - { - wfieldname_clean = StringToWString( - CPLSPrintf("%s_%d", - WStringToString(wfieldname_clean.substr( - 0, FIELD_NAME_MAX_SIZE - 2)) - .c_str(), - numRenames)); - numRenames++; - } - while ((m_pFeatureDefn->GetFieldIndex( - WStringToString(wfieldname_clean).c_str()) >= 0) && - (numRenames < 100)) - { - wfieldname_clean = StringToWString( - CPLSPrintf("%s_%d", - WStringToString(wfieldname_clean.substr( - 0, FIELD_NAME_MAX_SIZE - 3)) - .c_str(), - numRenames)); - numRenames++; - } - - fieldname_clean = WStringToString(wfieldname_clean); - if (fieldname_clean != fieldname) - { - if (!bApproxOK || - (m_pFeatureDefn->GetFieldIndex(fieldname_clean.c_str()) >= 0)) - { - CPLError(CE_Failure, CPLE_NotSupported, - "Failed to add field named '%s'", fieldname.c_str()); - return nullptr; - } - CPLError(CE_Warning, CPLE_NotSupported, - "Normalized/laundered field name: '%s' to '%s'", - fieldname.c_str(), fieldname_clean.c_str()); - - oField.SetName(fieldname_clean.c_str()); - } - } - - /* Then the Field definition */ - CPLXMLNode *defn_xml = CPLCreateXMLNode(nullptr, CXT_Element, "esri:Field"); - - /* Add the XML attributes to the Field node */ - FGDB_CPLAddXMLAttribute(defn_xml, "xmlns:xsi", - "http://www.w3.org/2001/XMLSchema-instance"); - FGDB_CPLAddXMLAttribute(defn_xml, "xmlns:xs", - "http://www.w3.org/2001/XMLSchema"); - FGDB_CPLAddXMLAttribute(defn_xml, "xmlns:esri", - "http://www.esri.com/schemas/ArcGIS/10.1"); - FGDB_CPLAddXMLAttribute(defn_xml, "xsi:type", "esri:Field"); - - /* Basic field information */ - CPLCreateXMLElementAndValue(defn_xml, "Name", fieldname_clean.c_str()); - CPLCreateXMLElementAndValue(defn_xml, "Type", gdbFieldType.c_str()); - CPLCreateXMLElementAndValue(defn_xml, "IsNullable", nullable.c_str()); - - /* Get the Length */ - int nLength = 0; - GDBFieldTypeToLengthInBytes(gdbFieldType, nLength); - if (oField.GetType() == OFTString) - { - const int nFieldWidth = oField.GetWidth(); - if (nFieldWidth > 0) - nLength = nFieldWidth; - } - - /* Write out the Length */ - char buf[100]; - snprintf(buf, 100, "%d", nLength); - CPLCreateXMLElementAndValue(defn_xml, "Length", buf); - - // According to https://resources.arcgis.com/en/help/arcobjects-java/api/arcobjects/com/esri/arcgis/geodatabase/Field.html#getPrecision() - // always 0 - CPLCreateXMLElementAndValue(defn_xml, "Precision", "0"); - - // According to https://resources.arcgis.com/en/help/arcobjects-java/api/arcobjects/com/esri/arcgis/geodatabase/Field.html#getScale() - // always 0 - CPLCreateXMLElementAndValue(defn_xml, "Scale", "0"); - - const char *pszAlternativeName = oField.GetAlternativeNameRef(); - if (pszAlternativeName != nullptr && pszAlternativeName[0]) - { - CPLCreateXMLElementAndValue(defn_xml, "AliasName", pszAlternativeName); - } - else if (fieldname != fieldname_clean) - { - /* Attempt to preserve the original fieldname */ - CPLCreateXMLElementAndValue(defn_xml, "AliasName", fieldname.c_str()); - } - - if (oField.GetDefault() != nullptr) - { - const char *pszDefault = oField.GetDefault(); - /*int nYear, nMonth, nDay, nHour, nMinute; - float fSecond;*/ - if (oField.GetType() == OFTString) - { - CPLString osVal = pszDefault; - if (osVal[0] == '\'' && osVal.back() == '\'') - { - osVal = osVal.substr(1); - osVal.pop_back(); - char *pszTmp = CPLUnescapeString(osVal, nullptr, CPLES_SQL); - osVal = pszTmp; - CPLFree(pszTmp); - } - CPLXMLNode *psDefaultValue = - CPLCreateXMLElementAndValue(defn_xml, "DefaultValue", osVal); - FGDB_CPLAddXMLAttribute(psDefaultValue, "xsi:type", "xs:string"); - } - else if (oField.GetType() == OFTInteger && - !EQUAL(gdbFieldType.c_str(), "esriFieldTypeSmallInteger") && - CPLGetValueType(pszDefault) == CPL_VALUE_INTEGER) - { - CPLXMLNode *psDefaultValue = CPLCreateXMLElementAndValue( - defn_xml, "DefaultValue", pszDefault); - FGDB_CPLAddXMLAttribute(psDefaultValue, "xsi:type", "xs:int"); - } - else if (oField.GetType() == OFTReal && - !EQUAL(gdbFieldType.c_str(), "esriFieldTypeSingle") && - CPLGetValueType(pszDefault) != CPL_VALUE_STRING) - { - CPLXMLNode *psDefaultValue = CPLCreateXMLElementAndValue( - defn_xml, "DefaultValue", pszDefault); - FGDB_CPLAddXMLAttribute(psDefaultValue, "xsi:type", "xs:double"); - } - /*else if( oField.GetType() == OFTDateTime && - sscanf(pszDefault, "'%d/%d/%d %d:%d:%f'", &nYear, &nMonth, - &nDay, &nHour, &nMinute, &fSecond) == 6 ) - { - CPLXMLNode* psDefaultValue = - CPLCreateXMLElementAndValue(defn_xml, "DefaultValue", - CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02d", - nYear, nMonth, nDay, nHour, nMinute, - (int)(fSecond + 0.5))); FGDB_CPLAddXMLAttribute(psDefaultValue, - "xsi:type", "xs:dateTime"); - }*/ - } - /* afternoon */ - - const auto &osDomainName = oField.GetDomainName(); - if (!osDomainName.empty()) - { - const auto poDomain = m_pDS->GetFieldDomain(osDomainName); - if (poDomain) - { - std::string failureReason; - std::string osXML = - BuildXMLFieldDomainDef(poDomain, true, failureReason); - if (!osXML.empty()) - { - auto psDomain = CPLParseXMLString(osXML.c_str()); - if (psDomain) - { - CPLFree(psDomain->pszValue); - psDomain->pszValue = CPLStrdup("Domain"); - CPLAddXMLChild(defn_xml, psDomain); - } - } - } - } - - /* Convert our XML tree into a string for FGDB */ - char *defn_str = CPLSerializeXMLTree(defn_xml); - CPLDebug("FGDB", "CreateField() generated XML for FGDB\n%s", defn_str); - - /* Free the XML */ - CPLDestroyXMLNode(defn_xml); - - return defn_str; -} - -/************************************************************************/ -/* CreateField() */ -/* Build up an FGDB XML field definition and use it to create a Field */ -/* Update the OGRFeatureDefn to reflect the new field. */ -/* */ -/************************************************************************/ - -OGRErr FGdbLayer::CreateField(const OGRFieldDefn *poField, int bApproxOK) -{ - OGRFieldDefn oField(poField); - std::string fieldname_clean; - std::string gdbFieldType; - - if (!m_pDS->GetUpdate() || m_pTable == nullptr) - return OGRERR_FAILURE; - - char *defn_str = - CreateFieldDefn(oField, bApproxOK, fieldname_clean, gdbFieldType); - if (defn_str == nullptr) - return OGRERR_FAILURE; - - /* Add the FGDB Field to the FGDB Table. */ - fgdbError hr = m_pTable->AddField(defn_str); - - CPLFree(defn_str); - - /* Check the status of the Field add */ - if (FAILED(hr)) - { - GDBErr(hr, "Failed at creating Field for " + - std::string(oField.GetNameRef())); - return OGRERR_FAILURE; - } - - /* Now add the OGRFieldDefn to the OGRFeatureDefn */ - m_pFeatureDefn->AddFieldDefn(&oField); - - m_vOGRFieldToESRIField.push_back(StringToWString(fieldname_clean)); - m_vOGRFieldToESRIFieldType.push_back(gdbFieldType); - - if (oField.GetType() == OFTBinary) - m_apoByteArrays.push_back(new ByteArray()); - - /* All done and happy */ - return OGRERR_NONE; -} - -/************************************************************************/ -/* DeleteField() */ -/************************************************************************/ - -OGRErr FGdbLayer::DeleteField(int iFieldToDelete) -{ - - if (!m_pDS->GetUpdate() || m_pTable == nullptr) - return OGRERR_FAILURE; - - if (iFieldToDelete < 0 || iFieldToDelete >= m_pFeatureDefn->GetFieldCount()) - { - CPLError(CE_Failure, CPLE_NotSupported, "Invalid field index"); - return OGRERR_FAILURE; - } - - ResetReading(); - - const char *pszFieldName = - m_pFeatureDefn->GetFieldDefn(iFieldToDelete)->GetNameRef(); - - fgdbError hr; - if (FAILED(hr = m_pTable->DeleteField(StringToWString(pszFieldName)))) - { - GDBErr(hr, "Failed deleting field " + std::string(pszFieldName)); - return OGRERR_FAILURE; - } - - m_vOGRFieldToESRIField.erase(m_vOGRFieldToESRIField.begin() + - iFieldToDelete); - m_vOGRFieldToESRIFieldType.erase(m_vOGRFieldToESRIFieldType.begin() + - iFieldToDelete); - - return m_pFeatureDefn->DeleteFieldDefn(iFieldToDelete); -} - -#ifdef AlterFieldDefn_implemented_but_not_working - -/************************************************************************/ -/* AlterFieldDefn() */ -/************************************************************************/ - -OGRErr FGdbLayer::AlterFieldDefn(int iFieldToAlter, - OGRFieldDefn *poNewFieldDefn, int nFlags) -{ - - if (!m_pDS->GetUpdate() || m_pTable == NULL) - return OGRERR_FAILURE; - - if (iFieldToAlter < 0 || iFieldToAlter >= m_pFeatureDefn->GetFieldCount()) - { - CPLError(CE_Failure, CPLE_NotSupported, "Invalid field index"); - return OGRERR_FAILURE; - } - - OGRFieldDefn *poFieldDefn = m_pFeatureDefn->GetFieldDefn(iFieldToAlter); - OGRFieldDefn oField(poFieldDefn); - - if (nFlags & ALTER_TYPE_FLAG) - { - oField.SetSubType(OFSTNone); - oField.SetType(poNewFieldDefn->GetType()); - oField.SetSubType(poNewFieldDefn->GetSubType()); - } - if (nFlags & ALTER_NAME_FLAG) - { - if (strcmp(poNewFieldDefn->GetNameRef(), oField.GetNameRef()) != 0) - { - CPLError(CE_Failure, CPLE_NotSupported, - "Altering field name is not supported"); - return OGRERR_FAILURE; - } - oField.SetName(poNewFieldDefn->GetNameRef()); - } - if (nFlags & ALTER_WIDTH_PRECISION_FLAG) - { - if (oField.GetType() == OFTString) - oField.SetWidth(poNewFieldDefn->GetWidth()); - } - - std::string fieldname_clean = - WStringToString(m_vOGRFieldToESRIField[iFieldToAlter]); - std::string gdbFieldType; - - char *defn_str = - CreateFieldDefn(oField, TRUE, fieldname_clean, gdbFieldType); - if (defn_str == NULL) - return OGRERR_FAILURE; - - ResetReading(); - - /* Add the FGDB Field to the FGDB Table. */ - fgdbError hr = m_pTable->AlterField(defn_str); - - CPLFree(defn_str); - - /* Check the status of the AlterField */ - if (FAILED(hr)) - { - GDBErr(hr, - "Failed at altering field " + std::string(oField.GetNameRef())); - return OGRERR_FAILURE; - } - - m_vOGRFieldToESRIFieldType[iFieldToAlter] = gdbFieldType; - - poFieldDefn->SetSubType(OFSTNone); - poFieldDefn->SetType(oField.GetType()); - poFieldDefn->SetType(oField.GetSubType()); - poFieldDefn->SetWidth(oField.GetWidth()); - - return OGRERR_NONE; -} -#endif // AlterFieldDefn_implemented_but_not_working - -/************************************************************************/ -/* XMLSpatialReference() */ -/* Build up an XML representation of an OGRSpatialReference. */ -/* Used in layer creation. */ -/* Fill oCoordPrec. */ -/************************************************************************/ - -static CPLXMLNode * -XMLSpatialReference(const OGRGeomFieldDefn *poSrcGeomFieldDefn, - CSLConstList papszOptions, - OGRGeomCoordinatePrecision &oCoordPrec) -{ - const auto poSRS = poSrcGeomFieldDefn->GetSpatialRef(); - - /* We always need a SpatialReference */ - CPLXMLNode *srs_xml = - CPLCreateXMLNode(nullptr, CXT_Element, "SpatialReference"); - - /* Extract the WKID before morphing */ - int nSRID = 0; - if (poSRS && poSRS->GetAuthorityCode(nullptr)) - { - nSRID = atoi(poSRS->GetAuthorityCode(nullptr)); - } - - /* NULL poSRS => UnknownCoordinateSystem */ - if (!poSRS) - { - FGDB_CPLAddXMLAttribute(srs_xml, "xsi:type", - "esri:UnknownCoordinateSystem"); - } - else - { - /* Set the SpatialReference type attribute correctly for GEOGCS/PROJCS - */ - if (poSRS->IsProjected()) - FGDB_CPLAddXMLAttribute(srs_xml, "xsi:type", - "esri:ProjectedCoordinateSystem"); - else - FGDB_CPLAddXMLAttribute(srs_xml, "xsi:type", - "esri:GeographicCoordinateSystem"); - - /* Add the WKT to the XML */ - SpatialReferenceInfo oESRI_SRS; - - /* Do we have a known SRID ? If so, directly query the ESRI SRS DB */ - if (nSRID && - SpatialReferences::FindSpatialReferenceBySRID(nSRID, oESRI_SRS)) - { - CPLDebug("FGDB", - "Layer SRS has a SRID (%d). Using WKT from ESRI SRS " - "DBFound perfect match. ", - nSRID); - CPLCreateXMLElementAndValue( - srs_xml, "WKT", WStringToString(oESRI_SRS.srtext).c_str()); - } - else - { - /* Make a clone so we can morph it without morphing the original */ - OGRSpatialReference *poSRSClone = poSRS->Clone(); - - /* Flip the WKT to ESRI form, return UnknownCoordinateSystem if we - * can't */ - if (poSRSClone->morphToESRI() != OGRERR_NONE) - { - delete poSRSClone; - FGDB_CPLAddXMLAttribute(srs_xml, "xsi:type", - "esri:UnknownCoordinateSystem"); - return srs_xml; - } - - char *wkt = nullptr; - poSRSClone->exportToWkt(&wkt); - if (wkt) - { - std::vector oaiCandidateSRS; - nSRID = 0; - - // Ask PROJ which known SRS matches poSRS - int nEntries = 0; - int *panMatchConfidence = nullptr; - auto pahSRS = - poSRS->FindMatches(nullptr, &nEntries, &panMatchConfidence); - for (int i = 0; i < nEntries; ++i) - { - if (panMatchConfidence[i] >= 70) - { - // Look for candidates in the EPSG/ESRI namespace, - // and find the correspond ESRI SRS from the code - const char *pszAuthName = - OSRGetAuthorityName(pahSRS[i], nullptr); - const char *pszAuthCode = - OSRGetAuthorityCode(pahSRS[i], nullptr); - if (pszAuthName && - (EQUAL(pszAuthName, "EPSG") || - EQUAL(pszAuthName, "ESRI")) && - pszAuthCode && - SpatialReferences::FindSpatialReferenceBySRID( - atoi(pszAuthCode), oESRI_SRS)) - { - const std::string osESRI_WKT = - WStringToString(oESRI_SRS.srtext); - OGRSpatialReference oSRS_FromESRI; - oSRS_FromESRI.SetAxisMappingStrategy( - OAMS_TRADITIONAL_GIS_ORDER); - if (oSRS_FromESRI.importFromWkt( - osESRI_WKT.c_str()) == OGRERR_NONE && - poSRSClone->IsSame(&oSRS_FromESRI)) - { - if (panMatchConfidence[i] == 100) - { - /* Exact match found (not sure this case - * happens) */ - nSRID = oESRI_SRS.auth_srid; - break; - } - oaiCandidateSRS.push_back(oESRI_SRS.auth_srid); - } - } - } - } - OSRFreeSRSArray(pahSRS); - CPLFree(panMatchConfidence); - - if (nSRID != 0) - { - CPLDebug("FGDB", - "Found perfect match in ESRI SRS DB " - "for layer SRS. SRID is %d", - nSRID); - } - else if (oaiCandidateSRS.empty()) - { - CPLDebug( - "FGDB", - "Did not found a match in ESRI SRS DB for layer SRS. " - "Using morphed SRS WKT. Failure is to be expected"); - } - else if (oaiCandidateSRS.size() == 1) - { - nSRID = oaiCandidateSRS[0]; - if (SpatialReferences::FindSpatialReferenceBySRID( - nSRID, oESRI_SRS)) - { - CPLDebug("FGDB", - "Found a single match in ESRI SRS DB " - "for layer SRS. SRID is %d", - nSRID); - nSRID = oESRI_SRS.auth_srid; - CPLFree(wkt); - wkt = CPLStrdup( - WStringToString(oESRI_SRS.srtext).c_str()); - } - } - else - { - /* Not sure this case can happen */ - - CPLString osCandidateSRS; - for (int i = 0; i < (int)oaiCandidateSRS.size() && i < 10; - i++) - { - if (!osCandidateSRS.empty()) - osCandidateSRS += ", "; - osCandidateSRS += CPLSPrintf("%d", oaiCandidateSRS[i]); - } - if (oaiCandidateSRS.size() > 10) - osCandidateSRS += "..."; - - CPLDebug( - "FGDB", - "As several candidates (%s) have been found in " - "ESRI SRS DB for layer SRS, none has been selected. " - "Using morphed SRS WKT. Failure is to be expected", - osCandidateSRS.c_str()); - } - - CPLCreateXMLElementAndValue(srs_xml, "WKT", wkt); - CPLFree(wkt); - } - - /* Dispose of our close */ - delete poSRSClone; - } - } - - /* Handle Origin/Scale/Tolerance */ - - oCoordPrec = GDBGridSettingsFromOGR(poSrcGeomFieldDefn, papszOptions); - const auto oIter = - oCoordPrec.oFormatSpecificOptions.find("FileGeodatabase"); - // Note: test is true - if (oIter != oCoordPrec.oFormatSpecificOptions.end()) - { - const auto &oGridsOptions = oIter->second; - for (int i = 0; i < oGridsOptions.size(); ++i) - { - char *pszKey = nullptr; - const char *pszValue = CPLParseNameValue(oGridsOptions[i], &pszKey); - if (pszKey && pszValue) - { - CPLCreateXMLElementAndValue(srs_xml, pszKey, pszValue); - } - CPLFree(pszKey); - } - } - - /* FGDB is always High Precision */ - CPLCreateXMLElementAndValue(srs_xml, "HighPrecision", "true"); - - /* Add the WKID to the XML */ - const char *pszWKID = CSLFetchNameValue(papszOptions, "WKID"); - if (pszWKID) - nSRID = atoi(pszWKID); - if (nSRID) - { - CPLCreateXMLElementAndValue(srs_xml, "WKID", CPLSPrintf("%d", nSRID)); - } - - return srs_xml; -} - -/************************************************************************/ -/* CreateFeatureDataset() */ -/************************************************************************/ - -bool FGdbLayer::CreateFeatureDataset(FGdbDataSource *pParentDataSource, - const std::string &feature_dataset_name, - const OGRGeomFieldDefn *poSrcGeomFieldDefn, - CSLConstList papszOptions) -{ - /* XML node */ - CPLXMLNode *xml_xml = CPLCreateXMLNode(nullptr, CXT_Element, "?xml"); - FGDB_CPLAddXMLAttribute(xml_xml, "version", "1.0"); - FGDB_CPLAddXMLAttribute(xml_xml, "encoding", "UTF-8"); - - /* First build up a bare-bones feature definition */ - CPLXMLNode *defn_xml = - CPLCreateXMLNode(nullptr, CXT_Element, "esri:DataElement"); - CPLAddXMLSibling(xml_xml, defn_xml); - - /* Add the attributes to the DataElement */ - FGDB_CPLAddXMLAttribute(defn_xml, "xmlns:xsi", - "http://www.w3.org/2001/XMLSchema-instance"); - FGDB_CPLAddXMLAttribute(defn_xml, "xmlns:xs", - "http://www.w3.org/2001/XMLSchema"); - FGDB_CPLAddXMLAttribute(defn_xml, "xmlns:esri", - "http://www.esri.com/schemas/ArcGIS/10.1"); - - /* Need to set this to esri:DEFeatureDataset or esri:DETable */ - FGDB_CPLAddXMLAttribute(defn_xml, "xsi:type", "esri:DEFeatureDataset"); - - /* Add in more children */ - std::string catalog_page = "\\" + feature_dataset_name; - CPLCreateXMLElementAndValue(defn_xml, "CatalogPath", catalog_page.c_str()); - CPLCreateXMLElementAndValue(defn_xml, "Name", feature_dataset_name.c_str()); - CPLCreateXMLElementAndValue(defn_xml, "ChildrenExpanded", "false"); - CPLCreateXMLElementAndValue(defn_xml, "DatasetType", - "esriDTFeatureDataset"); - CPLCreateXMLElementAndValue(defn_xml, "Versioned", "false"); - CPLCreateXMLElementAndValue(defn_xml, "CanVersion", "false"); - - /* Add in empty extent */ - CPLXMLNode *extent_xml = CPLCreateXMLNode(nullptr, CXT_Element, "Extent"); - FGDB_CPLAddXMLAttribute(extent_xml, "xsi:nil", "true"); - CPLAddXMLChild(defn_xml, extent_xml); - - /* Add the SRS */ - if (poSrcGeomFieldDefn) - { - OGRGeomCoordinatePrecision oCoordPrec; - CPLXMLNode *srs_xml = - XMLSpatialReference(poSrcGeomFieldDefn, papszOptions, oCoordPrec); - if (srs_xml) - CPLAddXMLChild(defn_xml, srs_xml); - } - - /* Convert our XML tree into a string for FGDB */ - char *defn_str = CPLSerializeXMLTree(xml_xml); - CPLDestroyXMLNode(xml_xml); - - /* TODO, tie this to debugging levels */ - CPLDebug("FGDB", "%s", defn_str); - - /* Create the FeatureDataset. */ - Geodatabase *gdb = pParentDataSource->GetGDB(); - fgdbError hr = gdb->CreateFeatureDataset(defn_str); - - /* Free the XML */ - CPLFree(defn_str); - - /* Check table create status */ - if (FAILED(hr)) - { - return GDBErr(hr, "Failed at creating FeatureDataset " + - feature_dataset_name); - } - - return true; -} - -/************************************************************************/ -/* Create() */ -/* Build up an FGDB XML layer definition and use it to create a Table */ -/* or Feature Class to work from. */ -/* */ -/* Layer creation options: */ -/* FEATURE_DATASET, nest layer inside a FeatureDataset folder */ -/* GEOMETRY_NAME, user-selected name for the geometry column */ -/* FID/OID_NAME, user-selected name for the FID column */ -/* XORIGIN, YORIGIN, ZORIGIN, origin of the snapping grid */ -/* XYSCALE, ZSCALE, inverse resolution of the snapping grid */ -/* XYTOLERANCE, ZTOLERANCE, snapping tolerance for topology/networks */ -/* */ -/************************************************************************/ - -bool FGdbLayer::Create(FGdbDataSource *pParentDataSource, - const char *pszLayerNameIn, - const OGRGeomFieldDefn *poSrcGeomFieldDefn, - CSLConstList papszOptions) -{ - std::string parent_path = ""; - std::wstring wtable_path, wparent_path; - std::string geometry_name = FGDB_GEOMETRY_NAME; - std::string fid_name = FGDB_OID_NAME; - std::string esri_type; - bool has_z = false; - bool has_m = false; - - const auto eType = - poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone; - if (eType == wkbNone) - poSrcGeomFieldDefn = nullptr; - -#ifdef EXTENT_WORKAROUND - m_bLayerJustCreated = true; -#endif - - /* Launder the Layer name */ - std::wstring wlayerName; - - wlayerName = FGDBLaunderName(StringToWString(pszLayerNameIn)); - wlayerName = FGDBEscapeReservedKeywords(wlayerName); - wlayerName = FGDBEscapeUnsupportedPrefixes(wlayerName); - - // https://desktop.arcgis.com/en/arcmap/latest/manage-data/administer-file-gdbs/file-geodatabase-size-and-name-limits.htm - // document 160 character limit but - // https://desktop.arcgis.com/en/arcmap/latest/manage-data/tables/fundamentals-of-adding-and-deleting-fields.htm#GUID-8E190093-8F8F-4132-AF4F-B0C9220F76B3 - // mentions 64. let be optimistic and aim for 160 - constexpr size_t TABLE_NAME_MAX_SIZE = 160; - if (wlayerName.size() > TABLE_NAME_MAX_SIZE) - wlayerName.resize(TABLE_NAME_MAX_SIZE); - - /* Ensures uniqueness of layer name */ - int numRenames = 1; - while ((pParentDataSource->GetLayerByName( - WStringToString(wlayerName).c_str()) != nullptr) && - (numRenames < 10)) - { - wlayerName = StringToWString(CPLSPrintf( - "%s_%d", - WStringToString(wlayerName.substr(0, TABLE_NAME_MAX_SIZE - 2)) - .c_str(), - numRenames)); - numRenames++; - } - while ((pParentDataSource->GetLayerByName( - WStringToString(wlayerName).c_str()) != nullptr) && - (numRenames < 100)) - { - wlayerName = StringToWString(CPLSPrintf( - "%s_%d", - WStringToString(wlayerName.substr(0, TABLE_NAME_MAX_SIZE - 3)) - .c_str(), - numRenames)); - numRenames++; - } + EnumRows enumRows; - const std::string layerName = WStringToString(wlayerName); - if (layerName != pszLayerNameIn) - { - CPLError(CE_Warning, CPLE_NotSupported, - "Normalized/laundered layer name: '%s' to '%s'", - pszLayerNameIn, layerName.c_str()); - } + if (FAILED(hr = m_pTable->Search(StringToWString("*"), + StringToWString(""), true, enumRows))) + return; + + if (FAILED(hr = enumRows.Next(row))) + return; - std::string table_path = "\\" + std::string(layerName); + if (hr != S_OK) + return; - /* Handle the FEATURE_DATASET case */ - if (CSLFetchNameValue(papszOptions, "FEATURE_DATASET") != nullptr) - { - std::string feature_dataset = - CSLFetchNameValue(papszOptions, "FEATURE_DATASET"); - - /* Check if FEATURE_DATASET exists. Otherwise create it */ - std::vector featuredatasets; - Geodatabase *gdb = pParentDataSource->GetGDB(); - int bFeatureDataSetExists = FALSE; - fgdbError hr; - if (!FAILED(hr = gdb->GetChildDatasets(L"\\", L"Feature Dataset", - featuredatasets))) - { - std::wstring feature_dataset_with_slash = - L"\\" + StringToWString(feature_dataset); - for (unsigned int i = 0; i < featuredatasets.size(); i++) - { - if (featuredatasets[i] == feature_dataset_with_slash) - bFeatureDataSetExists = TRUE; - } - } + /* Backup original shape buffer */ + ShapeBuffer originalGdbGeometry; + if (FAILED(hr = row.GetGeometry(originalGdbGeometry))) + return; - if (!bFeatureDataSetExists) + OGRGeometry *pOGRGeo = nullptr; + if ((!GDBGeometryToOGRGeometry(m_forceMulti, &originalGdbGeometry, + m_pSRS, &pOGRGeo)) || + pOGRGeo == nullptr) { - bool rv = CreateFeatureDataset(pParentDataSource, feature_dataset, - poSrcGeomFieldDefn, papszOptions); - if (!rv) - return rv; + delete pOGRGeo; + return; } - table_path = "\\" + feature_dataset + table_path; - parent_path = "\\" + feature_dataset; - } - - /* Convert table_path into wstring */ - wtable_path = StringToWString(table_path); - wparent_path = StringToWString(parent_path); + OGRwkbGeometryType eType = wkbFlatten(pOGRGeo->getGeometryType()); - /* Over-ride the geometry name if necessary */ - if (CSLFetchNameValue(papszOptions, "GEOMETRY_NAME") != nullptr) - geometry_name = CSLFetchNameValue(papszOptions, "GEOMETRY_NAME"); + delete pOGRGeo; + pOGRGeo = nullptr; - /* Over-ride the OID name if necessary */ - if (CSLFetchNameValue(papszOptions, "FID") != nullptr) - fid_name = CSLFetchNameValue(papszOptions, "FID"); - else if (CSLFetchNameValue(papszOptions, "OID_NAME") != nullptr) - fid_name = CSLFetchNameValue(papszOptions, "OID_NAME"); + OGRPoint oP1(floor(sLayerEnvelope.MinX), floor(sLayerEnvelope.MinY)); + OGRPoint oP2(ceil(sLayerEnvelope.MaxX), ceil(sLayerEnvelope.MaxY)); - m_bCreateMultipatch = CPLTestBool( - CSLFetchNameValueDef(papszOptions, "CREATE_MULTIPATCH", "NO")); + OGRLinearRing oLR; + oLR.addPoint(&oP1); + oLR.addPoint(&oP2); + oLR.addPoint(&oP1); - /* Figure out our geometry type */ - if (eType != wkbNone) - { - if (wkbFlatten(eType) == wkbUnknown) + if (eType == wkbPoint) { - return GDBErr(-1, "FGDB layers cannot be created with a wkbUnknown " - "layer geometry type."); + UpdateRowWithGeometry(row, &oP1); + UpdateRowWithGeometry(row, &oP2); } - if (!OGRGeometryToGDB(eType, &esri_type, &has_z, &has_m)) - return GDBErr(-1, "Unable to map OGR type to ESRI type"); - - if (wkbFlatten(eType) == wkbMultiPolygon && m_bCreateMultipatch) + else if (eType == wkbLineString) { - esri_type = "esriGeometryMultiPatch"; - has_z = true; + UpdateRowWithGeometry(row, &oLR); } - // For TIN and PolyhedralSurface, default to create a multipatch, - // unless the user explicitly disabled it - else if ((wkbFlatten(eType) == wkbTIN || - wkbFlatten(eType) == wkbPolyhedralSurface) && - CPLTestBool(CSLFetchNameValueDef(papszOptions, - "CREATE_MULTIPATCH", "YES"))) + else if (eType == wkbPolygon) { - m_bCreateMultipatch = true; - esri_type = "esriGeometryMultiPatch"; - has_z = true; - } - } + OGRPolygon oPoly; + oPoly.addRing(&oLR); - const auto eFlattenType = wkbFlatten(eType); - const bool bIsLine = - eFlattenType == wkbLineString || eFlattenType == wkbMultiLineString; - const bool bIsPolygon = - eFlattenType == wkbPolygon || eFlattenType == wkbMultiPolygon; - - const bool bCreateShapeLength = - (bIsLine || bIsPolygon) && !m_bCreateMultipatch && - CPLTestBool(CSLFetchNameValueDef( - papszOptions, "CREATE_SHAPE_AREA_AND_LENGTH_FIELDS", "NO")); - // Setting a non-default value doesn't work - const char *pszLengthFieldName = - CSLFetchNameValueDef(papszOptions, "LENGTH_FIELD_NAME", "Shape_Length"); - - const bool bCreateShapeArea = - bIsPolygon && !m_bCreateMultipatch && - CPLTestBool(CSLFetchNameValueDef( - papszOptions, "CREATE_SHAPE_AREA_AND_LENGTH_FIELDS", "NO")); - // Setting a non-default value doesn't work - const char *pszAreaFieldName = - CSLFetchNameValueDef(papszOptions, "AREA_FIELD_NAME", "Shape_Area"); - - m_bLaunderReservedKeywords = - CPLFetchBool(papszOptions, "LAUNDER_RESERVED_KEYWORDS", true); - - /* XML node */ - CPLXMLNode *xml_xml = CPLCreateXMLNode(nullptr, CXT_Element, "?xml"); - FGDB_CPLAddXMLAttribute(xml_xml, "version", "1.0"); - FGDB_CPLAddXMLAttribute(xml_xml, "encoding", "UTF-8"); - - /* First build up a bare-bones feature definition */ - CPLXMLNode *defn_xml = - CPLCreateXMLNode(nullptr, CXT_Element, "esri:DataElement"); - CPLAddXMLSibling(xml_xml, defn_xml); - - /* Add the attributes to the DataElement */ - FGDB_CPLAddXMLAttribute(defn_xml, "xmlns:xsi", - "http://www.w3.org/2001/XMLSchema-instance"); - FGDB_CPLAddXMLAttribute(defn_xml, "xmlns:xs", - "http://www.w3.org/2001/XMLSchema"); - FGDB_CPLAddXMLAttribute(defn_xml, "xmlns:esri", - "http://www.esri.com/schemas/ArcGIS/10.1"); - - /* Need to set this to esri:DEFeatureDataset or esri:DETable */ - FGDB_CPLAddXMLAttribute( - defn_xml, "xsi:type", - (eType == wkbNone ? "esri:DETable" : "esri:DEFeatureClass")); - - /* Add in more children */ - CPLCreateXMLElementAndValue(defn_xml, "CatalogPath", table_path.c_str()); - CPLCreateXMLElementAndValue(defn_xml, "Name", layerName.c_str()); - CPLCreateXMLElementAndValue(defn_xml, "ChildrenExpanded", "false"); - - /* WKB type of none implies this is a 'Table' otherwise it is a 'Feature - * Class' */ - std::string datasettype = - (eType == wkbNone ? "esriDTTable" : "esriDTFeatureClass"); - CPLCreateXMLElementAndValue(defn_xml, "DatasetType", datasettype.c_str()); - CPLCreateXMLElementAndValue(defn_xml, "Versioned", "false"); - CPLCreateXMLElementAndValue(defn_xml, "CanVersion", "false"); - - if (CSLFetchNameValue(papszOptions, "CONFIGURATION_KEYWORD") != nullptr) - CPLCreateXMLElementAndValue( - defn_xml, "ConfigurationKeyword", - CSLFetchNameValue(papszOptions, "CONFIGURATION_KEYWORD")); - - /* We might need to make OID optional later, but OGR likes to have a FID */ - CPLCreateXMLElementAndValue(defn_xml, "HasOID", "true"); - CPLCreateXMLElementAndValue(defn_xml, "OIDFieldName", fid_name.c_str()); - - /* Add in empty Fields */ - CPLXMLNode *fields_xml = CPLCreateXMLNode(defn_xml, CXT_Element, "Fields"); - FGDB_CPLAddXMLAttribute(fields_xml, "xsi:type", "esri:Fields"); - CPLXMLNode *fieldarray_xml = - CPLCreateXMLNode(fields_xml, CXT_Element, "FieldArray"); - FGDB_CPLAddXMLAttribute(fieldarray_xml, "xsi:type", "esri:ArrayOfField"); - - /* Feature Classes have an implicit geometry column, so we'll add it at - * creation time */ - CPLXMLNode *srs_xml = nullptr; - OGRGeomCoordinatePrecision oCoordPrec; - if (poSrcGeomFieldDefn) - { - CPLXMLNode *shape_xml = - CPLCreateXMLNode(fieldarray_xml, CXT_Element, "Field"); - FGDB_CPLAddXMLAttribute(shape_xml, "xsi:type", "esri:Field"); - CPLCreateXMLElementAndValue(shape_xml, "Name", geometry_name.c_str()); - CPLCreateXMLElementAndValue(shape_xml, "Type", "esriFieldTypeGeometry"); - if (CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true)) - CPLCreateXMLElementAndValue(shape_xml, "IsNullable", "true"); - else - CPLCreateXMLElementAndValue(shape_xml, "IsNullable", "false"); - CPLCreateXMLElementAndValue(shape_xml, "Length", "0"); - CPLCreateXMLElementAndValue(shape_xml, "Precision", "0"); - CPLCreateXMLElementAndValue(shape_xml, "Scale", "0"); - CPLCreateXMLElementAndValue(shape_xml, "Required", "true"); - CPLXMLNode *geom_xml = - CPLCreateXMLNode(shape_xml, CXT_Element, "GeometryDef"); - FGDB_CPLAddXMLAttribute(geom_xml, "xsi:type", "esri:GeometryDef"); - CPLCreateXMLElementAndValue(geom_xml, "AvgNumPoints", "0"); - CPLCreateXMLElementAndValue(geom_xml, "GeometryType", - esri_type.c_str()); - CPLCreateXMLElementAndValue(geom_xml, "HasM", - (has_m ? "true" : "false")); - CPLCreateXMLElementAndValue(geom_xml, "HasZ", - (has_z ? "true" : "false")); - - /* Add the SRS if we have one */ - srs_xml = - XMLSpatialReference(poSrcGeomFieldDefn, papszOptions, oCoordPrec); - if (srs_xml) - CPLAddXMLChild(geom_xml, srs_xml); - } + UpdateRowWithGeometry(row, &oPoly); + } + else if (eType == wkbMultiPoint) + { + OGRMultiPoint oColl; + oColl.addGeometry(&oP1); + oColl.addGeometry(&oP2); - /* All (?) Tables and Feature Classes will have an ObjectID */ - CPLXMLNode *oid_xml = - CPLCreateXMLNode(fieldarray_xml, CXT_Element, "Field"); - FGDB_CPLAddXMLAttribute(oid_xml, "xsi:type", "esri:Field"); - CPLCreateXMLElementAndValue(oid_xml, "Name", fid_name.c_str()); - CPLCreateXMLElementAndValue(oid_xml, "Type", "esriFieldTypeOID"); - CPLCreateXMLElementAndValue(oid_xml, "IsNullable", "false"); - CPLCreateXMLElementAndValue(oid_xml, "Length", "4"); - CPLCreateXMLElementAndValue(oid_xml, "Precision", "0"); - CPLCreateXMLElementAndValue(oid_xml, "Scale", "0"); - CPLCreateXMLElementAndValue(oid_xml, "Required", "true"); - - /* Add in empty Indexes */ - CPLXMLNode *indexes_xml = - CPLCreateXMLNode(defn_xml, CXT_Element, "Indexes"); - FGDB_CPLAddXMLAttribute(indexes_xml, "xsi:type", "esri:Indexes"); - CPLXMLNode *indexarray_xml = - CPLCreateXMLNode(indexes_xml, CXT_Element, "IndexArray"); - FGDB_CPLAddXMLAttribute(indexarray_xml, "xsi:type", "esri:ArrayOfIndex"); - - /* CLSID http://forums.arcgis.com/threads/34536?p=118484#post118484 */ - if (eType == wkbNone) - { - CPLCreateXMLElementAndValue(defn_xml, "CLSID", - "{7A566981-C114-11D2-8A28-006097AFF44E}"); - CPLCreateXMLElementAndValue(defn_xml, "EXTCLSID", ""); - } - else - { - CPLCreateXMLElementAndValue(defn_xml, "CLSID", - "{52353152-891A-11D0-BEC6-00805F7C4268}"); - CPLCreateXMLElementAndValue(defn_xml, "EXTCLSID", ""); - } + UpdateRowWithGeometry(row, &oColl); + } + else if (eType == wkbMultiLineString) + { + OGRMultiLineString oColl; + oColl.addGeometry(&oLR); - /* Set the alias for the Feature Class, check if we received an */ - /* explicit one in the options vector. */ - const char *pszLayerAlias = CSLFetchNameValue(papszOptions, "LAYER_ALIAS"); - if (pszLayerAlias != nullptr) - { - CPLCreateXMLElementAndValue(defn_xml, "AliasName", pszLayerAlias); - } - else if (pszLayerNameIn != layerName) - { - CPLCreateXMLElementAndValue(defn_xml, "AliasName", pszLayerNameIn); - } + UpdateRowWithGeometry(row, &oColl); + } + else if (eType == wkbMultiPolygon) + { + OGRMultiPolygon oColl; + OGRPolygon oPoly; + oPoly.addRing(&oLR); + oColl.addGeometry(&oPoly); - /* Map from OGR WKB type to ESRI type */ - if (eType != wkbNone) - { - /* Declare our feature type */ - CPLCreateXMLElementAndValue(defn_xml, "FeatureType", "esriFTSimple"); - CPLCreateXMLElementAndValue(defn_xml, "ShapeType", esri_type.c_str()); - CPLCreateXMLElementAndValue(defn_xml, "ShapeFieldName", - geometry_name.c_str()); - - /* Dimensionality */ - CPLCreateXMLElementAndValue(defn_xml, "HasM", - (has_m ? "true" : "false")); - CPLCreateXMLElementAndValue(defn_xml, "HasZ", - (has_z ? "true" : "false")); - - CPLCreateXMLElementAndValue(defn_xml, "HasSpatialIndex", "true"); - - /* These field are required for Arcmap to display aliases correctly */ - if (bCreateShapeArea) - CPLCreateXMLElementAndValue(defn_xml, "AreaFieldName", - pszAreaFieldName); + UpdateRowWithGeometry(row, &oColl); + } else - CPLCreateXMLNode(defn_xml, CXT_Element, "AreaFieldName"); + return; - if (bCreateShapeLength) - CPLCreateXMLElementAndValue(defn_xml, "LengthFieldName", - pszLengthFieldName); - else - CPLCreateXMLNode(defn_xml, CXT_Element, "LengthFieldName"); + /* Restore original ShapeBuffer */ + hr = row.SetGeometry(originalGdbGeometry); + if (FAILED(hr)) + return; - /* We can't know the extent at this point */ - CPLXMLNode *extn_xml = - CPLCreateXMLNode(defn_xml, CXT_Element, "Extent"); - FGDB_CPLAddXMLAttribute(extn_xml, "xsi:nil", "true"); - } + /* Update Row */ + hr = m_pTable->Update(row); + if (FAILED(hr)) + return; - /* Feature Class with known SRS gets an SRS entry */ - if (eType != wkbNone && srs_xml != nullptr) - { - CPLAddXMLChild(defn_xml, CPLCloneXMLTree(srs_xml)); + CPLDebug("FGDB", + "Workaround extent problem with Linux 64bit FGDB SDK 1.1"); } +} +#endif // EXTENT_WORKAROUND - /* Convert our XML tree into a string for FGDB */ - char *defn_str; - - if (CSLFetchNameValue(papszOptions, "XML_DEFINITION") != nullptr) - defn_str = CPLStrdup(CSLFetchNameValue(papszOptions, "XML_DEFINITION")); - else - defn_str = CPLSerializeXMLTree(xml_xml); - CPLDestroyXMLNode(xml_xml); - - /* TODO, tie this to debugging levels */ - CPLDebug("FGDB", "%s", defn_str); - // std::cout << defn_str << std::endl; - - /* Create the table. */ - Table *table = new Table; - Geodatabase *gdb = pParentDataSource->GetGDB(); - fgdbError hr = gdb->CreateTable(defn_str, wparent_path, *table); +/************************************************************************/ +/* GetRow() */ +/************************************************************************/ - /* Free the XML */ - CPLFree(defn_str); +OGRErr FGdbLayer::GetRow(EnumRows &enumRows, Row &row, GIntBig nFID) +{ + long hr; + CPLString osQuery; - /* Check table create status */ - if (FAILED(hr)) + /* Querying a 64bit FID causes a runtime exception in FileGDB... */ + if (!CPL_INT64_FITS_ON_INT32(nFID)) { - delete table; - return GDBErr(hr, "Failed at creating table for " + table_path); + return OGRERR_FAILURE; } - m_papszOptions = CSLDuplicate(papszOptions); - - // Default to YES here assuming ogr2ogr scenario - m_bBulkLoadAllowed = - CPLTestBool(CPLGetConfigOption("FGDB_BULK_LOAD", "YES")); + osQuery.Printf("%s = " CPL_FRMT_GIB, m_strOIDFieldName.c_str(), nFID); - /* Store the new FGDB Table pointer and set up the OGRFeatureDefn */ - bool bRet = - FGdbLayer::Initialize(pParentDataSource, table, wtable_path, L"Table"); - if (bRet) + if (FAILED(hr = m_pTable->Search(m_wstrSubfields, + StringToWString(osQuery.c_str()), true, + enumRows))) { - if (m_pFeatureDefn->GetGeomFieldCount() != 0) - m_pFeatureDefn->GetGeomFieldDefn(0)->SetCoordinatePrecision( - oCoordPrec); + GDBErr(hr, "Failed fetching row "); + return OGRERR_FAILURE; + } - if (bCreateShapeArea) - { - OGRFieldDefn oField(pszAreaFieldName, OFTReal); - oField.SetDefault("FILEGEODATABASE_SHAPE_AREA"); - bRet &= CreateField(&oField, false) == OGRERR_NONE; - } - if (bCreateShapeLength) - { - OGRFieldDefn oField(pszLengthFieldName, OFTReal); - oField.SetDefault("FILEGEODATABASE_SHAPE_LENGTH"); - bRet &= CreateField(&oField, false) == OGRERR_NONE; - } + if (FAILED(hr = enumRows.Next(row))) + { + GDBErr(hr, "Failed fetching row "); + return OGRERR_FAILURE; } - return bRet; + + if (hr != S_OK) + return OGRERR_NON_EXISTING_FEATURE; // none found - but no failure + + return OGRERR_NONE; } /*************************************************************************/ @@ -3364,8 +974,6 @@ void FGdbLayer::ResetReading() if (m_pTable == nullptr) return; - EndBulkLoad(); - #ifdef WORKAROUND_CRASH_ON_CDF_WITH_BINARY_FIELD const std::wstring wstrSubFieldBackup(m_wstrSubfields); if (!m_apoByteArrays.empty()) @@ -3428,28 +1036,12 @@ void FGdbLayer::SetSpatialFilter(OGRGeometry *pOGRGeom) OGRLayer::SetSpatialFilter(pOGRGeom); } -/************************************************************************/ -/* ResyncIDs() */ -/************************************************************************/ - -void FGdbLayer::ResyncIDs() -{ - if (m_oMapOGRFIDToFGDBFID.empty()) - return; - if (m_pDS->CloseInternal()) - m_pDS->ReOpen(); -} - /************************************************************************/ /* SetAttributeFilter() */ /************************************************************************/ OGRErr FGdbLayer::SetAttributeFilter(const char *pszQuery) { - if (pszQuery != nullptr && - CPLString(pszQuery).ifind(GetFIDColumn()) != std::string::npos) - ResyncIDs(); - m_wstrWhereClause = StringToWString((pszQuery != nullptr) ? pszQuery : ""); m_bFilterDirty = true; @@ -3735,8 +1327,6 @@ OGRFeature *FGdbLayer::GetNextFeature() if (m_bFilterDirty) ResetReading(); - EndBulkLoad(); - #ifdef WORKAROUND_CRASH_ON_CDF_WITH_BINARY_FIELD if (!m_apoByteArrays.empty() && m_bWorkaroundCrashOnCDFWithBinaryField) { @@ -3795,15 +1385,6 @@ OGRFeature *FGdbLayer::GetNextFeature() #endif OGRFeature *poFeature = FGdbBaseLayer::GetNextFeature(); - if (poFeature) - { - std::map::iterator oIter = - m_oMapFGDBFIDToOGRFID.find((int)poFeature->GetFID()); - if (oIter != m_oMapFGDBFIDToOGRFID.end()) - { - poFeature->SetFID(oIter->second); - } - } return poFeature; } @@ -3819,14 +1400,7 @@ OGRFeature *FGdbLayer::GetFeature(GIntBig oid) if (!CPL_INT64_FITS_ON_INT32(oid) || m_pTable == nullptr) return nullptr; - EndBulkLoad(); - int nFID32 = (int)oid; - std::map::iterator oIter = m_oMapOGRFIDToFGDBFID.find(nFID32); - if (oIter != m_oMapOGRFIDToFGDBFID.end()) - nFID32 = oIter->second; - else if (m_oMapFGDBFIDToOGRFID.find(nFID32) != m_oMapFGDBFIDToOGRFID.end()) - return nullptr; if (GetRow(enumRows, row, nFID32) != OGRERR_NONE) return nullptr; @@ -3856,8 +1430,6 @@ GIntBig FGdbLayer::GetFeatureCount(CPL_UNUSED int bForce) if (m_pTable == nullptr) return 0; - EndBulkLoad(); - if (m_poFilterGeom != nullptr || !m_wstrWhereClause.empty()) { ResetReading(); @@ -3932,19 +1504,6 @@ GIntBig FGdbLayer::GetFeatureCount(CPL_UNUSED int bForce) const char *FGdbLayer::GetMetadataItem(const char *pszName, const char *pszDomain) { - if (pszDomain != nullptr && EQUAL(pszDomain, "MAP_OGR_FID_TO_FGDB_FID")) - { - if (m_oMapOGRFIDToFGDBFID.find(atoi(pszName)) != - m_oMapOGRFIDToFGDBFID.end()) - return CPLSPrintf("%d", m_oMapOGRFIDToFGDBFID[atoi(pszName)]); - } - else if (pszDomain != nullptr && - EQUAL(pszDomain, "MAP_FGDB_FID_TO_OGR_FID")) - { - if (m_oMapFGDBFIDToOGRFID.find(atoi(pszName)) != - m_oMapFGDBFIDToOGRFID.end()) - return CPLSPrintf("%d", m_oMapFGDBFIDToOGRFID[atoi(pszName)]); - } return OGRLayer::GetMetadataItem(pszName, pszDomain); } @@ -3998,72 +1557,6 @@ OGRErr FGdbLayer::GetExtent(OGREnvelope *psExtent, int bForce) return OGRERR_NONE; } -/************************************************************************/ -/* StartBulkLoad() */ -/************************************************************************/ - -void FGdbLayer::StartBulkLoad() -{ - if (!m_pTable) - return; - - if (m_bBulkLoadInProgress) - return; - - m_bBulkLoadInProgress = TRUE; - m_pTable->LoadOnlyMode(true); - m_pTable->SetWriteLock(); -} - -/************************************************************************/ -/* EndBulkLoad() */ -/************************************************************************/ - -void FGdbLayer::EndBulkLoad() -{ - if (!m_pTable) - return; - - if (!m_bBulkLoadInProgress) - return; - - m_bBulkLoadInProgress = FALSE; - m_bBulkLoadAllowed = -1; /* so that the configuration option is read the - first time we CreateFeature() again */ - m_pTable->LoadOnlyMode(false); - m_pTable->FreeWriteLock(); -} - -/* OGRErr FGdbLayer::StartTransaction () -{ - if ( ! m_pTable ) - return OGRERR_FAILURE; - - m_pTable->LoadOnlyMode(true); - m_pTable->SetWriteLock(); - return OGRERR_NONE; -} */ - -/* OGRErr FGdbLayer::CommitTransaction () -{ - if ( ! m_pTable ) - return OGRERR_FAILURE; - - m_pTable->LoadOnlyMode(false); - m_pTable->FreeWriteLock(); - return OGRERR_NONE; -} */ - -/* OGRErr FGdbLayer::RollbackTransaction () -{ - if ( ! m_pTable ) - return OGRERR_FAILURE; - - m_pTable->LoadOnlyMode(false); - m_pTable->FreeWriteLock(); - return OGRERR_NONE; -} */ - /************************************************************************/ /* GetLayerXML() */ /* Return XML definition of the Layer as provided by FGDB. Caller must */ @@ -4116,45 +1609,6 @@ OGRErr FGdbLayer::GetLayerMetadataXML(char **ppXml) return OGRERR_NONE; } -/************************************************************************/ -/* Rename() */ -/************************************************************************/ - -OGRErr FGdbLayer::Rename(const char *pszDstTableName) -{ - if (!TestCapability(OLCRename)) - return OGRERR_FAILURE; - - if (m_pTable == nullptr) - return OGRERR_FAILURE; - - if (m_pDS->GetLayerByName(pszDstTableName) != nullptr) - { - CPLError(CE_Failure, CPLE_AppDefined, "Layer %s already exists", - pszDstTableName); - return OGRERR_FAILURE; - } - - long hr = m_pDS->GetGDB()->Rename(m_wstrTablePath, m_wstrType, - StringToWString(pszDstTableName)); - - if (FAILED(hr)) - { - GDBErr(hr, "Failed renaming layer"); - return OGRERR_FAILURE; - } - - m_strName = pszDstTableName; - auto strTablePath = WStringToString(m_wstrTablePath); - m_wstrTablePath = - StringToWString(strTablePath.substr(0, strTablePath.rfind('\\')) + - "\\" + pszDstTableName); - SetDescription(pszDstTableName); - m_pFeatureDefn->SetName(pszDstTableName); - - return OGRERR_NONE; -} - /************************************************************************/ /* TestCapability() */ /************************************************************************/ @@ -4174,40 +1628,14 @@ int FGdbLayer::TestCapability(const char *pszCap) else if (EQUAL(pszCap, OLCFastGetExtent)) return m_poFilterGeom == nullptr && m_wstrWhereClause.empty(); - else if (EQUAL(pszCap, OLCCreateField)) /* CreateField() */ - return m_pDS->GetUpdate(); - - else if (EQUAL(pszCap, OLCSequentialWrite)) /* ICreateFeature() */ - return m_pDS->GetUpdate(); - else if (EQUAL(pszCap, OLCStringsAsUTF8)) /* Native UTF16, converted to UTF8 */ return TRUE; - else if (EQUAL(pszCap, OLCDeleteFeature)) /* DeleteFeature() */ - return m_pDS->GetUpdate(); - - else if (EQUAL(pszCap, OLCRandomWrite)) /* ISetFeature() */ - return m_pDS->GetUpdate(); - - else if (EQUAL(pszCap, OLCDeleteField)) /* DeleteField() */ - return m_pDS->GetUpdate(); - -#ifdef AlterFieldDefn_implemented_but_not_working - else if (EQUAL(pszCap, OLCAlterFieldDefn)) /* AlterFieldDefn() */ - return m_pDS->GetUpdate(); -#endif - - else if (EQUAL(pszCap, OLCRename)) /* Rename() */ - return m_pDS->GetUpdate(); - else if (EQUAL(pszCap, OLCFastSetNextByIndex)) /* TBD FastSetNextByIndex() */ return FALSE; - else if (EQUAL(pszCap, OLCTransactions)) /* TBD Start/End Transactions() */ - return FALSE; - else if (EQUAL(pszCap, OLCIgnoreFields)) return TRUE; @@ -4221,102 +1649,6 @@ int FGdbLayer::TestCapability(const char *pszCap) return FALSE; } -/************************************************************************/ -/* CreateRealCopy() */ -/************************************************************************/ - -int FGdbLayer::CreateRealCopy() -{ - CPLAssert(m_bSymlinkFlag); - - // Find the FID of the layer in the system catalog - char *apszDrivers[2] = {nullptr}; - apszDrivers[0] = (char *)"OpenFileGDB"; - apszDrivers[1] = nullptr; - const std::string osSystemCatalog = - CPLFormFilenameSafe(m_pDS->GetFSName(), "a00000001.gdbtable", nullptr); - GDALDataset *poOpenFileGDBDS = GDALDataset::Open( - osSystemCatalog.c_str(), GDAL_OF_VECTOR, apszDrivers, nullptr, nullptr); - if (poOpenFileGDBDS == nullptr || poOpenFileGDBDS->GetLayer(0) == nullptr) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot open %s with OpenFileGDB driver. Should not happen.", - osSystemCatalog.c_str()); - GDALClose(poOpenFileGDBDS); - return FALSE; - } - - OGRLayer *poLayer = poOpenFileGDBDS->GetLayer(0); - CPLString osFilter = "name = '"; - osFilter += GetName(); - osFilter += "'"; - poLayer->SetAttributeFilter(osFilter); - poLayer->ResetReading(); - OGRFeature *poF = poLayer->GetNextFeature(); - if (poF == nullptr) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot find filename for layer %s", GetName()); - GDALClose(poOpenFileGDBDS); - return FALSE; - } - int nLayerFID = (int)poF->GetFID(); - delete poF; - GDALClose(poOpenFileGDBDS); - - if (!m_pDS->CloseInternal(TRUE)) - return FALSE; - - // Create real copies (in .tmp files now) instead of symlinks - char **papszFiles = VSIReadDir(m_pDS->GetFSName()); - CPLString osBasename(CPLSPrintf("a%08x", nLayerFID)); - int bError = FALSE; - std::vector aoFiles; - for (char **papszIter = papszFiles; !bError && papszIter && *papszIter; - papszIter++) - { - if (strncmp(*papszIter, osBasename.c_str(), osBasename.size()) == 0) - { - if (CPLCopyFile( - CPLFormFilenameSafe(m_pDS->GetFSName(), *papszIter, "tmp") - .c_str(), - CPLFormFilenameSafe(m_pDS->GetFSName(), *papszIter, nullptr) - .c_str()) != 0) - { - CPLError(CE_Failure, CPLE_AppDefined, "Cannot copy %s", - *papszIter); - bError = TRUE; - } - else - aoFiles.push_back(*papszIter); - } - } - CSLDestroy(papszFiles); - - // Rename the .tmp into normal filenames - for (size_t i = 0; !bError && i < aoFiles.size(); i++) - { - if (VSIUnlink( - CPLFormFilenameSafe(m_pDS->GetFSName(), aoFiles[i], nullptr) - .c_str()) != 0 || - VSIRename( - CPLFormFilenameSafe(m_pDS->GetFSName(), aoFiles[i], "tmp") - .c_str(), - CPLFormFilenameSafe(m_pDS->GetFSName(), aoFiles[i], nullptr) - .c_str()) != 0) - { - CPLError(CE_Failure, CPLE_AppDefined, "Cannot rename %s.tmp", - aoFiles[i].c_str()); - bError = TRUE; - } - } - - int bRet = !bError && m_pDS->ReOpen(); - if (bRet) - m_bSymlinkFlag = FALSE; - return bRet; -} - /************************************************************************/ /* GetDataset() */ /************************************************************************/ diff --git a/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h b/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h index 6bf120017708..631fd74c2580 100644 --- a/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h +++ b/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h @@ -17,7 +17,6 @@ #include #include #include "ogrsf_frmts.h" -#include "ogremulatedtransaction.h" /* GDAL string utilities */ #include "cpl_string.h" @@ -93,26 +92,9 @@ class FGdbLayer final : public FGdbBaseLayer bool m_bWorkaroundCrashOnCDFWithBinaryField = false; - int m_bBulkLoadAllowed; - int m_bBulkLoadInProgress; - virtual void CloseGDBObjects() override; - int EditIndexesForFIDHack(const char *pszRadixTablename); - int EditGDBTablX(const CPLString &osGDBTablX, - const CPLString &osNewGDBTablX); - int EditATXOrSPX(const CPLString &osIndex); - int EditATXOrSPX(VSILFILE *fp, int nThisPage, int &nLastPageVisited, - int nDepth, int nSizeIndexedValue, - GByte *pabyLastIndexedValue, int &bIndexedValueIsValid, - int &nFirstIndexAtThisValue, - std::vector &anPagesAtThisValue, int &bSortThisValue, - int &bInvalidateIndex); - - void StartBulkLoad(); - void EndBulkLoad(); #ifdef EXTENT_WORKAROUND - bool m_bLayerJustCreated; OGREnvelope sLayerEnvelope; bool m_bLayerEnvelopeValid; void WorkAroundExtentProblem(); @@ -123,22 +105,6 @@ class FGdbLayer final : public FGdbBaseLayer OGRErr PopulateRowWithFeature(Row &row, OGRFeature *poFeature); OGRErr GetRow(EnumRows &enumRows, Row &row, GIntBig nFID); - char **m_papszOptions; - - int m_bCreateMultipatch; - - std::map m_oMapOGRFIDToFGDBFID; - std::map m_oMapFGDBFIDToOGRFID; - int m_nResyncThreshold; - void ResyncIDs(); - - int m_bSymlinkFlag; - int CreateRealCopy(); - - char *CreateFieldDefn(OGRFieldDefn &oField, int bApproxOK, - std::string &fieldname_clean, - std::string &gdbFieldType); - public: FGdbLayer(); virtual ~FGdbLayer(); @@ -147,13 +113,6 @@ class FGdbLayer final : public FGdbBaseLayer bool Initialize(FGdbDataSource *pParentDataSource, Table *pTable, const std::wstring &wstrTablePath, const std::wstring &wstrType); - bool Create(FGdbDataSource *pParentDataSource, const char *pszLayerName, - const OGRGeomFieldDefn *poSrcGeomFieldDefn, - CSLConstList papszOptions); - static bool CreateFeatureDataset(FGdbDataSource *pParentDataSource, - const std::string &feature_dataset_name, - const OGRGeomFieldDefn *poSrcGeomFieldDefn, - CSLConstList papszOptions); // virtual const char *GetName(); virtual const char *GetFIDColumn() override @@ -180,18 +139,6 @@ class FGdbLayer final : public FGdbBaseLayer return m_wstrType; } - virtual OGRErr CreateField(const OGRFieldDefn *poField, - int bApproxOK) override; - virtual OGRErr DeleteField(int iFieldToDelete) override; -#ifdef AlterFieldDefn_implemented_but_not_working - virtual OGRErr AlterFieldDefn(int iFieldToAlter, - OGRFieldDefn *poNewFieldDefn, int nFlags); -#endif - - virtual OGRErr ICreateFeature(OGRFeature *poFeature) override; - virtual OGRErr ISetFeature(OGRFeature *poFeature) override; - virtual OGRErr DeleteFeature(GIntBig nFID) override; - virtual OGRErr GetExtent(OGREnvelope *psExtent, int bForce) override; virtual OGRErr GetExtent(int iGeomField, OGREnvelope *psExtent, @@ -210,10 +157,6 @@ class FGdbLayer final : public FGdbBaseLayer OGRLayer::SetSpatialFilter(iGeomField, poGeom); } - // virtual OGRErr StartTransaction( ); - // virtual OGRErr CommitTransaction( ); - // virtual OGRErr RollbackTransaction( ); - OGRFeatureDefn *GetLayerDefn() override { return m_pFeatureDefn; @@ -226,16 +169,9 @@ class FGdbLayer final : public FGdbBaseLayer OGRErr GetLayerXML(char **poXml); OGRErr GetLayerMetadataXML(char **poXmlMeta); - void SetSymlinkFlag() - { - m_bSymlinkFlag = TRUE; - } - virtual const char *GetMetadataItem(const char *pszName, const char *pszDomain) override; - virtual OGRErr Rename(const char *pszNewName) override; - GDALDataset *GetDataset() override; protected: @@ -309,9 +245,6 @@ class FGdbDataSource final : public GDALDataset std::map> m_osMapRelationships{}; - int FixIndexes(); - int bPerLayerCopyingForTransaction; - public: FGdbDataSource(bool bUseDriverMutex, FGdbDatabaseConnection *pConnection, bool bUseOpenFileGDB); @@ -331,12 +264,6 @@ class FGdbDataSource final : public GDALDataset OGRLayer *GetLayer(int) override; - OGRLayer *ICreateLayer(const char *pszName, - const OGRGeomFieldDefn *poGeomFieldDefn, - CSLConstList papszOptions) override; - - virtual OGRErr DeleteLayer(int) override; - virtual OGRLayer *ExecuteSQL(const char *pszSQLCommand, OGRGeometry *poSpatialFilter, const char *pszDialect) override; @@ -349,15 +276,6 @@ class FGdbDataSource final : public GDALDataset std::vector GetFieldDomainNames(CSLConstList papszOptions = nullptr) const override; - bool AddFieldDomain(std::unique_ptr &&domain, - std::string &failureReason) override; - - bool DeleteFieldDomain(const std::string &name, - std::string &failureReason) override; - - bool UpdateFieldDomain(std::unique_ptr &&domain, - std::string &failureReason) override; - std::vector GetRelationshipNames(CSLConstList papszOptions = nullptr) const override; @@ -374,11 +292,6 @@ class FGdbDataSource final : public GDALDataset return m_pGeodatabase; } - bool GetUpdate() - { - return m_bUpdate; - } - FGdbDatabaseConnection *GetConnection() { return m_pConnection; @@ -395,16 +308,6 @@ class FGdbDataSource final : public GDALDataset } int CloseInternal(int bCloseGeodatabase = FALSE); - int ReOpen(); - - int HasPerLayerCopyingForTransaction(); - - void SetPerLayerCopyingForTransaction(int bFlag) - { - bPerLayerCopyingForTransaction = bFlag; - } - - void SetSymlinkFlagOnAllLayers(); bool UseOpenFileGDB() const { @@ -426,7 +329,6 @@ class FGdbDataSource final : public GDALDataset FGdbDatabaseConnection *m_pConnection; std::vector m_layers; Geodatabase *m_pGeodatabase; - bool m_bUpdate; GDALDriver *m_poOpenFileGDBDrv; std::unique_ptr m_poOpenFileGDBDS; bool m_bUseOpenFileGDB = false; @@ -441,7 +343,7 @@ class FGdbDatabaseConnection public: FGdbDatabaseConnection(const std::string &osName, Geodatabase *pGeodatabase) : m_osName(osName), m_pGeodatabase(pGeodatabase), m_nRefCount(1), - m_bLocked(FALSE), m_bFIDHackInProgress(FALSE) + m_bLocked(FALSE) { } @@ -449,7 +351,6 @@ class FGdbDatabaseConnection Geodatabase *m_pGeodatabase; int m_nRefCount; int m_bLocked; - int m_bFIDHackInProgress; Geodatabase *GetGDB() { @@ -471,40 +372,16 @@ class FGdbDatabaseConnection return m_bLocked; } - int IsFIDHackInProgress() const - { - return m_bFIDHackInProgress; - } - - void SetFIDHackInProgress(int bFlag) - { - m_bFIDHackInProgress = bFlag; - } - int OpenGeodatabase(const char *pszOverriddenName); void CloseGeodatabase(); }; -class FGdbTransactionManager final : public IOGRTransactionBehaviour -{ - public: - virtual OGRErr StartTransaction(GDALDataset *&poDSInOut, - int &bOutHasReopenedDS) override; - virtual OGRErr CommitTransaction(GDALDataset *&poDSInOut, - int &bOutHasReopenedDS) override; - virtual OGRErr RollbackTransaction(GDALDataset *&poDSInOut, - int &bOutHasReopenedDS) override; -}; - class FGdbDriver final : public GDALDriver { public: static void Release(const char *pszName); - static FGdbTransactionManager *GetTransactionManager(); - static CPLMutex *hMutex; - static FGdbTransactionManager *m_poTransactionManager; }; CPL_C_START diff --git a/port/cpl_known_config_options.h b/port/cpl_known_config_options.h index 336110c05885..ec71cd938bbc 100644 --- a/port/cpl_known_config_options.h +++ b/port/cpl_known_config_options.h @@ -216,13 +216,7 @@ constexpr static const char* const apszKnownConfigOptions[] = "ES_OVERWRITE", // from ogrelasticdatasource.cpp "ES_WRITEMAP", // from ogrelasticdatasource.cpp "ESRI_XML_PAM", // from gtiffdataset.cpp, gtiffdataset_write.cpp - "FGDB_BULK_LOAD", // from FGdbLayer.cpp - "FGDB_PER_LAYER_COPYING_TRANSACTION", // from FGdbDatasource.cpp - "FGDB_RESYNC_THRESHOLD", // from FGdbLayer.cpp - "FGDB_SIMUL_FAIL", // from FGdbDriver.cpp - "FGDB_SIMUL_FAIL_REOPEN", // from FGdbDatasource.cpp "FGDB_STRING_WIDTH", // from FGdbUtils.cpp - "FILEGDB_DISABLE_SPARSE_PAGES", // from FGdbLayer.cpp "FORCE_BLOCKSIZE", // from hfaopen.cpp "GDAL_ALLOW_LARGE_LIBJPEG_MEM_ALLOC", // from JPEG_band.cpp, jpgdataset.cpp "GDAL_BAG_BLOCK_SIZE", // from bagdataset.cpp