Skip to content

Commit bd855c9

Browse files
[DBC] Fix for DBF long field names that collide after 10-char truncation. (#1964)
* [XSharp.VFP] Add CLOSE DATABASES, CLOSE TABLE commands * [XSharp.Core / XSharp.VFP] Add commands CLOSE DATABASES, CLOSE TABLES * [XSharp.Core] Fix visibility to give access to the ActiveDataBase for CLOSE DATABASES * [UDC] Merged CLOSE DATABASES and CLOSE TABLES variants into single UDC * [XSharp.Core / XSharp.VFP] Add support for ADD TABLE, REMOVE TABLE, RENAME TABLE in DBCs * [XSharp.Core/Data/VFP] CREATE TABLE: long field name support via DBC * [UDC] Fix an error in CLOSE DATABASE syntax * [DBC Support] Add several Tests in FoxTest project. Fix some related functions * [VFP] Fix SUM and AVERAGE UDCs clause must appear before TO * [VFP] Add support for COPY MEMO command * [VFP] Add support of REPLACE ... ADDITIVE command * [DBC] Fix for DBF long field names that collide after 10-char truncation. --------- Co-authored-by: Robert van der Hulst <robert@xsharp.eu>
1 parent a86ff93 commit bd855c9

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

src/Runtime/XSharp.Data/Parser/SQLParser.prg

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,30 @@ PARTIAL CLASS SQLParser
343343
table:ArrayName := SELF:ConsumeAndGet():Text
344344
ENDIF
345345
ENDIF
346+
// Deduplicate physical DBF field names that collide after 10-char truncation.
347+
// VFP strategy: keep the first occurrence as-is, rename subsequent collisions
348+
// by shortening the base and appending a numeric suffix (e.g. CustomerA0, CustomerA1).
349+
LOCAL usedNames AS HashSet<STRING>
350+
usedNames := HashSet<STRING>{StringComparer.OrdinalIgnoreCase}
351+
foreach var col in table:Columns
352+
IF ! usedNames:Add(col:Name)
353+
// Name already taken — find a unique suffixed alternative
354+
LOCAL suffix AS INT
355+
LOCAL candidate AS STRING
356+
suffix := 0
357+
DO WHILE TRUE
358+
LOCAL sfxStr AS STRING
359+
sfxStr := suffix:ToString()
360+
candidate := col:Name:Substring(0, 10 - sfxStr:Length) + sfxStr
361+
IF ! usedNames:Contains(candidate)
362+
EXIT
363+
ENDIF
364+
suffix += 1
365+
ENDDO
366+
col:Name := candidate
367+
usedNames:Add(candidate)
368+
ENDIF
369+
next
346370
foreach var column in table:Columns
347371
column:Table := table
348372
next

src/Tests/DbcTest/TestSuite_DbcCreateTable.prg

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ STATIC CLASS TestSuite_DbcCreateTable
4747
Safe("CREATE TABLE NAME stores long table name in DBC", {|| TestCreateTableWithName()})
4848
Safe("CREATE TABLE with quoted full path", {|| TestCreateTableQuotedPath()})
4949
Safe("CREATE TABLE with PRIVATE variable path", {|| TestCreateTablePrivatePath()})
50+
Safe("CREATE TABLE deduplicates colliding truncated names", {|| TestCreateTableDuplicateTruncatedNames()})
5051

5152
RETURN _nFail == 0
5253
END METHOD
@@ -200,6 +201,38 @@ STATIC CLASS TestSuite_DbcCreateTable
200201
END TRY
201202
END METHOD
202203

204+
// CustomerAddressLine and CustomerAddressCity share the same first 10 chars ("CustomerAd").
205+
// Without the dedup fix both physical DBF field names would be "CustomerAd", producing an
206+
// invalid DBF. With the fix the second field gets the suffix "0" → "CustomerA0".
207+
PRIVATE STATIC METHOD TestCreateTableDuplicateTruncatedNames() AS LOGIC
208+
LOCAL cDb := DbPath("crt_dedup") AS STRING
209+
LOCAL cTbl := DbPath("crt_dedup_t") AS STRING
210+
TestHelper.CleanupDb(cDb)
211+
TestHelper.CleanupTable(cTbl)
212+
TRY
213+
TestHelper.OpenActiveDb(cDb)
214+
CREATE TABLE crt_dedup_t (CustomerAddressLine C(50), CustomerAddressCity C(50))
215+
LOCAL oDb := Dbc.GetCurrent() AS DbcDatabase
216+
LOCAL oTable := oDb:FindTable("crt_dedup_t") AS DbcTable
217+
IF !AssertTrue(oTable != NULL_OBJECT, "Table must be registered in DBC") ; RETURN FALSE ; ENDIF
218+
// Physical names must be distinct
219+
LOCAL cField1 := FieldName(1):ToUpper() AS STRING
220+
LOCAL cField2 := FieldName(2):ToUpper() AS STRING
221+
IF !AssertTrue(cField1 != cField2, "Physical DBF field names must be unique after truncation deduplication") ; RETURN FALSE ; ENDIF
222+
// First field keeps plain 10-char truncation; second gets suffix "0"
223+
IF !AssertEqual(cField1, "CUSTOMERAD", "First field physical name") ; RETURN FALSE ; ENDIF
224+
IF !AssertEqual(cField2, "CUSTOMERA0", "Second field physical name after dedup suffix") ; RETURN FALSE ; ENDIF
225+
// DBC must preserve the full long names
226+
IF !AssertEqual(oTable:Fields[0]:ObjectName, "CustomerAddressLine", "DBC long name for field 1") ; RETURN FALSE ; ENDIF
227+
RETURN AssertEqual(oTable:Fields[1]:ObjectName, "CustomerAddressCity", "DBC long name for field 2")
228+
FINALLY
229+
CloseTable(cTbl)
230+
CLOSE DATABASES ALL
231+
TestHelper.CleanupDb(cDb)
232+
TestHelper.CleanupTable(cTbl)
233+
END TRY
234+
END METHOD
235+
203236
// -----------------------------------------------------------------------
204237
// Helpers
205238
// -----------------------------------------------------------------------

0 commit comments

Comments
 (0)