[RDD] Fixes #579 for SetDeleted(TRUE)

[RDD] Fixes #579 for SetDeleted(TRUE)

//
// 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
USING System.Collections.Generic
USING System.Diagnostics
USING System.Globalization
USING System.IO
USING System.Reflection
USING System.Text
USING System.Threading
USING XSharp.RDD.Enums
USING XSharp.RDD.Support
USING STATIC XSharp.Conversions
BEGIN NAMESPACE XSharp.RDD.CDX

    INTERNAL DELEGATE CompareFunc(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG

    [DebuggerDisplay("Tag: {OrderName}, Key: {Expression}, For: {Condition}")];
    INTERNAL SEALED PARTIAL CLASS CdxTag
        #region constants
        PRIVATE CONST MAX_KEY_LEN       := 240  AS WORD
        #endregion
        #region fields
        PRIVATE _Encoding AS Encoding
        PRIVATE _Hot AS LOGIC
        PRIVATE _Conditional AS LOGIC
        PRIVATE _Descending AS LOGIC
        PRIVATE _Unique AS LOGIC
        PRIVATE _Custom AS LOGIC
        PRIVATE _SingleField AS LONG            // 0 based FieldIndex
        PRIVATE _SourceIndex AS LONG
        PRIVATE _KeyCodeBlock AS ICodeblock
        PRIVATE _ForCodeBlock AS ICodeblock
        PRIVATE _KeyExpr AS STRING
        PRIVATE _ForExpr AS STRING
        PRIVATE _currentvalue AS RddKeyData
        INTERNAL _newvalue     AS RddKeyData
        PRIVATE _newKeyLen AS LONG
        PRIVATE _KeyExprType AS LONG
        PRIVATE _keySize AS WORD
        PRIVATE _sourcekeySize AS WORD
        PRIVATE _rootPage AS LONG
        PRIVATE _ordCondInfo AS DbOrderCondInfo
        INTERNAL _orderName AS STRING
        INTERNAL _Ansi AS LOGIC
        // Scopes
        PRIVATE DIM _Scopes[2] AS ScopeInfo
        PRIVATE _scopeEmpty    AS LOGIC
        // siblings
        PRIVATE _oRdd   AS DBFCDX
        PRIVATE _Header AS CdxTagHeader

        PRIVATE _stack          AS CdxPageStack
        INTERNAL __Compare       AS CompareFunc

        PRIVATE _bag            AS CdxOrderBag
        PRIVATE getKeyValue     AS ValueBlock       // Delegate to calculate the key
        PRIVATE _Collation      AS VfpCollation
        PRIVATE _Valid          AS LOGIC

#endregion

#region Properties
        INTERNAL PROPERTY Binary            AS LOGIC AUTO
        INTERNAL PROPERTY IsOpen            AS LOGIC AUTO
        INTERNAL PROPERTY Collation         AS VfpCollation GET _Collation
        INTERNAL PROPERTY Expression        AS STRING GET _KeyExpr
        INTERNAL PROPERTY Encoding          AS Encoding GET _Encoding
        INTERNAL PROPERTY RDD               AS DBFCDX GET _oRdd
        INTERNAL PROPERTY Condition         AS STRING GET _ForExpr
        INTERNAL PROPERTY OrderName         AS STRING GET _orderName
	    INTERNAL PROPERTY Shared 	        AS LOGIC GET _bag:Shared
        INTERNAL PROPERTY _RecNo 	        AS LONG GET _oRdd:RecNo
        INTERNAL PROPERTY FileName 	        AS STRING GET _bag:FullPath
        INTERNAL PROPERTY OrderBag          AS CdxOrderBag GET SELF:_bag
        INTERNAL PROPERTY Page              AS Int32 AUTO
        INTERNAL PROPERTY Descending        AS LOGIC GET _Descending SET _Descending := value
        INTERNAL PROPERTY IsConditional     AS LOGIC GET Options:HasFlag(CdxOptions.HasFor)
        INTERNAL PROPERTY IsHot             AS LOGIC GET _Hot
        INTERNAL PROPERTY Header            AS CdxTagHeader GET _Header
        INTERNAL PROPERTY KeyType        	AS INT GET SELF:_KeyExprType
        INTERNAL PROPERTY KeyCodeBlock      AS ICodeblock GET _KeyCodeBlock
        INTERNAL PROPERTY KeyLength         AS WORD GET SELF:_keySize
        INTERNAL PROPERTY SourceKeyLength   AS WORD GET SELF:_sourcekeySize
        INTERNAL PROPERTY Partial        	AS LOGIC GET SELF:Custom
        INTERNAL PROPERTY Conditional       AS LOGIC GET SELF:_Conditional
        INTERNAL PROPERTY Custom         	AS LOGIC GET SELF:_Custom
        INTERNAL PROPERTY Unique         	AS LOGIC GET SELF:_Unique
        INTERNAL PROPERTY Signature      	AS BYTE AUTO
        INTERNAL PROPERTY Options        	AS CdxOptions AUTO
        INTERNAL PROPERTY LockOffSet     	AS LONG AUTO
        INTERNAL PROPERTY CurrentStack      AS CdxStackEntry GET  SELF:_stack:Top
        INTERNAL PROPERTY RootPage          AS LONG AUTO
        INTERNAL PROPERTY Stack             AS CdxPageStack GET _stack

        // Scopes
        INTERNAL PROPERTY HasTopScope       AS LOGIC    GET _Scopes[SELF:TopScopeNo]:IsSet
        INTERNAL PROPERTY TopScope          AS OBJECT   GET _Scopes[SELF:TopScopeNo]:Value
        INTERNAL PROPERTY HasBottomScope    AS LOGIC    GET _Scopes[SELF:BottomScopeNo]:IsSet
        INTERNAL PROPERTY BottomScope       AS OBJECT   GET _Scopes[SELF:BottomScopeNo]:Value
        INTERNAL PROPERTY HasScope          AS LOGIC    GET _Scopes[0]:IsSet .OR. _Scopes[1]:IsSet
        PRIVATE  PROPERTY TopScopeNo        AS LONG     GET IIF(SELF:Descending, BOTTOMSCOPE, TOPSCOPE)
        PRIVATE  PROPERTY BottomScopeNo     AS LONG     GET IIF(SELF:Descending, TOPSCOPE, BOTTOMSCOPE)

#endregion

        PRIVATE METHOD _InitFields(oBag AS CdxOrderBag) AS VOID
            SELF:_bag           := oBag
            SELF:_oRdd          := oBag:_oRdd
            SELF:_stack         := CdxPageStack{SELF}
            SELF:_Encoding      := _oRdd:_Encoding

            SELF:_SingleField   := -1
            SELF:_currentvalue := RddKeyData{MAX_KEY_LEN}
            SELF:_newvalue     := RddKeyData{MAX_KEY_LEN}

        // Constructor for Creation of tags
        INTERNAL CONSTRUCTOR (oBag AS CdxOrderBag)
	        SUPER()
            SELF:_InitFields(oBag)

        // Constructor for Opening of tags
        INTERNAL CONSTRUCTOR (oBag AS CdxOrderBag, nPage AS Int32, cName AS STRING)
	        SUPER()
            SELF:_InitFields(oBag)
            SELF:_orderName := cName
            SELF:Page := nPage
            SELF:_Header := CdxTagHeader{oBag, nPage,SELF:OrderName, SELF}
            SELF:_bag:SetPage(SELF:_Header)
            SELF:IsOpen := SELF:Open()

         INTERNAL METHOD ReadHeader AS VOID
            SELF:_Header:Read()
            SELF:_keySize       := SELF:_Header:KeySize
            SELF:Options        := SELF:_Header:Options
            SELF:Signature      := SELF:_Header:Signature
            SELF:_Descending    := SELF:_Header:Descending
            SELF:_rootPage      := SELF:_Header:RootPage
            SELF:_KeyExpr       := SELF:_Header:KeyExpression
            SELF:_ForExpr       := SELF:_Header:ForExpression
            SELF:_Unique        := SELF:Options:HasFlag(CdxOptions.Unique)
            SELF:_Custom        := SELF:Options:HasFlag(CdxOptions.Custom)
            SELF:_Conditional   := SELF:Options:HasFlag(CdxOptions.HasFor)
            RETURN

	    INTERNAL METHOD Open() AS LOGIC
            SELF:_oRdd:GoTo(1)
            SELF:_Hot := FALSE
            SELF:ClearStack()
            SELF:ReadHeader()
            IF !String.IsNullOrEmpty(SELF:_Header:VFPCollation)
                IF SELF:_oRdd IS DBFVFP
                    SELF:_Collation := VfpCollation{SELF:_Header:VFPCollation,SELF:_oRdd:Header:CodePage:ToCodePage()}
                ELSE
                    SELF:ThrowException( Subcodes.ERDD_CORRUPT, Gencode.EG_CORRUPTION, "CdxTag.Open","Indexes with collation must be opened with the DBFVFP driver")
                ENDIF
            ENDIF
            SELF:_oRdd:__Goto(0)
            IF ! SELF:EvaluateExpressions()
                RETURN FALSE
            ENDIF
            SELF:AllocateBuffers()
            RETURN TRUE

        INTERNAL METHOD AllocateBuffers() AS VOID
            SELF:_newvalue          := RddKeyData{_keySize}
            SELF:_currentvalue      := RddKeyData{_keySize}
            SELF:_Scopes[0]:SetBuffer(_keySize)
            SELF:_Scopes[1]:SetBuffer(_keySize)
            RETURN

        INTERNAL METHOD SetLeafProperties(page AS CdxLeafPage) AS VOID
            VAR numRecs     := SELF:RDD:RecCount
            page:ClearRecordsAndKeys()
            page:KeyLength      := SELF:KeyLength
            page:SetRecordBits(numRecs)
            RETURN

        INTERNAL METHOD SetSyntaxError(ex AS Exception) AS VOID
            SELF:_oRdd:_dbfError(ex, Subcodes.EDB_EXPRESSION,Gencode.EG_SYNTAX,  "DBFCDX.EvaluateExpressions", FALSE)
            RETURN

        INTERNAL METHOD EvaluateExpressions() AS LOGIC
            LOCAL evalOk AS LOGIC
            LOCAL oKey := NULL AS OBJECT
            evalOk := TRUE
            SELF:_Valid := TRUE
            TRY
                SELF:_KeyCodeBlock := SELF:_oRdd:Compile(SELF:_KeyExpr)
            CATCH AS Exception
                SELF:_Valid := FALSE
                evalOk := FALSE
            END TRY
            IF ! evalOk
                RETURN FALSE
            ENDIF
            TRY
                oKey := SELF:_oRdd:EvalBlock(SELF:_KeyCodeBlock)
            CATCH AS Exception
                evalOk := FALSE
            END TRY
            IF !evalOk
                SELF:_Valid := FALSE
                RETURN FALSE
            ENDIF
            SELF:_KeyExprType := SELF:_oRdd:_getUsualType(oKey)
            IF SELF:_KeyExprType == __UsualType.String
                IF SELF:_Collation != NULL
                    SELF:__Compare := _compareCollation
                ELSE
                    SELF:__Compare := _compareText
                ENDIF
            ELSE
                SELF:__Compare := _compareBin
            ENDIF

            // If the Key Expression contains only a Field Name

            IF SELF:_oRdd IS DBFVFPSQL
                SELF:_SingleField := -1
            ELSE
                SELF:_SingleField := SELF:_oRdd:FieldIndex(SELF:_KeyExpr) -1
            ENDIF
            SELF:_SourceIndex := 0
            LOCAL isOk AS LOGIC
            IF SELF:_SingleField >= 0
                SELF:_SourceIndex   := SELF:_oRdd:_Fields[_SingleField]:Offset
                VAR fType           := SELF:_oRdd:_Fields[_SingleField]:FieldType
                SWITCH fType
                CASE DbFieldType.Number
                    SELF:_sourcekeySize := SELF:_keySize   := 8
                    SELF:getKeyValue := _getNumFieldValue
                    SELF:Binary      := TRUE
                CASE DbFieldType.Date
                    // CDX converts the Date to a Julian Number and stores that as a Real8 in the index
                    SELF:_sourcekeySize := SELF:_keySize   := 8
                    SELF:getKeyValue := _getDateFieldValue
                    SELF:Binary      := TRUE
                CASE DbFieldType.Integer
                    SELF:_sourcekeySize := SELF:_keySize   := 4
                    SELF:getKeyValue := _getIntFieldValue
                    SELF:Binary      := TRUE
                OTHERWISE
                    SELF:_sourcekeySize := SELF:_keySize   := (WORD) SELF:_oRdd:_Fields[_SingleField]:Length
                    SELF:getKeyValue := _getFieldValue
                    SELF:Binary      := FALSE
                    IF SELF:_Collation != NULL
                        SELF:_keySize := SELF:Header:KeySize
                        SELF:getKeyValue := _getFieldValueWithCollation
                    ENDIF

                END SWITCH
                isOk := TRUE
            ELSE
                SELF:_sourcekeySize := SELF:_keySize    := 0
                SELF:getKeyValue := _getExpressionValue
                isOk             := SELF:_determineSize(oKey)
                IF SELF:Header != NULL .AND. SELF:Header:KeySize != SELF:_keySize .AND. SELF:Header:KeySize != SELF:_keySize+1
                    IF SELF:_Collation != NULL
                        SELF:_keySize := SELF:Header:KeySize
                    ELSE
                        VAR ex := Exception{"Expression length in index does not match calculated expression length"}
                        SELF:SetSyntaxError(ex)
                    ENDIF
                ENDIF
            ENDIF
            IF ! isOk
                RETURN FALSE
            ENDIF
            SELF:AllocateBuffers()
            SELF:_Conditional       := FALSE
            IF SELF:_ForExpr:Length > 0
                TRY
                    SELF:_ForCodeBlock := SELF:_oRdd:Compile(SELF:_ForExpr)
                CATCH ex AS Exception
                    IF RuntimeState.LastRddError == NULL
                        SELF:SetSyntaxError(ex)
                    ENDIF
                    SELF:_Valid := FALSE
                    RETURN FALSE
                END TRY
                SELF:_oRdd:GoTo(1)
                evalOk := TRUE
                TRY
                    VAR oValue := SELF:_oRdd:EvalBlock(SELF:_ForCodeBlock)
                    evalOk     := SELF:_oRdd:_getUsualType(oValue) == __UsualType.Logic
                CATCH ex AS Exception
                    IF RuntimeState.LastRddError == NULL
                        SELF:SetSyntaxError(ex)
                    ENDIF
                    SELF:_Valid := FALSE
                    evalOk := FALSE
                END TRY
                IF !evalOk
                    RETURN FALSE
                ENDIF
                SELF:_Conditional := TRUE
            ENDIF

            RETURN isOk

        INTERNAL METHOD Flush() AS LOGIC
            // do NOT Call bag:flush to avoid recursion
            //SELF:_bag:Flush()
            RETURN TRUE

        INTERNAL METHOD Commit() AS LOGIC
            SELF:GoCold()
            SELF:_bag:Flush()
            RETURN TRUE

        INTERNAL METHOD Close() AS LOGIC
            RETURN SELF:Commit()

        INTERNAL METHOD GoCold() AS LOGIC
            IF SELF:_oRdd:IsHot
                RETURN SELF:_keyUpdate( SELF:_RecNo, SELF:_oRdd:IsNewRecord )
            ENDIF
            RETURN TRUE

        INTERNAL METHOD GoHot() AS LOGIC
            // Is called to save the current for value and key value
            RETURN SELF:_saveCurrentKey(SELF:_oRdd:RecNo, SELF:_currentvalue)

        INTERNAL METHOD GetPage(nPage AS LONG) AS CdxTreePage
            IF nPage == -1
                RETURN NULL
            ENDIF
            VAR page := SELF:_bag:GetPage(nPage, SELF:_keySize, SELF)
            RETURN page ASTYPE CdxTreePage

        INTERNAL STATIC METHOD _compareText(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            IF aRHS == NULL
                RETURN 0
            ENDIF
            VAR result := RuntimeState.StringCompare(aLHS, aRHS, nLength)
            IF result == 0
                result := _compareRecno(recnoLHS, recnoRHS)
            ENDIF
            RETURN result

        INTERNAL STATIC METHOD _compareCollation(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            RETURN _compareBin(aLHS, aRHS, nLength, recnoLHS, recnoRHS)

        INTERNAL STATIC METHOD _compareRecno(recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            IF recnoLHS == 0 .or. recnoRHS == 0
                // no record to compare against
                RETURN 0
            ENDIF
            IF recnoLHS < recnoRHS
                RETURN -1
            ELSEIF recnoLHS > recnoRHS
                RETURN 1
            ENDIF
            RETURN 0

        INTERNAL STATIC METHOD _compareBin(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            IF aRHS == NULL
                RETURN 0
            ENDIF
            FOR VAR nI := 0 TO nLength -1
                VAR bL := aLHS[nI]
                VAR bR := aRHS[nI]
                IF bL < bR
                    RETURN -1
                ELSEIF bL > bR
                    RETURN 1
                ENDIF
            NEXT
            RETURN _compareRecno(recnoLHS, recnoRHS)

        INTERNAL METHOD SetOffLine() AS VOID
            SELF:ClearStack()

        PRIVATE METHOD _saveCurrentRecord( node AS CdxNode) AS VOID
            IF SELF:_currentvalue:Recno != node:Recno .AND. ! SELF:RDD:EoF
                SELF:_currentvalue:Recno := node:Recno
                Array.Copy(node:KeyBytes, SELF:_currentvalue:Key, _keySize)
            ENDIF
        STATIC PRIVATE Jan_1_1901 := DateTime{1901, 1, 1 } AS DateTime

        PRIVATE STATIC METHOD _toJulian(dt AS DateTime) AS LONG
            VAR days      := dt:Subtract( Jan_1_1901 )
            RETURN Convert.ToInt32( days:TotalDays ) + 2415386   // Julian date number of 1901-01-01

        PRIVATE METHOD _ToString( toConvert AS OBJECT , sLen AS LONG ,  buffer AS BYTE[] ) AS LOGIC
            LOCAL resultLength AS LONG
            resultLength := 0
            RETURN SELF:_ToString( toConvert, sLen, buffer,  REF resultLength)

        PRIVATE METHOD _ToString( toConvert AS OBJECT , sLen AS LONG ,  buffer AS BYTE[] ,  resultLength REF LONG ) AS LOGIC
            LOCAL text AS STRING
            LOCAL typeCde AS TypeCode
            text := NULL
            IF toConvert IS IFloat // Float Value ?
                typeCde   := TypeCode.Double
            ELSEIF toConvert IS IDate VAR valueDate // Date Value
                toConvert := DateTime{valueDate:Year, valueDate:Month, valueDate:Day}
                typeCde   := TypeCode.DateTime
            ELSE
                typeCde := Type.GetTypeCode(toConvert:GetType())
            ENDIF
            IF typeCde == TypeCode.DateTime
                // Convert to Julian number and then process as normal numeric value
                toConvert     := _toJulian((DateTime) toConvert)
                typeCde       := TypeCode.Int32
            ENDIF
            SWITCH typeCde
            CASE TypeCode.Int16
            CASE TypeCode.Int32
            CASE TypeCode.Int64
            CASE TypeCode.Single
            CASE TypeCode.Double
            CASE TypeCode.Decimal
                IF sLen == 4
                    LongToFoxOrder(Convert.ToInt32(toConvert), buffer)
                    resultLength := 4
                ELSE
                    DoubleToFoxOrder(Convert.ToDouble(toConvert), buffer)
                    resultLength := 8
                ENDIF
                RETURN TRUE
            CASE TypeCode.String
                text := (STRING)toConvert
            CASE TypeCode.Boolean
                text := IIF ((LOGIC) toConvert, "T", "F")
            END SWITCH
            IF sLen > text:Length
                sLen := text:Length
            ENDIF
            resultLength := sLen
            SELF:_Encoding:GetBytes( text, 0, sLen, buffer, 0)
            IF SELF:_Collation != NULL
                SELF:_Collation:Translate(buffer)
            ENDIF
            RETURN TRUE

        INTERNAL METHOD SetOrderScope(itmScope AS OBJECT , uiScope AS DbOrder_Info ) AS LOGIC
            LOCAL uiRealLen AS LONG
            LOCAL result AS LOGIC
            uiRealLen := 0
            result := TRUE
            SWITCH uiScope
            CASE DbOrder_Info.DBOI_SCOPETOPCLEAR
                SELF:_Scopes[TOPSCOPE]:Clear()
                SELF:_scopeEmpty := FALSE
                SELF:_mustCheckEof := TRUE
            CASE DbOrder_Info.DBOI_SCOPEBOTTOMCLEAR
                SELF:_Scopes[BOTTOMSCOPE]:Clear()
                SELF:_scopeEmpty := FALSE
                SELF:_mustCheckEof := TRUE
            CASE DbOrder_Info.DBOI_SCOPETOP
            CASE DbOrder_Info.DBOI_SCOPEBOTTOM
                VAR nItem := IIF( uiScope == DbOrder_Info.DBOI_SCOPETOP, SELF:TopScopeNo, SELF:BottomScopeNo)
                IF (itmScope != SELF:_Scopes[nItem]:Value)
                    SELF:_scopeEmpty := FALSE
                    SELF:_Scopes[nItem]:Value := itmScope
                    IF itmScope != NULL
                        SELF:_ToString(itmScope, SELF:_keySize,  SELF:_Scopes[nItem]:Buffer, REF uiRealLen)
                        SELF:_Scopes[nItem]:Size := uiRealLen
                    ENDIF
                    SELF:_mustCheckEof := TRUE
                ENDIF
            OTHERWISE
                result := FALSE
            END SWITCH
            RETURN result

        INTERNAL METHOD _CountRecords(records REF LONG ) AS LOGIC
            LOCAL isOk AS LOGIC
            LOCAL oldRec AS LONG
            LOCAL recno AS LONG
            LOCAL last AS LONG
            LOCAL count AS LONG
            LOCAL isLocked := FALSE AS LOGIC
            LOCAL saveEmpty := SELF:_scopeEmpty AS LOGIC
            isOk := TRUE
            SELF:_bag:Flush()
            SELF:_oRdd:GoCold()
            oldRec := SELF:_RecNo
            TRY
                SELF:_scopeEmpty := FALSE
                IF SELF:Shared
                    isLocked := SELF:Slock()
                    IF !isLocked
                        RETURN FALSE
                    ENDIF
                ENDIF
                IF SELF:HasScope .AND. ! XSharp.RuntimeState.Deleted .AND. ! SELF:_oRdd:FilterInfo:Active
                    SELF:_ScopeSeek(DbOrder_Info.DBOI_SCOPEBOTTOM)
                    records := SELF:_getScopePos()
                ELSE
                    IF  XSharp.RuntimeState.Deleted  .OR. SELF:_oRdd:FilterInfo:Active
                        SELF:_oRdd:SkipFilter(1)
                        oldRec := SELF:_RecNo
                        records := 0
                        LOCAL lWasDescending AS LOGIC
                        lWasDescending := SELF:Descending
                        IF lWasDescending
                            SELF:Descending := FALSE
                        ENDIF
                        SELF:_oRdd:_SetEOF(FALSE)
                        SELF:_oRdd:_SetBOF(FALSE)
                        isOk := SELF:GoTop()
                        SELF:Descending := lWasDescending
                        IF !SELF:_isInScope()
                           records := 0
                        ELSE
                            SELF:Descending := FALSE
                            recno := SELF:_RecNo
                            last := SELF:_oRdd:RecCount + 1
                            count := 0
                            LOCAL previous AS LONG
                            previous := recno
                            DO WHILE recno != 0 .AND. recno < last
                                count++
                                recno := SELF:_ScopeSkip(1)
                                IF recno == previous
                                    EXIT
                                ENDIF
                                previous := recno
                            ENDDO
                            records := count
                        ENDIF
                        SELF:Descending := lWasDescending
                    ELSE
                         records := 0
                        IF SELF:GoTop() .AND. ! SELF:Stack:Empty
                            VAR topStack := SELF:CurrentStack
                            VAR page     := topStack:Page
                            DO WHILE TRUE
                                IF page == NULL
                                    EXIT
                                ENDIF
                                records += page:NumKeys
                                IF SELF:_Descending
                                    IF page:HasLeft
                                        VAR pageNo := page:LeftPtr
                                        page := SELF:GetPage(pageNo)
                                    ELSE
                                        EXIT
                                    ENDIF
                                ELSE
                                    IF page:HasRight
                                        VAR pageNo := page:RightPtr
                                        page := SELF:GetPage(pageNo)
                                    ELSE
                                        EXIT
                                    ENDIF
                                ENDIF
                            ENDDO
                        ENDIF
                    ENDIF
                ENDIF
                SELF:_GoToRecno(oldRec)
            CATCH
                isOk := FALSE
            FINALLY
                IF SELF:Shared .AND. isLocked
                    isOk := SELF:UnLock()
                ENDIF
                SELF:_scopeEmpty := saveEmpty
            END TRY
            RETURN isOk

        INTERNAL METHOD _getRecPos(record REF LONG ) AS LOGIC
            LOCAL oldRec AS LONG
            LOCAL recno AS LONG
            LOCAL count AS LONG
            LOCAL isLocked := FALSE AS LOGIC
            IF SELF:RDD:EoF
               record := 0
               RETURN TRUE
            ENDIF
            SELF:_oRdd:GoCold()
            oldRec := SELF:_RecNo
            TRY
                IF SELF:Shared
                    isLocked := SELF:Slock()
                    IF !isLocked
                        RETURN FALSE
                    ENDIF
                ENDIF
                recno := record
                IF recno == 0
                    recno := SELF:_RecNo
                ENDIF
                IF SELF:Stack:Empty
                    SELF:_GoToRecno(recno)
                ENDIF
                IF SELF:HasScope
                    record := SELF:_getScopePos()
                ELSE
                    IF XSharp.RuntimeState.Deleted .OR. SELF:_oRdd:FilterInfo:Active
                        SELF:_oRdd:SkipFilter(1)
                        oldRec := SELF:_RecNo
                        record := 0
                        IF !SELF:_oRdd:EoF
                            SELF:GoTop()
                            recno := SELF:_RecNo
                            count := 1
                            VAR dir := IIF(SELF:Descending, -1, 1)
                            DO WHILE recno != 0 .AND. recno != oldRec
                                count++
                                recno := SELF:_ScopeSkip(dir)
                            ENDDO
                            record := count
                        ENDIF
                    ELSE
                        record := SELF:_findItemPos()
                    ENDIF
                ENDIF
                SELF:_oRdd:__Goto(oldRec)
            FINALLY
                IF SELF:Shared .AND. isLocked
                    SELF:UnLock()
                ENDIF
            END TRY
            RETURN TRUE

        PRIVATE METHOD _nextKey( keyMove AS LONG ) AS LONG
            LOCAL recno			AS LONG
            LOCAL moveDirection	AS SkipDirection
            IF keyMove == 1
                recno := SELF:_getNextKey(SkipDirection.Forward)
            ELSE
                IF keyMove < 0
                    keyMove := -keyMove
                    moveDirection := SkipDirection.Backward
                ELSE
                    moveDirection := SkipDirection.Forward
                ENDIF
                IF keyMove != 0
                    REPEAT
                        recno := SELF:_getNextKey( moveDirection)
                        keyMove--
                    UNTIL !(recno != 0 .AND. keyMove != 0)
                ELSE
                    recno := 0
                ENDIF
                if (recno == 0 )
                    if moveDirection == SkipDirection.Backward
                        IF !SELF:Descending
                            recno := -1
                        ENDIF
                    else
                        // Goto 0 moves the record pointer to EOF which is correct
                    endif
                endif
            ENDIF
            RETURN recno

        INTERNAL METHOD PopPage() AS CdxStackEntry
            SELF:_stack:Pop()
            RETURN SELF:_stack:Top

        INTERNAL METHOD PushPage(oPage AS CdxTreePage) AS VOID
            SELF:_stack:Push(oPage, 0)
            RETURN

        INTERNAL PROPERTY CurrentLeaf AS CdxLeafPage GET SELF:_stack:Top:Page ASTYPE  CdxLeafPage

        INTERNAL PROPERTY CurrentTop  AS CdxTreePage GET SELF:_stack:Top:Page

        INTERNAL METHOD InsertOnTop(oPage AS CdxTreePage) AS LOGIC
            SELF:_stack:InsertOnTop(oPage)
            RETURN TRUE

        INTERNAL METHOD AdjustStack(originalPage AS CdxTreePage, oPage AS CdxTreePage, nPos AS WORD) AS VOID
            SELF:_stack:Replace(originalPage, oPage, nPos)
            RETURN

        INTERNAL METHOD PushPage(oPage AS CdxTreePage , nPos AS WORD) AS VOID
            SELF:_stack:Push(oPage, nPos)
            RETURN

        INTERNAL METHOD ClearStack() AS VOID
            SELF:_stack:Clear()
            RETURN

        INTERNAL METHOD _dump() AS VOID

            LOCAL hDump     AS IntPtr
            LOCAL cFile     AS STRING
            LOCAL sBlock    AS STRING
            LOCAL sName AS STRING
            sName := SELF:_bag:FullPath+"_"+SELF:OrderName
            VAR sRecords := System.Text.StringBuilder{}
            cFile := sName+".DMP"
            hDump := FCreate(cFile)
            IF hDump != F_ERROR
                SELF:_bag:_PageList:DumpHandle := hDump
                SELF:_bag:_PageList:Flush(FALSE)
                sBlock := SELF:_Header:Dump("Filedump for:"+sName)
                FWrite(hDump, sBlock)
                // Collect all pages
                LOCAL aPages AS List<LONG>
                LOCAL oPage AS CdxTreePage
                LOCAL nLevel AS LONG
                oPage  := SELF:GetPage(SELF:_rootPage)
                aPages := (List<INT>) oPage:GetChildren()
                nLevel := 1
                LOCAL sbLevels AS StringBuilder
                sbLevels := StringBuilder{}
                sbLevels:AppendLine("------------------------------")
                sbLevels:AppendLine("CDX Levels:")
                sbLevels:AppendLine("Level 1 : 1 page")
                DO WHILE aPages:Count > 0
                    LOCAL aChildren AS List<LONG>
                    nLevel += 1
                    sbLevels:AppendLine("Level "+nLevel:ToString()+" : "+aPages:Count:ToString()+" pages")
                    aChildren := List<LONG>{}
                    FOREACH pageNo AS LONG IN aPages
                        oPage := SELF:GetPage(pageNo)
                        aChildren:AddRange(oPage:GetChildren())
                    NEXT
                    aPages := aChildren
                ENDDO
                sbLevels:AppendLine("------------------------------")

                _oRdd:GoTop()
                sRecords:AppendLine("------------------------------")
                sRecords:AppendLine("List of Records in Index order")
                sRecords:AppendLine("------------------------------")
                sRecords:AppendLine("Recno      KeyValue")
                sRecords:AppendLine("------------------------------")
                DO WHILE ! _oRdd:EoF
                    VAR key := _oRdd:EvalBlock(SELF:_KeyCodeBlock)
                    IF key IS IDate VAR d
                        key := DateTime{d:Year, d:Month, d:Day}:ToString("yyyyMMdd")
                    ELSEIF key IS IFloat VAR f
                        key   :=  f:Value:ToString("F"+f:Decimals:ToString())
                    ENDIF
                    sRecords:AppendLine(String.Format("{0,10} {1}", _oRdd:RecNo, key))
                    _oRdd:Skip(1)
                ENDDO
                // Collect Recycled pages
                LOCAL sbFree AS StringBuilder
                sbFree := StringBuilder{}
                LOCAL nPage := SELF:OrderBag:Root:FreeList AS LONG
                sbFree:AppendLine("-------------------------------")
                sbFree:AppendLine("List of Free pages (Bag Level) ")
                sbFree:AppendLine("-------------------------------")
                sbFree:AppendLine("First free: "+ nPage:ToString("X8"))
                // stop dumping pages because from now on we only dump the page numbers
                SELF:_bag:_PageList:DumpHandle := IntPtr.Zero
                DO WHILE nPage != 0 .AND. nPage != -1
                    VAR oFree := SELF:GetPage(nPage) ASTYPE CdxTreePage
                    IF oFree != NULL
                        nPage := oFree:NextFree
                        sbFree:AppendLine("Next free : "+ nPage:ToString("X8"))
                    ELSE
                        EXIT
                    ENDIF
                ENDDO
                FWrite(hDump, sbFree:ToString())
                FWrite(hDump, sRecords:ToString())
                FWrite(hDump, sbLevels:ToString())
                FClose(hDump)

            ENDIF
            RETURN

        // Methods to calculate keys. We have split these to optimize index creating
        PRIVATE METHOD _getNumFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL oValue := SELF:_oRdd:GetValue(_SingleField+1) AS OBJECT
            DoubleToFoxOrder(Convert.ToDouble(oValue), byteArray)
            RETURN TRUE

       PRIVATE METHOD _getIntFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL oValue := SELF:_oRdd:GetValue(_SingleField+1) AS OBJECT
            LongToFoxOrder(Convert.ToInt32(oValue),byteArray)
            RETURN TRUE

        PRIVATE METHOD _getDateFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL oValue := SELF:_oRdd:GetValue(_SingleField+1) AS OBJECT
            IF oValue IS IDate VAR valueDate
                IF !valueDate:IsEmpty
                    oValue := DateTime{valueDate:Year, valueDate:Month, valueDate:Day}
                ELSE
                    oValue := DateTime.MinValue
                ENDIF
            ENDIF
            IF oValue IS DateTime VAR dt
                VAR longValue  := _toJulian(dt)
                DoubleToFoxOrder(Convert.ToDouble(longValue), byteArray)
                RETURN TRUE
            ENDIF
            RETURN FALSE

        PRIVATE METHOD _getFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            SELF:_oRdd:Validate()
            Array.Copy(SELF:_oRdd:RecordBuffer, sourceIndex, byteArray, 0, SELF:_keySize)
            RETURN TRUE

        PRIVATE METHOD _getFieldValueWithCollation(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            SELF:_getFieldValue(sourceIndex, byteArray)
            SELF:_Collation:Translate(byteArray)
            RETURN TRUE

        PRIVATE METHOD _getExpressionValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL result := TRUE AS LOGIC
            TRY
                SELF:_oRdd:Validate()
                VAR oKeyValue := SELF:_oRdd:EvalBlock(SELF:_KeyCodeBlock)
                LOCAL uiRealLen := 0 AS LONG
                result := SELF:_ToString(oKeyValue, SELF:_keySize,  byteArray,  REF uiRealLen)
            CATCH ex AS Exception
                SELF:SetSyntaxError(ex)
                result := FALSE
            END TRY
            RETURN result

    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
USING System.Collections.Generic
USING System.Diagnostics
USING System.Globalization
USING System.IO
USING System.Reflection
USING System.Text
USING System.Threading
USING XSharp.RDD.Enums
USING XSharp.RDD.Support
USING STATIC XSharp.Conversions
BEGIN NAMESPACE XSharp.RDD.CDX

    INTERNAL DELEGATE CompareFunc(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG

    [DebuggerDisplay("Tag: {OrderName}, Key: {Expression}, For: {Condition}")];
    INTERNAL SEALED PARTIAL CLASS CdxTag
        #region constants
        PRIVATE CONST MAX_KEY_LEN       := 240  AS WORD
        #endregion
        #region fields
        PRIVATE _Encoding AS Encoding
        PRIVATE _Hot AS LOGIC
        PRIVATE _Conditional AS LOGIC
        PRIVATE _Descending AS LOGIC
        PRIVATE _Unique AS LOGIC
        PRIVATE _Custom AS LOGIC
        PRIVATE _SingleField AS LONG            // 0 based FieldIndex
        PRIVATE _SourceIndex AS LONG
        PRIVATE _KeyCodeBlock AS ICodeblock
        PRIVATE _ForCodeBlock AS ICodeblock
        PRIVATE _KeyExpr AS STRING
        PRIVATE _ForExpr AS STRING
        PRIVATE _currentvalue AS RddKeyData
        INTERNAL _newvalue     AS RddKeyData
        PRIVATE _newKeyLen AS LONG
        PRIVATE _KeyExprType AS LONG
        PRIVATE _keySize AS WORD
        PRIVATE _sourcekeySize AS WORD
        PRIVATE _rootPage AS LONG
        PRIVATE _ordCondInfo AS DbOrderCondInfo
        INTERNAL _orderName AS STRING
        INTERNAL _Ansi AS LOGIC
        // Scopes
        PRIVATE DIM _Scopes[2] AS ScopeInfo
        PRIVATE _scopeEmpty    AS LOGIC
        // siblings
        PRIVATE _oRdd   AS DBFCDX
        PRIVATE _Header AS CdxTagHeader

        PRIVATE _stack          AS CdxPageStack
        INTERNAL __Compare       AS CompareFunc

        PRIVATE _bag            AS CdxOrderBag
        PRIVATE getKeyValue     AS ValueBlock       // Delegate to calculate the key
        PRIVATE _Collation      AS VfpCollation
        PRIVATE _Valid          AS LOGIC

#endregion

#region Properties
        INTERNAL PROPERTY Binary            AS LOGIC AUTO
        INTERNAL PROPERTY IsOpen            AS LOGIC AUTO
        INTERNAL PROPERTY Collation         AS VfpCollation GET _Collation
        INTERNAL PROPERTY Expression        AS STRING GET _KeyExpr
        INTERNAL PROPERTY Encoding          AS Encoding GET _Encoding
        INTERNAL PROPERTY RDD               AS DBFCDX GET _oRdd
        INTERNAL PROPERTY Condition         AS STRING GET _ForExpr
        INTERNAL PROPERTY OrderName         AS STRING GET _orderName
	    INTERNAL PROPERTY Shared 	        AS LOGIC GET _bag:Shared
        INTERNAL PROPERTY _RecNo 	        AS LONG GET _oRdd:RecNo
        INTERNAL PROPERTY FileName 	        AS STRING GET _bag:FullPath
        INTERNAL PROPERTY OrderBag          AS CdxOrderBag GET SELF:_bag
        INTERNAL PROPERTY Page              AS Int32 AUTO
        INTERNAL PROPERTY Descending        AS LOGIC GET _Descending SET _Descending := value
        INTERNAL PROPERTY IsConditional     AS LOGIC GET Options:HasFlag(CdxOptions.HasFor)
        INTERNAL PROPERTY IsHot             AS LOGIC GET _Hot
        INTERNAL PROPERTY Header            AS CdxTagHeader GET _Header
        INTERNAL PROPERTY KeyType        	AS INT GET SELF:_KeyExprType
        INTERNAL PROPERTY KeyCodeBlock      AS ICodeblock GET _KeyCodeBlock
        INTERNAL PROPERTY KeyLength         AS WORD GET SELF:_keySize
        INTERNAL PROPERTY SourceKeyLength   AS WORD GET SELF:_sourcekeySize
        INTERNAL PROPERTY Partial        	AS LOGIC GET SELF:Custom
        INTERNAL PROPERTY Conditional       AS LOGIC GET SELF:_Conditional
        INTERNAL PROPERTY Custom         	AS LOGIC GET SELF:_Custom
        INTERNAL PROPERTY Unique         	AS LOGIC GET SELF:_Unique
        INTERNAL PROPERTY Signature      	AS BYTE AUTO
        INTERNAL PROPERTY Options        	AS CdxOptions AUTO
        INTERNAL PROPERTY LockOffSet     	AS LONG AUTO
        INTERNAL PROPERTY CurrentStack      AS CdxStackEntry GET  SELF:_stack:Top
        INTERNAL PROPERTY RootPage          AS LONG AUTO
        INTERNAL PROPERTY Stack             AS CdxPageStack GET _stack

        // Scopes
        INTERNAL PROPERTY HasTopScope       AS LOGIC    GET _Scopes[SELF:TopScopeNo]:IsSet
        INTERNAL PROPERTY TopScope          AS OBJECT   GET _Scopes[SELF:TopScopeNo]:Value
        INTERNAL PROPERTY HasBottomScope    AS LOGIC    GET _Scopes[SELF:BottomScopeNo]:IsSet
        INTERNAL PROPERTY BottomScope       AS OBJECT   GET _Scopes[SELF:BottomScopeNo]:Value
        INTERNAL PROPERTY HasScope          AS LOGIC    GET _Scopes[0]:IsSet .OR. _Scopes[1]:IsSet
        PRIVATE  PROPERTY TopScopeNo        AS LONG     GET IIF(SELF:Descending, BOTTOMSCOPE, TOPSCOPE)
        PRIVATE  PROPERTY BottomScopeNo     AS LONG     GET IIF(SELF:Descending, TOPSCOPE, BOTTOMSCOPE)

#endregion

        PRIVATE METHOD _InitFields(oBag AS CdxOrderBag) AS VOID
            SELF:_bag           := oBag
            SELF:_oRdd          := oBag:_oRdd
            SELF:_stack         := CdxPageStack{SELF}
            SELF:_Encoding      := _oRdd:_Encoding

            SELF:_SingleField   := -1
            SELF:_currentvalue := RddKeyData{MAX_KEY_LEN}
            SELF:_newvalue     := RddKeyData{MAX_KEY_LEN}

        // Constructor for Creation of tags
        INTERNAL CONSTRUCTOR (oBag AS CdxOrderBag)
	        SUPER()
            SELF:_InitFields(oBag)

        // Constructor for Opening of tags
        INTERNAL CONSTRUCTOR (oBag AS CdxOrderBag, nPage AS Int32, cName AS STRING)
	        SUPER()
            SELF:_InitFields(oBag)
            SELF:_orderName := cName
            SELF:Page := nPage
            SELF:_Header := CdxTagHeader{oBag, nPage,SELF:OrderName, SELF}
            SELF:_bag:SetPage(SELF:_Header)
            SELF:IsOpen := SELF:Open()

         INTERNAL METHOD ReadHeader AS VOID
            SELF:_Header:Read()
            SELF:_keySize       := SELF:_Header:KeySize
            SELF:Options        := SELF:_Header:Options
            SELF:Signature      := SELF:_Header:Signature
            SELF:_Descending    := SELF:_Header:Descending
            SELF:_rootPage      := SELF:_Header:RootPage
            SELF:_KeyExpr       := SELF:_Header:KeyExpression
            SELF:_ForExpr       := SELF:_Header:ForExpression
            SELF:_Unique        := SELF:Options:HasFlag(CdxOptions.Unique)
            SELF:_Custom        := SELF:Options:HasFlag(CdxOptions.Custom)
            SELF:_Conditional   := SELF:Options:HasFlag(CdxOptions.HasFor)
            RETURN

	    INTERNAL METHOD Open() AS LOGIC
            SELF:_oRdd:GoTo(1)
            SELF:_Hot := FALSE
            SELF:ClearStack()
            SELF:ReadHeader()
            IF !String.IsNullOrEmpty(SELF:_Header:VFPCollation)
                IF SELF:_oRdd IS DBFVFP
                    SELF:_Collation := VfpCollation{SELF:_Header:VFPCollation,SELF:_oRdd:Header:CodePage:ToCodePage()}
                ELSE
                    SELF:ThrowException( Subcodes.ERDD_CORRUPT, Gencode.EG_CORRUPTION, "CdxTag.Open","Indexes with collation must be opened with the DBFVFP driver")
                ENDIF
            ENDIF
            SELF:_oRdd:__Goto(0)
            IF ! SELF:EvaluateExpressions()
                RETURN FALSE
            ENDIF
            SELF:AllocateBuffers()
            RETURN TRUE

        INTERNAL METHOD AllocateBuffers() AS VOID
            SELF:_newvalue          := RddKeyData{_keySize}
            SELF:_currentvalue      := RddKeyData{_keySize}
            SELF:_Scopes[0]:SetBuffer(_keySize)
            SELF:_Scopes[1]:SetBuffer(_keySize)
            RETURN

        INTERNAL METHOD SetLeafProperties(page AS CdxLeafPage) AS VOID
            VAR numRecs     := SELF:RDD:RecCount
            page:ClearRecordsAndKeys()
            page:KeyLength      := SELF:KeyLength
            page:SetRecordBits(numRecs)
            RETURN

        INTERNAL METHOD SetSyntaxError(ex AS Exception) AS VOID
            SELF:_oRdd:_dbfError(ex, Subcodes.EDB_EXPRESSION,Gencode.EG_SYNTAX,  "DBFCDX.EvaluateExpressions", FALSE)
            RETURN

        INTERNAL METHOD EvaluateExpressions() AS LOGIC
            LOCAL evalOk AS LOGIC
            LOCAL oKey := NULL AS OBJECT
            evalOk := TRUE
            SELF:_Valid := TRUE
            TRY
                SELF:_KeyCodeBlock := SELF:_oRdd:Compile(SELF:_KeyExpr)
            CATCH AS Exception
                SELF:_Valid := FALSE
                evalOk := FALSE
            END TRY
            IF ! evalOk
                RETURN FALSE
            ENDIF
            TRY
                oKey := SELF:_oRdd:EvalBlock(SELF:_KeyCodeBlock)
            CATCH AS Exception
                evalOk := FALSE
            END TRY
            IF !evalOk
                SELF:_Valid := FALSE
                RETURN FALSE
            ENDIF
            SELF:_KeyExprType := SELF:_oRdd:_getUsualType(oKey)
            IF SELF:_KeyExprType == __UsualType.String
                IF SELF:_Collation != NULL
                    SELF:__Compare := _compareCollation
                ELSE
                    SELF:__Compare := _compareText
                ENDIF
            ELSE
                SELF:__Compare := _compareBin
            ENDIF

            // If the Key Expression contains only a Field Name

            IF SELF:_oRdd IS DBFVFPSQL
                SELF:_SingleField := -1
            ELSE
                SELF:_SingleField := SELF:_oRdd:FieldIndex(SELF:_KeyExpr) -1
            ENDIF
            SELF:_SourceIndex := 0
            LOCAL isOk AS LOGIC
            IF SELF:_SingleField >= 0
                SELF:_SourceIndex   := SELF:_oRdd:_Fields[_SingleField]:Offset
                VAR fType           := SELF:_oRdd:_Fields[_SingleField]:FieldType
                SWITCH fType
                CASE DbFieldType.Number
                    SELF:_sourcekeySize := SELF:_keySize   := 8
                    SELF:getKeyValue := _getNumFieldValue
                    SELF:Binary      := TRUE
                CASE DbFieldType.Date
                    // CDX converts the Date to a Julian Number and stores that as a Real8 in the index
                    SELF:_sourcekeySize := SELF:_keySize   := 8
                    SELF:getKeyValue := _getDateFieldValue
                    SELF:Binary      := TRUE
                CASE DbFieldType.Integer
                    SELF:_sourcekeySize := SELF:_keySize   := 4
                    SELF:getKeyValue := _getIntFieldValue
                    SELF:Binary      := TRUE
                OTHERWISE
                    SELF:_sourcekeySize := SELF:_keySize   := (WORD) SELF:_oRdd:_Fields[_SingleField]:Length
                    SELF:getKeyValue := _getFieldValue
                    SELF:Binary      := FALSE
                    IF SELF:_Collation != NULL
                        SELF:_keySize := SELF:Header:KeySize
                        SELF:getKeyValue := _getFieldValueWithCollation
                    ENDIF

                END SWITCH
                isOk := TRUE
            ELSE
                SELF:_sourcekeySize := SELF:_keySize    := 0
                SELF:getKeyValue := _getExpressionValue
                isOk             := SELF:_determineSize(oKey)
                IF SELF:Header != NULL .AND. SELF:Header:KeySize != SELF:_keySize .AND. SELF:Header:KeySize != SELF:_keySize+1
                    IF SELF:_Collation != NULL
                        SELF:_keySize := SELF:Header:KeySize
                    ELSE
                        VAR ex := Exception{"Expression length in index does not match calculated expression length"}
                        SELF:SetSyntaxError(ex)
                    ENDIF
                ENDIF
            ENDIF
            IF ! isOk
                RETURN FALSE
            ENDIF
            SELF:AllocateBuffers()
            SELF:_Conditional       := FALSE
            IF SELF:_ForExpr:Length > 0
                TRY
                    SELF:_ForCodeBlock := SELF:_oRdd:Compile(SELF:_ForExpr)
                CATCH ex AS Exception
                    IF RuntimeState.LastRddError == NULL
                        SELF:SetSyntaxError(ex)
                    ENDIF
                    SELF:_Valid := FALSE
                    RETURN FALSE
                END TRY
                SELF:_oRdd:GoTo(1)
                evalOk := TRUE
                TRY
                    VAR oValue := SELF:_oRdd:EvalBlock(SELF:_ForCodeBlock)
                    evalOk     := SELF:_oRdd:_getUsualType(oValue) == __UsualType.Logic
                CATCH ex AS Exception
                    IF RuntimeState.LastRddError == NULL
                        SELF:SetSyntaxError(ex)
                    ENDIF
                    SELF:_Valid := FALSE
                    evalOk := FALSE
                END TRY
                IF !evalOk
                    RETURN FALSE
                ENDIF
                SELF:_Conditional := TRUE
            ENDIF

            RETURN isOk

        INTERNAL METHOD Flush() AS LOGIC
            // do NOT Call bag:flush to avoid recursion
            //SELF:_bag:Flush()
            RETURN TRUE

        INTERNAL METHOD Commit() AS LOGIC
            SELF:GoCold()
            SELF:_bag:Flush()
            RETURN TRUE

        INTERNAL METHOD Close() AS LOGIC
            RETURN SELF:Commit()

        INTERNAL METHOD GoCold() AS LOGIC
            IF SELF:_oRdd:IsHot
                RETURN SELF:_keyUpdate( SELF:_RecNo, SELF:_oRdd:IsNewRecord )
            ENDIF
            RETURN TRUE

        INTERNAL METHOD GoHot() AS LOGIC
            // Is called to save the current for value and key value
            RETURN SELF:_saveCurrentKey(SELF:_oRdd:RecNo, SELF:_currentvalue)

        INTERNAL METHOD GetPage(nPage AS LONG) AS CdxTreePage
            IF nPage == -1
                RETURN NULL
            ENDIF
            VAR page := SELF:_bag:GetPage(nPage, SELF:_keySize, SELF)
            RETURN page ASTYPE CdxTreePage

        INTERNAL STATIC METHOD _compareText(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            IF aRHS == NULL
                RETURN 0
            ENDIF
            VAR result := RuntimeState.StringCompare(aLHS, aRHS, nLength)
            IF result == 0
                result := _compareRecno(recnoLHS, recnoRHS)
            ENDIF
            RETURN result

        INTERNAL STATIC METHOD _compareCollation(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            RETURN _compareBin(aLHS, aRHS, nLength, recnoLHS, recnoRHS)

        INTERNAL STATIC METHOD _compareRecno(recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            IF recnoLHS == 0 .or. recnoRHS == 0
                // no record to compare against
                RETURN 0
            ENDIF
            IF recnoLHS < recnoRHS
                RETURN -1
            ELSEIF recnoLHS > recnoRHS
                RETURN 1
            ENDIF
            RETURN 0

        INTERNAL STATIC METHOD _compareBin(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            IF aRHS == NULL
                RETURN 0
            ENDIF
            FOR VAR nI := 0 TO nLength -1
                VAR bL := aLHS[nI]
                VAR bR := aRHS[nI]
                IF bL < bR
                    RETURN -1
                ELSEIF bL > bR
                    RETURN 1
                ENDIF
            NEXT
            RETURN _compareRecno(recnoLHS, recnoRHS)

        INTERNAL METHOD SetOffLine() AS VOID
            SELF:ClearStack()

        PRIVATE METHOD _saveCurrentRecord( node AS CdxNode) AS VOID
            IF SELF:_currentvalue:Recno != node:Recno .AND. ! SELF:RDD:EoF
                SELF:_currentvalue:Recno := node:Recno
                Array.Copy(node:KeyBytes, SELF:_currentvalue:Key, _keySize)
            ENDIF
        STATIC PRIVATE Jan_1_1901 := DateTime{1901, 1, 1 } AS DateTime

        PRIVATE STATIC METHOD _toJulian(dt AS DateTime) AS LONG
            VAR days      := dt:Subtract( Jan_1_1901 )
            RETURN Convert.ToInt32( days:TotalDays ) + 2415386   // Julian date number of 1901-01-01

        PRIVATE METHOD _ToString( toConvert AS OBJECT , sLen AS LONG ,  buffer AS BYTE[] ) AS LOGIC
            LOCAL resultLength AS LONG
            resultLength := 0
            RETURN SELF:_ToString( toConvert, sLen, buffer,  REF resultLength)

        PRIVATE METHOD _ToString( toConvert AS OBJECT , sLen AS LONG ,  buffer AS BYTE[] ,  resultLength REF LONG ) AS LOGIC
            LOCAL text AS STRING
            LOCAL typeCde AS TypeCode
            text := NULL
            IF toConvert IS IFloat // Float Value ?
                typeCde   := TypeCode.Double
            ELSEIF toConvert IS IDate VAR valueDate // Date Value
                toConvert := DateTime{valueDate:Year, valueDate:Month, valueDate:Day}
                typeCde   := TypeCode.DateTime
            ELSE
                typeCde := Type.GetTypeCode(toConvert:GetType())
            ENDIF
            IF typeCde == TypeCode.DateTime
                // Convert to Julian number and then process as normal numeric value
                toConvert     := _toJulian((DateTime) toConvert)
                typeCde       := TypeCode.Int32
            ENDIF
            SWITCH typeCde
            CASE TypeCode.Int16
            CASE TypeCode.Int32
            CASE TypeCode.Int64
            CASE TypeCode.Single
            CASE TypeCode.Double
            CASE TypeCode.Decimal
                IF sLen == 4
                    LongToFoxOrder(Convert.ToInt32(toConvert), buffer)
                    resultLength := 4
                ELSE
                    DoubleToFoxOrder(Convert.ToDouble(toConvert), buffer)
                    resultLength := 8
                ENDIF
                RETURN TRUE
            CASE TypeCode.String
                text := (STRING)toConvert
            CASE TypeCode.Boolean
                text := IIF ((LOGIC) toConvert, "T", "F")
            END SWITCH
            IF sLen > text:Length
                sLen := text:Length
            ENDIF
            resultLength := sLen
            SELF:_Encoding:GetBytes( text, 0, sLen, buffer, 0)
            IF SELF:_Collation != NULL
                SELF:_Collation:Translate(buffer)
            ENDIF
            RETURN TRUE

        INTERNAL METHOD SetOrderScope(itmScope AS OBJECT , uiScope AS DbOrder_Info ) AS LOGIC
            LOCAL uiRealLen AS LONG
            LOCAL result AS LOGIC
            uiRealLen := 0
            result := TRUE
            SWITCH uiScope
            CASE DbOrder_Info.DBOI_SCOPETOPCLEAR
                SELF:_Scopes[TOPSCOPE]:Clear()
                SELF:_scopeEmpty := FALSE
                SELF:_mustCheckEof := TRUE
            CASE DbOrder_Info.DBOI_SCOPEBOTTOMCLEAR
                SELF:_Scopes[BOTTOMSCOPE]:Clear()
                SELF:_scopeEmpty := FALSE
                SELF:_mustCheckEof := TRUE
            CASE DbOrder_Info.DBOI_SCOPETOP
            CASE DbOrder_Info.DBOI_SCOPEBOTTOM
                VAR nItem := IIF( uiScope == DbOrder_Info.DBOI_SCOPETOP, SELF:TopScopeNo, SELF:BottomScopeNo)
                IF (itmScope != SELF:_Scopes[nItem]:Value)
                    SELF:_scopeEmpty := FALSE
                    SELF:_Scopes[nItem]:Value := itmScope
                    IF itmScope != NULL
                        SELF:_ToString(itmScope, SELF:_keySize,  SELF:_Scopes[nItem]:Buffer, REF uiRealLen)
                        SELF:_Scopes[nItem]:Size := uiRealLen
                    ENDIF
                    SELF:_mustCheckEof := TRUE
                ENDIF
            OTHERWISE
                result := FALSE
            END SWITCH
            RETURN result

        INTERNAL METHOD _CountRecords(records REF LONG ) AS LOGIC
            LOCAL isOk AS LOGIC
            LOCAL oldRec AS LONG
            LOCAL recno AS LONG
            LOCAL last AS LONG
            LOCAL count AS LONG
            LOCAL isLocked := FALSE AS LOGIC
            LOCAL saveEmpty := SELF:_scopeEmpty AS LOGIC
            isOk := TRUE
            SELF:_bag:Flush()
            SELF:_oRdd:GoCold()
            oldRec := SELF:_RecNo
            TRY
                SELF:_scopeEmpty := FALSE
                IF SELF:Shared
                    isLocked := SELF:Slock()
                    IF !isLocked
                        RETURN FALSE
                    ENDIF
                ENDIF
                IF SELF:HasScope .AND. ! XSharp.RuntimeState.Deleted .AND. ! SELF:_oRdd:FilterInfo:Active
                    SELF:_ScopeSeek(DbOrder_Info.DBOI_SCOPEBOTTOM)
                    records := SELF:_getScopePos()
                ELSE
                    IF  XSharp.RuntimeState.Deleted  .OR. SELF:_oRdd:FilterInfo:Active
                        SELF:_oRdd:SkipFilter(1)
                        oldRec := SELF:_RecNo
                        records := 0
                        LOCAL lWasDescending AS LOGIC
                        lWasDescending := SELF:Descending
                        IF lWasDescending
                            SELF:Descending := FALSE
                        ENDIF
                        SELF:_oRdd:_SetEOF(FALSE)
                        SELF:_oRdd:_SetBOF(FALSE)
                        isOk := SELF:GoTop()
                        recno := SELF:_RecNo
                        last := SELF:_oRdd:RecCount + 1
                        count := 0
                        LOCAL previous AS LONG
                        previous := recno
                        DO WHILE recno != 0 .AND. recno < last
                            count++
                            recno := SELF:_ScopeSkip(1)
                            IF recno == previous
                                EXIT
                            ENDIF
                            previous := recno
                        ENDDO
                        records := count
                        SELF:Descending := lWasDescending
                    ELSE
                         records := 0
                        IF SELF:GoTop() .AND. ! SELF:Stack:Empty
                            VAR topStack := SELF:CurrentStack
                            VAR page     := topStack:Page
                            DO WHILE TRUE
                                IF page == NULL
                                    EXIT
                                ENDIF
                                records += page:NumKeys
                                IF SELF:_Descending
                                    IF page:HasLeft
                                        VAR pageNo := page:LeftPtr
                                        page := SELF:GetPage(pageNo)
                                    ELSE
                                        EXIT
                                    ENDIF
                                ELSE
                                    IF page:HasRight
                                        VAR pageNo := page:RightPtr
                                        page := SELF:GetPage(pageNo)
                                    ELSE
                                        EXIT
                                    ENDIF
                                ENDIF
                            ENDDO
                        ENDIF
                    ENDIF
                ENDIF
                SELF:_GoToRecno(oldRec)
            CATCH
                isOk := FALSE
            FINALLY
                IF SELF:Shared .AND. isLocked
                    isOk := SELF:UnLock()
                ENDIF
                SELF:_scopeEmpty := saveEmpty
            END TRY
            RETURN isOk

        INTERNAL METHOD _getRecPos(record REF LONG ) AS LOGIC
            LOCAL oldRec AS LONG
            LOCAL recno AS LONG
            LOCAL count AS LONG
            LOCAL isLocked := FALSE AS LOGIC
            IF SELF:RDD:EoF
               record := 0
               RETURN TRUE
            ENDIF
            SELF:_oRdd:GoCold()
            oldRec := SELF:_RecNo
            TRY
                IF SELF:Shared
                    isLocked := SELF:Slock()
                    IF !isLocked
                        RETURN FALSE
                    ENDIF
                ENDIF
                recno := record
                IF recno == 0
                    recno := SELF:_RecNo
                ENDIF
                IF SELF:Stack:Empty
                    SELF:_GoToRecno(recno)
                ENDIF
                IF SELF:HasScope
                    record := SELF:_getScopePos()
                ELSE
                    IF XSharp.RuntimeState.Deleted .OR. SELF:_oRdd:FilterInfo:Active
                        SELF:_oRdd:SkipFilter(1)
                        oldRec := SELF:_RecNo
                        record := 0
                        IF !SELF:_oRdd:EoF
                            SELF:GoTop()
                            recno := SELF:_RecNo
                            count := 1
                            VAR dir := IIF(SELF:Descending, -1, 1)
                            DO WHILE recno != 0 .AND. recno != oldRec
                                count++
                                recno := SELF:_ScopeSkip(dir)
                            ENDDO
                            record := count
                        ENDIF
                    ELSE
                        record := SELF:_findItemPos()
                    ENDIF
                ENDIF
                SELF:_oRdd:__Goto(oldRec)
            FINALLY
                IF SELF:Shared .AND. isLocked
                    SELF:UnLock()
                ENDIF
            END TRY
            RETURN TRUE

        PRIVATE METHOD _nextKey( keyMove AS LONG ) AS LONG
            LOCAL recno			AS LONG
            LOCAL moveDirection	AS SkipDirection
            IF keyMove == 1
                recno := SELF:_getNextKey(SkipDirection.Forward)
            ELSE
                IF keyMove < 0
                    keyMove := -keyMove
                    moveDirection := SkipDirection.Backward
                ELSE
                    moveDirection := SkipDirection.Forward
                ENDIF
                IF keyMove != 0
                    REPEAT
                        recno := SELF:_getNextKey( moveDirection)
                        keyMove--
                    UNTIL !(recno != 0 .AND. keyMove != 0)
                ELSE
                    recno := 0
                ENDIF
                if (recno == 0 )
                    if moveDirection == SkipDirection.Backward
                        IF !SELF:Descending
                            recno := -1
                        ENDIF
                    else
                        // Goto 0 moves the record pointer to EOF which is correct
                    endif
                endif
            ENDIF
            RETURN recno

        INTERNAL METHOD PopPage() AS CdxStackEntry
            SELF:_stack:Pop()
            RETURN SELF:_stack:Top

        INTERNAL METHOD PushPage(oPage AS CdxTreePage) AS VOID
            SELF:_stack:Push(oPage, 0)
            RETURN

        INTERNAL PROPERTY CurrentLeaf AS CdxLeafPage GET SELF:_stack:Top:Page ASTYPE  CdxLeafPage

        INTERNAL PROPERTY CurrentTop  AS CdxTreePage GET SELF:_stack:Top:Page

        INTERNAL METHOD InsertOnTop(oPage AS CdxTreePage) AS LOGIC
            SELF:_stack:InsertOnTop(oPage)
            RETURN TRUE

        INTERNAL METHOD AdjustStack(originalPage AS CdxTreePage, oPage AS CdxTreePage, nPos AS WORD) AS VOID
            SELF:_stack:Replace(originalPage, oPage, nPos)
            RETURN

        INTERNAL METHOD PushPage(oPage AS CdxTreePage , nPos AS WORD) AS VOID
            SELF:_stack:Push(oPage, nPos)
            RETURN

        INTERNAL METHOD ClearStack() AS VOID
            SELF:_stack:Clear()
            RETURN

        INTERNAL METHOD _dump() AS VOID

            LOCAL hDump     AS IntPtr
            LOCAL cFile     AS STRING
            LOCAL sBlock    AS STRING
            LOCAL sName AS STRING
            sName := SELF:_bag:FullPath+"_"+SELF:OrderName
            VAR sRecords := System.Text.StringBuilder{}
            cFile := sName+".DMP"
            hDump := FCreate(cFile)
            IF hDump != F_ERROR
                SELF:_bag:_PageList:DumpHandle := hDump
                SELF:_bag:_PageList:Flush(FALSE)
                sBlock := SELF:_Header:Dump("Filedump for:"+sName)
                FWrite(hDump, sBlock)
                // Collect all pages
                LOCAL aPages AS List<LONG>
                LOCAL oPage AS CdxTreePage
                LOCAL nLevel AS LONG
                oPage  := SELF:GetPage(SELF:_rootPage)
                aPages := (List<INT>) oPage:GetChildren()
                nLevel := 1
                LOCAL sbLevels AS StringBuilder
                sbLevels := StringBuilder{}
                sbLevels:AppendLine("------------------------------")
                sbLevels:AppendLine("CDX Levels:")
                sbLevels:AppendLine("Level 1 : 1 page")
                DO WHILE aPages:Count > 0
                    LOCAL aChildren AS List<LONG>
                    nLevel += 1
                    sbLevels:AppendLine("Level "+nLevel:ToString()+" : "+aPages:Count:ToString()+" pages")
                    aChildren := List<LONG>{}
                    FOREACH pageNo AS LONG IN aPages
                        oPage := SELF:GetPage(pageNo)
                        aChildren:AddRange(oPage:GetChildren())
                    NEXT
                    aPages := aChildren
                ENDDO
                sbLevels:AppendLine("------------------------------")

                _oRdd:GoTop()
                sRecords:AppendLine("------------------------------")
                sRecords:AppendLine("List of Records in Index order")
                sRecords:AppendLine("------------------------------")
                sRecords:AppendLine("Recno      KeyValue")
                sRecords:AppendLine("------------------------------")
                DO WHILE ! _oRdd:EoF
                    VAR key := _oRdd:EvalBlock(SELF:_KeyCodeBlock)
                    IF key IS IDate VAR d
                        key := DateTime{d:Year, d:Month, d:Day}:ToString("yyyyMMdd")
                    ELSEIF key IS IFloat VAR f
                        key   :=  f:Value:ToString("F"+f:Decimals:ToString())
                    ENDIF
                    sRecords:AppendLine(String.Format("{0,10} {1}", _oRdd:RecNo, key))
                    _oRdd:Skip(1)
                ENDDO
                // Collect Recycled pages
                LOCAL sbFree AS StringBuilder
                sbFree := StringBuilder{}
                LOCAL nPage := SELF:OrderBag:Root:FreeList AS LONG
                sbFree:AppendLine("-------------------------------")
                sbFree:AppendLine("List of Free pages (Bag Level) ")
                sbFree:AppendLine("-------------------------------")
                sbFree:AppendLine("First free: "+ nPage:ToString("X8"))
                // stop dumping pages because from now on we only dump the page numbers
                SELF:_bag:_PageList:DumpHandle := IntPtr.Zero
                DO WHILE nPage != 0 .AND. nPage != -1
                    VAR oFree := SELF:GetPage(nPage) ASTYPE CdxTreePage
                    IF oFree != NULL
                        nPage := oFree:NextFree
                        sbFree:AppendLine("Next free : "+ nPage:ToString("X8"))
                    ELSE
                        EXIT
                    ENDIF
                ENDDO
                FWrite(hDump, sbFree:ToString())
                FWrite(hDump, sRecords:ToString())
                FWrite(hDump, sbLevels:ToString())
                FClose(hDump)

            ENDIF
            RETURN

        // Methods to calculate keys. We have split these to optimize index creating
        PRIVATE METHOD _getNumFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL oValue := SELF:_oRdd:GetValue(_SingleField+1) AS OBJECT
            DoubleToFoxOrder(Convert.ToDouble(oValue), byteArray)
            RETURN TRUE

       PRIVATE METHOD _getIntFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL oValue := SELF:_oRdd:GetValue(_SingleField+1) AS OBJECT
            LongToFoxOrder(Convert.ToInt32(oValue),byteArray)
            RETURN TRUE

        PRIVATE METHOD _getDateFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL oValue := SELF:_oRdd:GetValue(_SingleField+1) AS OBJECT
            IF oValue IS IDate VAR valueDate
                IF !valueDate:IsEmpty
                    oValue := DateTime{valueDate:Year, valueDate:Month, valueDate:Day}
                ELSE
                    oValue := DateTime.MinValue
                ENDIF
            ENDIF
            IF oValue IS DateTime VAR dt
                VAR longValue  := _toJulian(dt)
                DoubleToFoxOrder(Convert.ToDouble(longValue), byteArray)
                RETURN TRUE
            ENDIF
            RETURN FALSE

        PRIVATE METHOD _getFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            SELF:_oRdd:Validate()
            Array.Copy(SELF:_oRdd:RecordBuffer, sourceIndex, byteArray, 0, SELF:_keySize)
            RETURN TRUE

        PRIVATE METHOD _getFieldValueWithCollation(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            SELF:_getFieldValue(sourceIndex, byteArray)
            SELF:_Collation:Translate(byteArray)
            RETURN TRUE

        PRIVATE METHOD _getExpressionValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL result := TRUE AS LOGIC
            TRY
                SELF:_oRdd:Validate()
                VAR oKeyValue := SELF:_oRdd:EvalBlock(SELF:_KeyCodeBlock)
                LOCAL uiRealLen := 0 AS LONG
                result := SELF:_ToString(oKeyValue, SELF:_keySize,  byteArray,  REF uiRealLen)
            CATCH ex AS Exception
                SELF:SetSyntaxError(ex)
                result := FALSE
            END TRY
            RETURN result

    END CLASS
END NAMESPACE


Generate error object when update fails and return false

//
// 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.Data.Common
USING System.Data
USING System.Collections.Generic
USING System.Reflection
USING System.Diagnostics

/// <include file="Sql.xml" path="doc/SQLSelect/*" />
[XSharp.Internal.TypesChanged];
[DebuggerDisplay( "SqlSelect {TableName,nq}" )] ;
PARTIAL CLASS SQLSelect INHERIT DataServer
	PROTECT oStmt           AS SQLStatement   // Statement object
	PROTECT oConn           AS SQLConnection
	PROTECT cCursor         AS STRING		// No longer used
	PROTECT lCsrOpenFlag 	AS LOGIC		// Is the server Open
	PROTECT lFetchFlag      AS LOGIC        // First Fetch has been performed
	PROTECT lBof            AS LOGIC        // Are we at BOF
	PROTECT lEof            AS LOGIC        // Are we at EOF

	PROTECT nNumCols		AS LONG			// # Of Columns in resultset
	PROTECT lAppendFlag     AS LOGIC        // Are we on a new record
	PROTECT aIndexCol       AS List<INT>
	PROTECT nRowCount		AS LONG			// Number of rows
	PROTECT cTableName      AS STRING
	PROTECT nNotifyCount    AS INT
	PROTECT lSuppressNotify AS LOGIC
	PROTECT aSQLColumns       AS SQLColumn[]    // Array of SqlColumn objects
	PROTECT aColumnAttributes AS SQLColumnAttributes[]   // Array of SQLColumnAttributes Objects.
	PROTECT lNullAsBlank	  AS LOGIC		// Return Null values as Blank
	PROTECT lTimeStampAsDate  AS LOGIC		// Should datetimes be handled as data ?
	PROTECT aLastArgs         AS ARRAY

    protect lReadOnly        as logic       // is the result set readonly
    protect lBatchUpdates    as logic       // does the result set support batch updates

	// Dummy Properties
	PROTECT nLastRecNum     AS LONG
	PROTECT nRecNum         AS LONG
	PROTECT lLastRecFound   AS LOGIC
	PROTECT lDeleteFlag     AS LOGIC

	// New .Net Properties
	PROTECT oNetConn		AS IDbConnection // Connection
	PROTECT oTable			AS DataTable	// Contents of resultset
	PROTECT oSchema			AS DataTable	// Schema definition of resultset
    PROTECT oAdapter        AS DbDataAdapter  // Data Adapter for Insert/Update/Delete
	PROTECT oCurrentRow		AS DataRow		// Current data row in result set. NULL when at EOF

	PROTECT oFieldHash      AS System.Collections.Hashtable // Hashtable of Strings and Symbols and column positions
	PROTECT nCurrentRow		AS LONG			// Current row number, 0 based
	PROTECT nSuspendNot     AS LONG			// Is used to keep track of the current notification state
	PROTECT lErrorFlag      AS LOGIC		// Was there an error with the last operation
	PROTECT lChanges        AS LOGIC

	#region Internal Methods

    method __CheckReadOnly as void
        if self:lReadOnly
            var error := Error{EG_READONLY}
            error:FuncSym := ProcName(2)
            throw error
        endif

 /// <exclude />
	METHOD __getDataTable() AS DataTable
		RETURN SELF:oTable

 /// <exclude />
	METHOD __AllocStmt() AS LOGIC STRICT
		IF SELF:oNetConn:State != ConnectionState.Open
			SELF:oNetConn:Open()
		ENDIF
		RETURN oStmt:__AllocStmt()

 /// <exclude />
	METHOD __CheckEOF AS VOID
		IF nRowCount == 0
			SELF:lBof := TRUE
			SELF:lEof := TRUE
		ELSE
			SELF:lBof := FALSE
			SELF:lEof := FALSE
			IF nCurrentRow < 0
				SELF:lBof := TRUE
				nCurrentRow := 0
			ELSEIF nCurrentRow >= nRowCount
				SELF:lEof := TRUE
				nCurrentRow := nRowCount
			ENDIF
			IF nCurrentRow < nRowCount
				oCurrentRow := oTable:Rows[nCurrentRow]
			ELSE
				oCurrentRow := NULL
			ENDIF
		ENDIF
		RETURN

 /// <exclude />
	[Obsolete];
	METHOD __CopySQLError( symMethod AS SYMBOL, oErrInfo AS SQLErrorInfo) AS VOID STRICT
		RETURN

 /// <exclude />
	METHOD __ForceOpen AS LOGIC STRICT
		TRY
			IF ! SELF:lCsrOpenFlag
				SELF:Execute(SELF:aLastArgs)
			ENDIF
		CATCH AS Exception
			lCsrOpenFlag := FALSE
		END TRY
		RETURN lCsrOpenFlag

 /// <exclude />
	METHOD __FreeStmt( fOption AS WORD ) AS LOGIC STRICT
		RETURN oStmt:__FreeStmt( fOption )

 /// <exclude />
	METHOD __GetColIndex( uFieldID AS USUAL, lAutoFetch AS LOGIC) AS DWORD STRICT
		LOCAL nPos AS DWORD
		LOCAL lOk  AS LOGIC
		nPos := 0
		IF lAutoFetch
			lOk := SELF:__ForceOpen()
		ELSE
			lOk := TRUE
		ENDIF
		IF lOk
			IF IsNumeric( uFieldID )
				IF uFieldID > 0 .AND. uFieldID <= nNumCols
					nPos := uFieldID
				ENDIF
			ELSEIF IsSymbol( uFieldID )
				LOCAL cField AS STRING
				cField := uFieldID
				IF (oFieldHash:ContainsKey(cField))
					nPos := (DWORD) oFieldHash[cField]
				ENDIF
			ELSEIF IsString( uFieldID )
				LOCAL cField AS STRING
				LOCAL nDot AS DWORD
				cField := Upper(uFieldID)
				nDot := At(".", cField)
				IF nDot > 0
					cField := SubStr2(cField, nDot+1)
				ENDIF
				IF (oFieldHash:ContainsKey(cField))
					nPos := (DWORD) oFieldHash[cField]
				ENDIF

			ENDIF
		ENDIF
		RETURN nPos

 /// <exclude />
	METHOD __PrepareForRecordMovement AS LOGIC STRICT
		LOCAL lOk AS LOGIC
		lOk := SELF:__ForceOpen()
		IF lOk .AND. SELF:lEof .AND. SELF:lBof
			lOk := FALSE
		ENDIF
		IF lOk .AND. ! SELF:__Notify( NOTIFYINTENTTOMOVE )
			lOk := FALSE
		ENDIF
		IF lOk .AND. ! SELF:__GoCold(FALSE)
			lOk := FALSE
		ENDIF
		RETURN lOk

	#endregion

 /// <exclude />
	METHOD __GetFieldName( uFieldPosition AS USUAL) AS STRING STRICT
		LOCAL nIndex AS DWORD
		LOCAL cRet   AS STRING

		nIndex := SELF:__GetColIndex( uFieldPosition, FALSE )

		IF nIndex = 0 .OR. nIndex > nNumCols
			oStmt:__GenerateSQLError( __CavoStr( __CAVOSTR_SQLCLASS__BADFLD ), #FieldName )
		ELSE
			oStmt:ErrInfo:ErrorFlag := FALSE
			cRet := ((SQLColumn)aSQLColumns[nIndex]):ColName
		ENDIF

		RETURN cRet

    METHOD __CreateDataAdapter AS VOID

        self:oAdapter := self:oConn:Factory:CreateDataAdapter()
        self:oAdapter:SelectCommand := self:oStmt:StatementHandle
        var builder := self:oConn:Factory:CreateCommandBuilder()
        builder:DataAdapter := self:oAdapter
        self:oAdapter:InsertCommand := builder:GetInsertCommand()
        self:oAdapter:UpdateCommand := builder:GetUpdateCommand()
        self:oAdapter:DeleteCommand := builder:GetDeleteCommand()

 /// <exclude />
	METHOD __GoCold(lUpdateBatch AS LOGIC) AS LOGIC STRICT
		local lOk as logic
        local lWriteBatch as logic
        lWriteBatch := lUpdateBatch .or. ! self:lBatchUpdates
        SELF:nRowCount := 0
		if self:lChanges .and. lWriteBatch
            local oChanges as DataTable

            oChanges := self:oTable:GetChanges()
            if oChanges != null .and. oChanges:Rows:Count > 0
                if self:oAdapter == null_object
                    SELF:__CreateDataAdapter()
                endif
                TRY
                    SELF:nRowCount := self:oAdapter:Update(self:oTable)
                    SELF:oTable:AcceptChanges()
                    SELF:lChanges := FALSE
			        lOk := TRUE
                CATCH e as Exception
                    oStmt:__GenerateSQLError( e:Message, #__GoCold )
                    lOk := FALSE
                END TRY
            ENDIF

		ELSE
			lOk := TRUE
		ENDIF
		RETURN lOk

 /// <exclude />
	METHOD __InitColumnDesc() AS LOGIC STRICT
		LOCAL oDF           AS DataField		// Datafield
		LOCAL oHL           AS HyperLabel		// Hyperlabel
		LOCAL aNames        AS ARRAY
		LOCAL cFldName      AS STRING
		LOCAL oCol          AS SQLColumn
		LOCAL i             AS DWORD
		LOCAL oColumns		AS DataColumnCollection
		LOCAL oFS           AS FieldSpec		// Fieldspec
		LOCAL oRow          AS DataRow
		LOCAL ihelp         AS SHORT
		oColumns            := oTable:Columns
		// Do not re-read the table definition with the # of columns has not changed
		IF TRUE // SELF:nNumCols != oColumns:Count
			SELF:nNumCols       := oColumns:Count
			SELF:wFieldCount    := (DWORD) nNumCols
			SELF:aDataFields    := ArrayNew(nNumCols )
			SELF:aSQLColumns    := SQLColumn[]{nNumCols }
			SELF:aColumnAttributes := SQLColumnAttributes[]{nNumCols }
			SELF:oFieldHash	    := System.Collections.Hashtable{}
			// aNames is used by FieldNameCheck to guarantee unicity of field names
			aNames := {}
			FOREACH oColumn AS DataColumn IN oColumns
				i			:= (DWORD) (oColumn:Ordinal +1)
				cFldName	:= oColumn:ColumnName
				// Remove unwanted characters
				cFldName    := SqlFunctions.CleanupColumnName(cFldName)
				cFldName	:= SqlFunctions.FieldNameCheck(cFldName, aNames)
                oFS         := DotNetType2VOType(oSchema, oColumn,cFldName)
				oHL    		:= oFS:HyperLabel
				oDF    		:= DataField{ oHL, oFS }
				oFieldHash:Add(Upper(oDF:Name), i)
				TRY
					IF oSchema != NULL
						//LOCAL oT AS System.Type
						//LOCAL iScale AS SHORT
						oRow := oSchema:Rows[oColumn:Ordinal]
						//oT := (System.Type) oRow["DataType"]
						IF Convert.IsDBNull(oRow["NumericScale"])
							ihelp := 0
						ELSE
							ihelp := Convert.ToInt16(oRow["NumericScale"])
						ENDIF
						oCol := SQLColumn{oHL, oFS, (System.Type) oRow["DataType"], ihelp,  oColumn:AllowDBNull, oColumn:Ordinal, cFldName, cFldName}
						//oCol := SqlColumn{oHL, oFS, oT, iScale,  oColumn:AllowDBNull, oColumn:Ordinal, oColumn:ColumnName, oColumn:ColumnName}
					ELSE
						oCol := SQLColumn{oHL, oFS, oColumn:DataType, 0,  oColumn:AllowDBNull, oColumn:Ordinal, oColumn:ColumnName, cFldName}
					ENDIF
					SELF:aSQLColumns[i] := oCol
					SELF:aDataFields[i] := oDF
				CATCH e AS Exception
					oStmt:__GenerateSQLError( "Error reading structure: "+e:Message, #__InitColumnDesc )
				END TRY
			NEXT
		ENDIF
		RETURN TRUE

 /// <exclude />
	METHOD __Notify(kNot AS LONG ,uDesc := NIL AS USUAL) AS LOGIC
		LOCAL lOk 	AS USUAL
		LOCAL oError AS Error
		lOk := SELF:Notify(kNot,uDesc)
		IF kNot == NOTIFYINTENTTOMOVE .AND. ! lOk
			oError := SqlFunctions.CreateError(0, "IntentToMove returned FALSE")
			oError:FuncSym	:= #Notify
			SELF:Error(oError)
		ENDIF
		RETURN lOk

 /// <exclude />
	METHOD __Open(Table AS DataTable, Schema AS DataTable) AS LOGIC
		LOCAL cName AS STRING
		LOCAL lRet AS LOGIC
		lRet := false
		oTable      := Table
		oSchema     := Schema
		nRowCount	:= oTable:Rows:Count
		nCurrentRow := 0
		SELF:__CheckEOF()
		IF oSchema != NULL_OBJECT .AND. oSchema:Rows:Count > 0
			LOCAL oRow AS DataRow
			oRow := oSchema:Rows[0]
			cName := oRow:Item["BaseTableName"]:ToString()
		ELSE
			cName := oTable:TableName
		ENDIF

		IF String.IsNullOrEmpty(cName)
			LOCAL nPos AS DWORD
			LOCAL cStmt AS STRING
			cStmt := oStmt:SQLString
			nPos := AtC(" FROM ", cStmt)
			IF nPos > 0
				cName := AllTrim(SubStr2(cStmt, nPos+6))
			ELSE
				cName := oStmt:SQLString
			ENDIF
		ENDIF
		// Limit name for Hyperlabel to Table Name(s), so get rid of Select and Where/Order By clause
		oHyperLabel  	:= HyperLabel{ String2Symbol(cName),cName }
		// Build the list of DataField objects, the Field array and the
		// DbStruct array

		SELF:lCsrOpenFlag := TRUE
		SELF:lBof         := FALSE
		SELF:lEof         := FALSE
		SELF:lFetchFlag   := FALSE

		lRet := SELF:__InitColumnDesc()
		SELF:__CheckEOF()
		IF SELF:nNumCols = 0
			lRet := FALSE
		ENDIF
		RETURN lRet

	STATIC METHOD FindTableName( cStmt AS STRING) AS STRING
		LOCAL cTable:="" AS STRING
		LOCAL nPos   AS DWORD
		IF cStmt != NULL_STRING
			nPos := AtC( "select ", cStmt )
			IF nPos > 0
				cTable := LTrim( SubStr2( cStmt, nPos + 7 ) )
			ENDIF

			nPos := AtC( " from ", cStmt )
			cTable := LTrim( SubStr2( cStmt, nPos + 6 ) )
			nPos := At2( " ", cTable )
			IF nPos >= 2
				cTable := SubStr3( cTable, 1, nPos - 1 )
			ENDIF
		ENDIF
		RETURN cTable

 /// <exclude />
	METHOD __FindTableName() AS VOID STRICT
		LOCAL cClass AS STRING
		LOCAL cStmt	 AS STRING
		// RvdH 050413 Optimized by using local strings

		cStmt := SELF:oStmt:SQLString
		IF cStmt != NULL_STRING
			cTableName := FindTableName(cStmt)
			// tell the DataServer the name...
			cClass := Symbol2String( ClassName( SELF ) )
			oHyperLabel := HyperLabel{ cTableName, cTableName,                                   ;
			cClass + ": "+cTableName, ;
			cClass + "_"+cTableName }
		ENDIF
		RETURN

 /// <exclude />
	METHOD __RecCount() AS LONGINT STRICT
		RETURN SELF:nRowCount

 /// <exclude />
	METHOD __SetRecordFlags( lBofNew AS USUAL, lEofNew AS USUAL) AS VOID STRICT
		IF IsLogic( lBofNew )
			SELF:lBof := lBofNew
		ENDIF

		IF IsLogic( lEofNew )

			SELF:lEof := lEofNew
		ENDIF

		RETURN

	#region Dummy Methods

/// <exclude />
	[Obsolete];
 	METHOD __BuildUpdateStmt AS STRING STRICT
		RETURN ""

 /// <exclude />
	[Obsolete];
	METHOD  __CopyDataBuffer( aSQLSource AS ARRAY, aSQLTarget AS ARRAY) AS ARRAY STRICT
		RETURN aSQLTarget

 /// <exclude />
	[Obsolete];
	METHOD __FigureScrollUpdateType() AS DWORD STRICT
		RETURN 0

 /// <exclude />
	[Obsolete];
	METHOD __GetCursorName () AS LOGIC STRICT
		RETURN TRUE

/// <exclude />
	[Obsolete];
 	METHOD __GetLongData( nODBCType AS SHORTINT, nIndex AS DWORD ) AS LOGIC STRICT
		RETURN TRUE

 /// <exclude />
	[Obsolete];
	METHOD __GetUpdateKey( lAppend AS LOGIC) AS STRING STRICT
		RETURN ""

 /// <exclude />
	[Obsolete];
	METHOD __GetUpdateStmt( nIndex AS DWORD, lAppend AS LOGIC) AS SQLStatement STRICT
		RETURN NULL

 /// <exclude />
	[Obsolete];
	METHOD __GetUpdateVal( lAppend AS LOGIC) AS STRING STRICT
		RETURN ""

 /// <exclude />
	[Obsolete];
	METHOD __InitColValue( nIndex AS DWORD ) AS LOGIC STRICT
		RETURN TRUE

 /// <exclude />
	[Obsolete];
	METHOD __MemFree() AS VOID STRICT
		RETURN

 /// <exclude />
	[Obsolete];
	METHOD  __PrepareStmtOptions( oStatement AS SQLStatement ) AS VOID STRICT
		RETURN

 /// <exclude />
	[Obsolete];
	METHOD __PutLongData( cValue AS STRING, nODBCType AS SHORTINT, nIndex AS DWORD, lAppend AS LOGIC ) AS STRING STRICT
		RETURN cValue

 /// <exclude />
	[Obsolete];
	METHOD __Reset() AS LOGIC STRICT
		RETURN TRUE

 /// <exclude />
	[Obsolete];
	METHOD __SetCursorName( cCursorName AS STRING) AS LOGIC STRICT
		RETURN TRUE

 /// <exclude />
	[Obsolete];
	METHOD __SetNullData( nODBCType AS SHORTINT, nIndex AS DWORD, aData AS ARRAY) AS VOID STRICT
		RETURN

 /// <exclude />
	[Obsolete];
	METHOD __SetScrollOptions( nConcurrency AS DWORD, nKeySet AS DWORD, lAsync AS LOGIC) AS LOGIC STRICT
		RETURN TRUE

 /// <exclude />
	[Obsolete];
	METHOD __SkipCursor( nRecCount AS LONGINT) AS LOGIC STRICT
		RETURN TRUE

 /// <exclude />
	[Obsolete];
	METHOD __UpdateLongData( lAppend AS LOGIC ) AS VOID STRICT
		RETURN
	#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.Data.Common
USING System.Data
USING System.Collections.Generic
USING System.Reflection
USING System.Diagnostics

/// <include file="Sql.xml" path="doc/SQLSelect/*" />
[XSharp.Internal.TypesChanged];
[DebuggerDisplay( "SqlSelect {TableName,nq}" )] ;
PARTIAL CLASS SQLSelect INHERIT DataServer
	PROTECT oStmt           AS SQLStatement   // Statement object
	PROTECT oConn           AS SQLConnection
	PROTECT cCursor         AS STRING		// No longer used
	PROTECT lCsrOpenFlag 	AS LOGIC		// Is the server Open
	PROTECT lFetchFlag      AS LOGIC        // First Fetch has been performed
	PROTECT lBof            AS LOGIC        // Are we at BOF
	PROTECT lEof            AS LOGIC        // Are we at EOF

	PROTECT nNumCols		AS LONG			// # Of Columns in resultset
	PROTECT lAppendFlag     AS LOGIC        // Are we on a new record
	PROTECT aIndexCol       AS List<INT>
	PROTECT nRowCount		AS LONG			// Number of rows
	PROTECT cTableName      AS STRING
	PROTECT nNotifyCount    AS INT
	PROTECT lSuppressNotify AS LOGIC
	PROTECT aSQLColumns       AS SQLColumn[]    // Array of SqlColumn objects
	PROTECT aColumnAttributes AS SQLColumnAttributes[]   // Array of SQLColumnAttributes Objects.
	PROTECT lNullAsBlank	  AS LOGIC		// Return Null values as Blank
	PROTECT lTimeStampAsDate  AS LOGIC		// Should datetimes be handled as data ?
	PROTECT aLastArgs         AS ARRAY

    protect lReadOnly        as logic       // is the result set readonly
    protect lBatchUpdates    as logic       // does the result set support batch updates

	// Dummy Properties
	PROTECT nLastRecNum     AS LONG
	PROTECT nRecNum         AS LONG
	PROTECT lLastRecFound   AS LOGIC
	PROTECT lDeleteFlag     AS LOGIC

	// New .Net Properties
	PROTECT oNetConn		AS IDbConnection // Connection
	PROTECT oTable			AS DataTable	// Contents of resultset
	PROTECT oSchema			AS DataTable	// Schema definition of resultset
    PROTECT oAdapter        AS DbDataAdapter  // Data Adapter for Insert/Update/Delete
	PROTECT oCurrentRow		AS DataRow		// Current data row in result set. NULL when at EOF

	PROTECT oFieldHash      AS System.Collections.Hashtable // Hashtable of Strings and Symbols and column positions
	PROTECT nCurrentRow		AS LONG			// Current row number, 0 based
	PROTECT nSuspendNot     AS LONG			// Is used to keep track of the current notification state
	PROTECT lErrorFlag      AS LOGIC		// Was there an error with the last operation
	PROTECT lChanges        AS LOGIC

	#region Internal Methods

    method __CheckReadOnly as void
        if self:lReadOnly
            var error := Error{EG_READONLY}
            error:FuncSym := ProcName(2)
            throw error
        endif

 /// <exclude />
	METHOD __getDataTable() AS DataTable
		RETURN SELF:oTable

 /// <exclude />
	METHOD __AllocStmt() AS LOGIC STRICT
		IF SELF:oNetConn:State != ConnectionState.Open
			SELF:oNetConn:Open()
		ENDIF
		RETURN oStmt:__AllocStmt()

 /// <exclude />
	METHOD __CheckEOF AS VOID
		IF nRowCount == 0
			SELF:lBof := TRUE
			SELF:lEof := TRUE
		ELSE
			SELF:lBof := FALSE
			SELF:lEof := FALSE
			IF nCurrentRow < 0
				SELF:lBof := TRUE
				nCurrentRow := 0
			ELSEIF nCurrentRow >= nRowCount
				SELF:lEof := TRUE
				nCurrentRow := nRowCount
			ENDIF
			IF nCurrentRow < nRowCount
				oCurrentRow := oTable:Rows[nCurrentRow]
			ELSE
				oCurrentRow := NULL
			ENDIF
		ENDIF
		RETURN

 /// <exclude />
	[Obsolete];
	METHOD __CopySQLError( symMethod AS SYMBOL, oErrInfo AS SQLErrorInfo) AS VOID STRICT
		RETURN

 /// <exclude />
	METHOD __ForceOpen AS LOGIC STRICT
		TRY
			IF ! SELF:lCsrOpenFlag
				SELF:Execute(SELF:aLastArgs)
			ENDIF
		CATCH AS Exception
			lCsrOpenFlag := FALSE
		END TRY
		RETURN lCsrOpenFlag

 /// <exclude />
	METHOD __FreeStmt( fOption AS WORD ) AS LOGIC STRICT
		RETURN oStmt:__FreeStmt( fOption )

 /// <exclude />
	METHOD __GetColIndex( uFieldID AS USUAL, lAutoFetch AS LOGIC) AS DWORD STRICT
		LOCAL nPos AS DWORD
		LOCAL lOk  AS LOGIC
		nPos := 0
		IF lAutoFetch
			lOk := SELF:__ForceOpen()
		ELSE
			lOk := TRUE
		ENDIF
		IF lOk
			IF IsNumeric( uFieldID )
				IF uFieldID > 0 .AND. uFieldID <= nNumCols
					nPos := uFieldID
				ENDIF
			ELSEIF IsSymbol( uFieldID )
				LOCAL symField AS SYMBOL
				symField := uFieldID
				IF (oFieldHash:ContainsKey(symField))
					nPos := (DWORD) oFieldHash[symField]
				ENDIF
			ELSEIF IsString( uFieldID )
				LOCAL cField AS STRING
				LOCAL nDot AS DWORD
				cField := Upper(uFieldID)
				nDot := At(".", cField)
				IF nDot > 0
					cField := SubStr2(cField, nDot+1)
				ENDIF
				IF (oFieldHash:ContainsKey(cField))
					nPos := (DWORD) oFieldHash[cField]
				ENDIF

			ENDIF
		ENDIF
		RETURN nPos

 /// <exclude />
	METHOD __PrepareForRecordMovement AS LOGIC STRICT
		LOCAL lOk AS LOGIC
		lOk := SELF:__ForceOpen()
		IF lOk .AND. SELF:lEof .AND. SELF:lBof
			lOk := FALSE
		ENDIF
		IF lOk .AND. ! SELF:__Notify( NOTIFYINTENTTOMOVE )
			lOk := FALSE
		ENDIF
		IF lOk .AND. ! SELF:__GoCold(FALSE)
			lOk := FALSE
		ENDIF
		RETURN lOk

	#endregion

 /// <exclude />
	METHOD __GetFieldName( uFieldPosition AS USUAL) AS STRING STRICT
		LOCAL nIndex AS DWORD
		LOCAL cRet   AS STRING

		nIndex := SELF:__GetColIndex( uFieldPosition, FALSE )

		IF nIndex = 0 .OR. nIndex > nNumCols
			oStmt:__GenerateSQLError( __CavoStr( __CAVOSTR_SQLCLASS__BADFLD ), #FieldName )
		ELSE
			oStmt:ErrInfo:ErrorFlag := FALSE
			cRet := ((SQLColumn)aSQLColumns[nIndex]):ColName
		ENDIF

		RETURN cRet

    METHOD __CreateDataAdapter AS VOID

        self:oAdapter := self:oConn:Factory:CreateDataAdapter()
        self:oAdapter:SelectCommand := self:oStmt:StatementHandle
        var builder := self:oConn:Factory:CreateCommandBuilder()
        builder:DataAdapter := self:oAdapter
        self:oAdapter:InsertCommand := builder:GetInsertCommand()
        self:oAdapter:UpdateCommand := builder:GetUpdateCommand()
        self:oAdapter:DeleteCommand := builder:GetDeleteCommand()

 /// <exclude />
	METHOD __GoCold(lUpdateBatch AS LOGIC) AS LOGIC STRICT
		local lOk as logic
        local lWriteBatch as logic
        lWriteBatch := lUpdateBatch .or. ! self:lBatchUpdates
        SELF:nRowCount := 0
		if self:lChanges .and. lWriteBatch
            local oChanges as DataTable

            oChanges := self:oTable:GetChanges()
            if oChanges != null .and. oChanges:Rows:Count > 0
                if self:oAdapter == null_object
                    SELF:__CreateDataAdapter()
                endif
                SELF:nRowCount := self:oAdapter:Update(self:oTable)
			    SELF:oTable:AcceptChanges()
            ENDIF
			SELF:lChanges := FALSE
			lOk := TRUE
		ELSE
			lOk := TRUE
		ENDIF
		RETURN lOk

 /// <exclude />
	METHOD __InitColumnDesc() AS LOGIC STRICT
		LOCAL oDF           AS DataField		// Datafield
		LOCAL oHL           AS HyperLabel		// Hyperlabel
		LOCAL aNames        AS ARRAY
		LOCAL cFldName      AS STRING
		LOCAL oCol          AS SQLColumn
		LOCAL i             AS DWORD
		LOCAL oColumns		AS DataColumnCollection
		LOCAL oFS           AS FieldSpec		// Fieldspec
		LOCAL oRow          AS DataRow
		LOCAL ihelp         AS SHORT
		oColumns            := oTable:Columns
		// Do not re-read the table definition with the # of columns has not changed
		IF TRUE // SELF:nNumCols != oColumns:Count
			SELF:nNumCols       := oColumns:Count
			SELF:wFieldCount    := (DWORD) nNumCols
			SELF:aDataFields    := ArrayNew(nNumCols )
			SELF:aSQLColumns    := SQLColumn[]{nNumCols }
			SELF:aColumnAttributes := SQLColumnAttributes[]{nNumCols }
			SELF:oFieldHash	    := System.Collections.Hashtable{}
			// aNames is used by FieldNameCheck to guarantee unicity of field names
			aNames := {}
			FOREACH oColumn AS DataColumn IN oColumns
				i			:= (DWORD) (oColumn:Ordinal +1)
				cFldName	:= oColumn:ColumnName
				// Remove unwanted characters
				cFldName    := SqlFunctions.CleanupColumnName(cFldName)
				cFldName	:= SqlFunctions.FieldNameCheck(cFldName, aNames)
                oFS         := DotNetType2VOType(oSchema, oColumn,cFldName)
				oHL    		:= oFS:HyperLabel
				oDF    		:= DataField{ oHL, oFS }
				oFieldHash:Add(oDF:NameSym, i)
				oFieldHash:Add(Upper(oDF:Name), i)
				TRY
					IF oSchema != NULL
						//LOCAL oT AS System.Type
						//LOCAL iScale AS SHORT
						oRow := oSchema:Rows[oColumn:Ordinal]
						//oT := (System.Type) oRow["DataType"]
						IF Convert.IsDBNull(oRow["NumericScale"])
							ihelp := 0
						ELSE
							ihelp := Convert.ToInt16(oRow["NumericScale"])
						ENDIF
						oCol := SQLColumn{oHL, oFS, (System.Type) oRow["DataType"], ihelp,  oColumn:AllowDBNull, oColumn:Ordinal, cFldName, cFldName}
						//oCol := SqlColumn{oHL, oFS, oT, iScale,  oColumn:AllowDBNull, oColumn:Ordinal, oColumn:ColumnName, oColumn:ColumnName}
					ELSE
						oCol := SQLColumn{oHL, oFS, oColumn:DataType, 0,  oColumn:AllowDBNull, oColumn:Ordinal, oColumn:ColumnName, cFldName}
					ENDIF
					SELF:aSQLColumns[i] := oCol
					SELF:aDataFields[i] := oDF
				CATCH e AS Exception
					oStmt:__GenerateSQLError( "Error reading structure: "+e:Message, #__InitColumnDesc )
				END TRY
			NEXT
		ENDIF
		RETURN TRUE

 /// <exclude />
	METHOD __Notify(kNot AS LONG ,uDesc := NIL AS USUAL) AS LOGIC
		LOCAL lOk 	AS USUAL
		LOCAL oError AS Error
		lOk := SELF:Notify(kNot,uDesc)
		IF kNot == NOTIFYINTENTTOMOVE .AND. ! lOk
			oError := SqlFunctions.CreateError(0, "IntentToMove returned FALSE")
			oError:FuncSym	:= #Notify
			SELF:Error(oError)
		ENDIF
		RETURN lOk

 /// <exclude />
	METHOD __Open(Table AS DataTable, Schema AS DataTable) AS LOGIC
		LOCAL cName AS STRING
		LOCAL lRet AS LOGIC
		lRet := false
		oTable      := Table
		oSchema     := Schema
		nRowCount	:= oTable:Rows:Count
		nCurrentRow := 0
		SELF:__CheckEOF()
		IF oSchema != NULL_OBJECT .AND. oSchema:Rows:Count > 0
			LOCAL oRow AS DataRow
			oRow := oSchema:Rows[0]
			cName := oRow:Item["BaseTableName"]:ToString()
		ELSE
			cName := oTable:TableName
		ENDIF

		IF String.IsNullOrEmpty(cName)
			LOCAL nPos AS DWORD
			LOCAL cStmt AS STRING
			cStmt := oStmt:SQLString
			nPos := AtC(" FROM ", cStmt)
			IF nPos > 0
				cName := AllTrim(SubStr2(cStmt, nPos+6))
			ELSE
				cName := oStmt:SQLString
			ENDIF
		ENDIF
		// Limit name for Hyperlabel to Table Name(s), so get rid of Select and Where/Order By clause
		oHyperLabel  	:= HyperLabel{ String2Symbol(cName),cName }
		// Build the list of DataField objects, the Field array and the
		// DbStruct array

		SELF:lCsrOpenFlag := TRUE
		SELF:lBof         := FALSE
		SELF:lEof         := FALSE
		SELF:lFetchFlag   := FALSE

		lRet := SELF:__InitColumnDesc()
		SELF:__CheckEOF()
		IF SELF:nNumCols = 0
			lRet := FALSE
		ENDIF
		RETURN lRet

	STATIC METHOD FindTableName( cStmt AS STRING) AS STRING
		LOCAL cTable:="" AS STRING
		LOCAL nPos   AS DWORD
		IF cStmt != NULL_STRING
			nPos := AtC( "select ", cStmt )
			IF nPos > 0
				cTable := LTrim( SubStr2( cStmt, nPos + 7 ) )
			ENDIF

			nPos := AtC( " from ", cStmt )
			cTable := LTrim( SubStr2( cStmt, nPos + 6 ) )
			nPos := At2( " ", cTable )
			IF nPos >= 2
				cTable := SubStr3( cTable, 1, nPos - 1 )
			ENDIF
		ENDIF
		RETURN cTable

 /// <exclude />
	METHOD __FindTableName() AS VOID STRICT
		LOCAL cClass AS STRING
		LOCAL cStmt	 AS STRING
		// RvdH 050413 Optimized by using local strings

		cStmt := SELF:oStmt:SQLString
		IF cStmt != NULL_STRING
			cTableName := FindTableName(cStmt)
			// tell the DataServer the name...
			cClass := Symbol2String( ClassName( SELF ) )
			oHyperLabel := HyperLabel{ cTableName, cTableName,                                   ;
			cClass + ": "+cTableName, ;
			cClass + "_"+cTableName }
		ENDIF
		RETURN

 /// <exclude />
	METHOD __RecCount() AS LONGINT STRICT
		RETURN SELF:nRowCount

 /// <exclude />
	METHOD __SetRecordFlags( lBofNew AS USUAL, lEofNew AS USUAL) AS VOID STRICT
		IF IsLogic( lBofNew )
			SELF:lBof := lBofNew
		ENDIF

		IF IsLogic( lEofNew )

			SELF:lEof := lEofNew
		ENDIF

		RETURN

	#region Dummy Methods

/// <exclude />
	[Obsolete];
 	METHOD __BuildUpdateStmt AS STRING STRICT
		RETURN ""

 /// <exclude />
	[Obsolete];
	METHOD  __CopyDataBuffer( aSQLSource AS ARRAY, aSQLTarget AS ARRAY) AS ARRAY STRICT
		RETURN aSQLTarget

 /// <exclude />
	[Obsolete];
	METHOD __FigureScrollUpdateType() AS DWORD STRICT
		RETURN 0

 /// <exclude />
	[Obsolete];
	METHOD __GetCursorName () AS LOGIC STRICT
		RETURN TRUE

/// <exclude />
	[Obsolete];
 	METHOD __GetLongData( nODBCType AS SHORTINT, nIndex AS DWORD ) AS LOGIC STRICT
		RETURN TRUE

 /// <exclude />
	[Obsolete];
	METHOD __GetUpdateKey( lAppend AS LOGIC) AS STRING STRICT
		RETURN ""

 /// <exclude />
	[Obsolete];
	METHOD __GetUpdateStmt( nIndex AS DWORD, lAppend AS LOGIC) AS SQLStatement STRICT
		RETURN NULL

 /// <exclude />
	[Obsolete];
	METHOD __GetUpdateVal( lAppend AS LOGIC) AS STRING STRICT
		RETURN ""

 /// <exclude />
	[Obsolete];
	METHOD __InitColValue( nIndex AS DWORD ) AS LOGIC STRICT
		RETURN TRUE

 /// <exclude />
	[Obsolete];
	METHOD __MemFree() AS VOID STRICT
		RETURN

 /// <exclude />
	[Obsolete];
	METHOD  __PrepareStmtOptions( oStatement AS SQLStatement ) AS VOID STRICT
		RETURN

 /// <exclude />
	[Obsolete];
	METHOD __PutLongData( cValue AS STRING, nODBCType AS SHORTINT, nIndex AS DWORD, lAppend AS LOGIC ) AS STRING STRICT
		RETURN cValue

 /// <exclude />
	[Obsolete];
	METHOD __Reset() AS LOGIC STRICT
		RETURN TRUE

 /// <exclude />
	[Obsolete];
	METHOD __SetCursorName( cCursorName AS STRING) AS LOGIC STRICT
		RETURN TRUE

 /// <exclude />
	[Obsolete];
	METHOD __SetNullData( nODBCType AS SHORTINT, nIndex AS DWORD, aData AS ARRAY) AS VOID STRICT
		RETURN

 /// <exclude />
	[Obsolete];
	METHOD __SetScrollOptions( nConcurrency AS DWORD, nKeySet AS DWORD, lAsync AS LOGIC) AS LOGIC STRICT
		RETURN TRUE

 /// <exclude />
	[Obsolete];
	METHOD __SkipCursor( nRecCount AS LONGINT) AS LOGIC STRICT
		RETURN TRUE

 /// <exclude />
	[Obsolete];
	METHOD __UpdateLongData( lAppend AS LOGIC ) AS VOID STRICT
		RETURN
	#endregion

END CLASS