[runtime tests] Added test for DBOrderInfo( DBOI_NUMBER ) problem

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
USING System
USING System.Collections.Generic
USING System.Linq
USING System.Text
USING XUnit
USING System.Globalization

BEGIN NAMESPACE XSharp.VO.Tests

	CLASS DbfTests

		[Fact, Trait("Category", "DBFFuncs")];
		METHOD DBCreate_Tests() AS VOID
			LOCAL aFields AS ARRAY
			aFields := {{"TEST","C",10,0}}

			LOCAL cFileName_WithExt AS STRING
			LOCAL cFileName_NoExt AS STRING
			cFileName_NoExt := "DBCreate_Tests"
			cFileName_WithExt := cFileName_NoExt + ".dbf"
			IF System.IO.File.Exists(cFileName_WithExt)
				System.IO.File.Delete(cFileName_WithExt)
			END IF
			Assert.True(  DBCreate(cFileName_NoExt , aFields , "DBFNTX")  )
			Assert.True(  System.IO.File.Exists(cFileName_WithExt) )

			IF System.IO.File.Exists(cFileName_WithExt)
				System.IO.File.Delete(cFileName_WithExt)
			END IF
			Assert.True(  DBCreate(cFileName_WithExt , aFields , "DBFNTX")  )
			Assert.True(  System.IO.File.Exists(cFileName_WithExt) )

			cFileName_WithExt := cFileName_NoExt + ".none"
			IF System.IO.File.Exists(cFileName_WithExt)
				System.IO.File.Delete(cFileName_WithExt)
			END IF
			Assert.True(  DBCreate(cFileName_WithExt , aFields , "DBFNTX")  )
			Assert.True(  System.IO.File.Exists(cFileName_WithExt) )
		RETURN

		[Fact, Trait("Category", "DBFFuncs")];
		METHOD DBAppend_Exclusive() AS VOID
			LOCAL aFields AS ARRAY
			LOCAL cFileName AS STRING
			aFields := {{"TEST","C",10,0}}
			cFileName := "DBAppend_Exclusive"

			Assert.True(  DBCreate(cFileName , aFields , "DBFNTX")  )
			Assert.True(  DBUseArea(,"DBFNTX",cFileName,,FALSE) )
			Assert.True(  RecCount() == 0 )
			Assert.True(  DBAppend() )
			FieldPut(1 , "test")
			Assert.True(  AllTrim(FieldGet(1)) == "test" )
			Assert.True(  DBCloseArea() )
		RETURN

		[Fact, Trait("Category", "DBFFuncs")];
		METHOD DBAppend_Shared() AS VOID
			LOCAL aFields AS ARRAY
			LOCAL cFileName AS STRING
			aFields := {{"TEST","C",10,0}}
			cFileName := "DBAppend_Shared"

			Assert.True(  DBCreate(cFileName , aFields , "DBFNTX")  )
			Assert.True(  DBUseArea(,"DBFNTX",cFileName,,TRUE) )
			Assert.True(  RecCount() == 0 )
			Assert.True(  DBAppend() )
			FieldPut(1 , "test")
			Assert.True(  AllTrim(FieldGet(1)) == "test" )
			Assert.True(  DBCloseArea() )
		RETURN

		[Fact, Trait("Category", "DBFFuncs")];
		METHOD DBAppend_more() AS VOID
		LOCAL cDbf AS STRING
			cDbf := "testappend.DbF"
			RDDSetDefault( "DBFNTX" )
			Assert.True(  DBCreate(cDbf , { {"TEST","C",10,0} }) )
			// Appending in exclusive mode:
			Assert.True( DBUseArea(, , cDbf , "alias1" , FALSE) )
			Assert.True( DBAppend() )
			Assert.True( RecCount() == 1 )
			FieldPut(1, "test") // ok
			Assert.True( AllTrim(FieldGet(1)) == "test" )
			Assert.True( DBCloseArea() )

			// Appending in SHARED mode:
			Assert.True( DBUseArea(, , cDbf , "alias2" , TRUE) )
			Assert.True( RecCount() == 1 )
			Assert.True( DBAppend() )// returns true but does not append record
			Assert.True( RecCount() == 2 )
			FieldPut(1, "test2") // ok
			Assert.True( AllTrim(FieldGet(1)) == "test2" )
			Assert.True( DBCloseArea() )
		RETURN

		[Fact, Trait("Category", "DBFFuncs")];
		METHOD DBUseArea_same_file_twice() AS VOID
		LOCAL cDbf AS STRING
			RDDSetDefault( "DBFNTX" )
			cDbf := "testtwice.DbF"
			Assert.True(  DBCreate(cDbf , { {"TEST","C",10,0} }) )

			// shared mode
			Assert.True( DBUseArea(, , cDbf , , FALSE) )
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea(, , cDbf , , FALSE) )
			Assert.True( DBCloseArea() )
		RETURN

		// TECH-K3TL5J8M7V
		[Fact, Trait("Category", "DBF")];
		METHOD VODBInfo() AS VOID

			LOCAL cFileName AS STRING
			cFileName := GetTempFileName("test")
			DBCreate(cFileName , { {"CFIELD","C",10,0} })
			DBUseArea(,,cFileName)
			DBAppend()

			LOCAL u AS USUAL
			VODBSkip(-1)

			VODBInfo(DBI_FULLPATH , REF u)
			LOCAL c AS STRING
			c := u
			? c
			Assert.True(c:EndsWith("test.DBF") .AND. c:Contains(":\"))
			VODBInfo(DBI_DB_VERSION , REF u)
			Assert.True(SLen(u) > 1)
			VODBInfo(DBI_ALIAS , REF u)
			Assert.Equal("test" , u)

			VODBInfo(DBI_BOF , REF u)
			Assert.Equal(TRUE , (LOGIC) u)
			VODBInfo(DBI_EOF , REF u)
			Assert.Equal(FALSE ,(LOGIC)  u)
			VODBInfo(DBI_ISANSI , REF u)
			Assert.Equal(SetAnsi() ,(LOGIC)  u)
			VODBInfo(DBI_FCOUNT , REF u)
			Assert.Equal(1, (LONG)  u)
			VODBInfo(DBI_READONLY , REF u)
			Assert.Equal(FALSE,(LOGIC)  u)

			DBCloseArea()
		RETURN

		// TECH-E6Y9GNHB99
		[Fact, Trait("Category", "DBF")];
		METHOD AppendShared() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName("testAppendShared")
			RDDSetDefault( "DBFNTX" )
			DBCreate(cDbf , { {"TEST","C",10,0} })

//			Appending in exclusive mode:
			DBUseArea(, , cDbf , "alias2" , FALSE)
			Assert.True( DBAppend() )
			Assert.Equal( 1 , RecCount() )
			FieldPut(1, "test") // ok
			DBCloseArea()

//			Appending in SHARED mode:
			DBUseArea(, , cDbf , "alias2" , TRUE)
			Assert.True( DBAppend() ) // returns true but does not append record
			Assert.Equal( 2 , RecCount() ) // returns 1, wrong
			DBCloseArea()
		RETURN

		// TECH-6U40UQ0JV3
		[Fact, Trait("Category", "DBF")];
		METHOD AliasNameReuse() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName("testdbf")

			RDDSetDefault( "DBFNTX" )

			DBCreate(cDbf , { {"TEST","C",10,0} })

//			opening and closing once
			DBUseArea(, , cDbf , , FALSE)
			DBCloseArea()

//			opening and closing again
			Assert.True( DBUseArea(, , cDbf , , FALSE) )
			Assert.True( DBCloseArea() )
		RETURN

		// TECH-SAK5955895
		[Fact, Trait("Category", "DBF")];
		METHOD SavingDecimalValues() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			DBCreate(cFileName, {{"FLD1","N",10,2},{"FLD2","N",10,0}})
			DBUseArea(,,cFileName)
			DBAppend()

			SetDecimalSep(Asc(","))
			FieldPut(1 , 12.34) // not saved in the dbf
			Assert.Equal(12.34 , (FLOAT) FieldGet(1)) // 0,00

			SetDecimalSep(Asc("."))
			FieldPut(1 , 12.34)
			Assert.Equal(12.34 , (FLOAT) FieldGet(1))

			DBCloseArea()
		RETURN

		// TECH-C8WB52EA4A , Runtime exception when reading from a float field, after writing to it and DBCommit()
		[Fact, Trait("Category", "DBF")];
		METHOD DBCommitAfterFieldput() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			DBCreate(cFileName, {{"FLD1","N",10,4}})
			DBUseArea( , , cFileName , "tempalias")
			DBAppend()
			DBCloseArea()

			DBUseArea( , , cFileName)
			FieldPut(1 , 46.11) // ! a float isn/t stored !
			DBCommit()
			Assert.Equal(46.11 , (FLOAT) FieldGet(1)) // runtime exception
			DBCloseArea()
		RETURN

		// TECH-J61EXJ870D , FieldName() and FieldSym() throw an exception with incorrect field no
		[Fact, Trait("Category", "DBF")];
		METHOD FieldNameSym() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			DBCreate(cFileName, {{"FLD1","N",10,4}})
			DBUseArea( , , cFileName , "tempalias")
			DBAppend()
			Assert.Equal("", FieldName(100)) // exception
			Assert.Equal("", FieldName(0))
			Assert.Equal(NULL_SYMBOL, FieldSym(100))
			Assert.Equal(NULL_SYMBOL, FieldSym(0))
			DBCloseArea()
		RETURN

		// TECH-560ANYQI2P , DBRLockList() always returns zero
		[Fact, Trait("Category", "DBF")];
		METHOD DBRLockList() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			DBCreate(cFileName, {{"FLD1","C",10,0}})
			DBUseArea( , , cFileName , "tempalias" , TRUE)
			DBAppend()
			DBAppend()
			DBGoTop()
			DBRLock()
			DBRLockList()
			Assert.Equal(1, (INT) ALen(DBRLockList()))
			Assert.Equal(1, (INT) DBRLockList()[1])
			DBUnlock()
			DBSkip()
			DBRLock()
			Assert.Equal(1, (INT)  ALen(DBRLockList()))
			Assert.Equal(2, (INT) DBRLockList()[1])
			DBCloseArea()
		RETURN

		// TECH-34OWD3RR1Z , DBF problems with filters
		[Fact, Trait("Category", "DBF")];
		METHOD DBFilter() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()
			IF System.IO.File.Exists(cDbf)
				System.IO.File.Delete(cDbf)
			END IF
			DBCreate(cDbf, {{"CFIELD","C",10,0}}, "DBFNTX", TRUE)
			DBAppend()
			FieldPut(1, "ABC")
			DBAppend()
			FieldPut(1, "DEF")
			DBAppend()
			FieldPut(1, "GHI")
			DBAppend()
			FieldPut(1, "JKL")
			Assert.Equal(4 , (INT) RecCount())
			Assert.Equal(4 , (INT) LastRec())
			DBCloseArea()

			DBUseArea(,,cDbf)
//			"Setting filter to GHI, should be one record:"
//			"Instead, record 1 and 3 are shown"
			DBSetFilter({||AllTrim(FIELD->CFIELD) == "GHI"})
			DBGoTop()
			LOCAL nCount := 0 AS INT
			DO WHILE .NOT. EoF()
				Assert.Equal(3 , (INT) RecNo())
				FieldGet(1)
				DBSkip(+1)
				nCount ++
			END DO
			Assert.Equal(1 , nCount)

			DBGoBottom()
			Assert.False( EOF() )
			nCount := 0
			DO WHILE .NOT. EoF()
				Assert.Equal(3 , (INT) RecNo())
				nCount ++
				FieldGet(1)
				DBSkip(+1)
			END DO
			Assert.Equal(1 , nCount)

			DBCloseArea()
		RETURN

		// TECH-8C175D53DN , DBRecordInfo() always returns NULL_OBJECT
		[Fact, Trait("Category", "DBF")];
		METHOD DBRecordInfo_test() AS VOID
			LOCAL cDbf AS STRING
			LOCAL l AS LOGIC
			cDbf := GetTempFileName()
			DBCreate(cDbf, {{"CFIELD","C",10,0}}, "DBFNTX", TRUE)
			DBAppend()
			FieldPut(1, "ABC")

			l := DBRecordInfo( DBRI_RECNO ) // exception
			Assert.True(l)
			l := DBRecordInfo( DBRI_DELETED ) // exception
			Assert.False(l)
			l := DBRecordInfo( DBRI_LOCKED ) // exception
			Assert.True(l)

			DBCloseArea()
		RETURN

		// TECH-NVMBVB2Y44 , NullReferenceExpetion with DBFieldInfo()
		[Fact, Trait("Category", "DBF")];
		METHOD DBFieldInfo_test() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()
			DBCreate(cDbf, {{"NFIELD","N",10,3}}, "DBFNTX", TRUE)
			DBAppend()
			FieldPut(1, "ABC")

			Assert.Equal("NFIELD",	DBFieldInfo( DBS_NAME , 1 ) ) // NullReferenceException
			Assert.Equal("N",		DBFieldInfo( DBS_TYPE , 1 ) )
			Assert.Equal(10,		(INT) DBFieldInfo( DBS_LEN , 1 ) )
			Assert.Equal(3,			(INT) DBFieldInfo( DBS_DEC , 1 ) )
			Assert.Equal(5,			(INT) DBFieldInfo( DBS_PROPERTIES , 1 ) )

			DBCloseArea()
		RETURN

		// TECH-52M9YX557W , DBRecordInfo() changes record pointer
		[Fact, Trait("Category", "DBF")];
		METHOD DBRecordInfo_test2() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()
			DBCreate(cDbf, {{"CFIELD","C",10,0}}, "DBFNTX", TRUE)
			DBAppend()
			FieldPut(1, "ABC")
			DBAppend()
			FieldPut(1, "DEF")

			DBGoTop()
			Assert.Equal(1, (INT) RecNo())
			Assert.Equal(FALSE, EOF())

//			 Any of the below cause the record pointer to go EOF
			Assert.False( DBRecordInfo(DBRI_DELETED , 0) )
			DBRecordInfo(DBRI_BUFFPTR , 0)
			DBRecordInfo(DBRI_RAWDATA , 0)

			Assert.Equal(1, (INT) RecNo())
			Assert.Equal(FALSE, EOF())

			DBCloseArea()
		RETURN

		// TECH-C6Y1L51V1O , DBContinue() not working correctly
		[Fact, Trait("Category", "DBF")];
		METHOD DBContinue_test() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()
			DBCreate(cDbf, {{"NFIELD","N",10,0}}, "DBFNTX", TRUE)
			DBAppend()
			FieldPut(1, 123)
			DBAppend()
			FieldPut(1, 456)
			DBAppend()
			FieldPut(1, 789)

			DBGoTop()
			Assert.True( DBLocate({||_FIELD->NFIELD > 300} , , , , TRUE) ) // DBSCOPEREST
			Assert.True( Found() )
			Assert.Equal(456.0 , (FLOAT)  FieldGet(1) )

//			DBContinue() returns TRUE (correct) but does not move record pointer at all
			Assert.True( DBContinue() )
			Assert.True( Found() )
			Assert.Equal( 789.0 , (FLOAT)  FieldGet(1) )

			Assert.True( DBContinue() )
			Assert.False( Found() )
			Assert.Equal( 0.0 , (FLOAT) FieldGet(1) )

			Assert.True( DBContinue() )
			Assert.False( Found() )
			Assert.Equal( 0.0 , (FLOAT) FieldGet(1) )

			DBCloseArea()
		RETURN

		// TECH-Y4UUA09473 , Problem on creating error for invalid RDD command
		[Fact, Trait("Category", "DBF")];
		METHOD DBAppendWithNoWorkarea() AS VOID
			DBCloseAll()
			Assert.False( DBAppend() )
		RETURN

		// TECH-8HN2I0UUNA , Index file not correctly created when dbf is opened in SHARED mode
		[Fact, Trait("Category", "DBF")];
		METHOD Shared_Ntx() AS VOID
			LOCAL cDbf AS STRING
			LOCAL cNtx AS STRING
			cDbf := GetTempFileName()
			cNtx := cDbf + ".ntx"

			Assert.True( DBCreate( cDbf , {{"CFIELD" , "C" , 10 , 0 }}) )
			Assert.True( DBUseArea(,,cDbf,,FALSE) )
			Assert.True( DBAppend() )
			FieldPut ( 1 , "B")
			Assert.True( DBAppend() )
			FieldPut ( 1 , "A")
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea(,,cDbf,,TRUE) ) // ----- opening in SHARED mode
			Assert.True( DBCreateIndex(cNtx , "CFIELD") ) // returns TRUE
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea(,,cDbf,,FALSE) )
			Assert.True( DBSetIndex(cNtx) )
			DBGoTop()
			Assert.True( AllTrim(FieldGet(1)) == "A" )
			DBGoBottom()
			Assert.True( AllTrim(FieldGet(1)) == "B" )
			Assert.True( DBCloseArea() ) // XSharp.RDD.RddError here
		RETURN

		// TECH-U43F26KOT7 , Runtime error saving NULL_DATE to DATE dbf field
		[Fact, Trait("Category", "DBF")];
		METHOD Save_NULL_DATE() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()
			Assert.True(  DBCreate( cDbf , {{"CFIELD" , "C" , 10 , 0 },;
			{"DFIELD" , "D" , 8 , 0 }}) )
			Assert.True( DBUseArea(,,cDbf) )
			DBAppend()
			FieldPut ( 1 , "B")
			DBAppend()
			FieldPut ( 1 , "A")
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea(,,cDbf) )
			LOCAL u AS USUAL
			u := FieldGet(2) // it should be a NULL_DATE
			Assert.True( u == NULL_DATE )
			FieldPut(2,u) // exception
			FieldPut(2,NULL_DATE) // exception
			Assert.True( FieldGet(2) == NULL_DATE )
			Assert.True(  DBCloseArea() )
		RETURN

		// TECH-588I8LB67J , Problems with NTX indexes
		[Fact, Trait("Category", "DBF")];
		METHOD Ntx_Issues() AS VOID
			LOCAL cDbf AS STRING
			LOCAL cNtx AS STRING
			LOCAL aResult AS ARRAY

			cDbf := GetTempFileName()
			cNtx := cDbf + ".ntx"

			DBCreate( cDbf , {{"CFIELD" , "C" , 10 , 0 }})
			DBUseArea(,,cDbf)
			DBAppend()
			FieldPut ( 1 , "ABC")
			DBAppend()
			FieldPut ( 1 , "GHI")
			DBAppend()
			FieldPut ( 1 , "DEF")
			DBAppend()
			FieldPut ( 1 , "K")
			DBCloseArea()

			Assert.True( DBUseArea(,,cDbf) )
			Assert.True( DBCreateIndex(cNtx , "CFIELD") )
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea(,,cDbf,,FALSE) )
			Assert.True( DBSetIndex(cNtx) )
			aResult := GetRecords()
//			should be ABC, DEF, GHI, K
			Assert.True( aResult[1] == "ABC")
			Assert.True( aResult[2] == "DEF")
			Assert.True( aResult[3] == "GHI")
			Assert.True( aResult[4] == "K")

			DBGoTop()
			DBSkip()
			FieldPut(1,"HHH")
			aResult := GetRecords()
//			should be ABC, GHI, HHH, K
			Assert.True( aResult[1] == "ABC")
			Assert.True( aResult[2] == "GHI")
			Assert.True( aResult[3] == "HHH")
			Assert.True( aResult[4] == "K")

			DBGoTop()
			DBSkip(2)
			FieldPut(1,"DEF") // restore it

			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea(,,cDbf,,TRUE) )
			Assert.True( DBSetIndex(cNtx) )
			aResult := GetRecords()
//			should be ABC, DEF, GHI, K
			Assert.True( aResult[1] == "ABC")
			Assert.True( aResult[2] == "DEF")
			Assert.True( aResult[3] == "GHI")
			Assert.True( aResult[4] == "K")

			DBGoTop()
			DBSkip()
			Assert.True(RLock())
			FieldPut(1,"III")
			aResult := GetRecords()
//			should be ABC, GHI, III, K
			Assert.True( aResult[1] == "ABC")
			Assert.True( aResult[2] == "GHI")
			Assert.True( aResult[3] == "III")
			Assert.True( aResult[4] == "K")

			Assert.True( DBCloseArea() )
		RETURN

		PRIVATE STATIC METHOD GetRecords() AS ARRAY
			LOCAL aResult AS ARRAY
			aResult := {}
			DBGoTop()
			DO WHILE .NOT. Eof()
				AAdd(aResult , AllTrim(FieldGet(1)))
				DBSkip()
			END DO
		RETURN aResult

		// TECH-V7A528Z0ZL , Problems with ntx indexes 2
		[Fact, Trait("Category", "DBF")];
		METHOD Ntx_Issues2() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			DBCreate(cFileName, {{"FLD1","C",10,0},{"FLD2","N",10,0}})
			DBUseArea( , , cFileName , , FALSE)
			FOR LOCAL n := 1 AS INT UPTO 10
				DBAppend()
				FieldPut(1, n:ToString())
				FieldPut(2, n)
			NEXT
			Assert.True( DBCreateIndex(cFileName + ".ntx" , "FLD2") )
			Assert.True( DBCloseArea() )

			DBUseArea( , , cFileName , , FALSE)
			Assert.True( DBSetIndex(cFileName + ".ntx") )
			DBGoTop()
			LOCAL nCount := 0 AS INT
			DO WHILE ! EOF()
				nCount ++
				Assert.True( FieldGet(2) == RecNo() )
				Assert.True( FieldGet(2) == nCount )
				Assert.True( DBSkip() )
			END DO
			Assert.True( DBCloseArea() )
		RETURN

		// TECH-965270UG7K , Workareas not being reused
		[Fact, Trait("Category", "DBF")];
		METHOD WorkareaNums() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			Assert.True( DBCreate(cFileName, {{"FLD1","C",10,0}}) )

			Assert.True( DBUseArea ( TRUE , , cFileName , "a1") )
			Assert.Equal( 1u , DBGetSelect() )
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea ( TRUE , , cFileName , "a2") )
			Assert.Equal( 1u , DBGetSelect() )
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea ( TRUE , , cFileName , "a3") )
			Assert.Equal( 1u , DBGetSelect() )
			Assert.True( DBCloseArea() )
		RETURN

		// FOX-ES1QLR6Y5L , dbRecordInfo ( DBRI_LOCKED ) always returns .f.
		[Fact, Trait("Category", "DBF")];
		METHOD DBRI_LOCKED_test() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			SetExclusive ( FALSE )
			DBCreate ( cFileName , { {"id", "C", 5, 0} })

			DBUseArea ( , , cFileName )
			DBAppend()
			DBAppend()
			DBAppend()
			DBGoTop()

			Assert.True( DBRLock ( RecNo() ) )
			Assert.True( DBRecordInfo ( DBRI_LOCKED ) ) // Should show TRUE
			Assert.True( AScan ( DBRLockList() , RecNo() ) > 0 )

			DBSkip()
//			record 2 - no lock
			Assert.False( DBRecordInfo ( DBRI_LOCKED ) )
			Assert.False( AScan ( DBRLockList() , RecNo() ) > 0 )

			DBSkip()
			Assert.True( DBRLock ( RecNo() ) )
			Assert.True( DBRecordInfo ( DBRI_LOCKED ) ) // Should show TRUE
			Assert.True( AScan ( DBRLockList() , RecNo() ) > 0 )

			LOCAL a AS ARRAY
			a:= DBRLockList()
			Assert.Equal( 2 , (INT) ALen(a) )
			Assert.Equal( 1 , (INT) a[1] )
			Assert.Equal( 3 , (INT) a[2] )

			DBCloseArea()

			SetExclusive ( TRUE ) // restore
		RETURN

		// TECH-XQES14W9J0 , Aliasxxx() funcs throw exceptions
		[Fact, Trait("Category", "DBF")];
		METHOD Alias_test() AS VOID
			DBCloseAll()
			Assert.True( Alias() == "" )
			Assert.True( Alias0() == "" )
			Assert.True( Alias0Sym() == "" )
		RETURN

		// TECH-IXV5X91A74 , DBCreate() problem after having opened a dbf in exclusive mode
		[Fact, Trait("Category", "DBF")];
		METHOD DBCreate_test() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()

			DBCreate(cFileName, {{"FLD1","C",10,0}})
			DBUseArea(,,cFileName,,FALSE)
			DBCloseArea()

//			exception here
			Assert.True( DBCreate(cFileName, {{"FLD1","N",10,0}}) )

			Assert.True( DBUseArea(,,cFileName) )
			DBAppend()
			FieldPut(1 , 123)
			Assert.True( FieldGet(1) == 123 )
			Assert.True( DBCloseArea() )
		RETURN

		// TECH-ONPOSM84VS , Runtime exception on Error:ToString()
		[Fact, Trait("Category", "DBF")];
		METHOD DBError_test() AS VOID
			LOCAL cDbf AS STRING
			LOCAL cNtx AS STRING

			cDbf := GetTempFileName()
			cNtx := cDbf + ".ntx"

			DBCreate( cDbf , {{"CFIELD" , "C" , 10 , 0 }})
			DBUseArea(,,cDbf,,TRUE)
			Assert.True( DBAppend() )
			Assert.True( DBUnlock() )
			TRY
				? FieldPut ( 1 , "ABC") // record not locked
			CATCH e AS XSharp.Error
				? e:Message
				? e:GenCodeText
				? e:SubCodeText // check if this displays correctly
				? e:OSCodeText
				? e:ToString() // exception here
			FINALLY
				Assert.True( DBCloseArea() )
			END TRY
		RETURN

		// TECH-9JPUGAOV3L , NTX problem with EoF after sequence of commands
		[Fact, Trait("Category", "DBF")];
		METHOD Ntx_Eof_test() AS VOID
			LOCAL cDbf AS STRING
			LOCAL cNtx AS STRING

			cDbf := GetTempFileName()
			cNtx := cDbf + ".ntx"

			DBCreate( cDbf , {{"NFIELD" , "N" , 5 , 0 }})
			DBUseArea(,,cDbf,,FALSE)
			DBAppend()
			FieldPut(1,123)
			DBAppend()
			FieldPut(1,456)
			DBCreateIndex(cNtx, "NFIELD")
			DBCloseArea()

			// necessary sequence to reproduce the problem below
			DBUseArea(,,cDbf,,FALSE)
			DBSetIndex(cNtx)
			DBGoTop()
			Assert.Equal(1, (INT)RecNo()) // 1
			DBGoBottom()
			Assert.Equal(2, (INT)RecNo()) // 2
			DBSkip(-1)
			Assert.Equal(1, (INT)RecNo()) // 1
			DBGoTo(2)
			Assert.Equal(2, (INT)RecNo()) // 2

			DBSkip(+1)
			Assert.Equal(TRUE, (LOGIC)EOF()) // FALSE, wrong
			Assert.Equal(3, (INT)RecNo()) // 2, wrong

			DBSkip(+1)
			Assert.Equal(TRUE, (LOGIC)EOF()) // TRUE
			Assert.Equal(3, (INT)RecNo()) // 3

			DBCloseArea()
		RETURN

		// TECH-546935N337, DBOrderInfo(DBOI_SETCODEBLOCK) causes an invalid cast exception, but only if a index is opened
		[Fact, Trait("Category", "DBF")];
		METHOD DBOrderInfo_DBOI_SETCODEBLOCK() AS VOID
			LOCAL cDBF, cNTX AS STRING

			cDBF := GetTempFileName("test")
			cNTX := cDBF + ".ntx"

			DBCreate( cDBF , {{"ID" , "C" , 5 , 0 }})
			DBUseArea(,,cDBF)

			DBAppend()
			FieldPut(1, "one")

			DBCreateIndex(cNTX , "Upper (ID)")
			DBCloseArea()

			DBUseArea(,,cDBF)
			DBSetIndex(cNTX)

			? "DBOI_SETCODEBLOCK" , DBOrderInfo( DBOI_SETCODEBLOCK )

			DBCloseArea()
		RETURN

		// TECH-3NY78C93EK, OrdScope() not working
		[Fact, Trait("Category", "DBF")];
		METHOD OrdScope_test() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()
			DBCreate( cDbf , {{"NFIELD" , "N" , 5 , 0 }})
			DBUseArea(,"DBFNTX",cDbf)
			FOR LOCAL n := 1 AS INT UPTO 20
				DBAppend()
				FieldPut(1,n)
			NEXT
			DBCreateIndex(cDbf , "NFIELD")
			DBCloseArea()

			DBUseArea(,"DBFNTX",cDbf) // 20 records
			DBSetIndex ( cDbf )
			Assert.Equal( (INT) DBOrderInfo( DBOI_KEYCOUNT ) , 20)
			Assert.Equal( (INT) OrdScope(TOPSCOPE, 5) , 5) // NULL
			Assert.Equal( (INT) OrdScope(BOTTOMSCOPE, 10) , 10) // NULL
			DBGoTop()

			Assert.Equal( (INT) DBOrderInfo( DBOI_KEYCOUNT ) , 6) // still 20 - but must be 6
			LOCAL nCount := 0 AS INT
			DO WHILE ! EOF() // all 20 records are listed
				? FieldGet ( 1 )
				DBSkip ( 1 )
				nCount ++
			ENDDO
			Assert.Equal( 6 , nCount)
			Assert.Equal( 5 , (INT) DBOrderInfo( DBOI_SCOPETOP ) ) // {(0x0000)0x00000000} CLASS
			Assert.Equal( 10 , (INT) DBOrderInfo( DBOI_SCOPEBOTTOM ) ) // {(0x0000)0x00000000} CLASS
			DBCloseArea()
		RETURN

		[Fact, Trait("Category", "DBF")];
		METHOD OrdScope_test_with_Ordinal_Collation() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()

			LOCAL uCollation AS USUAL
			uCollation := SetCollation(#ORDINAL)

			DBCreate( cDbf , {{"NFIELD" , "N" , 5 , 0 }})
			DBUseArea(,"DBFNTX",cDbf)
			FOR LOCAL n := 1 AS INT UPTO 20
				DBAppend()
				FieldPut(1,n)
			NEXT
			DBCreateIndex(cDbf , "NFIELD")
			DBCloseArea()

			DBUseArea(,"DBFNTX",cDbf)
			DBSetIndex ( cDbf )
			Assert.Equal( (INT) DBOrderInfo( DBOI_KEYCOUNT ) , 20)
			Assert.Equal( (INT) UsualType( DBOrderInfo( DBOI_KEYCOUNT )  ) , 1) // first time returns 6
			Assert.Equal( (INT) UsualType( DBOrderInfo( DBOI_KEYCOUNT )  ) , 1) // second time it returns 1 correctly
			DBCloseArea()

			SetCollation(uCollation)
		RETURN

		// TECH-9TW65Q3XQE, NTX corruption with updating multiple fields and shared mode
		[Fact, Trait("Category", "DBF")];
		METHOD NTX_test() AS VOID
			LOCAL aValues AS ARRAY
			LOCAL i AS DWORD
			LOCAL cDBF, cNTX AS STRING

			aValues := { "ssss" , "hhhh", "wwww" , "aaaa" }
			cDBF := GetTempFileName()
			cNTX := cDbf
			DBCreate( cDBF , {{"LAST" , "C" , 20 , 0 } , ;
								{"TEXT1" , "C" , 10 , 0 } , ;
								{"NUM1" , "N" , 10 , 2 }})

			DBUseArea(,,cDBF,,FALSE)
			FOR i := 1 UPTO ALen ( aValues )
				DBAppend()
				FieldPut(1,aValues [i])
			NEXT
			DBCreateIndex(cNTX, "Upper ( Last)")
			DBCloseArea()

			DBUseArea(,,cDBF,,TRUE) // open shared !
			DBSetIndex(cNTX)
			DBGoTop()
			? "current (indexed) order"
			DO WHILE ! EOF()
				? FieldGet ( 1 )
				DBSkip ( 1 )
			ENDDO
			DBGoTop()
			// "now replace the index field #LAST content 'aaaa' with 'pppp'"
			// "and also update another field"
			Assert.True( DBRLock ( RecNo() )  )
			// "Replacing", AllTrim(FieldGet(1)), "with 'pppp'"
			FieldPut ( 1 , "pppp" ) // Note: This is the index field

//	 this is what causes the problem, updating a second field
//	 if this fieldput is put above the first one, then sample works correctly
//	 either one of the fieldputs below cause the problem to surface
			FieldPut ( 2 , "Eins" )
			FieldPut ( 3 , 123.45 )

			DBCommit()

			DBRUnlock ( RecNo() )

			Assert.False( DBSeek( "AAAA" ) ) // must show .F.
			Assert.True( DBSeek ("PPPP" ) ) // must show .T.
			// "Record order now:"
			// "(should be hhhh, pppp, ssss , wwww)"
			DBGoTop()
			LOCAL nCount := 0 AS INT
			DO WHILE ! EOF()
				? AllTrim(FieldGet(1)) , FieldGet(3) , AllTrim(FieldGet(2))
				nCount ++
				DO CASE
				CASE nCount == 1
					Assert.Equal( "hhhh", AllTrim(FieldGet(1)) )
				CASE nCount == 2
					Assert.Equal( "pppp", AllTrim(FieldGet(1)) )
				CASE nCount == 3
					Assert.Equal( "ssss", AllTrim(FieldGet(1)) )
				CASE nCount == 4
					Assert.Equal( "wwww", AllTrim(FieldGet(1)) )
				END CASE
				DBSkip ( 1 )
			ENDDO
			DBCloseArea()
		RETURN

		// TECH-4JX6H10741, DBOrderInfo( DBOI_KEYCOUNT ) returns NIL when record pointer is at EoF
		[Fact, Trait("Category", "DBF")];
		METHOD DBOrderInfo_DBOI_KEYCOUNT() AS VOID
			LOCAL cDbf AS STRING
			LOCAL aValues AS ARRAY
			LOCAL i AS DWORD

			cDBF := GetTempFileName()
			aValues := { 44 , 12, 34 , 21 }
			DBCreate( cDBF , {{"AGE" , "N" , 3 , 0 } })
			DBUseArea(,"DBFNTX",cDBF,,FALSE)
			Assert.Equal(0 , (INT) DBOrderInfo( DBOI_KEYCOUNT ) ) // 0,  ok
			FOR i := 1 UPTO ALen ( aValues )
				DBAppend()
				FieldPut(1,aValues [i])
			NEXT
			Assert.Equal(0 , (INT) DBOrderInfo( DBOI_KEYCOUNT ) ) // 0,  ok
			DBCreateIndex(cDbf, "age" )
			Assert.Equal(4 , (INT) DBOrderInfo( DBOI_KEYCOUNT ) ) // 4, correct
			DBGoTop()
			Assert.Equal(4 , (INT) DBOrderInfo( DBOI_KEYCOUNT ) ) // 4, correct
			DO WHILE ! EOF()
//			? FieldGet ( 1 )
				DBSkip(1)
			ENDDO
			Assert.Equal(4 , (INT) DBOrderInfo( DBOI_KEYCOUNT ) ) // NIL, should be 4
			DBSkip(-1)
			Assert.Equal(4 , (INT) DBOrderInfo( DBOI_KEYCOUNT ) ) // 4, correct
			DBCloseArea ()
		RETURN

		[Fact, Trait("Category", "DBF")];
		METHOD SetDeleted_FALSE() AS VOID
			LOCAL cDbf AS STRING
			LOCAL aValues AS ARRAY
			LOCAL i AS DWORD

			cDBF := GetTempFileName()

			SetDeleted(FALSE)

//			test also with those
//			aValues := { "vaa" , "abba", "acb" , "aaab"  , "adab"  , "baac"  , "aeab"  , "baaAaa" }
			aValues := { "vvv" , "abb", "acb" , "aaa"  , "bbb" }
			DBCreate( cDBF , {{"LAST" , "C" ,10 , 0 } })
			DBUseArea(,"DBFNTX",cDBF,,FALSE)
			FOR i := 1 UPTO ALen ( aValues )
				DBAppend()
				FieldPut(1,aValues [i])
			NEXT

			DBUseArea(,"DBFNTX",cDBF,,TRUE)
			Assert.Equal(0 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			DBCreateIndex(cDbf, "Upper(LAST)" )
			Assert.Equal(5 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			// ? "Setting scope"
			LOCAL u AS USUAL
			u := "A"
			VODBOrderInfo( DBOI_SCOPETOP, "", NIL, REF u )
			VODBOrderInfo( DBOI_SCOPEBOTTOM, "", NIL, REF u )

			DBGoTop()
			Assert.Equal(4 , (INT)RecNo())
			DBGoBottom()
			Assert.Equal(3 , (INT)RecNo())

			DBGoTop()

			Assert.Equal(3 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			DO WHILE ! EOF()
				DBSkip(1)
			ENDDO
			Assert.Equal(3 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			DBCloseArea()

			SetDeleted(FALSE)
		RETURN

		// TECH-W0KKQ1I50C, Problems with SetDeleted(TRUE)
		[Fact, Trait("Category", "DBF")];
		METHOD SetDeleted_TRUE() AS VOID
			LOCAL cDbf AS STRING
			LOCAL aValues AS ARRAY
			LOCAL i AS DWORD

			cDBF := GetTempFileName()

			SetDeleted(TRUE)

//			test also with those
//			aValues := { "vaa" , "abba", "acb" , "aaab"  , "adab"  , "baac"  , "aeab"  , "baaAaa" }
			aValues := { "vvv" , "abb", "acb" , "aaa"  , "bbb" }
			DBCreate( cDBF , {{"LAST" , "C" ,10 , 0 } })
			DBUseArea(,"DBFNTX",cDBF,,FALSE)
			FOR i := 1 UPTO ALen ( aValues )
				DBAppend()
				FieldPut(1,aValues [i])
			NEXT

			DBUseArea(,"DBFNTX",cDBF,,TRUE)
			Assert.Equal(0 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			DBCreateIndex(cDbf, "Upper(LAST)" )
			Assert.Equal(5 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			// ? "Setting scope"
			LOCAL u AS USUAL
			u := "A"
			VODBOrderInfo( DBOI_SCOPETOP, "", NIL, REF u )
			VODBOrderInfo( DBOI_SCOPEBOTTOM, "", NIL, REF u )

			DBGoTop()
			Assert.Equal(4 , (INT)RecNo())
			DBGoBottom()
			Assert.Equal(3 , (INT)RecNo())

			DBGoTop()

			Assert.Equal(3 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			DO WHILE ! EOF()
				DBSkip(1)
			ENDDO
			Assert.Equal(3 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			DBCloseArea()

			SetDeleted(FALSE)
		RETURN

		// TECH-DD1J2Z3UVI, DBOrderInfo( DBOI_NUMBER ) incorrect results
		[Fact, Trait("Category", "DBF")];
		METHOD DBOrderInfo_DBOI_NUMBER() AS VOID
			LOCAL aValues AS ARRAY
			LOCAL i AS DWORD
			LOCAL cDBF AS STRING
			LOCAL cNTX AS STRING
			aValues := { 44 , 12, 34 , 21 }
			cDBF := GetTempFileName("testdbf")
			cNTX := cDbf + ".ntx"
			IF System.IO.File.Exists(cNtx)
				System.IO.file.Delete(cNtx)
			END IF
			DBCreate( cDBF , {{"AGE" , "N" , 3 , 0 } })
			DBUseArea(,"DBFNTX",cDBF,,FALSE)
			Assert.Equal(0, (INT)DBOrderInfo( DBOI_KEYCOUNT ) )   //  0  ok
			FOR i := 1 UPTO ALen ( aValues )
				DBAppend()
				FieldPut(1,aValues [i])
			NEXT
			Assert.Equal(0, (INT)DBOrderInfo( DBOI_KEYCOUNT ) ) //  0 ,ok
			Assert.Equal(0, (INT)DBOrderInfo( DBOI_NUMBER ) )  //  -1,  but should show 0

			DBCreateIndex( cNTX, "age" )
			DBGoTop()
			DO WHILE ! EOF()
//				? FieldGet ( 1 )
				DBSkip(1)
			ENDDO

			Assert.True( DBClearIndex( cNTX) )
			Assert.True( DBSetIndex ( cNTX ) )

			Assert.Equal(4, (INT) DBOrderInfo( DBOI_KEYCOUNT ) ) // 4, ok
			Assert.Equal(1, (INT) DBOrderInfo( DBOI_NUMBER ) )  // still  -1 , but should show  1
			Assert.Equal("TESTDBF", (STRING) DBOrderInfo( DBOI_NAME ) )  // ok , "TESTDBF"
			DBCloseArea ()
		RETURN

		STATIC PRIVATE METHOD GetTempFileName() AS STRING
		RETURN GetTempFileName("testdbf")
		STATIC PRIVATE METHOD GetTempFileName(cFileName AS STRING) AS STRING
			// we may want to put them to a specific folder etc
		RETURN cFileName

	END CLASS
END NAMESPACE

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
USING System
USING System.Collections.Generic
USING System.Linq
USING System.Text
USING XUnit
USING System.Globalization

BEGIN NAMESPACE XSharp.VO.Tests

	CLASS DbfTests

		[Fact, Trait("Category", "DBFFuncs")];
		METHOD DBCreate_Tests() AS VOID
			LOCAL aFields AS ARRAY
			aFields := {{"TEST","C",10,0}}

			LOCAL cFileName_WithExt AS STRING
			LOCAL cFileName_NoExt AS STRING
			cFileName_NoExt := "DBCreate_Tests"
			cFileName_WithExt := cFileName_NoExt + ".dbf"
			IF System.IO.File.Exists(cFileName_WithExt)
				System.IO.File.Delete(cFileName_WithExt)
			END IF
			Assert.True(  DBCreate(cFileName_NoExt , aFields , "DBFNTX")  )
			Assert.True(  System.IO.File.Exists(cFileName_WithExt) )

			IF System.IO.File.Exists(cFileName_WithExt)
				System.IO.File.Delete(cFileName_WithExt)
			END IF
			Assert.True(  DBCreate(cFileName_WithExt , aFields , "DBFNTX")  )
			Assert.True(  System.IO.File.Exists(cFileName_WithExt) )

			cFileName_WithExt := cFileName_NoExt + ".none"
			IF System.IO.File.Exists(cFileName_WithExt)
				System.IO.File.Delete(cFileName_WithExt)
			END IF
			Assert.True(  DBCreate(cFileName_WithExt , aFields , "DBFNTX")  )
			Assert.True(  System.IO.File.Exists(cFileName_WithExt) )
		RETURN

		[Fact, Trait("Category", "DBFFuncs")];
		METHOD DBAppend_Exclusive() AS VOID
			LOCAL aFields AS ARRAY
			LOCAL cFileName AS STRING
			aFields := {{"TEST","C",10,0}}
			cFileName := "DBAppend_Exclusive"

			Assert.True(  DBCreate(cFileName , aFields , "DBFNTX")  )
			Assert.True(  DBUseArea(,"DBFNTX",cFileName,,FALSE) )
			Assert.True(  RecCount() == 0 )
			Assert.True(  DBAppend() )
			FieldPut(1 , "test")
			Assert.True(  AllTrim(FieldGet(1)) == "test" )
			Assert.True(  DBCloseArea() )
		RETURN

		[Fact, Trait("Category", "DBFFuncs")];
		METHOD DBAppend_Shared() AS VOID
			LOCAL aFields AS ARRAY
			LOCAL cFileName AS STRING
			aFields := {{"TEST","C",10,0}}
			cFileName := "DBAppend_Shared"

			Assert.True(  DBCreate(cFileName , aFields , "DBFNTX")  )
			Assert.True(  DBUseArea(,"DBFNTX",cFileName,,TRUE) )
			Assert.True(  RecCount() == 0 )
			Assert.True(  DBAppend() )
			FieldPut(1 , "test")
			Assert.True(  AllTrim(FieldGet(1)) == "test" )
			Assert.True(  DBCloseArea() )
		RETURN

		[Fact, Trait("Category", "DBFFuncs")];
		METHOD DBAppend_more() AS VOID
		LOCAL cDbf AS STRING
			cDbf := "testappend.DbF"
			RDDSetDefault( "DBFNTX" )
			Assert.True(  DBCreate(cDbf , { {"TEST","C",10,0} }) )
			// Appending in exclusive mode:
			Assert.True( DBUseArea(, , cDbf , "alias1" , FALSE) )
			Assert.True( DBAppend() )
			Assert.True( RecCount() == 1 )
			FieldPut(1, "test") // ok
			Assert.True( AllTrim(FieldGet(1)) == "test" )
			Assert.True( DBCloseArea() )

			// Appending in SHARED mode:
			Assert.True( DBUseArea(, , cDbf , "alias2" , TRUE) )
			Assert.True( RecCount() == 1 )
			Assert.True( DBAppend() )// returns true but does not append record
			Assert.True( RecCount() == 2 )
			FieldPut(1, "test2") // ok
			Assert.True( AllTrim(FieldGet(1)) == "test2" )
			Assert.True( DBCloseArea() )
		RETURN

		[Fact, Trait("Category", "DBFFuncs")];
		METHOD DBUseArea_same_file_twice() AS VOID
		LOCAL cDbf AS STRING
			RDDSetDefault( "DBFNTX" )
			cDbf := "testtwice.DbF"
			Assert.True(  DBCreate(cDbf , { {"TEST","C",10,0} }) )

			// shared mode
			Assert.True( DBUseArea(, , cDbf , , FALSE) )
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea(, , cDbf , , FALSE) )
			Assert.True( DBCloseArea() )
		RETURN

		// TECH-K3TL5J8M7V
		[Fact, Trait("Category", "DBF")];
		METHOD VODBInfo() AS VOID

			LOCAL cFileName AS STRING
			cFileName := GetTempFileName("test")
			DBCreate(cFileName , { {"CFIELD","C",10,0} })
			DBUseArea(,,cFileName)
			DBAppend()

			LOCAL u AS USUAL
			VODBSkip(-1)

			VODBInfo(DBI_FULLPATH , REF u)
			LOCAL c AS STRING
			c := u
			? c
			Assert.True(c:EndsWith("test.DBF") .AND. c:Contains(":\"))
			VODBInfo(DBI_DB_VERSION , REF u)
			Assert.True(SLen(u) > 1)
			VODBInfo(DBI_ALIAS , REF u)
			Assert.Equal("test" , u)

			VODBInfo(DBI_BOF , REF u)
			Assert.Equal(TRUE , (LOGIC) u)
			VODBInfo(DBI_EOF , REF u)
			Assert.Equal(FALSE ,(LOGIC)  u)
			VODBInfo(DBI_ISANSI , REF u)
			Assert.Equal(SetAnsi() ,(LOGIC)  u)
			VODBInfo(DBI_FCOUNT , REF u)
			Assert.Equal(1, (LONG)  u)
			VODBInfo(DBI_READONLY , REF u)
			Assert.Equal(FALSE,(LOGIC)  u)

			DBCloseArea()
		RETURN

		// TECH-E6Y9GNHB99
		[Fact, Trait("Category", "DBF")];
		METHOD AppendShared() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName("testAppendShared")
			RDDSetDefault( "DBFNTX" )
			DBCreate(cDbf , { {"TEST","C",10,0} })

//			Appending in exclusive mode:
			DBUseArea(, , cDbf , "alias2" , FALSE)
			Assert.True( DBAppend() )
			Assert.Equal( 1 , RecCount() )
			FieldPut(1, "test") // ok
			DBCloseArea()

//			Appending in SHARED mode:
			DBUseArea(, , cDbf , "alias2" , TRUE)
			Assert.True( DBAppend() ) // returns true but does not append record
			Assert.Equal( 2 , RecCount() ) // returns 1, wrong
			DBCloseArea()
		RETURN

		// TECH-6U40UQ0JV3
		[Fact, Trait("Category", "DBF")];
		METHOD AliasNameReuse() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName("testdbf")

			RDDSetDefault( "DBFNTX" )

			DBCreate(cDbf , { {"TEST","C",10,0} })

//			opening and closing once
			DBUseArea(, , cDbf , , FALSE)
			DBCloseArea()

//			opening and closing again
			Assert.True( DBUseArea(, , cDbf , , FALSE) )
			Assert.True( DBCloseArea() )
		RETURN

		// TECH-SAK5955895
		[Fact, Trait("Category", "DBF")];
		METHOD SavingDecimalValues() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			DBCreate(cFileName, {{"FLD1","N",10,2},{"FLD2","N",10,0}})
			DBUseArea(,,cFileName)
			DBAppend()

			SetDecimalSep(Asc(","))
			FieldPut(1 , 12.34) // not saved in the dbf
			Assert.Equal(12.34 , (FLOAT) FieldGet(1)) // 0,00

			SetDecimalSep(Asc("."))
			FieldPut(1 , 12.34)
			Assert.Equal(12.34 , (FLOAT) FieldGet(1))

			DBCloseArea()
		RETURN

		// TECH-C8WB52EA4A , Runtime exception when reading from a float field, after writing to it and DBCommit()
		[Fact, Trait("Category", "DBF")];
		METHOD DBCommitAfterFieldput() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			DBCreate(cFileName, {{"FLD1","N",10,4}})
			DBUseArea( , , cFileName , "tempalias")
			DBAppend()
			DBCloseArea()

			DBUseArea( , , cFileName)
			FieldPut(1 , 46.11) // ! a float isn/t stored !
			DBCommit()
			Assert.Equal(46.11 , (FLOAT) FieldGet(1)) // runtime exception
			DBCloseArea()
		RETURN

		// TECH-J61EXJ870D , FieldName() and FieldSym() throw an exception with incorrect field no
		[Fact, Trait("Category", "DBF")];
		METHOD FieldNameSym() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			DBCreate(cFileName, {{"FLD1","N",10,4}})
			DBUseArea( , , cFileName , "tempalias")
			DBAppend()
			Assert.Equal("", FieldName(100)) // exception
			Assert.Equal("", FieldName(0))
			Assert.Equal(NULL_SYMBOL, FieldSym(100))
			Assert.Equal(NULL_SYMBOL, FieldSym(0))
			DBCloseArea()
		RETURN

		// TECH-560ANYQI2P , DBRLockList() always returns zero
		[Fact, Trait("Category", "DBF")];
		METHOD DBRLockList() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			DBCreate(cFileName, {{"FLD1","C",10,0}})
			DBUseArea( , , cFileName , "tempalias" , TRUE)
			DBAppend()
			DBAppend()
			DBGoTop()
			DBRLock()
			DBRLockList()
			Assert.Equal(1, (INT) ALen(DBRLockList()))
			Assert.Equal(1, (INT) DBRLockList()[1])
			DBUnlock()
			DBSkip()
			DBRLock()
			Assert.Equal(1, (INT)  ALen(DBRLockList()))
			Assert.Equal(2, (INT) DBRLockList()[1])
			DBCloseArea()
		RETURN

		// TECH-34OWD3RR1Z , DBF problems with filters
		[Fact, Trait("Category", "DBF")];
		METHOD DBFilter() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()
			IF System.IO.File.Exists(cDbf)
				System.IO.File.Delete(cDbf)
			END IF
			DBCreate(cDbf, {{"CFIELD","C",10,0}}, "DBFNTX", TRUE)
			DBAppend()
			FieldPut(1, "ABC")
			DBAppend()
			FieldPut(1, "DEF")
			DBAppend()
			FieldPut(1, "GHI")
			DBAppend()
			FieldPut(1, "JKL")
			Assert.Equal(4 , (INT) RecCount())
			Assert.Equal(4 , (INT) LastRec())
			DBCloseArea()

			DBUseArea(,,cDbf)
//			"Setting filter to GHI, should be one record:"
//			"Instead, record 1 and 3 are shown"
			DBSetFilter({||AllTrim(FIELD->CFIELD) == "GHI"})
			DBGoTop()
			LOCAL nCount := 0 AS INT
			DO WHILE .NOT. EoF()
				Assert.Equal(3 , (INT) RecNo())
				FieldGet(1)
				DBSkip(+1)
				nCount ++
			END DO
			Assert.Equal(1 , nCount)

			DBGoBottom()
			Assert.False( EOF() )
			nCount := 0
			DO WHILE .NOT. EoF()
				Assert.Equal(3 , (INT) RecNo())
				nCount ++
				FieldGet(1)
				DBSkip(+1)
			END DO
			Assert.Equal(1 , nCount)

			DBCloseArea()
		RETURN

		// TECH-8C175D53DN , DBRecordInfo() always returns NULL_OBJECT
		[Fact, Trait("Category", "DBF")];
		METHOD DBRecordInfo_test() AS VOID
			LOCAL cDbf AS STRING
			LOCAL l AS LOGIC
			cDbf := GetTempFileName()
			DBCreate(cDbf, {{"CFIELD","C",10,0}}, "DBFNTX", TRUE)
			DBAppend()
			FieldPut(1, "ABC")

			l := DBRecordInfo( DBRI_RECNO ) // exception
			Assert.True(l)
			l := DBRecordInfo( DBRI_DELETED ) // exception
			Assert.False(l)
			l := DBRecordInfo( DBRI_LOCKED ) // exception
			Assert.True(l)

			DBCloseArea()
		RETURN

		// TECH-NVMBVB2Y44 , NullReferenceExpetion with DBFieldInfo()
		[Fact, Trait("Category", "DBF")];
		METHOD DBFieldInfo_test() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()
			DBCreate(cDbf, {{"NFIELD","N",10,3}}, "DBFNTX", TRUE)
			DBAppend()
			FieldPut(1, "ABC")

			Assert.Equal("NFIELD",	DBFieldInfo( DBS_NAME , 1 ) ) // NullReferenceException
			Assert.Equal("N",		DBFieldInfo( DBS_TYPE , 1 ) )
			Assert.Equal(10,		(INT) DBFieldInfo( DBS_LEN , 1 ) )
			Assert.Equal(3,			(INT) DBFieldInfo( DBS_DEC , 1 ) )
			Assert.Equal(5,			(INT) DBFieldInfo( DBS_PROPERTIES , 1 ) )

			DBCloseArea()
		RETURN

		// TECH-52M9YX557W , DBRecordInfo() changes record pointer
		[Fact, Trait("Category", "DBF")];
		METHOD DBRecordInfo_test2() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()
			DBCreate(cDbf, {{"CFIELD","C",10,0}}, "DBFNTX", TRUE)
			DBAppend()
			FieldPut(1, "ABC")
			DBAppend()
			FieldPut(1, "DEF")

			DBGoTop()
			Assert.Equal(1, (INT) RecNo())
			Assert.Equal(FALSE, EOF())

//			 Any of the below cause the record pointer to go EOF
			Assert.False( DBRecordInfo(DBRI_DELETED , 0) )
			DBRecordInfo(DBRI_BUFFPTR , 0)
			DBRecordInfo(DBRI_RAWDATA , 0)

			Assert.Equal(1, (INT) RecNo())
			Assert.Equal(FALSE, EOF())

			DBCloseArea()
		RETURN

		// TECH-C6Y1L51V1O , DBContinue() not working correctly
		[Fact, Trait("Category", "DBF")];
		METHOD DBContinue_test() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()
			DBCreate(cDbf, {{"NFIELD","N",10,0}}, "DBFNTX", TRUE)
			DBAppend()
			FieldPut(1, 123)
			DBAppend()
			FieldPut(1, 456)
			DBAppend()
			FieldPut(1, 789)

			DBGoTop()
			Assert.True( DBLocate({||_FIELD->NFIELD > 300} , , , , TRUE) ) // DBSCOPEREST
			Assert.True( Found() )
			Assert.Equal(456.0 , (FLOAT)  FieldGet(1) )

//			DBContinue() returns TRUE (correct) but does not move record pointer at all
			Assert.True( DBContinue() )
			Assert.True( Found() )
			Assert.Equal( 789.0 , (FLOAT)  FieldGet(1) )

			Assert.True( DBContinue() )
			Assert.False( Found() )
			Assert.Equal( 0.0 , (FLOAT) FieldGet(1) )

			Assert.True( DBContinue() )
			Assert.False( Found() )
			Assert.Equal( 0.0 , (FLOAT) FieldGet(1) )

			DBCloseArea()
		RETURN

		// TECH-Y4UUA09473 , Problem on creating error for invalid RDD command
		[Fact, Trait("Category", "DBF")];
		METHOD DBAppendWithNoWorkarea() AS VOID
			DBCloseAll()
			Assert.False( DBAppend() )
		RETURN

		// TECH-8HN2I0UUNA , Index file not correctly created when dbf is opened in SHARED mode
		[Fact, Trait("Category", "DBF")];
		METHOD Shared_Ntx() AS VOID
			LOCAL cDbf AS STRING
			LOCAL cNtx AS STRING
			cDbf := GetTempFileName()
			cNtx := cDbf + ".ntx"

			Assert.True( DBCreate( cDbf , {{"CFIELD" , "C" , 10 , 0 }}) )
			Assert.True( DBUseArea(,,cDbf,,FALSE) )
			Assert.True( DBAppend() )
			FieldPut ( 1 , "B")
			Assert.True( DBAppend() )
			FieldPut ( 1 , "A")
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea(,,cDbf,,TRUE) ) // ----- opening in SHARED mode
			Assert.True( DBCreateIndex(cNtx , "CFIELD") ) // returns TRUE
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea(,,cDbf,,FALSE) )
			Assert.True( DBSetIndex(cNtx) )
			DBGoTop()
			Assert.True( AllTrim(FieldGet(1)) == "A" )
			DBGoBottom()
			Assert.True( AllTrim(FieldGet(1)) == "B" )
			Assert.True( DBCloseArea() ) // XSharp.RDD.RddError here
		RETURN

		// TECH-U43F26KOT7 , Runtime error saving NULL_DATE to DATE dbf field
		[Fact, Trait("Category", "DBF")];
		METHOD Save_NULL_DATE() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()
			Assert.True(  DBCreate( cDbf , {{"CFIELD" , "C" , 10 , 0 },;
			{"DFIELD" , "D" , 8 , 0 }}) )
			Assert.True( DBUseArea(,,cDbf) )
			DBAppend()
			FieldPut ( 1 , "B")
			DBAppend()
			FieldPut ( 1 , "A")
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea(,,cDbf) )
			LOCAL u AS USUAL
			u := FieldGet(2) // it should be a NULL_DATE
			Assert.True( u == NULL_DATE )
			FieldPut(2,u) // exception
			FieldPut(2,NULL_DATE) // exception
			Assert.True( FieldGet(2) == NULL_DATE )
			Assert.True(  DBCloseArea() )
		RETURN

		// TECH-588I8LB67J , Problems with NTX indexes
		[Fact, Trait("Category", "DBF")];
		METHOD Ntx_Issues() AS VOID
			LOCAL cDbf AS STRING
			LOCAL cNtx AS STRING
			LOCAL aResult AS ARRAY

			cDbf := GetTempFileName()
			cNtx := cDbf + ".ntx"

			DBCreate( cDbf , {{"CFIELD" , "C" , 10 , 0 }})
			DBUseArea(,,cDbf)
			DBAppend()
			FieldPut ( 1 , "ABC")
			DBAppend()
			FieldPut ( 1 , "GHI")
			DBAppend()
			FieldPut ( 1 , "DEF")
			DBAppend()
			FieldPut ( 1 , "K")
			DBCloseArea()

			Assert.True( DBUseArea(,,cDbf) )
			Assert.True( DBCreateIndex(cNtx , "CFIELD") )
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea(,,cDbf,,FALSE) )
			Assert.True( DBSetIndex(cNtx) )
			aResult := GetRecords()
//			should be ABC, DEF, GHI, K
			Assert.True( aResult[1] == "ABC")
			Assert.True( aResult[2] == "DEF")
			Assert.True( aResult[3] == "GHI")
			Assert.True( aResult[4] == "K")

			DBGoTop()
			DBSkip()
			FieldPut(1,"HHH")
			aResult := GetRecords()
//			should be ABC, GHI, HHH, K
			Assert.True( aResult[1] == "ABC")
			Assert.True( aResult[2] == "GHI")
			Assert.True( aResult[3] == "HHH")
			Assert.True( aResult[4] == "K")

			DBGoTop()
			DBSkip(2)
			FieldPut(1,"DEF") // restore it

			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea(,,cDbf,,TRUE) )
			Assert.True( DBSetIndex(cNtx) )
			aResult := GetRecords()
//			should be ABC, DEF, GHI, K
			Assert.True( aResult[1] == "ABC")
			Assert.True( aResult[2] == "DEF")
			Assert.True( aResult[3] == "GHI")
			Assert.True( aResult[4] == "K")

			DBGoTop()
			DBSkip()
			Assert.True(RLock())
			FieldPut(1,"III")
			aResult := GetRecords()
//			should be ABC, GHI, III, K
			Assert.True( aResult[1] == "ABC")
			Assert.True( aResult[2] == "GHI")
			Assert.True( aResult[3] == "III")
			Assert.True( aResult[4] == "K")

			Assert.True( DBCloseArea() )
		RETURN

		PRIVATE STATIC METHOD GetRecords() AS ARRAY
			LOCAL aResult AS ARRAY
			aResult := {}
			DBGoTop()
			DO WHILE .NOT. Eof()
				AAdd(aResult , AllTrim(FieldGet(1)))
				DBSkip()
			END DO
		RETURN aResult

		// TECH-V7A528Z0ZL , Problems with ntx indexes 2
		[Fact, Trait("Category", "DBF")];
		METHOD Ntx_Issues2() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			DBCreate(cFileName, {{"FLD1","C",10,0},{"FLD2","N",10,0}})
			DBUseArea( , , cFileName , , FALSE)
			FOR LOCAL n := 1 AS INT UPTO 10
				DBAppend()
				FieldPut(1, n:ToString())
				FieldPut(2, n)
			NEXT
			Assert.True( DBCreateIndex(cFileName + ".ntx" , "FLD2") )
			Assert.True( DBCloseArea() )

			DBUseArea( , , cFileName , , FALSE)
			Assert.True( DBSetIndex(cFileName + ".ntx") )
			DBGoTop()
			LOCAL nCount := 0 AS INT
			DO WHILE ! EOF()
				nCount ++
				Assert.True( FieldGet(2) == RecNo() )
				Assert.True( FieldGet(2) == nCount )
				Assert.True( DBSkip() )
			END DO
			Assert.True( DBCloseArea() )
		RETURN

		// TECH-965270UG7K , Workareas not being reused
		[Fact, Trait("Category", "DBF")];
		METHOD WorkareaNums() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			Assert.True( DBCreate(cFileName, {{"FLD1","C",10,0}}) )

			Assert.True( DBUseArea ( TRUE , , cFileName , "a1") )
			Assert.Equal( 1u , DBGetSelect() )
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea ( TRUE , , cFileName , "a2") )
			Assert.Equal( 1u , DBGetSelect() )
			Assert.True( DBCloseArea() )

			Assert.True( DBUseArea ( TRUE , , cFileName , "a3") )
			Assert.Equal( 1u , DBGetSelect() )
			Assert.True( DBCloseArea() )
		RETURN

		// FOX-ES1QLR6Y5L , dbRecordInfo ( DBRI_LOCKED ) always returns .f.
		[Fact, Trait("Category", "DBF")];
		METHOD DBRI_LOCKED_test() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()
			SetExclusive ( FALSE )
			DBCreate ( cFileName , { {"id", "C", 5, 0} })

			DBUseArea ( , , cFileName )
			DBAppend()
			DBAppend()
			DBAppend()
			DBGoTop()

			Assert.True( DBRLock ( RecNo() ) )
			Assert.True( DBRecordInfo ( DBRI_LOCKED ) ) // Should show TRUE
			Assert.True( AScan ( DBRLockList() , RecNo() ) > 0 )

			DBSkip()
//			record 2 - no lock
			Assert.False( DBRecordInfo ( DBRI_LOCKED ) )
			Assert.False( AScan ( DBRLockList() , RecNo() ) > 0 )

			DBSkip()
			Assert.True( DBRLock ( RecNo() ) )
			Assert.True( DBRecordInfo ( DBRI_LOCKED ) ) // Should show TRUE
			Assert.True( AScan ( DBRLockList() , RecNo() ) > 0 )

			LOCAL a AS ARRAY
			a:= DBRLockList()
			Assert.Equal( 2 , (INT) ALen(a) )
			Assert.Equal( 1 , (INT) a[1] )
			Assert.Equal( 3 , (INT) a[2] )

			DBCloseArea()

			SetExclusive ( TRUE ) // restore
		RETURN

		// TECH-XQES14W9J0 , Aliasxxx() funcs throw exceptions
		[Fact, Trait("Category", "DBF")];
		METHOD Alias_test() AS VOID
			DBCloseAll()
			Assert.True( Alias() == "" )
			Assert.True( Alias0() == "" )
			Assert.True( Alias0Sym() == "" )
		RETURN

		// TECH-IXV5X91A74 , DBCreate() problem after having opened a dbf in exclusive mode
		[Fact, Trait("Category", "DBF")];
		METHOD DBCreate_test() AS VOID
			LOCAL cFileName AS STRING
			cFileName := GetTempFileName()

			DBCreate(cFileName, {{"FLD1","C",10,0}})
			DBUseArea(,,cFileName,,FALSE)
			DBCloseArea()

//			exception here
			Assert.True( DBCreate(cFileName, {{"FLD1","N",10,0}}) )

			Assert.True( DBUseArea(,,cFileName) )
			DBAppend()
			FieldPut(1 , 123)
			Assert.True( FieldGet(1) == 123 )
			Assert.True( DBCloseArea() )
		RETURN

		// TECH-ONPOSM84VS , Runtime exception on Error:ToString()
		[Fact, Trait("Category", "DBF")];
		METHOD DBError_test() AS VOID
			LOCAL cDbf AS STRING
			LOCAL cNtx AS STRING

			cDbf := GetTempFileName()
			cNtx := cDbf + ".ntx"

			DBCreate( cDbf , {{"CFIELD" , "C" , 10 , 0 }})
			DBUseArea(,,cDbf,,TRUE)
			Assert.True( DBAppend() )
			Assert.True( DBUnlock() )
			TRY
				? FieldPut ( 1 , "ABC") // record not locked
			CATCH e AS XSharp.Error
				? e:Message
				? e:GenCodeText
				? e:SubCodeText // check if this displays correctly
				? e:OSCodeText
				? e:ToString() // exception here
			FINALLY
				Assert.True( DBCloseArea() )
			END TRY
		RETURN

		// TECH-9JPUGAOV3L , NTX problem with EoF after sequence of commands
		[Fact, Trait("Category", "DBF")];
		METHOD Ntx_Eof_test() AS VOID
			LOCAL cDbf AS STRING
			LOCAL cNtx AS STRING

			cDbf := GetTempFileName()
			cNtx := cDbf + ".ntx"

			DBCreate( cDbf , {{"NFIELD" , "N" , 5 , 0 }})
			DBUseArea(,,cDbf,,FALSE)
			DBAppend()
			FieldPut(1,123)
			DBAppend()
			FieldPut(1,456)
			DBCreateIndex(cNtx, "NFIELD")
			DBCloseArea()

			// necessary sequence to reproduce the problem below
			DBUseArea(,,cDbf,,FALSE)
			DBSetIndex(cNtx)
			DBGoTop()
			Assert.Equal(1, (INT)RecNo()) // 1
			DBGoBottom()
			Assert.Equal(2, (INT)RecNo()) // 2
			DBSkip(-1)
			Assert.Equal(1, (INT)RecNo()) // 1
			DBGoTo(2)
			Assert.Equal(2, (INT)RecNo()) // 2

			DBSkip(+1)
			Assert.Equal(TRUE, (LOGIC)EOF()) // FALSE, wrong
			Assert.Equal(3, (INT)RecNo()) // 2, wrong

			DBSkip(+1)
			Assert.Equal(TRUE, (LOGIC)EOF()) // TRUE
			Assert.Equal(3, (INT)RecNo()) // 3

			DBCloseArea()
		RETURN

		// TECH-546935N337, DBOrderInfo(DBOI_SETCODEBLOCK) causes an invalid cast exception, but only if a index is opened
		[Fact, Trait("Category", "DBF")];
		METHOD DBOrderInfo_DBOI_SETCODEBLOCK() AS VOID
			LOCAL cDBF, cNTX AS STRING

			cDBF := GetTempFileName("test")
			cNTX := cDBF + ".ntx"

			DBCreate( cDBF , {{"ID" , "C" , 5 , 0 }})
			DBUseArea(,,cDBF)

			DBAppend()
			FieldPut(1, "one")

			DBCreateIndex(cNTX , "Upper (ID)")
			DBCloseArea()

			DBUseArea(,,cDBF)
			DBSetIndex(cNTX)

			? "DBOI_SETCODEBLOCK" , DBOrderInfo( DBOI_SETCODEBLOCK )

			DBCloseArea()
		RETURN

		// TECH-3NY78C93EK, OrdScope() not working
		[Fact, Trait("Category", "DBF")];
		METHOD OrdScope_test() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()
			DBCreate( cDbf , {{"NFIELD" , "N" , 5 , 0 }})
			DBUseArea(,"DBFNTX",cDbf)
			FOR LOCAL n := 1 AS INT UPTO 20
				DBAppend()
				FieldPut(1,n)
			NEXT
			DBCreateIndex(cDbf , "NFIELD")
			DBCloseArea()

			DBUseArea(,"DBFNTX",cDbf) // 20 records
			DBSetIndex ( cDbf )
			Assert.Equal( (INT) DBOrderInfo( DBOI_KEYCOUNT ) , 20)
			Assert.Equal( (INT) OrdScope(TOPSCOPE, 5) , 5) // NULL
			Assert.Equal( (INT) OrdScope(BOTTOMSCOPE, 10) , 10) // NULL
			DBGoTop()

			Assert.Equal( (INT) DBOrderInfo( DBOI_KEYCOUNT ) , 6) // still 20 - but must be 6
			LOCAL nCount := 0 AS INT
			DO WHILE ! EOF() // all 20 records are listed
				? FieldGet ( 1 )
				DBSkip ( 1 )
				nCount ++
			ENDDO
			Assert.Equal( 6 , nCount)
			Assert.Equal( 5 , (INT) DBOrderInfo( DBOI_SCOPETOP ) ) // {(0x0000)0x00000000} CLASS
			Assert.Equal( 10 , (INT) DBOrderInfo( DBOI_SCOPEBOTTOM ) ) // {(0x0000)0x00000000} CLASS
			DBCloseArea()
		RETURN

		[Fact, Trait("Category", "DBF")];
		METHOD OrdScope_test_with_Ordinal_Collation() AS VOID
			LOCAL cDbf AS STRING
			cDbf := GetTempFileName()

			LOCAL uCollation AS USUAL
			uCollation := SetCollation(#ORDINAL)

			DBCreate( cDbf , {{"NFIELD" , "N" , 5 , 0 }})
			DBUseArea(,"DBFNTX",cDbf)
			FOR LOCAL n := 1 AS INT UPTO 20
				DBAppend()
				FieldPut(1,n)
			NEXT
			DBCreateIndex(cDbf , "NFIELD")
			DBCloseArea()

			DBUseArea(,"DBFNTX",cDbf)
			DBSetIndex ( cDbf )
			Assert.Equal( (INT) DBOrderInfo( DBOI_KEYCOUNT ) , 20)
			Assert.Equal( (INT) UsualType( DBOrderInfo( DBOI_KEYCOUNT )  ) , 1) // first time returns 6
			Assert.Equal( (INT) UsualType( DBOrderInfo( DBOI_KEYCOUNT )  ) , 1) // second time it returns 1 correctly
			DBCloseArea()

			SetCollation(uCollation)
		RETURN

		// TECH-9TW65Q3XQE, NTX corruption with updating multiple fields and shared mode
		[Fact, Trait("Category", "DBF")];
		METHOD NTX_test() AS VOID
			LOCAL aValues AS ARRAY
			LOCAL i AS DWORD
			LOCAL cDBF, cNTX AS STRING

			aValues := { "ssss" , "hhhh", "wwww" , "aaaa" }
			cDBF := GetTempFileName()
			cNTX := cDbf
			DBCreate( cDBF , {{"LAST" , "C" , 20 , 0 } , ;
								{"TEXT1" , "C" , 10 , 0 } , ;
								{"NUM1" , "N" , 10 , 2 }})

			DBUseArea(,,cDBF,,FALSE)
			FOR i := 1 UPTO ALen ( aValues )
				DBAppend()
				FieldPut(1,aValues [i])
			NEXT
			DBCreateIndex(cNTX, "Upper ( Last)")
			DBCloseArea()

			DBUseArea(,,cDBF,,TRUE) // open shared !
			DBSetIndex(cNTX)
			DBGoTop()
			? "current (indexed) order"
			DO WHILE ! EOF()
				? FieldGet ( 1 )
				DBSkip ( 1 )
			ENDDO
			DBGoTop()
			// "now replace the index field #LAST content 'aaaa' with 'pppp'"
			// "and also update another field"
			Assert.True( DBRLock ( RecNo() )  )
			// "Replacing", AllTrim(FieldGet(1)), "with 'pppp'"
			FieldPut ( 1 , "pppp" ) // Note: This is the index field

//	 this is what causes the problem, updating a second field
//	 if this fieldput is put above the first one, then sample works correctly
//	 either one of the fieldputs below cause the problem to surface
			FieldPut ( 2 , "Eins" )
			FieldPut ( 3 , 123.45 )

			DBCommit()

			DBRUnlock ( RecNo() )

			Assert.False( DBSeek( "AAAA" ) ) // must show .F.
			Assert.True( DBSeek ("PPPP" ) ) // must show .T.
			// "Record order now:"
			// "(should be hhhh, pppp, ssss , wwww)"
			DBGoTop()
			LOCAL nCount := 0 AS INT
			DO WHILE ! EOF()
				? AllTrim(FieldGet(1)) , FieldGet(3) , AllTrim(FieldGet(2))
				nCount ++
				DO CASE
				CASE nCount == 1
					Assert.Equal( "hhhh", AllTrim(FieldGet(1)) )
				CASE nCount == 2
					Assert.Equal( "pppp", AllTrim(FieldGet(1)) )
				CASE nCount == 3
					Assert.Equal( "ssss", AllTrim(FieldGet(1)) )
				CASE nCount == 4
					Assert.Equal( "wwww", AllTrim(FieldGet(1)) )
				END CASE
				DBSkip ( 1 )
			ENDDO
			DBCloseArea()
		RETURN

		// TECH-4JX6H10741, DBOrderInfo( DBOI_KEYCOUNT ) returns NIL when record pointer is at EoF
		[Fact, Trait("Category", "DBF")];
		METHOD DBOrderInfo_DBOI_KEYCOUNT() AS VOID
			LOCAL cDbf AS STRING
			LOCAL aValues AS ARRAY
			LOCAL i AS DWORD

			cDBF := GetTempFileName()
			aValues := { 44 , 12, 34 , 21 }
			DBCreate( cDBF , {{"AGE" , "N" , 3 , 0 } })
			DBUseArea(,"DBFNTX",cDBF,,FALSE)
			Assert.Equal(0 , (INT) DBOrderInfo( DBOI_KEYCOUNT ) ) // 0,  ok
			FOR i := 1 UPTO ALen ( aValues )
				DBAppend()
				FieldPut(1,aValues [i])
			NEXT
			Assert.Equal(0 , (INT) DBOrderInfo( DBOI_KEYCOUNT ) ) // 0,  ok
			DBCreateIndex(cDbf, "age" )
			Assert.Equal(4 , (INT) DBOrderInfo( DBOI_KEYCOUNT ) ) // 4, correct
			DBGoTop()
			Assert.Equal(4 , (INT) DBOrderInfo( DBOI_KEYCOUNT ) ) // 4, correct
			DO WHILE ! EOF()
//			? FieldGet ( 1 )
				DBSkip(1)
			ENDDO
			Assert.Equal(4 , (INT) DBOrderInfo( DBOI_KEYCOUNT ) ) // NIL, should be 4
			DBSkip(-1)
			Assert.Equal(4 , (INT) DBOrderInfo( DBOI_KEYCOUNT ) ) // 4, correct
			DBCloseArea ()
		RETURN

		[Fact, Trait("Category", "DBF")];
		METHOD SetDeleted_FALSE() AS VOID
			LOCAL cDbf AS STRING
			LOCAL aValues AS ARRAY
			LOCAL i AS DWORD

			cDBF := GetTempFileName()

			SetDeleted(FALSE)

//			test also with those
//			aValues := { "vaa" , "abba", "acb" , "aaab"  , "adab"  , "baac"  , "aeab"  , "baaAaa" }
			aValues := { "vvv" , "abb", "acb" , "aaa"  , "bbb" }
			DBCreate( cDBF , {{"LAST" , "C" ,10 , 0 } })
			DBUseArea(,"DBFNTX",cDBF,,FALSE)
			FOR i := 1 UPTO ALen ( aValues )
				DBAppend()
				FieldPut(1,aValues [i])
			NEXT

			DBUseArea(,"DBFNTX",cDBF,,TRUE)
			Assert.Equal(0 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			DBCreateIndex(cDbf, "Upper(LAST)" )
			Assert.Equal(5 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			// ? "Setting scope"
			LOCAL u AS USUAL
			u := "A"
			VODBOrderInfo( DBOI_SCOPETOP, "", NIL, REF u )
			VODBOrderInfo( DBOI_SCOPEBOTTOM, "", NIL, REF u )

			DBGoTop()
			Assert.Equal(4 , (INT)RecNo())
			DBGoBottom()
			Assert.Equal(3 , (INT)RecNo())

			DBGoTop()

			Assert.Equal(3 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			DO WHILE ! EOF()
				DBSkip(1)
			ENDDO
			Assert.Equal(3 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			DBCloseArea()

			SetDeleted(FALSE)
		RETURN

		// TECH-W0KKQ1I50C, Problems with SetDeleted(TRUE)
		[Fact, Trait("Category", "DBF")];
		METHOD SetDeleted_TRUE() AS VOID
			LOCAL cDbf AS STRING
			LOCAL aValues AS ARRAY
			LOCAL i AS DWORD

			cDBF := GetTempFileName()

			SetDeleted(TRUE)

//			test also with those
//			aValues := { "vaa" , "abba", "acb" , "aaab"  , "adab"  , "baac"  , "aeab"  , "baaAaa" }
			aValues := { "vvv" , "abb", "acb" , "aaa"  , "bbb" }
			DBCreate( cDBF , {{"LAST" , "C" ,10 , 0 } })
			DBUseArea(,"DBFNTX",cDBF,,FALSE)
			FOR i := 1 UPTO ALen ( aValues )
				DBAppend()
				FieldPut(1,aValues [i])
			NEXT

			DBUseArea(,"DBFNTX",cDBF,,TRUE)
			Assert.Equal(0 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			DBCreateIndex(cDbf, "Upper(LAST)" )
			Assert.Equal(5 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			// ? "Setting scope"
			LOCAL u AS USUAL
			u := "A"
			VODBOrderInfo( DBOI_SCOPETOP, "", NIL, REF u )
			VODBOrderInfo( DBOI_SCOPEBOTTOM, "", NIL, REF u )

			DBGoTop()
			Assert.Equal(4 , (INT)RecNo())
			DBGoBottom()
			Assert.Equal(3 , (INT)RecNo())

			DBGoTop()

			Assert.Equal(3 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			DO WHILE ! EOF()
				DBSkip(1)
			ENDDO
			Assert.Equal(3 , (INT)DBOrderInfo( DBOI_KEYCOUNT ))
			DBCloseArea()

			SetDeleted(FALSE)
		RETURN

		STATIC PRIVATE METHOD GetTempFileName() AS STRING
		RETURN GetTempFileName("testdbf")
		STATIC PRIVATE METHOD GetTempFileName(cFileName AS STRING) AS STRING
			// we may want to put them to a specific folder etc
		RETURN cFileName

	END CLASS
END NAMESPACE


Fixes CDX/SDF problems with empty dates.

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//

USING XSharp.RDD.Support
USING System.Text
USING System.Collections.Generic
USING System.Globalization
USING XSharp.RDD.Enums

BEGIN NAMESPACE XSharp.RDD
    /// <summary>Abstract TextRDD. For reading and writing delimited files and SDF files.</summary>
    ABSTRACT CLASS TEXTRDD INHERIT Workarea
        PROTECT INTERNAL _Encoding      AS Encoding
        PROTECT _Hot            AS LOGIC
        PROTECT _Ansi           AS LOGIC
        PROTECT _BufferValid    AS LOGIC
        PROTECT _Reccount       AS LONG
        PROTECT _Recno          AS LONG
        PROTECT _fieldData      AS OBJECT[]
        PROTECT _numformat      AS NumberFormatInfo
        PROTECT _Buffer         AS BYTE[]
        PROTECT _Writing        as LOGIC
        PROTECT _RecordSeparator AS STRING
        INTERNAL PROPERTY FieldSeparator    AS STRING GET _Separator SET _Separator := VALUE
        INTERNAL PROPERTY StringDelimiter   AS STRING GET _Delimiter SET _Delimiter := VALUE
        INTERNAL PROPERTY RecordSeparator   AS STRING GET _RecordSeparator SET _RecordSeparator := VALUE

        STATIC PRIVATE  culture := CultureInfo.InvariantCulture AS CultureInfo
        PROTECT PROPERTY IsOpen AS LOGIC GET SELF:_hFile != F_ERROR
        INTERNAL _OpenInfo		AS DbOpenInfo // current dbOpenInfo structure in OPEN/CREATE method

        #region abstract methods, must be implemented in subclass
        ABSTRACT PROTECTED METHOD _getLastRec()  AS LONG
        ABSTRACT PROTECTED METHOD _writeRecord() AS LOGIC
        ABSTRACT PROTECTED METHOD _readRecord() AS LOGIC
        #endregion

        #region worker methods
        PROTECTED METHOD _prepareFields AS VOID
            SELF:_fieldData     := OBJECT[]{SELF:_Fields:Length}
            local nSize := 0 AS LONG
            foreach var fld in SELF:_Fields
                nSize += fld:Length
            next
            SELF:_RecordLength := nSize

        PROTECTED METHOD _getFieldValue(oField AS RddFieldInfo, sValue AS STRING) AS OBJECT
            SWITCH oField:FieldType
                CASE DbFieldType.Character   // C
                CASE DbFieldType.VarChar     // 'V'
                CASE DbFieldType.VarBinary   // 'Q'
                    return sValue
                CASE DbFieldType.Date
                    if sValue:Length >= 8
                        var year  := Convert.ToInt32(sValue:Substring(0,4))
                        var month := Convert.ToInt32(sValue:Substring(4,2))
                        var day   := Convert.ToInt32(sValue:Substring(6,2))
                        return DbDate{year,month,day}
                    endif
                    SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                CASE DbFieldType.DateTime      // 'T'
                    local oDt as DateTime
                    IF DateTime.TryParse(sValue, OUT oDt)
                        return oDt
                    ENDIF
                    SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                CASE DbFieldType.Logic
                    return sValue[0] == 'T' .or. sValue[0] == 'Y'

                CASE DbFieldType.Number         // 'N'
                CASE DbFieldType.Float          // 'F'
                CASE DbFieldType.Double         // 'B'
                CASE DbFieldType.Currency		// 'Y'
                CASE DbFieldType.Integer        // 'I'
                    var result := _Val(sValue)
                    if result is REAL8 VAR r8
                        return DbFloat{r8, oField:Length, oField:Decimals}
                    elseif result != null
                        return result
                    endif
                    SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                Otherwise
                    // Skip column contents
                    NOP
            END SWITCH
            return NULL

        PROTECTED METHOD _getFieldString(oField AS RddFieldInfo, oValue AS OBJECT) AS STRING
            LOCAL sValue := "" as STRING
            SWITCH oField:FieldType
                CASE DbFieldType.Character   // C
                CASE DbFieldType.VarChar     // 'V'
                CASE DbFieldType.VarBinary   // 'Q'
                    sValue := oValue:ToString():TrimEnd()
                CASE DbFieldType.Date
                    if oValue IS IDate var oDate
                        VAR oDt := DateTime{oDate:Year, oDate:Month, oDate:Day}
                        sValue := DtToS(oDt)

                    elseif oValue is DateTime var oDt
                        sValue := DtToS(oDt)
                    else
                        SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                    endif
                CASE DbFieldType.DateTime      // 'T'
                    local oDt  as DateTime
                    if oValue IS IDate var oDate
                        if oDate:IsEmpty
                            oDt := DateTime.MinValue
                        ELSE
                            oDt := DateTime{oDate:Year, oDate:Month, oDate:Day}
                        ENDIF
                    elseif oValue is DateTime var oDt2
                        oDt := oDt2
                    else
                        oDt := DateTime.MinValue
                        SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                    endif
                    sValue := DtToS(oDt)
                CASE DbFieldType.Logic

                    IF oValue is LOGIC VAR logValue
                        sValue := iif(logValue,"T","F")
                    ELSE
                        SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                    ENDIF
                CASE DbFieldType.Number         // 'N'
                CASE DbFieldType.Float          // 'F'
                CASE DbFieldType.Double         // 'B'
                CASE DbFieldType.Currency		// 'Y'
                CASE DbFieldType.Integer        // 'I'
                    _numformat:NumberDecimalDigits :=   oField:Decimals
                    IF oValue is IFloat VAR flValue
                        sValue := flValue:Value:ToString("F", _numformat)
                    ELSEIF oValue is LONG VAR longValue
                        sValue := longValue:ToString("F", _numformat)
                    ELSEIF oValue is REAL8 VAR r8Value
                        sValue :=  r8Value:ToString("F", _numformat)
                    ELSE
                        SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                    ENDIF
                    IF sValue:Length > oField:Length
                        sValue := STRING{'*', oField:Length}
                    ENDIF
                Otherwise
                    // Skip column contents
                    NOP
            END SWITCH
            RETURN sValue

        Protected Method _WriteString(strValue as STRING) AS LOGIC
            if strValue:Length > SELF:_Buffer:Length
                SELF:_Buffer := Byte[]{strValue:Length }
            ENDIF
            SELF:_Encoding:GetBytes(strValue, 0, strValue:Length, _Buffer, 0)
            RETURN FWrite3(SELF:_hFile, _Buffer, (DWORD) strValue:Length) == (DWORD) strValue:Length

        #endregion
        /// <inheritdoc />
        CONSTRUCTOR
            SUPER()
            SELF:_hFile          := IntPtr.Zero
            SELF:_TransRec 		 := TRUE
            SELF:_RecordLength 	 := 0
            SELF:StringDelimiter := e"\""
            SELF:FieldSeparator  := ","
            SELF:RecordSeparator := RuntimeState.Eol
            SELF:_fieldData      := OBJECT[]{0}
            SELF:_numformat := (NumberFormatInfo) culture:NumberFormat:Clone()
            SELF:_numformat:NumberDecimalSeparator := "."
            SELF:_Buffer        := Byte[]{512}
            SELF:_Writing       := FALSE
            SELF:_Separator     := ","

            /// <inheritdoc />
        METHOD GoBottom() AS LOGIC
            RETURN FALSE

            /// <inheritdoc />
        METHOD GoTop() AS LOGIC
            IF SELF:IsOpen
                BEGIN LOCK SELF
                    FSeek3(SELF:_hFile, 0, FS_SET)
                    SELF:_Top := TRUE
                    SELF:_Bottom := FALSE
                    SELF:_BufferValid := FALSE
                    SELF:_Recno := 1
                END LOCK
                RETURN TRUE
            ENDIF
            RETURN FALSE

            /// <inheritdoc />
        METHOD Skip(nToSkip AS INT) AS LOGIC
            IF SELF:_Recno <= SELF:_Reccount
                SELF:_Recno += 1
                SELF:_BufferValid := FALSE
                RETURN SELF:_readRecord()
            ELSE
                SELF:_Recno += 1
                SELF:EoF := TRUE
                RETURN FALSE
            ENDIF

            /// <inheritdoc />
        METHOD Append(lReleaseLock AS LOGIC) AS LOGIC
            LOCAL isOK as LOGIC
            isOK := SELF:GoCold()
            IF isOK
                SELF:_fieldData := OBJECT[]{SELF:_Fields:Length}
            ENDIF
            RETURN isOK

        METHOD Close() 			AS LOGIC
            LOCAL isOk := FALSE AS LOGIC
            IF SELF:IsOpen
                isOk := SELF:GoCold()
                IF SELF:_Writing .and.  RuntimeState.Eof
                    FWrite(SELF:_hFile,_chr(26),1)
                ENDIF
                IF isOk
                    IF !SELF:_ReadOnly
                        SELF:Flush()
                    ENDIF
                    //
                    TRY
                        isOk := FClose( SELF:_hFile )

                        isOk := SUPER:Close() .AND. isOk
                    CATCH ex AS Exception
                        isOk := FALSE
                        SELF:_txtError(ex, Subcodes.ERDD_CLOSE_FILE,Gencode.EG_CLOSE,  "TEXTRDD.Close")

                    END TRY
                    SELF:_hFile := F_ERROR
                ENDIF
            ENDIF
            RETURN isOk

        PRIVATE METHOD _DetermineCodePage() AS LOGIC
            LOCAL codePage AS LONG
            IF XSharp.RuntimeState.Ansi
                SELF:_Ansi := TRUE
                codePage := XSharp.RuntimeState.WinCodePage
            ELSE
                SELF:_Ansi := FALSE
                codePage := XSharp.RuntimeState.DosCodePage
            ENDIF
            SELF:_Encoding := System.Text.Encoding.GetEncoding( codePage )
            return TRUE

        /// <inheritdoc />
        METHOD Create(info AS DbOpenInfo) AS LOGIC
            LOCAL isOK AS LOGIC
            isOK := FALSE
            IF SELF:_Fields:Length == 0
                RETURN FALSE
            ENDIF
            SELF:_OpenInfo := info
            SELF:_OpenInfo:FileName := System.IO.Path.ChangeExtension( SELF:_OpenInfo:FileName, SELF:_OpenInfo:Extension )
            //
            SELF:_Hot := FALSE
            SELF:_FileName := SELF:_OpenInfo:FileName
            SELF:_Alias := SELF:_OpenInfo:Alias
            SELF:_Shared := SELF:_OpenInfo:Shared
            SELF:_ReadOnly := SELF:_OpenInfo:ReadOnly

            //
            SELF:_hFile    := FCreate2( SELF:_FileName, FO_EXCLUSIVE)
            IF SELF:IsOpen
                isOK := SELF:_DetermineCodePage()
                SELF:_prepareFields()
            ELSE
                isOK := FALSE
                LOCAL ex := FException() AS Exception
                SELF:_txtError( ex, ERDD.CREATE_FILE, XSharp.Gencode.EG_CREATE )
            ENDIF
            SELF:_Writing  := TRUE
            RETURN isOK

            /// <inheritdoc />
        METHOD Open(info AS DbOpenInfo) AS LOGIC
            LOCAL isOK AS LOGIC
            //
            isOK := FALSE
            SELF:_OpenInfo := info
            SELF:_Hot := FALSE
            IF !String.IsNullOrEmpty(SELF:_OpenInfo:Extension)
                SELF:_OpenInfo:FileName := System.IO.Path.ChangeExtension( SELF:_OpenInfo:FileName, SELF:_OpenInfo:Extension )
            ENDIF
            SELF:_FileName := SELF:_OpenInfo:FileName
            IF File(SELF:_FileName)
                SELF:_FileName := FPathName()
                SELF:_OpenInfo:FileName := SELF:_FileName
            ENDIF
            SELF:_Alias := SELF:_OpenInfo:Alias
            SELF:_Shared := SELF:_OpenInfo:Shared
            SELF:_ReadOnly := SELF:_OpenInfo:ReadOnly
            SELF:_hFile    := FOpen(SELF:_FileName, SELF:_OpenInfo:FileMode)
            IF SELF:IsOpen
                isOK := SELF:_DetermineCodePage()
                IF FSize(SELF:_hFile) < Int32.MaxValue
                    FConvertToMemoryStream(SELF:_hFile)
                ENDIF
                SELF:_prepareFields()
                SELF:_Reccount := SELF:_getLastRec()
                SELF:GoTop()
            ELSE
                // Error or just FALSE ?
                isOK := FALSE
                LOCAL ex := FException() AS Exception
                SELF:_txtError( ex, ERDD.OPEN_FILE, XSharp.Gencode.EG_OPEN )
            ENDIF
            RETURN isOK

            /// <inheritdoc />
        METHOD Flush() 			AS LOGIC
            RETURN FFlush(SELF:_hFile)

            /// <inheritdoc />
        METHOD GoCold()			AS LOGIC
            IF SELF:_Hot
                SELF:_writeRecord()
                SELF:_Hot := FALSE
            ENDIF
            RETURN TRUE

            /// <inheritdoc />
        METHOD GetValue(nFldPos AS INT) AS OBJECT
            // Subclass fills the _fieldData list
            IF SELF:_readRecord()
                if nFldPos <= _fieldData:Length
                    return _fieldData[nFldPos -1]
                endif
            endif
            RETURN NULL

            /// <inheritdoc />
        METHOD PutValue(nFldPos AS INT, oValue AS OBJECT) AS LOGIC
            // Subclass persists the _fieldData list
            IF nFldPos <= _fieldData:Length
                SELF:_fieldData[nFldPos-1] := oValue
                SELF:_Hot := TRUE
            ENDIF
            RETURN TRUE

            /// <inheritdoc />
        METHOD Info(nOrdinal AS INT, oNewValue AS OBJECT) AS OBJECT
            SWITCH nOrdinal
                CASE DBI_ISDBF
                    return FALSE
                CASE DBI_GETHEADERSIZE
                    return 0
                case DBI_FILEHANDLE
                    return SELF:_hFile
                CASE DBI_FULLPATH
                    return SELF:_FileName
                CASE DBI_SETDELIMITER                   // Field delimiter
                    VAR result := SELF:FieldSeparator
                    IF oNewValue IS STRING
                        SELF:FieldSeparator := (STRING) oNewValue
                    ENDIF
                    RETURN result
                case DBI_SEPARATOR
                    VAR result := SELF:RecordSeparator
                    IF oNewValue IS STRING
                        SELF:RecordSeparator := (STRING) oNewValue
                    ENDIF
                    RETURN result

                case DBI_GETDELIMITER
                    return SELF:_Delimiter
                CASE DBI_CANPUTREC
                    RETURN FALSE
            END SWITCH
            return SUPER:Info(nOrdinal, oNewValue)

            // Properties

            /// <inheritdoc />
        PROPERTY Deleted 	AS LOGIC GET 	FALSE
        /// <inheritdoc />
        PROPERTY RecCount	AS LONG GET _Reccount
        /// <inheritdoc />
        PROPERTY RecId		AS OBJECT GET  _Recno
        /// <inheritdoc />
        PROPERTY RecNo		AS LONG 	GET _Recno
        /// <inheritdoc />
        VIRTUAL PROPERTY Driver AS STRING GET "TEXTRDD"

        INTERNAL METHOD _txtError(ex AS Exception, iSubCode AS DWORD, iGenCode AS DWORD) AS VOID
            SELF:_txtError(ex, iSubCode, iGenCode, String.Empty, ex?:Message, XSharp.Severity.ES_ERROR)

        INTERNAL METHOD _txtError(iSubCode AS DWORD, iGenCode AS DWORD) AS VOID
            SELF:_txtError(NULL, iSubCode, iGenCode, String.Empty, String.Empty, XSharp.Severity.ES_ERROR)

        INTERNAL METHOD _txtError(ex AS Exception,iSubCode AS DWORD, iGenCode AS DWORD, iSeverity AS DWORD) AS VOID
            SELF:_txtError(ex, iSubCode, iGenCode, String.Empty, String.Empty, iSeverity)

        INTERNAL METHOD _txtError(iSubCode AS DWORD, iGenCode AS DWORD, iSeverity AS DWORD) AS VOID
            SELF:_txtError(NULL, iSubCode, iGenCode, String.Empty, String.Empty, iSeverity)

        INTERNAL METHOD _txtError(iSubCode AS DWORD, iGenCode AS DWORD, strFunction AS STRING) AS VOID
            SELF:_txtError(NULL, iSubCode, iGenCode, strFunction, String.Empty, XSharp.Severity.ES_ERROR)

        INTERNAL METHOD _txtError(ex AS Exception, iSubCode AS DWORD, iGenCode AS DWORD, strFunction AS STRING) AS VOID
            SELF:_txtError(ex, iSubCode, iGenCode, strFunction, String.Empty, XSharp.Severity.ES_ERROR)

        INTERNAL METHOD _txtError(iSubCode AS DWORD, iGenCode AS DWORD, strFunction AS STRING, strMessage AS STRING) AS VOID
            SELF:_txtError(NULL, iSubCode, iGenCode, strFunction,strMessage, XSharp.Severity.ES_ERROR)

        INTERNAL METHOD _txtError(ex AS Exception, iGenCode AS DWORD, iSubCode AS DWORD, strFunction AS STRING, strMessage AS STRING, iSeverity AS DWORD) AS VOID
            LOCAL oError AS RddError
            //
            IF ex != NULL
                oError := RddError{ex,iGenCode, iSubCode}
            ELSE
                oError := RddError{iGenCode, iSubCode}
            ENDIF
            oError:SubSystem := SELF:Driver
            oError:Severity := iSeverity
            oError:FuncSym  := IIF(strFunction == NULL, "", strFunction) // code in the SDK expects all string properties to be non-NULL
            oError:FileName := SELF:_FileName
            IF String.IsNullOrEmpty(strMessage)  .AND. ex != NULL
                strMessage := ex:Message
            ENDIF
            oError:Description := IIF(strMessage == NULL , "", strMessage)
            RuntimeState.LastRddError := oError
            //
            THROW oError

    END CLASS
END NAMESPACE

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//

USING XSharp.RDD.Support
USING System.Text
USING System.Collections.Generic
USING System.Globalization
USING XSharp.RDD.Enums

BEGIN NAMESPACE XSharp.RDD
    /// <summary>Abstract TextRDD. For reading and writing delimited files and SDF files.</summary>
    ABSTRACT CLASS TEXTRDD INHERIT Workarea
        PROTECT INTERNAL _Encoding      AS Encoding
        PROTECT _Hot            AS LOGIC
        PROTECT _Ansi           AS LOGIC
        PROTECT _BufferValid    AS LOGIC
        PROTECT _Reccount       AS LONG
        PROTECT _Recno          AS LONG
        PROTECT _fieldData      AS OBJECT[]
        PROTECT _numformat      AS NumberFormatInfo
        PROTECT _Buffer         AS BYTE[]
        PROTECT _Writing        as LOGIC
        PROTECT _RecordSeparator AS STRING
        INTERNAL PROPERTY FieldSeparator    AS STRING GET _Separator SET _Separator := VALUE
        INTERNAL PROPERTY StringDelimiter   AS STRING GET _Delimiter SET _Delimiter := VALUE
        INTERNAL PROPERTY RecordSeparator   AS STRING GET _RecordSeparator SET _RecordSeparator := VALUE

        STATIC PRIVATE  culture := CultureInfo.InvariantCulture AS CultureInfo
        PROTECT PROPERTY IsOpen AS LOGIC GET SELF:_hFile != F_ERROR
        INTERNAL _OpenInfo		AS DbOpenInfo // current dbOpenInfo structure in OPEN/CREATE method

        #region abstract methods, must be implemented in subclass
        ABSTRACT PROTECTED METHOD _getLastRec()  AS LONG
        ABSTRACT PROTECTED METHOD _writeRecord() AS LOGIC
        ABSTRACT PROTECTED METHOD _readRecord() AS LOGIC
        #endregion

        #region worker methods
        PROTECTED METHOD _prepareFields AS VOID
            SELF:_fieldData     := OBJECT[]{SELF:_Fields:Length}
            local nSize := 0 AS LONG
            foreach var fld in SELF:_Fields
                nSize += fld:Length
            next
            SELF:_RecordLength := nSize

        PROTECTED METHOD _getFieldValue(oField AS RddFieldInfo, sValue AS STRING) AS OBJECT
            SWITCH oField:FieldType
                CASE DbFieldType.Character   // C
                CASE DbFieldType.VarChar     // 'V'
                CASE DbFieldType.VarBinary   // 'Q'
                    return sValue
                CASE DbFieldType.Date
                    if sValue:Length >= 8
                        var year  := Convert.ToInt32(sValue:Substring(0,4))
                        var month := Convert.ToInt32(sValue:Substring(4,2))
                        var day   := Convert.ToInt32(sValue:Substring(6,2))
                        return DbDate{year,month,day}
                    endif
                    SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                CASE DbFieldType.DateTime      // 'T'
                    local oDt as DateTime
                    IF DateTime.TryParse(sValue, OUT oDt)
                        return oDt
                    ENDIF
                    SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                CASE DbFieldType.Logic
                    return sValue[0] == 'T' .or. sValue[0] == 'Y'

                CASE DbFieldType.Number         // 'N'
                CASE DbFieldType.Float          // 'F'
                CASE DbFieldType.Double         // 'B'
                CASE DbFieldType.Currency		// 'Y'
                CASE DbFieldType.Integer        // 'I'
                    var result := _Val(sValue)
                    if result is REAL8 VAR r8
                        return DbFloat{r8, oField:Length, oField:Decimals}
                    elseif result != null
                        return result
                    endif
                    SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                Otherwise
                    // Skip column contents
                    NOP
            END SWITCH
            return NULL

        PROTECTED METHOD _getFieldString(oField AS RddFieldInfo, oValue AS OBJECT) AS STRING
            LOCAL sValue := "" as STRING
            SWITCH oField:FieldType
                CASE DbFieldType.Character   // C
                CASE DbFieldType.VarChar     // 'V'
                CASE DbFieldType.VarBinary   // 'Q'
                    sValue := oValue:ToString():TrimEnd()
                CASE DbFieldType.Date
                    if oValue IS IDate var oDate
                        VAR oDt := DateTime{oDate:Year, oDate:Month, oDate:Day}
                        sValue := DtToS(oDt)

                    elseif oValue is DateTime var oDt
                        sValue := DtToS(oDt)
                    else
                        SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                    endif
                CASE DbFieldType.DateTime      // 'T'
                    local oDt  as DateTime
                    if oValue IS IDate var oDate
                        oDt := DateTime{oDate:Year, oDate:Month, oDate:Day}
                    elseif oValue is DateTime var oDt2
                        oDt := oDt2
                    else
                        oDt := DateTime.MinValue
                        SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                    endif
                    sValue := DtToS(oDt)
                CASE DbFieldType.Logic

                    IF oValue is LOGIC VAR logValue
                        sValue := iif(logValue,"T","F")
                    ELSE
                        SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                    ENDIF
                CASE DbFieldType.Number         // 'N'
                CASE DbFieldType.Float          // 'F'
                CASE DbFieldType.Double         // 'B'
                CASE DbFieldType.Currency		// 'Y'
                CASE DbFieldType.Integer        // 'I'
                    _numformat:NumberDecimalDigits :=   oField:Decimals
                    IF oValue is IFloat VAR flValue
                        sValue := flValue:Value:ToString("F", _numformat)
                    ELSEIF oValue is LONG VAR longValue
                        sValue := longValue:ToString("F", _numformat)
                    ELSEIF oValue is REAL8 VAR r8Value
                        sValue :=  r8Value:ToString("F", _numformat)
                    ELSE
                        SELF:_txtError(Subcodes.ERDD_DATATYPE, EG_DATATYPE)
                    ENDIF
                    IF sValue:Length > oField:Length
                        sValue := STRING{'*', oField:Length}
                    ENDIF
                Otherwise
                    // Skip column contents
                    NOP
            END SWITCH
            RETURN sValue

        Protected Method _WriteString(strValue as STRING) AS LOGIC
            if strValue:Length > SELF:_Buffer:Length
                SELF:_Buffer := Byte[]{strValue:Length }
            ENDIF
            SELF:_Encoding:GetBytes(strValue, 0, strValue:Length, _Buffer, 0)
            RETURN FWrite3(SELF:_hFile, _Buffer, (DWORD) strValue:Length) == (DWORD) strValue:Length

        #endregion
        /// <inheritdoc />
        CONSTRUCTOR
            SUPER()
            SELF:_hFile          := IntPtr.Zero
            SELF:_TransRec 		 := TRUE
            SELF:_RecordLength 	 := 0
            SELF:StringDelimiter := e"\""
            SELF:FieldSeparator  := ","
            SELF:RecordSeparator := RuntimeState.Eol
            SELF:_fieldData      := OBJECT[]{0}
            SELF:_numformat := (NumberFormatInfo) culture:NumberFormat:Clone()
            SELF:_numformat:NumberDecimalSeparator := "."
            SELF:_Buffer        := Byte[]{512}
            SELF:_Writing       := FALSE
            SELF:_Separator     := ","

            /// <inheritdoc />
        METHOD GoBottom() AS LOGIC
            RETURN FALSE

            /// <inheritdoc />
        METHOD GoTop() AS LOGIC
            IF SELF:IsOpen
                BEGIN LOCK SELF
                    FSeek3(SELF:_hFile, 0, FS_SET)
                    SELF:_Top := TRUE
                    SELF:_Bottom := FALSE
                    SELF:_BufferValid := FALSE
                    SELF:_Recno := 1
                END LOCK
                RETURN TRUE
            ENDIF
            RETURN FALSE

            /// <inheritdoc />
        METHOD Skip(nToSkip AS INT) AS LOGIC
            IF SELF:_Recno <= SELF:_Reccount
                SELF:_Recno += 1
                SELF:_BufferValid := FALSE
                RETURN SELF:_readRecord()
            ELSE
                SELF:_Recno += 1
                SELF:EoF := TRUE
                RETURN FALSE
            ENDIF

            /// <inheritdoc />
        METHOD Append(lReleaseLock AS LOGIC) AS LOGIC
            LOCAL isOK as LOGIC
            isOK := SELF:GoCold()
            IF isOK
                SELF:_fieldData := OBJECT[]{SELF:_Fields:Length}
            ENDIF
            RETURN isOK

        METHOD Close() 			AS LOGIC
            LOCAL isOk := FALSE AS LOGIC
            IF SELF:IsOpen
                isOk := SELF:GoCold()
                IF SELF:_Writing .and.  RuntimeState.Eof
                    FWrite(SELF:_hFile,_chr(26),1)
                ENDIF
                IF isOk
                    IF !SELF:_ReadOnly
                        SELF:Flush()
                    ENDIF
                    //
                    TRY
                        isOk := FClose( SELF:_hFile )

                        isOk := SUPER:Close() .AND. isOk
                    CATCH ex AS Exception
                        isOk := FALSE
                        SELF:_txtError(ex, Subcodes.ERDD_CLOSE_FILE,Gencode.EG_CLOSE,  "TEXTRDD.Close")

                    END TRY
                    SELF:_hFile := F_ERROR
                ENDIF
            ENDIF
            RETURN isOk

        PRIVATE METHOD _DetermineCodePage() AS LOGIC
            LOCAL codePage AS LONG
            IF XSharp.RuntimeState.Ansi
                SELF:_Ansi := TRUE
                codePage := XSharp.RuntimeState.WinCodePage
            ELSE
                SELF:_Ansi := FALSE
                codePage := XSharp.RuntimeState.DosCodePage
            ENDIF
            SELF:_Encoding := System.Text.Encoding.GetEncoding( codePage )
            return TRUE

        /// <inheritdoc />
        METHOD Create(info AS DbOpenInfo) AS LOGIC
            LOCAL isOK AS LOGIC
            isOK := FALSE
            IF SELF:_Fields:Length == 0
                RETURN FALSE
            ENDIF
            SELF:_OpenInfo := info
            SELF:_OpenInfo:FileName := System.IO.Path.ChangeExtension( SELF:_OpenInfo:FileName, SELF:_OpenInfo:Extension )
            //
            SELF:_Hot := FALSE
            SELF:_FileName := SELF:_OpenInfo:FileName
            SELF:_Alias := SELF:_OpenInfo:Alias
            SELF:_Shared := SELF:_OpenInfo:Shared
            SELF:_ReadOnly := SELF:_OpenInfo:ReadOnly

            //
            SELF:_hFile    := FCreate2( SELF:_FileName, FO_EXCLUSIVE)
            IF SELF:IsOpen
                isOK := SELF:_DetermineCodePage()
                SELF:_prepareFields()
            ELSE
                isOK := FALSE
                LOCAL ex := FException() AS Exception
                SELF:_txtError( ex, ERDD.CREATE_FILE, XSharp.Gencode.EG_CREATE )
            ENDIF
            SELF:_Writing  := TRUE
            RETURN isOK

            /// <inheritdoc />
        METHOD Open(info AS DbOpenInfo) AS LOGIC
            LOCAL isOK AS LOGIC
            //
            isOK := FALSE
            SELF:_OpenInfo := info
            SELF:_Hot := FALSE
            IF !String.IsNullOrEmpty(SELF:_OpenInfo:Extension)
                SELF:_OpenInfo:FileName := System.IO.Path.ChangeExtension( SELF:_OpenInfo:FileName, SELF:_OpenInfo:Extension )
            ENDIF
            SELF:_FileName := SELF:_OpenInfo:FileName
            IF File(SELF:_FileName)
                SELF:_FileName := FPathName()
                SELF:_OpenInfo:FileName := SELF:_FileName
            ENDIF
            SELF:_Alias := SELF:_OpenInfo:Alias
            SELF:_Shared := SELF:_OpenInfo:Shared
            SELF:_ReadOnly := SELF:_OpenInfo:ReadOnly
            SELF:_hFile    := FOpen(SELF:_FileName, SELF:_OpenInfo:FileMode)
            IF SELF:IsOpen
                isOK := SELF:_DetermineCodePage()
                IF FSize(SELF:_hFile) < Int32.MaxValue
                    FConvertToMemoryStream(SELF:_hFile)
                ENDIF
                SELF:_prepareFields()
                SELF:_Reccount := SELF:_getLastRec()
                SELF:GoTop()
            ELSE
                // Error or just FALSE ?
                isOK := FALSE
                LOCAL ex := FException() AS Exception
                SELF:_txtError( ex, ERDD.OPEN_FILE, XSharp.Gencode.EG_OPEN )
            ENDIF
            RETURN isOK

            /// <inheritdoc />
        METHOD Flush() 			AS LOGIC
            RETURN FFlush(SELF:_hFile)

            /// <inheritdoc />
        METHOD GoCold()			AS LOGIC
            IF SELF:_Hot
                SELF:_writeRecord()
                SELF:_Hot := FALSE
            ENDIF
            RETURN TRUE

            /// <inheritdoc />
        METHOD GetValue(nFldPos AS INT) AS OBJECT
            // Subclass fills the _fieldData list
            IF SELF:_readRecord()
                if nFldPos <= _fieldData:Length
                    return _fieldData[nFldPos -1]
                endif
            endif
            RETURN NULL

            /// <inheritdoc />
        METHOD PutValue(nFldPos AS INT, oValue AS OBJECT) AS LOGIC
            // Subclass persists the _fieldData list
            IF nFldPos <= _fieldData:Length
                SELF:_fieldData[nFldPos-1] := oValue
                SELF:_Hot := TRUE
            ENDIF
            RETURN TRUE

            /// <inheritdoc />
        METHOD Info(nOrdinal AS INT, oNewValue AS OBJECT) AS OBJECT
            SWITCH nOrdinal
                CASE DBI_ISDBF
                    return FALSE
                CASE DBI_GETHEADERSIZE
                    return 0
                case DBI_FILEHANDLE
                    return SELF:_hFile
                CASE DBI_FULLPATH
                    return SELF:_FileName
                CASE DBI_SETDELIMITER                   // Field delimiter
                    VAR result := SELF:FieldSeparator
                    IF oNewValue IS STRING
                        SELF:FieldSeparator := (STRING) oNewValue
                    ENDIF
                    RETURN result
                case DBI_SEPARATOR
                    VAR result := SELF:RecordSeparator
                    IF oNewValue IS STRING
                        SELF:RecordSeparator := (STRING) oNewValue
                    ENDIF
                    RETURN result

                case DBI_GETDELIMITER
                    return SELF:_Delimiter
                CASE DBI_CANPUTREC
                    RETURN FALSE
            END SWITCH
            return SUPER:Info(nOrdinal, oNewValue)

            // Properties

            /// <inheritdoc />
        PROPERTY Deleted 	AS LOGIC GET 	FALSE
        /// <inheritdoc />
        PROPERTY RecCount	AS LONG GET _Reccount
        /// <inheritdoc />
        PROPERTY RecId		AS OBJECT GET  _Recno
        /// <inheritdoc />
        PROPERTY RecNo		AS LONG 	GET _Recno
        /// <inheritdoc />
        VIRTUAL PROPERTY Driver AS STRING GET "TEXTRDD"

        INTERNAL METHOD _txtError(ex AS Exception, iSubCode AS DWORD, iGenCode AS DWORD) AS VOID
            SELF:_txtError(ex, iSubCode, iGenCode, String.Empty, ex?:Message, XSharp.Severity.ES_ERROR)

        INTERNAL METHOD _txtError(iSubCode AS DWORD, iGenCode AS DWORD) AS VOID
            SELF:_txtError(NULL, iSubCode, iGenCode, String.Empty, String.Empty, XSharp.Severity.ES_ERROR)

        INTERNAL METHOD _txtError(ex AS Exception,iSubCode AS DWORD, iGenCode AS DWORD, iSeverity AS DWORD) AS VOID
            SELF:_txtError(ex, iSubCode, iGenCode, String.Empty, String.Empty, iSeverity)

        INTERNAL METHOD _txtError(iSubCode AS DWORD, iGenCode AS DWORD, iSeverity AS DWORD) AS VOID
            SELF:_txtError(NULL, iSubCode, iGenCode, String.Empty, String.Empty, iSeverity)

        INTERNAL METHOD _txtError(iSubCode AS DWORD, iGenCode AS DWORD, strFunction AS STRING) AS VOID
            SELF:_txtError(NULL, iSubCode, iGenCode, strFunction, String.Empty, XSharp.Severity.ES_ERROR)

        INTERNAL METHOD _txtError(ex AS Exception, iSubCode AS DWORD, iGenCode AS DWORD, strFunction AS STRING) AS VOID
            SELF:_txtError(ex, iSubCode, iGenCode, strFunction, String.Empty, XSharp.Severity.ES_ERROR)

        INTERNAL METHOD _txtError(iSubCode AS DWORD, iGenCode AS DWORD, strFunction AS STRING, strMessage AS STRING) AS VOID
            SELF:_txtError(NULL, iSubCode, iGenCode, strFunction,strMessage, XSharp.Severity.ES_ERROR)

        INTERNAL METHOD _txtError(ex AS Exception, iGenCode AS DWORD, iSubCode AS DWORD, strFunction AS STRING, strMessage AS STRING, iSeverity AS DWORD) AS VOID
            LOCAL oError AS RddError
            //
            IF ex != NULL
                oError := RddError{ex,iGenCode, iSubCode}
            ELSE
                oError := RddError{iGenCode, iSubCode}
            ENDIF
            oError:SubSystem := SELF:Driver
            oError:Severity := iSeverity
            oError:FuncSym  := IIF(strFunction == NULL, "", strFunction) // code in the SDK expects all string properties to be non-NULL
            oError:FileName := SELF:_FileName
            IF String.IsNullOrEmpty(strMessage)  .AND. ex != NULL
                strMessage := ex:Message
            ENDIF
            oError:Description := IIF(strMessage == NULL , "", strMessage)
            RuntimeState.LastRddError := oError
            //
            THROW oError

    END CLASS
END NAMESPACE