[Advnatage] Added support for AppenDb() and CopyDb()

//
// 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.IO
USING System.Collections.Generic
USING System.Text
USING XSharp.RDD
USING XSharp.RDD.Enums
USING XSharp.RDD.Support
USING System.Diagnostics

/// <summary>
/// The AdsRDD class.
/// </summary>
[DebuggerDisplay("AdsRDD ({Alias,nq})")];
CLASS XSharp.ADS.AdsRDD INHERIT Workarea
  #region Fields
    PRIVATE  _aRlocks AS DWORD[]
    INTERNAL _Encoding AS System.Text.Encoding
    INTERNAL _Connection AS System.IntPtr
    INTERNAL _Index AS System.IntPtr
    INTERNAL _Table AS System.IntPtr
    INTERNAL _Collation AS STRING
    INTERNAL _Driver AS STRING
    INTERNAL _CheckRights AS WORD
    INTERNAL _LockType AS WORD
    INTERNAL _TableType AS WORD
    INTERNAL _MaxKeySize AS WORD
    INTERNAL _Ansi  AS LOGIC
    INTERNAL _HasMemo AS LOGIC
    INTERNAL _fieldCount AS LONG

  #endregion

  /// <summary>Create instande of RDD </summary>
CONSTRUCTOR()
    SUPER()
    SELF:_Order         := AdsIndex{SELF}
    SELF:_Memo          := AdsMemo{SELF}
    SELF:_Table         := System.IntPtr.Zero
    SELF:_Index         := System.IntPtr.Zero
    SELF:_Connection    := System.IntPtr.Zero
    SELF:_Driver        := String.Empty
    SELF:_Collation     := String.Empty
    SELF:_Driver        := "ADSRDD"
    SELF:_fileName       := String.Empty

    #region Helper Methods that check for error conditions
INTERNAL STATIC METHOD _HasFlag(dw AS DWORD, flag AS DWORD) AS LOGIC
RETURN _AND(dw, flag) == flag

INTERNAL METHOD _CheckError(ulRetCode AS DWORD, dwGenCode := EG_NOCLASS AS DWORD, strFunction := "" AS STRING) AS LOGIC
    IF ulRetCode != 0
        IF String.IsNullOrEmpty(strFunction)
            strFunction := ProcName(1)
        ENDIF
        SELF:ADSERROR( ulRetCode, dwGenCode, strFunction)
        RETURN FALSE
    ENDIF
RETURN TRUE

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

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

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

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

INTERNAL METHOD ADSERROR(iSubCode AS DWORD, iGenCode AS DWORD, strFunction AS STRING, strMessage AS STRING, iSeverity AS DWORD) AS VOID
    LOCAL lastError AS DWORD
    LOCAL message AS CHAR[]
    LOCAL wBufLen AS WORD
    LOCAL oError AS RddError
    IF String.IsNullOrEmpty(strMessage)
      //
        message := CHAR[]{ACE.ADS_MAX_ERROR_LEN}
        wBufLen := (WORD) message:Length
        IF ACE.AdsGetLastError(OUT lastError, message, REF wBufLen) == 0 .AND. lastError != 0 .AND. wBufLen > 0
            strMessage := STRING{message, 0, wBufLen}
        ENDIF
    ENDIF
    IF String.IsNullOrEmpty(strMessage) .AND. iSubCode != 0
        strMessage := ErrString(iGenCode)
    ENDIF
    oError   := AdsError{strMessage, iGenCode, iSubcode,_Driver, iSeverity, strFunction, _FileName}
    RuntimeState.LastRDDError := oError
    THROW oError

    #endregion
  #region Helper Methods

INTERNAL METHOD _SynchronizeVODeletedFlag() AS DWORD
    RETURN ACE.AdsShowDeleted(IIF(RuntimeState.Deleted,(WORD)0 ,(WORD)1 ))

INTERNAL METHOD _SynchronizeSettings() AS VOID
    ACE.AdsSetDateFormat(RuntimeState.DateFormat)
    ACE.AdsSetExact((WORD) IIF(RuntimeState.Exact,1 , 0 ))
    ACE.AdsSetDecimals((WORD)RuntimeState.Decimals )
    ACE.AdsSetEpoch((WORD)RuntimeState.Epoch )
RETURN

INTERNAL METHOD _CheckRDDInfo() AS VOID
    LOCAL oRet := NULL_OBJECT AS OBJECT
    IF AX_AXSLocking()
        SELF:_LockType := ACE.ADS_PROPRIETARY_LOCKING
    ELSE
        SELF:_LockType := ACE.ADS_COMPATIBLE_LOCKING
    ENDIF
    IF AX_RightsCheck()
        SELF:_CheckRights := ACE.ADS_CHECKRIGHTS
    ELSE
        SELF:_CheckRights := ACE.ADS_IGNORERIGHTS
    ENDIF
    IF CoreDb.RddInfo(_SET_CONNECTION_HANDLE, REF oRet) .AND. oRet != NULL_OBJECT
        IF oRet IS IntPtr VAR p
            SELF:_Connection := p
        ELSEIF oRet IS DWORD VAR d
            SELF:_Connection := IntPtr{d}
        ELSE
            SELF:_Connection := IntPtr.Zero
        ENDIF
    ELSE
        SELF:_Connection := IntPtr.Zero
    ENDIF
    IF SELF:_TableType == ACE.ADS_ADT .OR. SELF:_TableType == ACE.ADS_VFP
        oRet := NULL
        IF CoreDb.RddInfo(_SET_COLLATION_NAME, REF oRet) .AND. oRet != NULL
            SELF:_Collation := (STRING) oRet
        ELSE
            SELF:_Collation := string.Empty
        ENDIF
    ENDIF
RETURN

INTERNAL METHOD _Ansi2Unicode(source AS CHAR[], iLength AS LONG) AS STRING
    TRY
        VAR bytes := SELF:_Encoding:GetBytes(source, 0, iLength)
        RETURN SELF:_Encoding:GetString(bytes)
    CATCH
        RETURN String.Empty
    END TRY

    #endregion

  #region Open and Close
PROTECTED METHOD _FieldSub() AS LOGIC
    LOCAL num AS DWORD
    LOCAL sb AS CHAR[]
    LOCAL wLen AS WORD
    LOCAL fi AS RddFieldInfo
    LOCAL wDecimals AS WORD
    LOCAL wFields AS WORD
    SELF:_CheckError(ACE.AdsGetNumFields(SELF:_Table, OUT wFields),EG_OPEN)
    IF !SELF:SetFieldExtent(wFields)
        RETURN FALSE
    ENDIF
    _fieldCount := wFields
    sb := CHAR[]{ACE.ADS_MAX_FIELD_NAME+1}
    FOR num := 1u TO wFields STEP 1
        LOCAL cName AS STRING
        SELF:_CheckError(ACE.AdsGetFieldType(SELF:_Table, num, OUT VAR usType),EG_OPEN)
        SELF:_CheckError(ACE.AdsGetFieldLength(SELF:_Table, num, OUT VAR ulLength),EG_OPEN)
        wLen := (WORD) sb:Length
        SELF:_CheckError(ACE.AdsGetFieldName(SELF:_Table, (WORD)num, sb, REF wLen),EG_OPEN)
        cName := STRING{sb,0, wLen}
        fi := RddFieldInfo{cName,DbFieldType.Character,1,0}
        fi:Length := (INT) ulLength
        LOCAL eType := (AdsFieldType) usType AS AdsFieldType
        SWITCH eType
        CASE AdsFieldType.TIMESTAMP
        CASE AdsFieldType.MODTIME
            fi:Length := 22
            fi:fieldType := DbFieldType.Character
        CASE AdsFieldType.TIME
            fi:Length := 11
            fi:fieldType := DbFieldType.Character
        CASE AdsFieldType.MONEY
        CASE AdsFieldType.ROWVERSION
            fi:Length := 22
            fi:fieldType := DbFieldType.Character
        CASE AdsFieldType.STRING
        CASE AdsFieldType.VARCHAR
        CASE AdsFieldType.CISTRING
        CASE AdsFieldType.VARCHAR_FOX
        CASE AdsFieldType.NCHAR
        CASE AdsFieldType.NVARCHAR
            fi:fieldType := DbFieldType.Character
        CASE AdsFieldType.MEMO
        CASE AdsFieldType.BINARY
        CASE AdsFieldType.IMAGE
        CASE AdsFieldType.NMEMO
            fi:fieldType := DbFieldType.Memo
            fi:Length := 10
        CASE AdsFieldType.RAW
        CASE AdsFieldType.VARBINARY_FOX
            fi:fieldType := DbFieldType.Memo
        CASE AdsFieldType.SHORTINT
        CASE AdsFieldType.AUTOINC
        CASE AdsFieldType.INTEGER
        CASE AdsFieldType.NUMERIC
            fi:fieldType := DbFieldType.Number
            SWITCH eType
            CASE AdsFieldType.SHORTINT
                fi:Length := 6
            CASE AdsFieldType.AUTOINC
                fi:Length := 10
            CASE AdsFieldType.INTEGER
                fi:Length := 11
            OTHERWISE // Numeric
                fi:Length := (INT) ulLength
            END SWITCH
            SELF:_CheckError(ACE.AdsGetFieldDecimals(SELF:_Table, num, OUT wDecimals),EG_OPEN)
            fi:Decimals := wDecimals
        CASE AdsFieldType.LOGICAL
            fi:fieldType := DbFieldType.Logic
        CASE AdsFieldType.DATE
        CASE AdsFieldType.COMPACTDATE
            fi:fieldType := DbFieldType.Date
            fi:Length := 8
        CASE AdsFieldType.DOUBLE
        CASE AdsFieldType.CURDOUBLE
            fi:Length := 20
            fi:fieldType := DbFieldType.Number
            SELF:_CheckError(ACE.AdsGetFieldDecimals(SELF:_Table, num, OUT wDecimals),EG_OPEN)
            fi:Decimals := wDecimals
        OTHERWISE
            SELF:ADSERROR(ERDD_CORRUPT_HEADER, EG_DATATYPE)
            RETURN FALSE
        END SWITCH
        fi:Alias := NULL
        LOCAL oCol AS AdsColumn
        oCol := AdsColumn.Create(fi, SELF, eType, num)
        IF ! SELF:AddField(oCol)
            RETURN FALSE
        ENDIF
    NEXT
RETURN TRUE

PRIVATE METHOD _GetFieldInfo(strFieldDef REF STRING ) AS LOGIC
    strFieldDef := ""
    FOREACH fld AS RDDFieldInfo IN SUPER:_Fields
        strFieldDef += fld:Name+", "
        SWITCH fld:FieldType
        CASE DbFieldType.Character
            strFieldDef += "C,"
          // allow clipper field lengths
            strFieldDef += (fld:Length + fld:Decimals*256):ToString()+";"
        CASE DbFieldType.Number
        CASE DbFieldType.Integer
            strFieldDef += "N,"+fld:Length:ToString()+", "+fld:Decimals:ToString()+";"
        CASE DbFieldType.Date
            strFieldDef += "D;"
        CASE DbFieldType.Logic
            strFieldDef += "L;"
        CASE DbFieldType.Memo
            IF fld:Length == 23 .AND. fld:Decimals == 4
                strFieldDef += "Money;"
            ELSE
                _HasMemo := TRUE
                strFieldDef += "M;"
            ENDIF
        OTHERWISE
            SELF:ADSERROR(ERDD_CORRUPT, EG_CORRUPTION)
            RETURN FALSE
        END SWITCH
    NEXT
RETURN TRUE

INTERNAL METHOD _SetPaths() AS DWORD
    IF !string.IsNullOrEmpty(SetDefault())
        SELF:_CheckError(ACE.AdsSetDefault(SetDefault()),EG_OPEN)
    ENDIF
    IF !string.IsNullOrEmpty(SetPath())
        SELF:_CheckError(ACE.AdsSetSearchPath(SetPath()),EG_OPEN)
    ENDIF
RETURN 0u

    /// <inheritdoc />
VIRTUAL METHOD Open(info AS DbOpenInfo) AS LOGIC
    LOCAL openmode AS WORD
    LOCAL alias AS STRING
    LOCAL charset AS WORD
    LOCAL usTableType AS WORD
    LOCAL fileName AS STRING
    //
    openmode := 0
    SELF:_CheckRDDInfo()
    alias := Path.GetFileNameWithoutExtension(info:Alias)
    // both Clipper and XPP use weight tables
    IF RuntimeState.CollationMode == CollationMode.Clipper .OR. RuntimeState.CollationMode == CollationMode.Xpp
        charset := ACE.ADS_OEM
    ELSE
        charset := ACE.ADS_ANSI
    ENDIF
    IF info:ReadOnly
        openmode |= (WORD) ACE.ADS_READONLY
    ENDIF
    IF !info:Shared
        openmode |= (WORD) ACE.ADS_EXCLUSIVE
    ENDIF
    IF SELF:_SetPaths() != 0
        RETURN FALSE
    ENDIF
    IF SELF:_Connection != IntPtr.Zero
        usTableType := ACE.ADS_DATABASE_TABLE
        fileName := Path.GetFileNameWithoutExtension(info:FileName)
    ELSE
        usTableType := SELF:_TableType
        fileName := info:FileName + info:Extension
    ENDIF
    IF alias:Length > 10
        alias := string.Empty
    ENDIF
    IF SELF:_TableType == ACE.ADS_ADT .AND. Path.GetExtension(fileName):ToUpper() == ".DBF"
        fileName := Path.ChangeExtension(fileName, ".ADT")
    ENDIF
    LOCAL result AS DWORD
    LOCAL tries := 0 AS LONG
    REPEAT
        // wait 100 ms before retrying
        IF tries > 0
            System.Threading.Thread.Sleep(100)
        ENDIF
        tries += 1

        result := ACE.AdsOpenTable90(SELF:_Connection, fileName, alias, usTableType, charset, SELF:_LockType, SELF:_CheckRights, openmode,SELF:_Collation, OUT SELF:_Table)
        IF result != 0 .AND. SELF:_Connection != IntPtr.Zero
            usTableType := SELF:_TableType
            IF fileName[0] != '#'
                fileName := info:FileName + info:Extension
                IF SELF:_TableType == ACE.ADS_ADT .AND. Path.GetExtension(fileName):ToUpper() == ".DBF"
                    fileName := Path.ChangeExtension(fileName, ".ADT")
                ENDIF
            ENDIF
            result := ACE.AdsOpenTable90(SELF:_Connection, fileName, alias, usTableType, charset, SELF:_LockType, SELF:_CheckRights, openmode, SELF:_Collation, OUT SELF:_Table)
        ENDIF
    UNTIL tries == 10 .OR. SELF:_Table != IntPtr.Zero
    IF result != 0
        NetErr(TRUE)
        SELF:Close()
        SELF:_FileName := fileName
        SELF:ADSERROR(ERDD_OPEN_FILE, EG_OPEN, "Open")
        RETURN FALSE
    ENDIF
    IF !SELF:_FieldSub()
        SELF:Close()
        RETURN FALSE
    ENDIF
    LOCAL length := MAX_PATH AS WORD
    LOCAL afileName AS CHAR[]
    afileName := CHAR[]{length}
    SELF:_CheckError(ACE.AdsGetTableFilename(SELF:_Table, ACE.ADS_FULLPATHNAME, afileName, REF length),EG_OPEN)
    SELF:_FileName := STRING {aFileName,0, length}
    IF result != 0
        SELF:Close()
        RETURN FALSE
    ENDIF
    SELF:_CheckError(ACE.AdsGetNumIndexes(SELF:_Table, OUT VAR numIndexes),EG_OPEN)
    IF numIndexes > 0
        SELF:_CheckError(ACE.AdsGetIndexHandleByOrder(SELF:_Table, 1, OUT SELF:_Index))
    ENDIF
    SELF:_Encoding := Encoding.GetEncoding(IIF (charset == ACE.ADS_ANSI, RuntimeState.WinCodePage,RuntimeState.DosCodePage))
    IF ACE.AdsGetMemoBlockSize(SELF:_Table, OUT VAR blockSize) == 0 .AND. blockSize > 0
        _HasMemo := TRUE
    ELSE
        _HasMemo := FALSE
    ENDIF
    SELF:_CheckError(ACE.AdsGetRecordLength(SELF:_Table, OUT VAR dwLength),EG_OPEN)
    SELF:_RecordLength := (LONG) dwLength
    SELF:Alias   := info:Alias
    SELF:Area    := info:WorkArea
RETURN SELF:RecordMovement()

    /// <inheritdoc />
VIRTUAL METHOD Close() AS LOGIC
    LOCAL result AS LOGIC
    result := SUPER:Close()
    IF SELF:_Table != IntPtr.Zero
        SELF:_CheckError(ACE.AdsCloseTable(SELF:_Table),EG_CLOSE)
        SELF:_Table := IntPtr.Zero
    ENDIF
    SELF:_Index := IntPtr.Zero
    SELF:_CheckRights := 0
    SELF:_LockType := 0
    SELF:_TableType := 0
    SELF:Area  := 0
RETURN result

    /// <inheritdoc />
VIRTUAL METHOD Create(info AS DbOpenInfo) AS LOGIC
    LOCAL strFieldDef AS STRING
    LOCAL charset AS WORD
    LOCAL alias AS STRING
    LOCAL length AS WORD
    //
    strFieldDef     := string.Empty
    SELF:_Alias     := info:Alias
    SELF:_Area      := info:Workarea
    SELF:_Shared    := info:Shared
    SELF:_ReadOnly  := info:ReadOnly
    SELF:_Ansi      := RuntimeState.Ansi

    IF !SELF:_GetFieldInfo(REF strFieldDef)
        RETURN FALSE
    ENDIF
    SELF:_CheckRDDInfo()
    IF SELF:_SetPaths() != 0
        RETURN FALSE
    ENDIF
    // both Clipper and XPP use weight tables
    IF RuntimeState.CollationMode == CollationMode.Clipper .OR. RuntimeState.CollationMode == CollationMode.Xpp
        charset := ACE.ADS_OEM
    ELSE
        charset := ACE.ADS_ANSI
    ENDIF
    alias := Path.GetFileNameWithoutExtension(info:Alias)
    IF alias:Length > 10
        alias := string.Empty
    ENDIF
    SELF:_FileName := info:FileName
    SELF:_CheckError(ACE.AdsCreateTable90(SELF:_Connection, info:FileName, alias, SELF:_TableType, charset, SELF:_LockType, SELF:_CheckRights, 0, strFieldDef, 0u, SELF:_Collation, OUT SELF:_Table),EG_CREATE)
    SELF:_Encoding := IIF (charset== ACE.ADS_ANSI, StringHelpers.WinEncoding, StringHelpers.DosEncoding)
    length := MAX_PATH
    LOCAL fileName AS CHAR[]
    fileName := CHAR[]{length}
    SELF:_CheckError(ACE.AdsGetTableFilename(SELF:_Table, ACE.ADS_FULLPATHNAME , fileName, REF length),EG_CREATE)
    SELF:_FileName := STRING{fileName,0, length}
    SELF:_CheckError(ACE.AdsGetRecordLength(SELF:_Table, OUT VAR dwLength),EG_CREATE)
    SELF:_RecordLength := (LONG) dwLength
    // Workarea has no implementation of Create
    //    IF !SUPER:Create(info)
    //      SELF:Close()
    //      RETURN FALSE
    //    ENDIF
RETURN SELF:RecordMovement()

    // Check if a Field definition is correct :
    // Date Length must be 8, Number are long enough to store Dot and Decs (if any), ...
PROTECT OVERRIDE METHOD _checkFields(info AS RddFieldInfo) AS LOGIC
    // FieldName
    info:Name := info:Name:ToUpper():Trim()
    IF info:Name:Length > 10
        info:Name := info:Name:Substring(0,10)
    ENDIF
    IF ! info:Validate()
        SELF:ADSERROR(ERDD.CREATE_FILE, XSharp.Gencode.EG_ARG )
    ENDIF
    RETURN TRUE

VIRTUAL METHOD AddField(info AS RddFieldInfo) AS LOGIC
    LOCAL isOk AS LOGIC
    isOk := SUPER:AddField(info)
    IF isOk  .AND. info:FieldType == DbFieldType.Memo
        SELF:_HasMemo := TRUE
    ENDIF
RETURN isOk

    #endregion
  #region Navigation
METHOD RecordMovement() AS LOGIC
    SELF:_CheckError(ACE.AdsAtBOF(SELF:_Table, OUT VAR atBOF),EG_READ)
    SUPER:_Bof := (atBOF == 1)
    SELF:_CheckError(ACE.AdsAtEOF(SELF:_Table, OUT VAR atEOF),EG_READ)
    SUPER:_Eof := (atEOF == 1)
    SELF:_CheckError(ACE.AdsIsFound(SELF:_Table,  OUT VAR isFound),EG_READ)
    SUPER:_Found := (isFound == 1)
    IF atBOF == 1 .AND. atEOF == 0
        // Do not call self:GoTop() to avoid recursion
        SELF:_CheckError(ACE.AdsGotoTop(SELF:CurrentOrder),EG_READ)
        SUPER:_Bof := TRUE
    ENDIF
    IF SUPER:_Relations != NULL
        FOREACH dbrelinfo AS DbRelInfo IN SUPER:_Relations
            IF dbRelInfo:Child IS AdsRDD VAR ChildRDD
                ChildRDD:RecordMovement()
            ENDIF
        NEXT
    ENDIF
RETURN TRUE

    /// <inheritdoc />
VIRTUAL METHOD GoBottom() AS LOGIC
    SELF:_SynchronizeVODeletedFlag()
    SELF:_CheckError(ACE.AdsGotoBottom(SELF:CurrentOrder),EG_READ)
RETURN SELF:RecordMovement()

    /// <inheritdoc />
VIRTUAL METHOD GoTop() AS LOGIC
    SELF:_SynchronizeVODeletedFlag()
    SELF:_CheckError(ACE.AdsGotoTop(SELF:CurrentOrder),EG_READ)
RETURN SELF:RecordMovement()

    /// <inheritdoc />
VIRTUAL METHOD GoTo(lRec AS DWORD) AS LOGIC
    SELF:_CheckError(ACE.AdsGetRecordNum(SELF:_Table, ACE.ADS_IGNOREFILTERS, OUT VAR recordnum),EG_READ)
    IF recordnum == lRec
        SELF:_CheckError(ACE.AdsAtEOF(SELF:_Table, OUT VAR atEOF),EG_READ)
        SELF:_CheckError(ACE.AdsAtBOF(SELF:_Table, OUT VAR atBOF),EG_READ)
        IF atEOF== 0 .AND. atBOF == 0
            SELF:_CheckError(ACE.AdsWriteRecord(SELF:_Table),EG_WRITE)
            SELF:_CheckError(ACE.AdsRefreshRecord(SELF:_Table),EG_READ)
        ENDIF
    ELSE
        SELF:_CheckError(ACE.AdsGotoRecord(SELF:_Table, lRec),EG_READ)
    ENDIF
RETURN SELF:RecordMovement()

    /// <inheritdoc />
VIRTUAL METHOD GoToId(oRecnum AS OBJECT) AS LOGIC
    LOCAL recNum AS DWORD
    TRY
        recNum := System.Convert.ToUInt32(oRecnum)
    CATCH e AS Exception
        SELF:ADSERROR(ERDD.DATATYPE, XSharp.Gencode.EG_DataType, "GoToId",e:Message)
        RETURN FALSE
    END TRY
RETURN SELF:GoTo(recNum)

    /// <inheritdoc />
VIRTUAL METHOD Skip(lCount AS LONG) AS LOGIC
    LOCAL result AS DWORD
    LOCAL flag AS LOGIC
    SELF:_SynchronizeVODeletedFlag()
    IF lCount == 0
        SELF:_CheckError(ACE.AdsWriteRecord(SELF:_Table),EG_WRITE)
        result := ACE.AdsRefreshRecord(SELF:_Table)
    ELSE
        result := ACE.AdsSkip(SELF:CurrentOrder, lCount)
    ENDIF
    IF result != ACE.AE_NO_CURRENT_RECORD .AND. result != 0
        SELF:_CheckError(result,EG_READ)
    ENDIF
    flag := SELF:RecordMovement()
    IF lCount > 0
        SUPER:_Bof := FALSE
        RETURN flag
    ENDIF
    IF lCount < 0
        SUPER:_Eof := FALSE
    ENDIF
RETURN flag

    /// <inheritdoc />
VIRTUAL METHOD SkipFilter(lCount AS LONG) AS LOGIC
    // ADS has no difference with normal skip
RETURN SELF:Skip(lCount)

    /// <inheritdoc />
VIRTUAL METHOD SkipRaw(lCount AS LONG) AS LOGIC
    // ADS has no difference with normal skip
RETURN SELF:Skip(lCount)

    /// <inheritdoc />

INTERNAL METHOD _GetScope(hIndex AS System.IntPtr, usScopeOption AS WORD) AS OBJECT
    LOCAL aScope    AS CHAR[]
    LOCAL wBufLen   AS WORD
    LOCAL result AS DWORD
    aScope    := CHAR[]{(SELF:_MaxKeySize + 1)}
    wBufLen   := (WORD)(SELF:_MaxKeySize + 1)
    result := ACE.AdsGetScope(hIndex, usScopeOption, aScope, REF wBufLen)
    IF result == 0
        RETURN SELF:_Ansi2Unicode(aScope, wBufLen)
    ENDIF
    IF result != ACE.AE_NO_SCOPE
        SELF:_CheckError(result,EG_READ)
    ENDIF
    RETURN NULL

INTERNAL METHOD _SetScope(hIndex AS System.IntPtr, usScopeOption AS WORD, lClear AS LOGIC, oScope AS OBJECT ) AS OBJECT
    LOCAL oldVal AS OBJECT
    LOCAL result AS DWORD
    oldVal    := SELF:_GetScope(hIndex, usScopeOption)
    IF lClear .OR. oScope == NULL
        result := ACE.AdsClearScope(hIndex, usScopeOption)
    ELSE
        VAR str := oScope:ToString()
        IF !String.IsNullOrEmpty(str)
            result := ACE.AdsSetScope(hIndex, usScopeOption, str, (WORD)str:Length , ACE.ADS_STRINGKEY)
            SELF:_CheckError(result,EG_READ)
        ELSE
            result := ACE.AdsClearScope(hIndex, usScopeOption)
        ENDIF
    ENDIF
    RETURN oldVal

    #endregion
  #region Updates

  /// <inheritdoc />
VIRTUAL METHOD Flush() AS LOGIC
RETURN SELF:GoCold()

  /// <inheritdoc />
VIRTUAL METHOD Refresh() AS LOGIC
    SELF:_CheckError(ACE.AdsRefreshRecord(SELF:_Table),EG_READ)
RETURN SELF:RecordMovement()

    /// <inheritdoc />
VIRTUAL METHOD GoCold() AS LOGIC
    SELF:_CheckError(ACE.AdsWriteRecord(SELF:_Table),EG_READ)
RETURN SELF:RecordMovement()

    /// <inheritdoc />
VIRTUAL METHOD GoHot() AS LOGIC
    LOCAL dwRecNo AS DWORD
    LOCAL result AS DWORD
    //
    SELF:_CheckError(ACE.AdsGetTableOpenOptions(SELF:_Table, OUT VAR options),EG_WRITE)
    // GoHot must have lock when not exclusive
    IF !_HasFlag(options, ACE.ADS_EXCLUSIVE)
      // Only allowed when not Readonly
        IF _HasFlag(options,ACE.ADS_READONLY)
            SELF:ADSERROR(ERDD.READONLY, XSharp.Gencode.EG_READONLY)
        ENDIF
        SELF:_CheckError(ACE.AdsIsTableLocked(SELF:_Table, OUT VAR isTableLocked),EG_LOCK)
        IF isTableLocked == 0
            SELF:_CheckError(ACE.AdsIsRecordLocked(SELF:_Table, 0, OUT VAR isRecordLocked),EG_LOCK)
            IF isRecordLocked == 0
                dwRecNo := 0
                IF SELF:IsADT
                    result := ACE.AdsGetRecordCount(SELF:_Table, ACE.ADS_IGNOREFILTERS, OUT VAR numRecs)
                    IF result == 0
                        result := ACE.AdsGetRecordNum(SELF:_Table, ACE.ADS_IGNOREFILTERS, OUT dwRecNo)
                    ENDIF
                    IF result == 0 .AND. numRecs < dwRecNo .AND. ! SUPER:_Bof .AND. ! SUPER:_Eof
                        result := ACE.AdsLockRecord(SELF:_Table, 0)
                        IF result == 0 .OR. result == ACE.AE_TABLE_NOT_SHARED
                            RETURN TRUE
                        ENDIF
                        SELF:_CheckError(result,EG_LOCK)
                    ENDIF
                ENDIF
                SELF:ADSERROR(ERDD.UNLOCKED, XSharp.Gencode.EG_LOCK, XSharp.Severity.ES_ERROR)
            ENDIF
        ENDIF
    ENDIF
RETURN TRUE

    /// <inheritdoc />
VIRTUAL METHOD Zap() AS LOGIC
    SELF:_CheckError(ACE.AdsGetTableOpenOptions(SELF:_Table, OUT VAR options),EG_WRITE)
    // Only allowed when opened exclusively
    IF !_HasFlag(options, ACE.ADS_EXCLUSIVE)
        SELF:ADSERROR(ACE.AE_TABLE_NOT_EXCLUSIVE, EG_SHARED , "Zap")
    ENDIF
    // Only allowed when not Readonly
    IF _HasFlag(options, ACE.ADS_READONLY)
        SELF:ADSERROR(ERDD.READONLY, EG_READONLY, "Zap")
    ENDIF
    SELF:_CheckError(ACE.AdsZapTable(SELF:_Table),EG_WRITE)
RETURN SELF:GoTop()

    /// <inheritdoc />
VIRTUAL METHOD Append(fReleaseLocks AS LOGIC) AS LOGIC
    LOCAL result        AS DWORD
    IF fReleaseLocks
        SELF:_CheckError(ACE.AdsGetHandleType(SELF:_Table, OUT VAR handleType),EG_APPENDLOCK)
        IF handleType != ACE.ADS_CURSOR
            result := ACE.AdsIsTableLocked(SELF:_Table, OUT VAR isTableLocked)
            IF isTableLocked == 0
                result := ACE.AdsUnlockTable(SELF:_Table)
          // When Unlock fails because not shared or because not locked, then no problem
                IF result != ACE.AE_TABLE_NOT_SHARED .AND. result != ACE.AE_TABLE_NOT_LOCKED  .AND. result != 0
                    SELF:_CheckError(result,EG_SHARED)
                ENDIF
            ENDIF
        ENDIF
    ENDIF
    SELF:_CheckError(ACE.AdsAppendRecord(SELF:_Table),EG_APPENDLOCK)
    result := ACE.AdsLockRecord(SELF:_Table, 0)
    IF result != ACE.AE_TABLE_NOT_SHARED .AND. result != 0
        SELF:_CheckError(result,EG_SHARED)
    ENDIF
RETURN SELF:RecordMovement()

VIRTUAL METHOD Delete() AS LOGIC
    SELF:_CheckError(ACE.AdsDeleteRecord(SELF:_Table),EG_WRITE)
RETURN TRUE

VIRTUAL METHOD Recall() AS LOGIC
    SELF:_CheckError(ACE.AdsRecallRecord(SELF:_Table),EG_WRITE)
RETURN SELF:RecordMovement()

    #endregion

  #region Read and Write
METHOD GetValue(nFldPos AS INT) AS OBJECT
    VAR column   := (AdsColumn ) SELF:_Fields[nFldPos-1]
    RETURN column:GetValue()

METHOD Pack () AS LOGIC
    SELF:_CheckError(ACE.AdsGetTableOpenOptions(SELF:_Table, OUT VAR options),EG_READ)
    IF !_HasFlag(options, ACE.ADS_EXCLUSIVE)
        SELF:ADSERROR(ACE.AE_TABLE_NOT_EXCLUSIVE, EG_SHARED, "Pack")
        RETURN FALSE
    ENDIF
    IF _HasFlag(options , ACE.ADS_READONLY)
        SELF:ADSERROR(ERDD_READONLY, EG_READONLY, "Pack")
        RETURN FALSE
    ENDIF
    SELF:_CheckError(ACE.AdsPackTable(SELF:_Table),EG_WRITE)
RETURN SELF:GoTop()

METHOD PutValue(nFldPos AS INT, oValue AS OBJECT) AS LOGIC
    IF ! SELF:GoHot()
        RETURN FALSE
    ENDIF
    VAR column := (AdsColumn) SELF:_Fields[nFldPos-1]
    RETURN column:PutValue(oValue)
    #endregion

  #region Properties
PROPERTY ACEConnectionHandle    AS IntPtr GET _Connection
PROPERTY ACEIndexHandle         AS IntPtr GET _Index
PROPERTY ACETableHandle         AS IntPtr GET _Table
PRIVATE PROPERTY CurrentOrder AS System.IntPtr
    GET
        IF SELF:_Index != System.IntPtr.Zero
            RETURN SELF:_Index
        ENDIF
        RETURN SELF:_Table
    END GET
END PROPERTY

  /// <inheritdoc />
PROPERTY BOF AS LOGIC GET SUPER:_Bof
  /// <inheritdoc />
PROPERTY EOF AS LOGIC GET SUPER:_Eof
  /// <inheritdoc />
PROPERTY Found AS LOGIC GET SUPER:_Found
INTERNAL PROPERTY IsADT AS LOGIC GET _TableType == ACE.ADS_ADT
VIRTUAL PROPERTY Driver AS STRING GET _Driver

      /// <inheritdoc />
PROPERTY Deleted AS LOGIC
    GET
        LOCAL result AS DWORD
        result := ACE.AdsIsRecordDeleted(SELF:_Table, OUT VAR isDeleted)
        IF result == ACE.AE_NO_CURRENT_RECORD
            IF SELF:_Eof
                RETURN FALSE
            ELSE
                RETURN TRUE
            ENDIF
        ELSEIF result != 0
            SELF:_CheckError(result,EG_READ)
        ENDIF
        RETURN isDeleted != 0

    END GET
END PROPERTY

PROPERTY Reccount AS LONG
    GET
        SELF:_CheckError(ACE.AdsGetRecordCount(SELF:_Table, ACE.ADS_IGNOREFILTERS, OUT VAR dwCount),EG_READ)
        RETURN (LONG) dwCount
    END GET
END PROPERTY
PROPERTY RecNo AS LONG GET (LONG) SELF:_Recno
PROPERTY _RecNo AS DWORD
    GET
        LOCAL result AS DWORD
        result := ACE.AdsGetRecordNum(SELF:_Table, ACE.ADS_IGNOREFILTERS, OUT VAR dwRecno)
        IF result == ACE.AE_NO_CURRENT_RECORD
            dwRecno := 0
        ELSEIF result != 0
            SELF:_CheckError(result,EG_READ)
        ENDIF
        RETURN dwRecno
    END GET
END PROPERTY

#endregion

    #region Locking

    /// <inheritdoc />
VIRTUAL METHOD Lock(lockInfo REF DbLockInfo) AS LOGIC
    LOCAL lRecno := 0 AS DWORD
    LOCAL result := 0 AS DWORD
      //
    lockInfo:Result := FALSE
    IF lockInfo:recID == NULL
        lRecno := SELF:_Recno
    ELSE
        TRY
            lRecno := System.Convert.ToUInt32(lockInfo:RecId)
        CATCH e AS Exception
            SELF:ADSERROR(ERDD.DATATYPE, XSharp.Gencode.EG_DATATYPE, "Lock", e:Message)
        END TRY
    ENDIF
    IF lockInfo:@@Method == DBLockInfo.LockMethod.File
        result := ACE.AdsLockTable(SELF:_Table)
//      SELF:_CheckError(result)
    ELSE
        SELF:_CheckError(ACE.AdsGetHandleType(SELF:_Table, OUT VAR handleType),EG_LOCK)
        IF lRecno == 0 .AND. handleType != ACE.ADS_CURSOR
            SELF:_CheckError(ACE.AdsIsTableLocked(SELF:_Table, OUT VAR isLocked),EG_LOCK)
            IF isLocked == 0
                result := ACE.AdsUnlockTable(SELF:_Table)
                IF result != ACE.AE_TABLE_NOT_LOCKED  .AND. result != ACE.AE_TABLE_NOT_SHARED .AND. result != 0
                    SELF:_CheckError(result,EG_LOCK)
                ENDIF
            ENDIF
        ENDIF
        result  := ACE.AdsLockRecord(SELF:_Table, lRecno)
    ENDIF

    IF result != ACE.AE_TABLE_NOT_SHARED .AND. result != 0
        SELF:_CheckError(result,EG_SHARED)
    ENDIF
    lockInfo:Result := TRUE
RETURN TRUE

      /// <inheritdoc />
VIRTUAL METHOD Unlock(recordID AS OBJECT) AS LOGIC
    LOCAL result AS DWORD
    LOCAL dwRecno := 0 AS DWORD
    TRY
        dwRecno := System.Convert.ToUInt32(recordID)
    CATCH e AS Exception
        SELF:ADSERROR(ERDD.DATATYPE, XSharp.Gencode.EG_DATATYPE, "Unlock",e:Message)
    END TRY
    IF dwRecno == 0
        result := ACE.AdsUnlockTable(SELF:_Table)
    ELSE
        result := ACE.AdsUnlockRecord(SELF:_Table, dwRecno)
    ENDIF
    IF result != ACE.AE_TABLE_NOT_SHARED .AND. result != ACE.AE_TABLE_NOT_LOCKED .AND. result != 0
        SELF:_CheckError(result)
    ENDIF
RETURN TRUE

      #endregion

    #region Filters
    /// <inheritdoc />
VIRTUAL METHOD ClearFilter() AS LOGIC
    LOCAL result AS DWORD
    IF SELF:_Table != System.IntPtr.Zero
        // Clear normal filter
        result := ACE.AdsClearFilter(SELF:_Table)
        IF result != ACE.AE_NO_FILTER .AND. result != 0
            SELF:_CheckError(result)
        ENDIF
        // Clear optimized filter
        result := ACE.AdsClearAOF(SELF:_Table)
        IF result != ACE.AE_NO_FILTER .AND. result != 0
            SELF:_CheckError(result)
        ENDIF
    ENDIF
    SELF:_FilterInfo := DbFilterInfo{}
RETURN TRUE

METHOD SetFieldExtent( fieldCount AS LONG ) AS LOGIC
    SELF:_HasMemo := FALSE
RETURN SUPER:SetFieldExtent(fieldCount)

      /// <inheritdoc />
VIRTUAL METHOD SetFilter(fi AS DBFILTERINFO) AS LOGIC
    LOCAL result AS DWORD
      // Get the current date format so we can handle literal dates in the filter
    SELF:_SynchronizeSettings()
    IF String.IsNullOrEmpty(fi:FilterText)
        // clear filter
        // Ignore "No filter" error
        result := ACE.AdsClearFilter(SELF:_Table)
        IF result != ACE.AE_NO_FILTER
            SELF:_CheckError(result)
        ENDIF
        result := ACE.AdsClearAOF(SELF:_Table)
        IF result != ACE.AE_NO_FILTER
            SELF:_CheckError(result)
        ENDIF
    ELSE
        IF RuntimeState.Optimize
            SELF:_CheckError(ACE.AdsSetAOF(SELF:_Table, fi:FilterText, (WORD) (ACE.ADS_RESOLVE_DYNAMIC | ACE.ADS_DYNAMIC_AOF)))
        ELSE
            SELF:_CheckError(ACE.AdsSetFilter(SELF:_Table, fi:FilterText))
        ENDIF
    ENDIF
    fi:Active := TRUE
    SELF:_FilterInfo := fi
RETURN TRUE

      #endregion
    #region Relations
    /// <inheritdoc />
VIRTUAL METHOD ClearRel() AS LOGIC
    IF SELF:_Table != System.IntPtr.Zero
        SELF:_CheckError(ACE.AdsClearRelation(SELF:_Table))
    ENDIF
RETURN TRUE

      /// <inheritdoc />
VIRTUAL METHOD SetRel(relinfo AS DBRELINFO) AS LOGIC
      // Needs a better solution of course. SELF:_Driver currently returns the fullname of the class, while Driver property is not implemented yet
    IF relinfo:Child:Driver != SELF:Driver
        SELF:ADSERROR(ERDD.UNSUPPORTED, XSharp.Gencode.EG_UNSUPPORTED, "SetRel", "Related workareas must be opened with the same driver.")
        RETURN FALSE
    ENDIF
    LOCAL child := (ADSRDD) relInfo:Child AS ADSRDD
    SELF:_CheckError(ACE.AdsSetRelation(SELF:_Table, child:ACEIndexHandle, relinfo:Key))
RETURN TRUE

      #endregion

    #region Info
    /// <inheritdoc />
VIRTUAL METHOD FieldInfo(uiPos AS LONG, uiOrdinal AS INT, oNewValue AS OBJECT) AS OBJECT
    LOCAL result AS DWORD
    SWITCH uiOrdinal
    CASE DBS_BLOB_TYPE
        SELF:_CheckError(ACE.AdsGetFieldType(SELF:_Table, (DWORD)uiPos  ,  OUT VAR fieldType))
        SWITCH fieldType
        CASE ACE.ADS_MEMO
        CASE ACE.ADS_BINARY
        CASE ACE.ADS_IMAGE
        CASE ACE.ADS_NMEMO
            RETURN "?"
        END SWITCH
    CASE DBS_BLOB_LEN
        IF SUPER:_fields[uiPos-1]:fieldType != DBFieldType.Memo
            RETURN -1
        ELSE
            result := ACE.AdsGetMemoLength(SELF:_Table, (DWORD)uiPos , OUT VAR length)
            IF result == ACE.AE_INVALID_FIELD_TYPE
                RETURN -1
            ENDIF
            IF result != 0
                SELF:_CheckError(result)
            ENDIF
            RETURN length
        ENDIF

    CASE DBS_BLOB_POINTER
        RETURN NULL

    END SWITCH
RETURN SUPER:FieldInfo(uiPos, uiOrdinal, oNewValue)

VIRTUAL METHOD RecInfo(uiOrdinal AS LONG, iRecID AS OBJECT, oNewValue AS OBJECT) AS OBJECT
    LOCAL dwRecno AS DWORD
      //
    SWITCH (DBRecordInfo) uiOrdinal
    CASE DBRecordInfo.DBRI_LOCKED
        TRY
            dwRecno := Convert.ToUInt32(iRecID)
/*          IF dwRecNo == 0
                dwRecNo := SELF:RecNo // looks like the dll func does this anyway
            END IF*/
        CATCH
            SELF:ADSERROR(ERDD_DATATYPE, EG_DATATYPE, "RecInfo")
            RETURN FALSE
        END TRY
        _CheckError(ACE.AdsIsRecordLocked(SELF:_Table, dwRecno, OUT VAR locked))
        RETURN locked != 0

    CASE DBRecordInfo.DBRI_RECSIZE
        RETURN SELF:_RecordLength

    CASE DBRecordInfo.DBRI_UPDATED
        SELF:_CheckError(ACE.AdsRefreshRecord(SELF:_Table))
        RETURN NULL

    CASE DBRecordInfo.DBRI_RECNO
        RETURN SELF:RecNo
    OTHERWISE
        SELF:ADSERROR(ERDD_UNSUPPORTED, EG_UNSUPPORTED, "RecInfo")
    END SWITCH
RETURN SUPER:RecInfo( uiOrdinal, iRecID, oNewValue)

      /// <inheritdoc />
VIRTUAL METHOD Info(uiOrdinal AS LONG, oNewValue AS OBJECT) AS OBJECT
    LOCAL result AS DWORD
    SWITCH (DbInfo)uiOrdinal
    CASE DbInfo.DBI_ISDBF
        RETURN !SELF:IsADT

    CASE DbInfo.DBI_CANPUTREC
        RETURN FALSE

    CASE DbInfo.DBI_GETRECSIZE
        RETURN SELF:_RecordLength

    CASE DbInfo.DBI_TABLEEXT
        IF SELF:IsADT
            RETURN ".ADT"
        ELSE
            RETURN ".DBF"
        ENDIF

    CASE DbInfo.DBI_GETHEADERSIZE
        LOCAL dwValue := 0 AS LONG
        dwValue := SELF:FieldCount * 32 + 32
        RETURN (LONG) dwValue

    CASE DbInfo.DBI_LASTUPDATE
        LOCAL aDate AS CHAR[]
        LOCAL DateLen AS WORD

        aDate := CHAR[]{ACE.ADS_MAX_DATEMASK+1}
        DateLen := (WORD) aDate:Length
        SELF:_CheckError(ACE.AdsSetDateFormat("MM/DD/YY"))
        SELF:_CheckError(ACE.AdsGetLastTableUpdate(SELF:_Table, aDate, REF DateLen))
        SELF:_CheckError(ACEUNPUB.AdsConvertStringToJulian(aDate, DateLen, OUT VAR julDate))
        SELF:_SynchronizeSettings()
        RETURN (LONG)julDate

    CASE DbInfo.DBI_GETLOCKARRAY
    CASE DbInfo.DBI_LOCKCOUNT
        SELF:_CheckError(ACE.AdsGetNumLocks(SELF:_Table, OUT VAR numLocks))
        IF (uiOrdinal == DbInfo.DBI_LOCKCOUNT)
            RETURN numLocks
        ENDIF
        IF numLocks > 0
            IF SELF:_aRlocks == NULL .OR. SELF:_aRlocks:Length < numLocks
                SELF:_aRlocks := DWORD[]{numLocks}
            ENDIF
            SELF:_CheckError(ACE.AdsGetAllLocks(SELF:_Table, SELF:_aRlocks, REF numLocks))
            RETURN SELF:_aRlocks
        ELSE
            RETURN NULL
        ENDIF
    CASE DbInfo.DBI_ISFLOCK
        SELF:_CheckError(ACE.AdsIsTableLocked(SELF:_Table, OUT VAR isLocked))
        RETURN isLocked != 0

    CASE DbInfo.DBI_FILEHANDLE
        RETURN IntPtr.Zero

    CASE DbInfo.DBI_ISANSI
        RETURN FALSE

    CASE DbInfo.DBI_FOUND
        SELF:_CheckError(ACE.AdsIsFound(SELF:_Table, OUT VAR isFound))
        RETURN isFound != 0

    CASE DbInfo.DBI_SHARED
        SELF:_CheckError(ACE.AdsGetTableOpenOptions(SELF:_Table, OUT VAR options))
        RETURN !_HasFlag(options,ACE.ADS_EXCLUSIVE)

    CASE DbInfo.DBI_MEMOEXT
        SWITCH SELF:_TableType
        CASE ACE.ADS_ADT
            RETURN ".ADM"
        CASE ACE.ADS_CDX
        CASE ACE.ADS_VFP
            RETURN ".FPT"
        OTHERWISE
            RETURN ".DBT"
        END SWITCH

    CASE DbInfo.DBI_RDD_VERSION
        RETURN System.Reflection.Assembly.GetExecutingAssembly():GetName():Version:ToString()

    CASE DbInfo.DBI_GET_ACE_TABLE_HANDLE
        RETURN SELF:_Table

    CASE DbInfo.DBI_MEMOBLOCKSIZE
        LOCAL memoBlockSize AS WORD
        result := ACE.AdsGetMemoBlockSize(SELF:_Table, OUT memoBlockSize)
        IF result != ACE.AE_NO_MEMO_FILE
            RETURN 0
        ELSEIF result != 0
            SELF:_CheckError(result)
        ENDIF
        RETURN memoBlockSize

    CASE DbInfo.DBI_CODEPAGE
        RETURN SELF:_Encoding:CodePage

    CASE DbInfo.DBI_DB_VERSION
        RETURN 0

    CASE DbInfo.DBI_FULLPATH
        RETURN SUPER:_FileName

    CASE DbInfo.DBI_SETDELIMITER
    CASE DbInfo.DBI_VALIDBUFFER
    CASE DbInfo.DBI_LOCKOFFSET
    CASE DbInfo.DBI_MEMOHANDLE
    CASE DbInfo.DBI_NEWINDEXLOCK
    CASE DbInfo.DBI_MEMOFIELD
        RETURN NULL
    CASE DbInfo.DBI_READONLY
    CASE DbInfo.DBI_CHILDCOUNT
    CASE DbInfo.DBI_BOF
    CASE DbInfo.DBI_EOF
    CASE DbInfo.DBI_DBFILTER
    CASE DbInfo.DBI_FCOUNT
    CASE DbInfo.DBI_ALIAS
    CASE DbInfo.DBI_GETSCOPE
        RETURN SUPER:Info(uiOrdinal,oNewValue )

    CASE DbInfo.BLOB_INFO_HANDLE
    CASE DbInfo.BLOB_FILE_RECOVER
    CASE DbInfo.BLOB_FILE_INTEGRITY
    CASE DbInfo.BLOB_OFFSET
    CASE DbInfo.BLOB_POINTER
    CASE DbInfo.BLOB_LEN
    CASE DbInfo.BLOB_TYPE
    CASE DbInfo.BLOB_EXPORT
    CASE DbInfo.BLOB_ROOT_UNLOCK
    CASE DbInfo.BLOB_ROOT_PUT
    CASE DbInfo.BLOB_ROOT_GET
    CASE DbInfo.BLOB_ROOT_LOCK
    CASE DbInfo.BLOB_IMPORT
    CASE DbInfo.BLOB_DIRECT_PUT
    CASE DbInfo.BLOB_DIRECT_GET
    CASE DbInfo.BLOB_GET
    CASE DbInfo.BLOB_DIRECT_EXPORT
    CASE DbInfo.BLOB_DIRECT_IMPORT
    CASE DbInfo.BLOB_NMODE
    CASE DbInfo.BLOB_EXPORT_APPEND
    CASE DbInfo.BLOB_EXPORT_OVERWRITE
        RETURN NULL
    CASE DbInfo.DBI_RL_AND
    CASE DbInfo.DBI_RL_CLEAR
    CASE DbInfo.DBI_RL_COUNT
    CASE DbInfo.DBI_RL_DESTROY
    CASE DbInfo.DBI_RL_EXFILTER
    CASE DbInfo.DBI_RL_GETFILTER
    CASE DbInfo.DBI_RL_HASMAYBE
    CASE DbInfo.DBI_RL_LEN
    CASE DbInfo.DBI_RL_MAYBEEVAL
    CASE DbInfo.DBI_RL_NEW
    CASE DbInfo.DBI_RL_NEWDUP
    CASE DbInfo.DBI_RL_NEWQUERY
    CASE DbInfo.DBI_RL_NEXTRECNO
    CASE DbInfo.DBI_RL_NOT
    CASE DbInfo.DBI_RL_OR
    CASE DbInfo.DBI_RL_PREVRECNO
    CASE DbInfo.DBI_RL_SET
    CASE DbInfo.DBI_RL_SETFILTER
    CASE DbInfo.DBI_RL_TEST
        RETURN NULL
    END SWITCH
RETURN SUPER:Info(uiOrdinal, oNewValue)

/// <inheritdoc/>
VIRTUAL METHOD Trans(info AS DbTransInfo) AS LOGIC
	RETURN SUPER:Trans(info)
/// <inheritdoc/>
VIRTUAL METHOD TransRec(info AS DbTransInfo) AS LOGIC
	RETURN SUPER:TransRec(info)

      #endregion

    #region Unsupported
    /// <inheritdoc />

INTERNAL METHOD Unsupported(strFunctionName AS STRING) AS LOGIC
    SELF:ADSERROR(ERDD.UNSUPPORTED, XSharp.Gencode.EG_UnSupported, strFunctionName)
RETURN FALSE

      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD AppendLock(uiMode AS DbLockMode) AS LOGIC
RETURN SELF:Unsupported("AppendLock")

      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD BlobInfo(uiPos AS DWORD, uiOrdinal AS DWORD) AS OBJECT
    SELF:Unsupported("BlobInfo")
RETURN NULL

      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD ForceRel() AS LOGIC
RETURN SELF:Unsupported("ForceRel")

      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD GetRec() AS BYTE[]
    SELF:Unsupported("GetRec")
RETURN NULL

      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD GetValueFile(nFldPos AS INT, cFileName AS STRING) AS LOGIC
    SELF:Unsupported("GetValueFile")
RETURN FALSE
      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD HeaderLock(uiMode AS DbLockMode) AS LOGIC
RETURN SELF:Unsupported("HeaderLock")
      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD PutRec(aRec AS BYTE[])			AS LOGIC
RETURN SELF:Unsupported("PutRec")
      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD PutValueFile(nFldPos AS INT, cFileName AS STRING) AS LOGIC
    SELF:Unsupported("PutValueFile")
RETURN FALSE

      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD Sort(info AS DbSortInfo) AS LOGIC
RETURN SELF:Unsupported("Sort")

      #endregion
END CLASS

//
// 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.IO
USING System.Collections.Generic
USING System.Text
USING XSharp.RDD
USING XSharp.RDD.Enums
USING XSharp.RDD.Support
USING System.Diagnostics

/// <summary>
/// The AdsRDD class.
/// </summary>
[DebuggerDisplay("AdsRDD ({Alias,nq})")];
CLASS XSharp.ADS.AdsRDD INHERIT Workarea
  #region Fields
    PRIVATE  _aRlocks AS DWORD[]
    INTERNAL _Encoding AS System.Text.Encoding
    INTERNAL _Connection AS System.IntPtr
    INTERNAL _Index AS System.IntPtr
    INTERNAL _Table AS System.IntPtr
    INTERNAL _Collation AS STRING
    INTERNAL _Driver AS STRING
    INTERNAL _CheckRights AS WORD
    INTERNAL _LockType AS WORD
    INTERNAL _TableType AS WORD
    INTERNAL _MaxKeySize AS WORD
    INTERNAL _Ansi  AS LOGIC
    INTERNAL _HasMemo AS LOGIC
    INTERNAL _fieldCount AS LONG

  #endregion

  /// <summary>Create instande of RDD </summary>
CONSTRUCTOR()
    SUPER()
    SELF:_Order         := AdsIndex{SELF}
    SELF:_Memo          := AdsMemo{SELF}
    SELF:_Table         := System.IntPtr.Zero
    SELF:_Index         := System.IntPtr.Zero
    SELF:_Connection    := System.IntPtr.Zero
    SELF:_Driver        := String.Empty
    SELF:_Collation     := String.Empty
    SELF:_Driver        := "ADSRDD"
    SELF:_fileName       := String.Empty

    #region Helper Methods that check for error conditions
INTERNAL STATIC METHOD _HasFlag(dw AS DWORD, flag AS DWORD) AS LOGIC
RETURN _AND(dw, flag) == flag

INTERNAL METHOD _CheckError(ulRetCode AS DWORD, dwGenCode := EG_NOCLASS AS DWORD, strFunction := "" AS STRING) AS LOGIC
    IF ulRetCode != 0
        IF String.IsNullOrEmpty(strFunction)
            strFunction := ProcName(1)
        ENDIF
        SELF:ADSERROR( ulRetCode, dwGenCode, strFunction)
        RETURN FALSE
    ENDIF
RETURN TRUE

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

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

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

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

INTERNAL METHOD ADSERROR(iSubCode AS DWORD, iGenCode AS DWORD, strFunction AS STRING, strMessage AS STRING, iSeverity AS DWORD) AS VOID
    LOCAL lastError AS DWORD
    LOCAL message AS CHAR[]
    LOCAL wBufLen AS WORD
    LOCAL oError AS RddError
    IF String.IsNullOrEmpty(strMessage)
      //
        message := CHAR[]{ACE.ADS_MAX_ERROR_LEN}
        wBufLen := (WORD) message:Length
        IF ACE.AdsGetLastError(OUT lastError, message, REF wBufLen) == 0 .AND. lastError != 0 .AND. wBufLen > 0
            strMessage := STRING{message, 0, wBufLen}
        ENDIF
    ENDIF
    IF String.IsNullOrEmpty(strMessage) .AND. iSubCode != 0
        strMessage := ErrString(iGenCode)
    ENDIF
    oError   := AdsError{strMessage, iGenCode, iSubcode,_Driver, iSeverity, strFunction, _FileName}
    RuntimeState.LastRDDError := oError
    THROW oError

    #endregion
  #region Helper Methods

INTERNAL METHOD _SynchronizeVODeletedFlag() AS DWORD
    RETURN ACE.AdsShowDeleted(IIF(RuntimeState.Deleted,(WORD)0 ,(WORD)1 ))

INTERNAL METHOD _SynchronizeSettings() AS VOID
    ACE.AdsSetDateFormat(RuntimeState.DateFormat)
    ACE.AdsSetExact((WORD) IIF(RuntimeState.Exact,1 , 0 ))
    ACE.AdsSetDecimals((WORD)RuntimeState.Decimals )
    ACE.AdsSetEpoch((WORD)RuntimeState.Epoch )
RETURN

INTERNAL METHOD _CheckRDDInfo() AS VOID
    LOCAL oRet := NULL_OBJECT AS OBJECT
    IF AX_AXSLocking()
        SELF:_LockType := ACE.ADS_PROPRIETARY_LOCKING
    ELSE
        SELF:_LockType := ACE.ADS_COMPATIBLE_LOCKING
    ENDIF
    IF AX_RightsCheck()
        SELF:_CheckRights := ACE.ADS_CHECKRIGHTS
    ELSE
        SELF:_CheckRights := ACE.ADS_IGNORERIGHTS
    ENDIF
    IF CoreDb.RddInfo(_SET_CONNECTION_HANDLE, REF oRet) .AND. oRet != NULL_OBJECT
        IF oRet IS IntPtr VAR p
            SELF:_Connection := p
        ELSEIF oRet IS DWORD VAR d
            SELF:_Connection := IntPtr{d}
        ELSE
            SELF:_Connection := IntPtr.Zero
        ENDIF
    ELSE
        SELF:_Connection := IntPtr.Zero
    ENDIF
    IF SELF:_TableType == ACE.ADS_ADT .OR. SELF:_TableType == ACE.ADS_VFP
        oRet := NULL
        IF CoreDb.RddInfo(_SET_COLLATION_NAME, REF oRet) .AND. oRet != NULL
            SELF:_Collation := (STRING) oRet
        ELSE
            SELF:_Collation := string.Empty
        ENDIF
    ENDIF
RETURN

INTERNAL METHOD _Ansi2Unicode(source AS CHAR[], iLength AS LONG) AS STRING
    TRY
        VAR bytes := SELF:_Encoding:GetBytes(source, 0, iLength)
        RETURN SELF:_Encoding:GetString(bytes)
    CATCH
        RETURN String.Empty
    END TRY

    #endregion

  #region Open and Close
PROTECTED METHOD _FieldSub() AS LOGIC
    LOCAL num AS DWORD
    LOCAL sb AS CHAR[]
    LOCAL wLen AS WORD
    LOCAL fi AS RddFieldInfo
    LOCAL wDecimals AS WORD
    LOCAL wFields AS WORD
    SELF:_CheckError(ACE.AdsGetNumFields(SELF:_Table, OUT wFields),EG_OPEN)
    IF !SELF:SetFieldExtent(wFields)
        RETURN FALSE
    ENDIF
    _fieldCount := wFields
    sb := CHAR[]{ACE.ADS_MAX_FIELD_NAME+1}
    FOR num := 1u TO wFields STEP 1
        LOCAL cName AS STRING
        SELF:_CheckError(ACE.AdsGetFieldType(SELF:_Table, num, OUT VAR usType),EG_OPEN)
        SELF:_CheckError(ACE.AdsGetFieldLength(SELF:_Table, num, OUT VAR ulLength),EG_OPEN)
        wLen := (WORD) sb:Length
        SELF:_CheckError(ACE.AdsGetFieldName(SELF:_Table, (WORD)num, sb, REF wLen),EG_OPEN)
        cName := STRING{sb,0, wLen}
        fi := RddFieldInfo{cName,DbFieldType.Character,1,0}
        fi:Length := (INT) ulLength
        LOCAL eType := (AdsFieldType) usType AS AdsFieldType
        SWITCH eType
        CASE AdsFieldType.TIMESTAMP
        CASE AdsFieldType.MODTIME
            fi:Length := 22
            fi:fieldType := DbFieldType.Character
        CASE AdsFieldType.TIME
            fi:Length := 11
            fi:fieldType := DbFieldType.Character
        CASE AdsFieldType.MONEY
        CASE AdsFieldType.ROWVERSION
            fi:Length := 22
            fi:fieldType := DbFieldType.Character
        CASE AdsFieldType.STRING
        CASE AdsFieldType.VARCHAR
        CASE AdsFieldType.CISTRING
        CASE AdsFieldType.VARCHAR_FOX
        CASE AdsFieldType.NCHAR
        CASE AdsFieldType.NVARCHAR
            fi:fieldType := DbFieldType.Character
        CASE AdsFieldType.MEMO
        CASE AdsFieldType.BINARY
        CASE AdsFieldType.IMAGE
        CASE AdsFieldType.NMEMO
            fi:fieldType := DbFieldType.Memo
            fi:Length := 10
        CASE AdsFieldType.RAW
        CASE AdsFieldType.VARBINARY_FOX
            fi:fieldType := DbFieldType.Memo
        CASE AdsFieldType.SHORTINT
        CASE AdsFieldType.AUTOINC
        CASE AdsFieldType.INTEGER
        CASE AdsFieldType.NUMERIC
            fi:fieldType := DbFieldType.Number
            SWITCH eType
            CASE AdsFieldType.SHORTINT
                fi:Length := 6
            CASE AdsFieldType.AUTOINC
                fi:Length := 10
            CASE AdsFieldType.INTEGER
                fi:Length := 11
            OTHERWISE // Numeric
                fi:Length := (INT) ulLength
            END SWITCH
            SELF:_CheckError(ACE.AdsGetFieldDecimals(SELF:_Table, num, OUT wDecimals),EG_OPEN)
            fi:Decimals := wDecimals
        CASE AdsFieldType.LOGICAL
            fi:fieldType := DbFieldType.Logic
        CASE AdsFieldType.DATE
        CASE AdsFieldType.COMPACTDATE
            fi:fieldType := DbFieldType.Date
            fi:Length := 8
        CASE AdsFieldType.DOUBLE
        CASE AdsFieldType.CURDOUBLE
            fi:Length := 20
            fi:fieldType := DbFieldType.Number
            SELF:_CheckError(ACE.AdsGetFieldDecimals(SELF:_Table, num, OUT wDecimals),EG_OPEN)
            fi:Decimals := wDecimals
        OTHERWISE
            SELF:ADSERROR(ERDD_CORRUPT_HEADER, EG_DATATYPE)
            RETURN FALSE
        END SWITCH
        fi:Alias := NULL
        LOCAL oCol AS AdsColumn
        oCol := AdsColumn.Create(fi, SELF, eType, num)
        IF ! SELF:AddField(oCol)
            RETURN FALSE
        ENDIF
    NEXT
RETURN TRUE

PRIVATE METHOD _GetFieldInfo(strFieldDef REF STRING ) AS LOGIC
    strFieldDef := ""
    FOREACH fld AS RDDFieldInfo IN SUPER:_Fields
        strFieldDef += fld:Name+", "
        SWITCH fld:FieldType
        CASE DbFieldType.Character
            strFieldDef += "C,"
          // allow clipper field lengths
            strFieldDef += (fld:Length + fld:Decimals*256):ToString()+";"
        CASE DbFieldType.Number
        CASE DbFieldType.Integer
            strFieldDef += "N,"+fld:Length:ToString()+", "+fld:Decimals:ToString()+";"
        CASE DbFieldType.Date
            strFieldDef += "D;"
        CASE DbFieldType.Logic
            strFieldDef += "L;"
        CASE DbFieldType.Memo
            IF fld:Length == 23 .AND. fld:Decimals == 4
                strFieldDef += "Money;"
            ELSE
                _HasMemo := TRUE
                strFieldDef += "M;"
            ENDIF
        OTHERWISE
            SELF:ADSERROR(ERDD_CORRUPT, EG_CORRUPTION)
            RETURN FALSE
        END SWITCH
    NEXT
RETURN TRUE

INTERNAL METHOD _SetPaths() AS DWORD
    IF !string.IsNullOrEmpty(SetDefault())
        SELF:_CheckError(ACE.AdsSetDefault(SetDefault()),EG_OPEN)
    ENDIF
    IF !string.IsNullOrEmpty(SetPath())
        SELF:_CheckError(ACE.AdsSetSearchPath(SetPath()),EG_OPEN)
    ENDIF
RETURN 0u

    /// <inheritdoc />
VIRTUAL METHOD Open(info AS DbOpenInfo) AS LOGIC
    LOCAL openmode AS WORD
    LOCAL alias AS STRING
    LOCAL charset AS WORD
    LOCAL usTableType AS WORD
    LOCAL fileName AS STRING
    //
    openmode := 0
    SELF:_CheckRDDInfo()
    alias := Path.GetFileNameWithoutExtension(info:Alias)
    // both Clipper and XPP use weight tables
    IF RuntimeState.CollationMode == CollationMode.Clipper .OR. RuntimeState.CollationMode == CollationMode.Xpp
        charset := ACE.ADS_OEM
    ELSE
        charset := ACE.ADS_ANSI
    ENDIF
    IF info:ReadOnly
        openmode |= (WORD) ACE.ADS_READONLY
    ENDIF
    IF !info:Shared
        openmode |= (WORD) ACE.ADS_EXCLUSIVE
    ENDIF
    IF SELF:_SetPaths() != 0
        RETURN FALSE
    ENDIF
    IF SELF:_Connection != IntPtr.Zero
        usTableType := ACE.ADS_DATABASE_TABLE
        fileName := Path.GetFileNameWithoutExtension(info:FileName)
    ELSE
        usTableType := SELF:_TableType
        fileName := info:FileName + info:Extension
    ENDIF
    IF alias:Length > 10
        alias := string.Empty
    ENDIF
    IF SELF:_TableType == ACE.ADS_ADT .AND. Path.GetExtension(fileName):ToUpper() == ".DBF"
        fileName := Path.ChangeExtension(fileName, ".ADT")
    ENDIF
    LOCAL result AS DWORD
    LOCAL tries := 0 AS LONG
    REPEAT
        // wait 100 ms before retrying
        IF tries > 0
            System.Threading.Thread.Sleep(100)
        ENDIF
        tries += 1

        result := ACE.AdsOpenTable90(SELF:_Connection, fileName, alias, usTableType, charset, SELF:_LockType, SELF:_CheckRights, openmode,SELF:_Collation, OUT SELF:_Table)
        IF result != 0 .AND. SELF:_Connection != IntPtr.Zero
            usTableType := SELF:_TableType
            IF fileName[0] != '#'
                fileName := info:FileName + info:Extension
                IF SELF:_TableType == ACE.ADS_ADT .AND. Path.GetExtension(fileName):ToUpper() == ".DBF"
                    fileName := Path.ChangeExtension(fileName, ".ADT")
                ENDIF
            ENDIF
            result := ACE.AdsOpenTable90(SELF:_Connection, fileName, alias, usTableType, charset, SELF:_LockType, SELF:_CheckRights, openmode, SELF:_Collation, OUT SELF:_Table)
        ENDIF
    UNTIL tries == 10 .OR. SELF:_Table != IntPtr.Zero
    IF result != 0
        NetErr(TRUE)
        SELF:Close()
        SELF:_FileName := fileName
        SELF:ADSERROR(ERDD_OPEN_FILE, EG_OPEN, "Open")
        RETURN FALSE
    ENDIF
    IF !SELF:_FieldSub()
        SELF:Close()
        RETURN FALSE
    ENDIF
    LOCAL length := MAX_PATH AS WORD
    LOCAL afileName AS CHAR[]
    afileName := CHAR[]{length}
    SELF:_CheckError(ACE.AdsGetTableFilename(SELF:_Table, ACE.ADS_FULLPATHNAME, afileName, REF length),EG_OPEN)
    SELF:_FileName := STRING {aFileName,0, length}
    IF result != 0
        SELF:Close()
        RETURN FALSE
    ENDIF
    SELF:_CheckError(ACE.AdsGetNumIndexes(SELF:_Table, OUT VAR numIndexes),EG_OPEN)
    IF numIndexes > 0
        SELF:_CheckError(ACE.AdsGetIndexHandleByOrder(SELF:_Table, 1, OUT SELF:_Index))
    ENDIF
    SELF:_Encoding := Encoding.GetEncoding(IIF (charset == ACE.ADS_ANSI, RuntimeState.WinCodePage,RuntimeState.DosCodePage))
    IF ACE.AdsGetMemoBlockSize(SELF:_Table, OUT VAR blockSize) == 0 .AND. blockSize > 0
        _HasMemo := TRUE
    ELSE
        _HasMemo := FALSE
    ENDIF
    SELF:_CheckError(ACE.AdsGetRecordLength(SELF:_Table, OUT VAR dwLength),EG_OPEN)
    SELF:_RecordLength := (LONG) dwLength
    SELF:Alias   := info:Alias
    SELF:Area    := info:WorkArea
RETURN SELF:RecordMovement()

    /// <inheritdoc />
VIRTUAL METHOD Close() AS LOGIC
    LOCAL result AS LOGIC
    result := SUPER:Close()
    IF SELF:_Table != IntPtr.Zero
        SELF:_CheckError(ACE.AdsCloseTable(SELF:_Table),EG_CLOSE)
        SELF:_Table := IntPtr.Zero
    ENDIF
    SELF:_Index := IntPtr.Zero
    SELF:_CheckRights := 0
    SELF:_LockType := 0
    SELF:_TableType := 0
    SELF:Area  := 0
RETURN result

    /// <inheritdoc />
VIRTUAL METHOD Create(info AS DbOpenInfo) AS LOGIC
    LOCAL strFieldDef AS STRING
    LOCAL charset AS WORD
    LOCAL alias AS STRING
    LOCAL length AS WORD
    //
    strFieldDef     := string.Empty
    SELF:_Alias     := info:Alias
    SELF:_Area      := info:Workarea
    SELF:_Shared    := info:Shared
    SELF:_ReadOnly  := info:ReadOnly
    SELF:_Ansi      := RuntimeState.Ansi

    IF !SELF:_GetFieldInfo(REF strFieldDef)
        RETURN FALSE
    ENDIF
    SELF:_CheckRDDInfo()
    IF SELF:_SetPaths() != 0
        RETURN FALSE
    ENDIF
    // both Clipper and XPP use weight tables
    IF RuntimeState.CollationMode == CollationMode.Clipper .OR. RuntimeState.CollationMode == CollationMode.Xpp
        charset := ACE.ADS_OEM
    ELSE
        charset := ACE.ADS_ANSI
    ENDIF
    alias := Path.GetFileNameWithoutExtension(info:Alias)
    IF alias:Length > 10
        alias := string.Empty
    ENDIF
    SELF:_FileName := info:FileName
    SELF:_CheckError(ACE.AdsCreateTable90(SELF:_Connection, info:FileName, alias, SELF:_TableType, charset, SELF:_LockType, SELF:_CheckRights, 0, strFieldDef, 0u, SELF:_Collation, OUT SELF:_Table),EG_CREATE)
    SELF:_Encoding := IIF (charset== ACE.ADS_ANSI, StringHelpers.WinEncoding, StringHelpers.DosEncoding)
    length := MAX_PATH
    LOCAL fileName AS CHAR[]
    fileName := CHAR[]{length}
    SELF:_CheckError(ACE.AdsGetTableFilename(SELF:_Table, ACE.ADS_FULLPATHNAME , fileName, REF length),EG_CREATE)
    SELF:_FileName := STRING{fileName,0, length}
    SELF:_CheckError(ACE.AdsGetRecordLength(SELF:_Table, OUT VAR dwLength),EG_CREATE)
    SELF:_RecordLength := (LONG) dwLength
    // Workarea has no implementation of Create
    //    IF !SUPER:Create(info)
    //      SELF:Close()
    //      RETURN FALSE
    //    ENDIF
RETURN SELF:RecordMovement()

    // Check if a Field definition is correct :
    // Date Length must be 8, Number are long enough to store Dot and Decs (if any), ...
PROTECT OVERRIDE METHOD _checkFields(info AS RddFieldInfo) AS LOGIC
    // FieldName
    info:Name := info:Name:ToUpper():Trim()
    IF info:Name:Length > 10
        info:Name := info:Name:Substring(0,10)
    ENDIF
    IF ! info:Validate()
        SELF:ADSERROR(ERDD.CREATE_FILE, XSharp.Gencode.EG_ARG )
    ENDIF
    RETURN TRUE

VIRTUAL METHOD AddField(info AS RddFieldInfo) AS LOGIC
    LOCAL isOk AS LOGIC
    isOk := SUPER:AddField(info)
    IF isOk  .AND. info:FieldType == DbFieldType.Memo
        SELF:_HasMemo := TRUE
    ENDIF
RETURN isOk

    #endregion
  #region Navigation
METHOD RecordMovement() AS LOGIC
    SELF:_CheckError(ACE.AdsAtBOF(SELF:_Table, OUT VAR atBOF),EG_READ)
    SUPER:_Bof := (atBOF == 1)
    SELF:_CheckError(ACE.AdsAtEOF(SELF:_Table, OUT VAR atEOF),EG_READ)
    SUPER:_Eof := (atEOF == 1)
    SELF:_CheckError(ACE.AdsIsFound(SELF:_Table,  OUT VAR isFound),EG_READ)
    SUPER:_Found := (isFound == 1)
    IF atBOF == 1 .AND. atEOF == 0
        // Do not call self:GoTop() to avoid recursion
        SELF:_CheckError(ACE.AdsGotoTop(SELF:CurrentOrder),EG_READ)
        SUPER:_Bof := TRUE
    ENDIF
    IF SUPER:_Relations != NULL
        FOREACH dbrelinfo AS DbRelInfo IN SUPER:_Relations
            IF dbRelInfo:Child IS AdsRDD VAR ChildRDD
                ChildRDD:RecordMovement()
            ENDIF
        NEXT
    ENDIF
RETURN TRUE

    /// <inheritdoc />
VIRTUAL METHOD GoBottom() AS LOGIC
    SELF:_SynchronizeVODeletedFlag()
    SELF:_CheckError(ACE.AdsGotoBottom(SELF:CurrentOrder),EG_READ)
RETURN SELF:RecordMovement()

    /// <inheritdoc />
VIRTUAL METHOD GoTop() AS LOGIC
    SELF:_SynchronizeVODeletedFlag()
    SELF:_CheckError(ACE.AdsGotoTop(SELF:CurrentOrder),EG_READ)
RETURN SELF:RecordMovement()

    /// <inheritdoc />
VIRTUAL METHOD GoTo(lRec AS DWORD) AS LOGIC
    SELF:_CheckError(ACE.AdsGetRecordNum(SELF:_Table, ACE.ADS_IGNOREFILTERS, OUT VAR recordnum),EG_READ)
    IF recordnum == lRec
        SELF:_CheckError(ACE.AdsAtEOF(SELF:_Table, OUT VAR atEOF),EG_READ)
        SELF:_CheckError(ACE.AdsAtBOF(SELF:_Table, OUT VAR atBOF),EG_READ)
        IF atEOF== 0 .AND. atBOF == 0
            SELF:_CheckError(ACE.AdsWriteRecord(SELF:_Table),EG_WRITE)
            SELF:_CheckError(ACE.AdsRefreshRecord(SELF:_Table),EG_READ)
        ENDIF
    ELSE
        SELF:_CheckError(ACE.AdsGotoRecord(SELF:_Table, lRec),EG_READ)
    ENDIF
RETURN SELF:RecordMovement()

    /// <inheritdoc />
VIRTUAL METHOD GoToId(oRecnum AS OBJECT) AS LOGIC
    LOCAL recNum AS DWORD
    TRY
        recNum := System.Convert.ToUInt32(oRecnum)
    CATCH e AS Exception
        SELF:ADSERROR(ERDD.DATATYPE, XSharp.Gencode.EG_DataType, "GoToId",e:Message)
        RETURN FALSE
    END TRY
RETURN SELF:GoTo(recNum)

    /// <inheritdoc />
VIRTUAL METHOD Skip(lCount AS LONG) AS LOGIC
    LOCAL result AS DWORD
    LOCAL flag AS LOGIC
    SELF:_SynchronizeVODeletedFlag()
    IF lCount == 0
        SELF:_CheckError(ACE.AdsWriteRecord(SELF:_Table),EG_WRITE)
        result := ACE.AdsRefreshRecord(SELF:_Table)
    ELSE
        result := ACE.AdsSkip(SELF:CurrentOrder, lCount)
    ENDIF
    IF result != ACE.AE_NO_CURRENT_RECORD .AND. result != 0
        SELF:_CheckError(result,EG_READ)
    ENDIF
    flag := SELF:RecordMovement()
    IF lCount > 0
        SUPER:_Bof := FALSE
        RETURN flag
    ENDIF
    IF lCount < 0
        SUPER:_Eof := FALSE
    ENDIF
RETURN flag

    /// <inheritdoc />
VIRTUAL METHOD SkipFilter(lCount AS LONG) AS LOGIC
    // ADS has no difference with normal skip
RETURN SELF:Skip(lCount)

    /// <inheritdoc />
VIRTUAL METHOD SkipRaw(lCount AS LONG) AS LOGIC
    // ADS has no difference with normal skip
RETURN SELF:Skip(lCount)

    /// <inheritdoc />

INTERNAL METHOD _GetScope(hIndex AS System.IntPtr, usScopeOption AS WORD) AS OBJECT
    LOCAL aScope    AS CHAR[]
    LOCAL wBufLen   AS WORD
    LOCAL result AS DWORD
    aScope    := CHAR[]{(SELF:_MaxKeySize + 1)}
    wBufLen   := (WORD)(SELF:_MaxKeySize + 1)
    result := ACE.AdsGetScope(hIndex, usScopeOption, aScope, REF wBufLen)
    IF result == 0
        RETURN SELF:_Ansi2Unicode(aScope, wBufLen)
    ENDIF
    IF result != ACE.AE_NO_SCOPE
        SELF:_CheckError(result,EG_READ)
    ENDIF
    RETURN NULL

INTERNAL METHOD _SetScope(hIndex AS System.IntPtr, usScopeOption AS WORD, lClear AS LOGIC, oScope AS OBJECT ) AS OBJECT
    LOCAL oldVal AS OBJECT
    LOCAL result AS DWORD
    oldVal    := SELF:_GetScope(hIndex, usScopeOption)
    IF lClear .OR. oScope == NULL
        result := ACE.AdsClearScope(hIndex, usScopeOption)
    ELSE
        VAR str := oScope:ToString()
        IF !String.IsNullOrEmpty(str)
            result := ACE.AdsSetScope(hIndex, usScopeOption, str, (WORD)str:Length , ACE.ADS_STRINGKEY)
            SELF:_CheckError(result,EG_READ)
        ELSE
            result := ACE.AdsClearScope(hIndex, usScopeOption)
        ENDIF
    ENDIF
    RETURN oldVal

    #endregion
  #region Updates

  /// <inheritdoc />
VIRTUAL METHOD Flush() AS LOGIC
RETURN SELF:GoCold()

  /// <inheritdoc />
VIRTUAL METHOD Refresh() AS LOGIC
    SELF:_CheckError(ACE.AdsRefreshRecord(SELF:_Table),EG_READ)
RETURN SELF:RecordMovement()

    /// <inheritdoc />
VIRTUAL METHOD GoCold() AS LOGIC
    SELF:_CheckError(ACE.AdsWriteRecord(SELF:_Table),EG_READ)
RETURN SELF:RecordMovement()

    /// <inheritdoc />
VIRTUAL METHOD GoHot() AS LOGIC
    LOCAL dwRecNo AS DWORD
    LOCAL result AS DWORD
    //
    SELF:_CheckError(ACE.AdsGetTableOpenOptions(SELF:_Table, OUT VAR options),EG_WRITE)
    // GoHot must have lock when not exclusive
    IF !_HasFlag(options, ACE.ADS_EXCLUSIVE)
      // Only allowed when not Readonly
        IF _HasFlag(options,ACE.ADS_READONLY)
            SELF:ADSERROR(ERDD.READONLY, XSharp.Gencode.EG_READONLY)
        ENDIF
        SELF:_CheckError(ACE.AdsIsTableLocked(SELF:_Table, OUT VAR isTableLocked),EG_LOCK)
        IF isTableLocked == 0
            SELF:_CheckError(ACE.AdsIsRecordLocked(SELF:_Table, 0, OUT VAR isRecordLocked),EG_LOCK)
            IF isRecordLocked == 0
                dwRecNo := 0
                IF SELF:IsADT
                    result := ACE.AdsGetRecordCount(SELF:_Table, ACE.ADS_IGNOREFILTERS, OUT VAR numRecs)
                    IF result == 0
                        result := ACE.AdsGetRecordNum(SELF:_Table, ACE.ADS_IGNOREFILTERS, OUT dwRecNo)
                    ENDIF
                    IF result == 0 .AND. numRecs < dwRecNo .AND. ! SUPER:_Bof .AND. ! SUPER:_Eof
                        result := ACE.AdsLockRecord(SELF:_Table, 0)
                        IF result == 0 .OR. result == ACE.AE_TABLE_NOT_SHARED
                            RETURN TRUE
                        ENDIF
                        SELF:_CheckError(result,EG_LOCK)
                    ENDIF
                ENDIF
                SELF:ADSERROR(ERDD.UNLOCKED, XSharp.Gencode.EG_LOCK, XSharp.Severity.ES_ERROR)
            ENDIF
        ENDIF
    ENDIF
RETURN TRUE

    /// <inheritdoc />
VIRTUAL METHOD Zap() AS LOGIC
    SELF:_CheckError(ACE.AdsGetTableOpenOptions(SELF:_Table, OUT VAR options),EG_WRITE)
    // Only allowed when opened exclusively
    IF !_HasFlag(options, ACE.ADS_EXCLUSIVE)
        SELF:ADSERROR(ACE.AE_TABLE_NOT_EXCLUSIVE, EG_SHARED , "Zap")
    ENDIF
    // Only allowed when not Readonly
    IF _HasFlag(options, ACE.ADS_READONLY)
        SELF:ADSERROR(ERDD.READONLY, EG_READONLY, "Zap")
    ENDIF
    SELF:_CheckError(ACE.AdsZapTable(SELF:_Table),EG_WRITE)
RETURN SELF:GoTop()

    /// <inheritdoc />
VIRTUAL METHOD Append(fReleaseLocks AS LOGIC) AS LOGIC
    LOCAL result        AS DWORD
    IF fReleaseLocks
        SELF:_CheckError(ACE.AdsGetHandleType(SELF:_Table, OUT VAR handleType),EG_APPENDLOCK)
        IF handleType != ACE.ADS_CURSOR
            result := ACE.AdsIsTableLocked(SELF:_Table, OUT VAR isTableLocked)
            IF isTableLocked == 0
                result := ACE.AdsUnlockTable(SELF:_Table)
          // When Unlock fails because not shared or because not locked, then no problem
                IF result != ACE.AE_TABLE_NOT_SHARED .AND. result != ACE.AE_TABLE_NOT_LOCKED  .AND. result != 0
                    SELF:_CheckError(result,EG_SHARED)
                ENDIF
            ENDIF
        ENDIF
    ENDIF
    SELF:_CheckError(ACE.AdsAppendRecord(SELF:_Table),EG_APPENDLOCK)
    result := ACE.AdsLockRecord(SELF:_Table, 0)
    IF result != ACE.AE_TABLE_NOT_SHARED .AND. result != 0
        SELF:_CheckError(result,EG_SHARED)
    ENDIF
RETURN SELF:RecordMovement()

VIRTUAL METHOD Delete() AS LOGIC
    SELF:_CheckError(ACE.AdsDeleteRecord(SELF:_Table),EG_WRITE)
RETURN TRUE

VIRTUAL METHOD Recall() AS LOGIC
    SELF:_CheckError(ACE.AdsRecallRecord(SELF:_Table),EG_WRITE)
RETURN SELF:RecordMovement()

    #endregion

  #region Read and Write
METHOD GetValue(nFldPos AS INT) AS OBJECT
    VAR column   := (AdsColumn ) SELF:_Fields[nFldPos-1]
    RETURN column:GetValue()

METHOD Pack () AS LOGIC
    SELF:_CheckError(ACE.AdsGetTableOpenOptions(SELF:_Table, OUT VAR options),EG_READ)
    IF !_HasFlag(options, ACE.ADS_EXCLUSIVE)
        SELF:ADSERROR(ACE.AE_TABLE_NOT_EXCLUSIVE, EG_SHARED, "Pack")
        RETURN FALSE
    ENDIF
    IF _HasFlag(options , ACE.ADS_READONLY)
        SELF:ADSERROR(ERDD_READONLY, EG_READONLY, "Pack")
        RETURN FALSE
    ENDIF
    SELF:_CheckError(ACE.AdsPackTable(SELF:_Table),EG_WRITE)
RETURN SELF:GoTop()

METHOD PutValue(nFldPos AS INT, oValue AS OBJECT) AS LOGIC
    IF ! SELF:GoHot()
        RETURN FALSE
    ENDIF
    VAR column := (AdsColumn) SELF:_Fields[nFldPos-1]
    RETURN column:PutValue(oValue)
    #endregion

  #region Properties
PROPERTY ACEConnectionHandle    AS IntPtr GET _Connection
PROPERTY ACEIndexHandle         AS IntPtr GET _Index
PROPERTY ACETableHandle         AS IntPtr GET _Table
PRIVATE PROPERTY CurrentOrder AS System.IntPtr
    GET
        IF SELF:_Index != System.IntPtr.Zero
            RETURN SELF:_Index
        ENDIF
        RETURN SELF:_Table
    END GET
END PROPERTY

  /// <inheritdoc />
PROPERTY BOF AS LOGIC GET SUPER:_Bof
  /// <inheritdoc />
PROPERTY EOF AS LOGIC GET SUPER:_Eof
  /// <inheritdoc />
PROPERTY Found AS LOGIC GET SUPER:_Found
INTERNAL PROPERTY IsADT AS LOGIC GET _TableType == ACE.ADS_ADT
VIRTUAL PROPERTY Driver AS STRING GET _Driver

      /// <inheritdoc />
PROPERTY Deleted AS LOGIC
    GET
        LOCAL result AS DWORD
        result := ACE.AdsIsRecordDeleted(SELF:_Table, OUT VAR isDeleted)
        IF result == ACE.AE_NO_CURRENT_RECORD
            IF SELF:_Eof
                RETURN FALSE
            ELSE
                RETURN TRUE
            ENDIF
        ELSEIF result != 0
            SELF:_CheckError(result,EG_READ)
        ENDIF
        RETURN isDeleted != 0

    END GET
END PROPERTY

PROPERTY Reccount AS LONG
    GET
        SELF:_CheckError(ACE.AdsGetRecordCount(SELF:_Table, ACE.ADS_IGNOREFILTERS, OUT VAR dwCount),EG_READ)
        RETURN (LONG) dwCount
    END GET
END PROPERTY
PROPERTY RecNo AS LONG GET (LONG) SELF:_Recno
PROPERTY _RecNo AS DWORD
    GET
        LOCAL result AS DWORD
        result := ACE.AdsGetRecordNum(SELF:_Table, ACE.ADS_IGNOREFILTERS, OUT VAR dwRecno)
        IF result == ACE.AE_NO_CURRENT_RECORD
            dwRecno := 0
        ELSEIF result != 0
            SELF:_CheckError(result,EG_READ)
        ENDIF
        RETURN dwRecno
    END GET
END PROPERTY

#endregion

    #region Locking

    /// <inheritdoc />
VIRTUAL METHOD Lock(lockInfo REF DbLockInfo) AS LOGIC
    LOCAL lRecno := 0 AS DWORD
    LOCAL result := 0 AS DWORD
      //
    lockInfo:Result := FALSE
    IF lockInfo:recID == NULL
        lRecno := SELF:_Recno
    ELSE
        TRY
            lRecno := System.Convert.ToUInt32(lockInfo:RecId)
        CATCH e AS Exception
            SELF:ADSERROR(ERDD.DATATYPE, XSharp.Gencode.EG_DATATYPE, "Lock", e:Message)
        END TRY
    ENDIF
    IF lockInfo:@@Method == DBLockInfo.LockMethod.File
        result := ACE.AdsLockTable(SELF:_Table)
//      SELF:_CheckError(result)
    ELSE
        SELF:_CheckError(ACE.AdsGetHandleType(SELF:_Table, OUT VAR handleType),EG_LOCK)
        IF lRecno == 0 .AND. handleType != ACE.ADS_CURSOR
            SELF:_CheckError(ACE.AdsIsTableLocked(SELF:_Table, OUT VAR isLocked),EG_LOCK)
            IF isLocked == 0
                result := ACE.AdsUnlockTable(SELF:_Table)
                IF result != ACE.AE_TABLE_NOT_LOCKED  .AND. result != ACE.AE_TABLE_NOT_SHARED .AND. result != 0
                    SELF:_CheckError(result,EG_LOCK)
                ENDIF
            ENDIF
        ENDIF
        result  := ACE.AdsLockRecord(SELF:_Table, lRecno)
    ENDIF

    IF result != ACE.AE_TABLE_NOT_SHARED .AND. result != 0
        SELF:_CheckError(result,EG_SHARED)
    ENDIF
    lockInfo:Result := TRUE
RETURN TRUE

      /// <inheritdoc />
VIRTUAL METHOD Unlock(recordID AS OBJECT) AS LOGIC
    LOCAL result AS DWORD
    LOCAL dwRecno := 0 AS DWORD
    TRY
        dwRecno := System.Convert.ToUInt32(recordID)
    CATCH e AS Exception
        SELF:ADSERROR(ERDD.DATATYPE, XSharp.Gencode.EG_DATATYPE, "Unlock",e:Message)
    END TRY
    IF dwRecno == 0
        result := ACE.AdsUnlockTable(SELF:_Table)
    ELSE
        result := ACE.AdsUnlockRecord(SELF:_Table, dwRecno)
    ENDIF
    IF result != ACE.AE_TABLE_NOT_SHARED .AND. result != ACE.AE_TABLE_NOT_LOCKED .AND. result != 0
        SELF:_CheckError(result)
    ENDIF
RETURN TRUE

      #endregion

    #region Filters
    /// <inheritdoc />
VIRTUAL METHOD ClearFilter() AS LOGIC
    LOCAL result AS DWORD
    IF SELF:_Table != System.IntPtr.Zero
        // Clear normal filter
        result := ACE.AdsClearFilter(SELF:_Table)
        IF result != ACE.AE_NO_FILTER .AND. result != 0
            SELF:_CheckError(result)
        ENDIF
        // Clear optimized filter
        result := ACE.AdsClearAOF(SELF:_Table)
        IF result != ACE.AE_NO_FILTER .AND. result != 0
            SELF:_CheckError(result)
        ENDIF
    ENDIF
    SELF:_FilterInfo := DbFilterInfo{}
RETURN TRUE

METHOD SetFieldExtent( fieldCount AS LONG ) AS LOGIC
    SELF:_HasMemo := FALSE
RETURN SUPER:SetFieldExtent(fieldCount)

      /// <inheritdoc />
VIRTUAL METHOD SetFilter(fi AS DBFILTERINFO) AS LOGIC
    LOCAL result AS DWORD
      // Get the current date format so we can handle literal dates in the filter
    SELF:_SynchronizeSettings()
    IF String.IsNullOrEmpty(fi:FilterText)
        // clear filter
        // Ignore "No filter" error
        result := ACE.AdsClearFilter(SELF:_Table)
        IF result != ACE.AE_NO_FILTER
            SELF:_CheckError(result)
        ENDIF
        result := ACE.AdsClearAOF(SELF:_Table)
        IF result != ACE.AE_NO_FILTER
            SELF:_CheckError(result)
        ENDIF
    ELSE
        IF RuntimeState.Optimize
            SELF:_CheckError(ACE.AdsSetAOF(SELF:_Table, fi:FilterText, (WORD) (ACE.ADS_RESOLVE_DYNAMIC | ACE.ADS_DYNAMIC_AOF)))
        ELSE
            SELF:_CheckError(ACE.AdsSetFilter(SELF:_Table, fi:FilterText))
        ENDIF
    ENDIF
    fi:Active := TRUE
    SELF:_FilterInfo := fi
RETURN TRUE

      #endregion
    #region Relations
    /// <inheritdoc />
VIRTUAL METHOD ClearRel() AS LOGIC
    IF SELF:_Table != System.IntPtr.Zero
        SELF:_CheckError(ACE.AdsClearRelation(SELF:_Table))
    ENDIF
RETURN TRUE

      /// <inheritdoc />
VIRTUAL METHOD SetRel(relinfo AS DBRELINFO) AS LOGIC
      // Needs a better solution of course. SELF:_Driver currently returns the fullname of the class, while Driver property is not implemented yet
    IF relinfo:Child:Driver != SELF:Driver
        SELF:ADSERROR(ERDD.UNSUPPORTED, XSharp.Gencode.EG_UNSUPPORTED, "SetRel", "Related workareas must be opened with the same driver.")
        RETURN FALSE
    ENDIF
    LOCAL child := (ADSRDD) relInfo:Child AS ADSRDD
    SELF:_CheckError(ACE.AdsSetRelation(SELF:_Table, child:ACEIndexHandle, relinfo:Key))
RETURN TRUE

      #endregion

    #region Info
    /// <inheritdoc />
VIRTUAL METHOD FieldInfo(uiPos AS LONG, uiOrdinal AS INT, oNewValue AS OBJECT) AS OBJECT
    LOCAL result AS DWORD
    SWITCH uiOrdinal
    CASE DBS_BLOB_TYPE
        SELF:_CheckError(ACE.AdsGetFieldType(SELF:_Table, (DWORD)uiPos  ,  OUT VAR fieldType))
        SWITCH fieldType
        CASE ACE.ADS_MEMO
        CASE ACE.ADS_BINARY
        CASE ACE.ADS_IMAGE
        CASE ACE.ADS_NMEMO
            RETURN "?"
        END SWITCH
    CASE DBS_BLOB_LEN
        IF SUPER:_fields[uiPos-1]:fieldType != DBFieldType.Memo
            RETURN -1
        ELSE
            result := ACE.AdsGetMemoLength(SELF:_Table, (DWORD)uiPos , OUT VAR length)
            IF result == ACE.AE_INVALID_FIELD_TYPE
                RETURN -1
            ENDIF
            IF result != 0
                SELF:_CheckError(result)
            ENDIF
            RETURN length
        ENDIF

    CASE DBS_BLOB_POINTER
        RETURN NULL

    END SWITCH
RETURN SUPER:FieldInfo(uiPos, uiOrdinal, oNewValue)

VIRTUAL METHOD RecInfo(uiOrdinal AS LONG, iRecID AS OBJECT, oNewValue AS OBJECT) AS OBJECT
    LOCAL dwRecno AS DWORD
      //
    SWITCH (DBRecordInfo) uiOrdinal
    CASE DBRecordInfo.DBRI_LOCKED
        TRY
            dwRecno := Convert.ToUInt32(iRecID)
/*          IF dwRecNo == 0
                dwRecNo := SELF:RecNo // looks like the dll func does this anyway
            END IF*/
        CATCH
            SELF:ADSERROR(ERDD_DATATYPE, EG_DATATYPE, "RecInfo")
            RETURN FALSE
        END TRY
        _CheckError(ACE.AdsIsRecordLocked(SELF:_Table, dwRecno, OUT VAR locked))
        RETURN locked != 0

    CASE DBRecordInfo.DBRI_RECSIZE
        RETURN SELF:_RecordLength

    CASE DBRecordInfo.DBRI_UPDATED
        SELF:_CheckError(ACE.AdsRefreshRecord(SELF:_Table))
        RETURN NULL

    CASE DBRecordInfo.DBRI_RECNO
        RETURN SELF:RecNo
    OTHERWISE
        SELF:ADSERROR(ERDD_UNSUPPORTED, EG_UNSUPPORTED, "RecInfo")
    END SWITCH
RETURN SUPER:RecInfo( uiOrdinal, iRecID, oNewValue)

      /// <inheritdoc />
VIRTUAL METHOD Info(uiOrdinal AS LONG, oNewValue AS OBJECT) AS OBJECT
    LOCAL result AS DWORD
    SWITCH (DbInfo)uiOrdinal
    CASE DbInfo.DBI_ISDBF
        RETURN !SELF:IsADT

    CASE DbInfo.DBI_CANPUTREC
        RETURN FALSE

    CASE DbInfo.DBI_GETRECSIZE
        RETURN SELF:_RecordLength

    CASE DbInfo.DBI_TABLEEXT
        IF SELF:IsADT
            RETURN ".ADT"
        ELSE
            RETURN ".DBF"
        ENDIF

    CASE DbInfo.DBI_GETHEADERSIZE
        LOCAL dwValue := 0 AS LONG
        dwValue := SELF:FieldCount * 32 + 32
        RETURN (LONG) dwValue

    CASE DbInfo.DBI_LASTUPDATE
        LOCAL aDate AS CHAR[]
        LOCAL DateLen AS WORD

        aDate := CHAR[]{ACE.ADS_MAX_DATEMASK+1}
        DateLen := (WORD) aDate:Length
        SELF:_CheckError(ACE.AdsSetDateFormat("MM/DD/YY"))
        SELF:_CheckError(ACE.AdsGetLastTableUpdate(SELF:_Table, aDate, REF DateLen))
        SELF:_CheckError(ACEUNPUB.AdsConvertStringToJulian(aDate, DateLen, OUT VAR julDate))
        SELF:_SynchronizeSettings()
        RETURN (LONG)julDate

    CASE DbInfo.DBI_GETLOCKARRAY
    CASE DbInfo.DBI_LOCKCOUNT
        SELF:_CheckError(ACE.AdsGetNumLocks(SELF:_Table, OUT VAR numLocks))
        IF (uiOrdinal == DbInfo.DBI_LOCKCOUNT)
            RETURN numLocks
        ENDIF
        IF numLocks > 0
            IF SELF:_aRlocks == NULL .OR. SELF:_aRlocks:Length < numLocks
                SELF:_aRlocks := DWORD[]{numLocks}
            ENDIF
            SELF:_CheckError(ACE.AdsGetAllLocks(SELF:_Table, SELF:_aRlocks, REF numLocks))
            RETURN SELF:_aRlocks
        ELSE
            RETURN NULL
        ENDIF
    CASE DbInfo.DBI_ISFLOCK
        SELF:_CheckError(ACE.AdsIsTableLocked(SELF:_Table, OUT VAR isLocked))
        RETURN isLocked != 0

    CASE DbInfo.DBI_FILEHANDLE
        RETURN IntPtr.Zero

    CASE DbInfo.DBI_ISANSI
        RETURN FALSE

    CASE DbInfo.DBI_FOUND
        SELF:_CheckError(ACE.AdsIsFound(SELF:_Table, OUT VAR isFound))
        RETURN isFound != 0

    CASE DbInfo.DBI_SHARED
        SELF:_CheckError(ACE.AdsGetTableOpenOptions(SELF:_Table, OUT VAR options))
        RETURN !_HasFlag(options,ACE.ADS_EXCLUSIVE)

    CASE DbInfo.DBI_MEMOEXT
        SWITCH SELF:_TableType
        CASE ACE.ADS_ADT
            RETURN ".ADM"
        CASE ACE.ADS_CDX
        CASE ACE.ADS_VFP
            RETURN ".FPT"
        OTHERWISE
            RETURN ".DBT"
        END SWITCH

    CASE DbInfo.DBI_RDD_VERSION
        RETURN System.Reflection.Assembly.GetExecutingAssembly():GetName():Version:ToString()

    CASE DbInfo.DBI_GET_ACE_TABLE_HANDLE
        RETURN SELF:_Table

    CASE DbInfo.DBI_MEMOBLOCKSIZE
        LOCAL memoBlockSize AS WORD
        result := ACE.AdsGetMemoBlockSize(SELF:_Table, OUT memoBlockSize)
        IF result != ACE.AE_NO_MEMO_FILE
            RETURN 0
        ELSEIF result != 0
            SELF:_CheckError(result)
        ENDIF
        RETURN memoBlockSize

    CASE DbInfo.DBI_CODEPAGE
        RETURN SELF:_Encoding:CodePage

    CASE DbInfo.DBI_DB_VERSION
        RETURN 0

    CASE DbInfo.DBI_FULLPATH
        RETURN SUPER:_FileName

    CASE DbInfo.DBI_SETDELIMITER
    CASE DbInfo.DBI_VALIDBUFFER
    CASE DbInfo.DBI_LOCKOFFSET
    CASE DbInfo.DBI_MEMOHANDLE
    CASE DbInfo.DBI_NEWINDEXLOCK
    CASE DbInfo.DBI_MEMOFIELD
        RETURN NULL
    CASE DbInfo.DBI_READONLY
    CASE DbInfo.DBI_CHILDCOUNT
    CASE DbInfo.DBI_BOF
    CASE DbInfo.DBI_EOF
    CASE DbInfo.DBI_DBFILTER
    CASE DbInfo.DBI_FCOUNT
    CASE DbInfo.DBI_ALIAS
    CASE DbInfo.DBI_GETSCOPE
        RETURN SUPER:Info(uiOrdinal,oNewValue )

    CASE DbInfo.BLOB_INFO_HANDLE
    CASE DbInfo.BLOB_FILE_RECOVER
    CASE DbInfo.BLOB_FILE_INTEGRITY
    CASE DbInfo.BLOB_OFFSET
    CASE DbInfo.BLOB_POINTER
    CASE DbInfo.BLOB_LEN
    CASE DbInfo.BLOB_TYPE
    CASE DbInfo.BLOB_EXPORT
    CASE DbInfo.BLOB_ROOT_UNLOCK
    CASE DbInfo.BLOB_ROOT_PUT
    CASE DbInfo.BLOB_ROOT_GET
    CASE DbInfo.BLOB_ROOT_LOCK
    CASE DbInfo.BLOB_IMPORT
    CASE DbInfo.BLOB_DIRECT_PUT
    CASE DbInfo.BLOB_DIRECT_GET
    CASE DbInfo.BLOB_GET
    CASE DbInfo.BLOB_DIRECT_EXPORT
    CASE DbInfo.BLOB_DIRECT_IMPORT
    CASE DbInfo.BLOB_NMODE
    CASE DbInfo.BLOB_EXPORT_APPEND
    CASE DbInfo.BLOB_EXPORT_OVERWRITE
        RETURN NULL
    CASE DbInfo.DBI_RL_AND
    CASE DbInfo.DBI_RL_CLEAR
    CASE DbInfo.DBI_RL_COUNT
    CASE DbInfo.DBI_RL_DESTROY
    CASE DbInfo.DBI_RL_EXFILTER
    CASE DbInfo.DBI_RL_GETFILTER
    CASE DbInfo.DBI_RL_HASMAYBE
    CASE DbInfo.DBI_RL_LEN
    CASE DbInfo.DBI_RL_MAYBEEVAL
    CASE DbInfo.DBI_RL_NEW
    CASE DbInfo.DBI_RL_NEWDUP
    CASE DbInfo.DBI_RL_NEWQUERY
    CASE DbInfo.DBI_RL_NEXTRECNO
    CASE DbInfo.DBI_RL_NOT
    CASE DbInfo.DBI_RL_OR
    CASE DbInfo.DBI_RL_PREVRECNO
    CASE DbInfo.DBI_RL_SET
    CASE DbInfo.DBI_RL_SETFILTER
    CASE DbInfo.DBI_RL_TEST
        RETURN NULL
    END SWITCH
RETURN SUPER:Info(uiOrdinal, oNewValue)

      #endregion

    #region Unsupported
    /// <inheritdoc />

INTERNAL METHOD Unsupported(strFunctionName AS STRING) AS LOGIC
    SELF:ADSERROR(ERDD.UNSUPPORTED, XSharp.Gencode.EG_UnSupported, strFunctionName)
RETURN FALSE

      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD AppendLock(uiMode AS DbLockMode) AS LOGIC
RETURN SELF:Unsupported("AppendLock")

      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD BlobInfo(uiPos AS DWORD, uiOrdinal AS DWORD) AS OBJECT
    SELF:Unsupported("BlobInfo")
RETURN NULL

      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD ForceRel() AS LOGIC
RETURN SELF:Unsupported("ForceRel")

      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD GetRec() AS BYTE[]
    SELF:Unsupported("GetRec")
RETURN NULL

      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD GetValueFile(nFldPos AS INT, cFileName AS STRING) AS LOGIC
    SELF:Unsupported("GetValueFile")
RETURN FALSE
      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD HeaderLock(uiMode AS DbLockMode) AS LOGIC
RETURN SELF:Unsupported("HeaderLock")
      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD PutRec(aRec AS BYTE[])			AS LOGIC
RETURN SELF:Unsupported("PutRec")
      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD PutValueFile(nFldPos AS INT, cFileName AS STRING) AS LOGIC
    SELF:Unsupported("PutValueFile")
RETURN FALSE

      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD Sort(info AS DbSortInfo) AS LOGIC
RETURN SELF:Unsupported("Sort")
      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD Trans(info AS DbTransInfo) AS LOGIC
RETURN SELF:Unsupported("Trans")
      /// <summary>This method is not supported by the AdsRDD class </summary>
VIRTUAL METHOD TransRec(info AS DbTransInfo) AS LOGIC
RETURN SELF:Unsupported("TransRec")

      #endregion
END CLASS


No need to have `letrec` when we can grab the enclosing lambda to a variable :-)

-- see also http://cs.ioc.ee/~james/papers/Relative_Monads.pdf
-- "Monads Need Not Be Endofunctors"
-- which references Fiore, Plotkin and Turi's work
-- Hamana also has much to say (http://www.cs.gunma-u.ac.jp/~hamana/)
--

-- TODO:
--  o add another parameter (VAR | FUN | NORMAL)
--  o Arg is VAR
--  o Up is VAR -> VAR
--  o Enc is VAR -> FUN
--  o introduce constructor Enc, signifying the function value enclosing the corresponding variable
--    so we can write fak as Lm `fak $ If 0tm == 0 then 1 else App (Enc 0tm) (0tm - 1)

import "Inventory.prg"

data Tm :: Inventory Tag ~> * where
  -- lambda
  Lm :: Label t -> Tm [inv; t]i -> Tm inv
  -- argument access
  Arg :: Tm [ups; t]i
  Up :: Tm [ups; t]i -> Tm [ups; t, t']i
  -- application
  App :: Tm inv -> Tm inv -> Tm inv
  -- recursive let
  Let :: Label t -> Tm [inv; t]i ->  Tm [inv; t]i -> Tm inv
 deriving syntax (tm) Nat(Arg, Up) LeftPair(App)

-- identity function
--
identity :: Tm []i
identity = Lm `a 0tm

-- ($) = \f . \a . f a
--
dollar :: Tm []i
dollar = Lm `f (Lm `a (1tm, 0tm)tm)

-- let a = a in a
leta :: Tm []i
leta = Let `a 0tm 0tm

-- see also http://cs.ioc.ee/~james/papers/Relative_Monads.pdf
-- "Monads Need Not Be Endofunctors"
-- which references Fiore, Plotkin and Turi's work
-- Hamana also has much to say (http://www.cs.gunma-u.ac.jp/~hamana/)
--

import "Inventory.prg"

data Tm :: Inventory Tag ~> * where
  -- lambda
  Lm :: Label t -> Tm [inv; t]i -> Tm inv
  -- argument access
  Arg :: Tm [ups; t]i
  Up :: Tm [ups; t]i -> Tm [ups; t, t']i
  -- application
  App :: Tm inv -> Tm inv -> Tm inv
  -- recursive let
  Let :: Label t -> Tm [inv; t]i ->  Tm [inv; t]i -> Tm inv
 deriving syntax (tm) Nat(Arg, Up) LeftPair(App)

-- identity function
--
identity :: Tm []i
identity = Lm `a 0tm

-- ($) = \f . \a . f a
--
dollar :: Tm []i
dollar = Lm `f (Lm `a (1tm, 0tm)tm)

-- let a = a in a
leta :: Tm []i
leta = Let `a 0tm 0tm


QtScriptTools: data for code generation updated

Modified:
codegen/QtScriptTools/QScriptEngineDebugger.prg

%%
%% Qt5xHb - Bindings libraries for Harbour/xHarbour and Qt Framework 5
%%
%% Copyright (C) 2018 Marcos Antonio Gambeta <marcosgambeta AT outlook DOT com>
%%

$header

#include "hbclass.ch"

#ifndef QT5XHB_NO_REQUESTS
REQUEST QACTION
REQUEST QMENU
REQUEST QTOOLBAR
REQUEST QMAINWINDOW
REQUEST QWIDGET
#endif

CLASS QScriptEngineDebugger INHERIT QObject

   METHOD new
   METHOD delete
   METHOD action
   METHOD attachTo
   METHOD autoShowStandardWindow
   METHOD createStandardMenu
   METHOD createStandardToolBar
   METHOD detach
   METHOD setAutoShowStandardWindow
   METHOD standardWindow
   METHOD state
   METHOD widget

   METHOD onEvaluationResumed
   METHOD onEvaluationSuspended

   DESTRUCTOR destroyObject

END CLASS

$destructor

#pragma BEGINDUMP

$includes

#include <QAction>

$prototype=QScriptEngineDebugger(QObject * parent = 0)
$constructor=|new|QObject *=0

$deleteMethod

$prototype=QAction * action(DebuggerAction action) const
$method=|QAction *|action|QScriptEngineDebugger::DebuggerAction

$prototype=void attachTo(QScriptEngine * engine)
$method=|void|attachTo|QScriptEngine *

$prototype=bool autoShowStandardWindow() const
$method=|bool|autoShowStandardWindow|

$prototype=QMenu * createStandardMenu(QWidget * parent = 0)
$method=|QMenu *|createStandardMenu|QWidget *=0

$prototype=QToolBar * createStandardToolBar(QWidget * parent = 0)
$method=|QToolBar *|createStandardToolBar|QWidget *=0

$prototype=void detach()
$method=|void|detach|

$prototype=void setAutoShowStandardWindow(bool autoShow)
$method=|void|setAutoShowStandardWindow|bool

$prototype=QMainWindow * standardWindow() const
$method=|QMainWindow *|standardWindow|

$prototype=DebuggerState state() const
$method=|QScriptEngineDebugger::DebuggerState|state|

$prototype=QWidget * widget(DebuggerWidget widget) const
$method=|QWidget *|widget|QScriptEngineDebugger::DebuggerWidget

#pragma ENDDUMP

%%
%% Qt5xHb - Bindings libraries for Harbour/xHarbour and Qt Framework 5
%%
%% Copyright (C) 2018 Marcos Antonio Gambeta <marcosgambeta AT outlook DOT com>
%%

$header

#include "hbclass.ch"

#ifndef QT5XHB_NO_REQUESTS
REQUEST QACTION
REQUEST QMENU
REQUEST QTOOLBAR
REQUEST QMAINWINDOW
REQUEST QWIDGET
#endif

CLASS QScriptEngineDebugger INHERIT QObject

   METHOD new
   METHOD delete
   METHOD action
   METHOD attachTo
   METHOD autoShowStandardWindow
   METHOD createStandardMenu
   METHOD createStandardToolBar
   METHOD detach
   METHOD setAutoShowStandardWindow
   METHOD standardWindow
   METHOD state
   METHOD widget

   METHOD onEvaluationResumed
   METHOD onEvaluationSuspended

   DESTRUCTOR destroyObject

END CLASS

$destructor

#pragma BEGINDUMP

$includes

$prototype=QScriptEngineDebugger(QObject * parent = 0)
$constructor=|new|QObject *=0

$deleteMethod

$prototype=QAction * action(DebuggerAction action) const
$method=|QAction *|action|QScriptEngineDebugger::DebuggerAction

$prototype=void attachTo(QScriptEngine * engine)
$method=|void|attachTo|QScriptEngine *

$prototype=bool autoShowStandardWindow() const
$method=|bool|autoShowStandardWindow|

$prototype=QMenu * createStandardMenu(QWidget * parent = 0)
$method=|QMenu *|createStandardMenu|QWidget *=0

$prototype=QToolBar * createStandardToolBar(QWidget * parent = 0)
$method=|QToolBar *|createStandardToolBar|QWidget *=0

$prototype=void detach()
$method=|void|detach|

$prototype=void setAutoShowStandardWindow(bool autoShow)
$method=|void|setAutoShowStandardWindow|bool

$prototype=QMainWindow * standardWindow() const
$method=|QMainWindow *|standardWindow|

$prototype=DebuggerState state() const
$method=|QScriptEngineDebugger::DebuggerState|state|

$prototype=QWidget * widget(DebuggerWidget widget) const
$method=|QWidget *|widget|QScriptEngineDebugger::DebuggerWidget

#pragma ENDDUMP