From fb50db8a062a76eead86584a5fe8bfefc98020e7 Mon Sep 17 00:00:00 2001 From: Thomas Beutlich Date: Mon, 15 Jul 2024 20:30:11 +0200 Subject: [PATCH 1/2] Add test to write NULL Date of any width and check for NULL --- tests/dbf_test.cc | 66 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/tests/dbf_test.cc b/tests/dbf_test.cc index 252c6c2..773db12 100644 --- a/tests/dbf_test.cc +++ b/tests/dbf_test.cc @@ -134,7 +134,8 @@ static auto WriteDate(const fs::path &filename, return success; } -static auto ReadDate(const fs::path &filename) -> auto +static auto ReadDate(const fs::path &filename, int expectIsNull, + int expectWidth) -> auto { const auto handle = DBFOpen(filename.string().c_str(), "rb"); EXPECT_NE(nullptr, handle); @@ -147,8 +148,10 @@ static auto ReadDate(const fs::path &filename) -> auto DBFGetFieldInfo(handle, 0, fieldname->data(), &width, &decimals); EXPECT_EQ(FTDate, fieldtype); EXPECT_EQ(0, std::strcmp("date", fieldname->data())); - EXPECT_EQ(8, width); + EXPECT_EQ(expectWidth, width); EXPECT_EQ(0, decimals); + const auto isNull = DBFIsAttributeNULL(handle, 0, 0); + EXPECT_EQ(expectIsNull, isNull); const auto recordcount = DBFGetRecordCount(handle); EXPECT_EQ(1, recordcount); const auto date = DBFReadDateAttribute(handle, 0, 0); @@ -205,14 +208,14 @@ TEST(DBFFieldTest, SetAndGetDateToday) EXPECT_TRUE(success); const auto size = fs::file_size(filename); EXPECT_EQ(75, size); - const auto date = ReadDate(filename); + const auto date = ReadDate(filename, 0, 8); EXPECT_EQ(today->year, date->year); EXPECT_EQ(today->month, date->month); EXPECT_EQ(today->day, date->day); fs::remove(filename); } -TEST(DBFFieldTest, SetAndGetDateInvalid) +TEST(DBFFieldTest, SetAndGetDateEmpty) { const auto filename = fs::temp_directory_path() / GenerateUniqueFilename(".dbf"); @@ -220,7 +223,60 @@ TEST(DBFFieldTest, SetAndGetDateInvalid) EXPECT_TRUE(success); const auto size = fs::file_size(filename); EXPECT_EQ(75, size); - const auto date = ReadDate(filename); + const auto date = ReadDate(filename, 1, 8); + EXPECT_EQ(0, date->year); + EXPECT_EQ(0, date->month); + EXPECT_EQ(0, date->day); + fs::remove(filename); +} + +TEST(DBFFieldTest, SetAndGetDateNull) +{ + const auto filename = + fs::temp_directory_path() / GenerateUniqueFilename(".dbf"); + for (int width = 1; width <= 8; ++width) + { + const auto success = [&filename, &width] + { + const auto handle = DBFCreate(filename.string().c_str()); + EXPECT_NE(nullptr, handle); + const auto fid = DBFAddField(handle, "date", FTDate, width, 0); + EXPECT_GE(fid, 0); + const auto success = DBFWriteNULLAttribute(handle, 0, 0); + DBFClose(handle); + return success; + }(); + EXPECT_TRUE(success); + const auto size = fs::file_size(filename); + EXPECT_EQ(67 + width, size); + const auto date = ReadDate(filename, 1, width); + EXPECT_EQ(0, date->year); + EXPECT_EQ(0, date->month); + EXPECT_EQ(0, date->day); + fs::remove(filename); + } +} + +TEST(DBFFieldTest, SetAndGetDateEmptyString) +{ + const auto filename = + fs::temp_directory_path() / GenerateUniqueFilename(".dbf"); + const auto success = [&filename] + { + const auto handle = DBFCreate(filename.string().c_str()); + EXPECT_NE(nullptr, handle); + const auto fid = DBFAddField(handle, "date", FTDate, 8, 0); + EXPECT_GE(fid, 0); + constexpr const auto emptyString = ""; + const auto success = + DBFWriteAttributeDirectly(handle, 0, 0, emptyString); + DBFClose(handle); + return success; + }(); + EXPECT_TRUE(success); + const auto size = fs::file_size(filename); + EXPECT_EQ(75, size); + const auto date = ReadDate(filename, 1, 8); EXPECT_EQ(0, date->year); EXPECT_EQ(0, date->month); EXPECT_EQ(0, date->day); From c116845ff15aaf079bec2291e18afa04a82ea450 Mon Sep 17 00:00:00 2001 From: Thomas Beutlich Date: Mon, 15 Jul 2024 20:33:17 +0200 Subject: [PATCH 2/2] Accept string containing of width times "0" as NULL Date --- dbfopen.c | 22 +++++++++++++++------- tests/dbf_test.cc | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/dbfopen.c b/dbfopen.c index b56b041..66ab2cb 100644 --- a/dbfopen.c +++ b/dbfopen.c @@ -1119,7 +1119,7 @@ SHPDate SHPAPI_CALL DBFReadDateAttribute(DBFHandle psDBF, int iRecord, /* Return TRUE if the passed string is NULL. */ /************************************************************************/ -static bool DBFIsValueNULL(char chType, const char *pszValue) +static bool DBFIsValueNULL(char chType, const char *pszValue, int size) { if (pszValue == SHPLIB_NULLPTR) return true; @@ -1144,14 +1144,19 @@ static bool DBFIsValueNULL(char chType, const char *pszValue) return true; case 'D': - /* NULL date fields have value "00000000" */ + /* NULL date fields have value "00000000" or "0"*size */ /* Some DBF files have fields filled with spaces */ /* (trimmed by DBFReadStringAttribute) to indicate null */ /* values for dates (#4265). */ /* And others have ' 0': https://lists.osgeo.org/pipermail/gdal-dev/2023-November/058010.html */ /* And others just empty string: https://github.com/OSGeo/gdal/issues/10405 */ - return pszValue[0] == 0 || strncmp(pszValue, "00000000", 8) == 0 || - strcmp(pszValue, " ") == 0 || strcmp(pszValue, "0") == 0; + if (pszValue[0] == 0 || strncmp(pszValue, "00000000", 8) == 0 || + strcmp(pszValue, " ") == 0 || strcmp(pszValue, "0") == 0) + return true; + for (int i = 0; i < size; i++) + if (pszValue[i] != '0') + return false; + return true; case 'L': /* NULL boolean fields have value "?" */ @@ -1179,7 +1184,8 @@ int SHPAPI_CALL DBFIsAttributeNULL(const DBFHandle psDBF, int iRecord, if (pszValue == SHPLIB_NULLPTR) return TRUE; - return DBFIsValueNULL(psDBF->pachFieldType[iField], pszValue); + return DBFIsValueNULL(psDBF->pachFieldType[iField], pszValue, + psDBF->panFieldSize[iField]); } /************************************************************************/ @@ -2166,7 +2172,8 @@ int SHPAPI_CALL DBFAlterFieldDefn(DBFHandle psDBF, int iField, } memcpy(pszOldField, pszRecord + nOffset, nOldWidth); - const bool bIsNULL = DBFIsValueNULL(chOldType, pszOldField); + const bool bIsNULL = + DBFIsValueNULL(chOldType, pszOldField, nOldWidth); if (nWidth != nOldWidth) { @@ -2243,7 +2250,8 @@ int SHPAPI_CALL DBFAlterFieldDefn(DBFHandle psDBF, int iField, } memcpy(pszOldField, pszRecord + nOffset, nOldWidth); - const bool bIsNULL = DBFIsValueNULL(chOldType, pszOldField); + const bool bIsNULL = + DBFIsValueNULL(chOldType, pszOldField, nOldWidth); if (nOffset + nOldWidth < nOldRecordLength) { diff --git a/tests/dbf_test.cc b/tests/dbf_test.cc index 773db12..5e34ba4 100644 --- a/tests/dbf_test.cc +++ b/tests/dbf_test.cc @@ -234,7 +234,7 @@ TEST(DBFFieldTest, SetAndGetDateNull) { const auto filename = fs::temp_directory_path() / GenerateUniqueFilename(".dbf"); - for (int width = 1; width <= 8; ++width) + for (int width = 1; width <= XBASE_FLD_MAX_WIDTH; ++width) { const auto success = [&filename, &width] {