[Runtime] Replace some function calls inside VoDb with the method calls in the 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 XSharp.RDD
USING XSharp.RDD.Enums
USING XSharp.RDD.Support
USING System.Collections.Generic
USING System.Linq

/// The VoDb class extends the CoreDb class with methods that take usual parameters or return usual values.

/// All other methods are identical and inherited from the CoreDb class.
PARTIAL CLASS XSharp.VoDb INHERIT XSharp.CoreDb
PRIVATE STATIC METHOD DecodeResult(oValue as OBJECT) AS USUAL
    IF oValue == DBNull.Value
        RETURN NIL
    ENDIF
    RETURN oValue

/// <inheritdoc cref='CoreDb.BlobInfo'/>

STATIC METHOD BlobInfo(nOrdinal AS DWORD,nPos AS DWORD,ptrRet REF USUAL) AS LOGIC
    LOCAL oRet := ptrRet AS OBJECT
    LOCAL result AS LOGIC
    result := CoreDb.BlobInfo(nOrdinal, nPos, REF oRet)
    ptrRet := DecodeResult(oRet)
    RETURN result

/// <inheritdoc cref='CoreDb.BlobInfo'/>
STATIC METHOD BlobInfo(nOrdinal AS DWORD,nPos AS DWORD,uValue AS USUAL) AS LOGIC
    RETURN CoreDb.BlobInfo(nOrdinal, nPos, (OBJECT) uValue)

/// <inheritdoc cref='CoreDb.FieldInfo'/>
STATIC METHOD FieldInfo(nOrdinal AS DWORD,nFldPos AS DWORD,oValue REF USUAL) AS LOGIC
    LOCAL oRet := oValue AS OBJECT
    LOCAL result AS LOGIC
    result := CoreDb.FieldInfo(nOrdinal, nFldPos, REF oRet)
    oValue := DecodeResult(oRet)
    RETURN result

/// <inheritdoc cref="CoreDb.FieldInfo"/>
STATIC METHOD FieldInfo(nOrdinal AS DWORD,nFldPos AS DWORD,oValue AS USUAL) AS LOGIC
    RETURN CoreDb.FieldInfo(nOrdinal, nFldPos, (OBJECT) oValue)

/// <inheritdoc cref="CoreDb.FieldGet"/>
STATIC METHOD FieldGet(nPos AS DWORD,uRet REF USUAL) AS LOGIC
    LOCAL lResult AS LOGIC
    LOCAL oValue := uRet AS OBJECT
    lResult := CoreDb.FieldGet(nPos, REF oValue)
    // Note: FieldGet SHOULD return DBNull.Value when needed
    uRet := oValue
    RETURN lResult

/// <inheritdoc cref="CoreDb.Info"/>
/// <remarks> <inheritdoc cref="CoreDb.Info"/>
///

 <note type="tip">The difference between VoDb.Info and CoreDb.Info is that VoDb.Info takes a USUAL parameter</note></remarks>
STATIC METHOD Info(nOrdinal AS DWORD,oValue REF USUAL) AS LOGIC
    LOCAL oRet := oValue AS OBJECT
    LOCAL result AS LOGIC
    IF oValue:IsArray
        LOCAL aValue := oValue AS ARRAY
        oRet := (OBJECT[]) aValue
    ENDIF
    result := CoreDb.Info(nOrdinal, REF oRet)
    oValue := DecodeResult(oRet)
    RETURN result

/// <inheritdoc cref='CoreDb.Info'/>
/// <remarks> <inheritdoc cref='CoreDb.Info'/>
///

 <note type="tip">The difference between VoDb.Info and CoreDb.Info is that VoDb.Info takes a USUAL parameter</note></remarks>
STATIC METHOD Info(nOrdinal AS DWORD,oValue AS USUAL) AS LOGIC
    IF oValue:IsArray
        LOCAL aValue := oValue AS ARRAY
        oValue := (OBJECT[]) aValue
    ENDIF

    RETURN CoreDb.Info(nOrdinal, (OBJECT) oValue)

/// <inheritdoc cref='CoreDb.OrderInfo'/>
/// <remarks> <inheritdoc cref='CoreDb.OrderInfo'/>
///

 <note type="tip">The difference between VoDb.OrderInfo and CoreDb.OrderInfo is that VoDb.Info takes a USUAL parameter</note></remarks>
STATIC METHOD OrderInfo(nOrdinal AS DWORD,cBagName AS STRING,uOrder AS OBJECT,oValue REF USUAL) AS LOGIC
    LOCAL oRet := oValue AS OBJECT
    LOCAL result AS LOGIC
    result := CoreDb.OrderInfo(nOrdinal, cBagName,  uOrder, REF oRet)
    IF oRet == NULL
        oValue := NIL
    ELSE
        oValue := DecodeResult(oRet)
    ENDIF
    RETURN result

/// <inheritdoc cref="CoreDb.OrderInfo" />
/// <remarks> <inheritdoc cref='CoreDb.OrderInfo'/>
///

 <note type="tip">The difference between VoDb.OrderInfo and CoreDb.OrderInfo is that VoDb.Info takes a USUAL parameter</note></remarks>
STATIC METHOD OrderInfo(nOrdinal AS DWORD,cBagName AS STRING,uOrder AS OBJECT,oValue AS USUAL) AS LOGIC
    RETURN CoreDb.OrderInfo(nOrdinal, cBagName,  uOrder, (OBJECT) oValue)

/// <inheritdoc cref='CoreDb.RddInfo'/>
/// <remarks> <inheritdoc cref='CoreDb.RddInfo'/>
///

 <note type="tip">The difference between VoDb.RddInfo and CoreDb.RddInfo is that VoDb.RddInfo takes a USUAL parameter</note></remarks>
STATIC METHOD RddInfo(nOrdinal AS DWORD,uRet REF USUAL) AS LOGIC
    LOCAL oValue AS OBJECT
    oValue := uRet
    LOCAL result := CoreDb.RddInfo(nOrdinal, REF oValue) AS LOGIC
    uRet := DecodeResult(oValue)
    RETURN result

/// <inheritdoc cref='CoreDb.RddInfo'/>
/// <remarks> <inheritdoc cref='CoreDb.RddInfo'/>
///

 <note type="tip">The difference between VoDb.RddInfo and CoreDb.RddInfo is that VoDb.RddInfo takes a USUAL parameter</note></remarks>
STATIC METHOD RddInfo(nOrdinal AS DWORD,uValue AS USUAL) AS LOGIC
    RETURN CoreDb.RddInfo(nOrdinal, (OBJECT) uValue)

/// <inheritdoc cref='CoreDb.RecordInfo'/>
/// <remarks> <inheritdoc cref='CoreDb.RecordInfo'/>
///

 <note type="tip">The difference between VoDb.RecordInfo and CoreDb.RecordInfo is that VoDb.RecordInfo takes a USUAL parameter</note></remarks>
STATIC METHOD RecordInfo(nOrdinal AS DWORD,oRecID AS USUAL,oValue REF USUAL) AS LOGIC
    LOCAL oRet := oValue AS OBJECT
    LOCAL lResult AS LOGIC
    lResult := CoreDb.RecordInfo(nOrdinal, oRecID, REF oRet)
    oValue := DecodeResult(oRet)
    RETURN lResult

/// <inheritdoc cref='CoreDb.RecordInfo'/>
/// <remarks> <inheritdoc cref='CoreDb.RecordInfo'/>
///

 <note type="tip">The difference between VoDb.RecordInfo and CoreDb.RecordInfo is that VoDb.RecordInfo takes a USUAL parameter</note></remarks>
STATIC METHOD RecordInfo(nOrdinal AS DWORD,oRecID AS USUAL,oValue AS USUAL) AS LOGIC
    RETURN CoreDb.RecordInfo(nOrdinal, oRecID,  (OBJECT) oValue)

/// <inheritdoc cref='CoreDb.Select'/>
/// <remarks> <inheritdoc cref='CoreDb.Select'/>
///

 <note type="tip">The difference between VoDb.Select and CoreDb.Select is that VoDb.Select takes a USUAL parameter</note></remarks>
STATIC METHOD Select(nNew AS DWORD,riOld OUT USUAL) AS LOGIC
    LOCAL nOld := 0 AS DWORD
    LOCAL lResult AS LOGIC
    lResult := CoreDb.Select(nNew, OUT nOld)
    riOld := nOld
    RETURN lResult

/// <inheritdoc cref='CoreDb.SetFilter'/>
/// <remarks> <note type="tip">The difference between VoDb.SetFilter and CoreDb.SetFilter is that VoDb.SetFilter takes a USUAL parameter</note></remarks>
STATIC METHOD SetFilter(oBlock AS USUAL,cFilter AS STRING) AS LOGIC
    LOCAL cb AS ICodeblock
    IF oBlock:IsCodeblock
       cb := (ICodeblock) oBlock
    ELSE
        cb := NULL
    ENDIF
    RETURN CoreDb.SetFilter(cb, cFilter)

    INTERNAL STATIC METHOD ParamError(cFuncSym AS STRING, dwArgNum  AS DWORD , dwArgType AS DWORD, cArgName := "" AS STRING) AS Error

        LOCAL oError    AS Error
        oError := Error{RuntimeState.LastRddError}
        oError:SubSystem    := "DBCMD"
        oError:Gencode      := EG_ARG
        oError:Severity     := ES_ERROR
        oError:CanDefault   := .F.
        oError:CanRetry     := .T.
        oError:CanSubstitute := .F.
        oError:ArgType      := dwArgType
        oError:ArgNum       := dwArgNum
        oError:Arg          := cArgName
        oError:FuncSym      := cFuncSym
        RETURN oError

    INTERNAL STATIC METHOD DbCmdError(cFuncSym AS STRING)  AS Error
        LOCAL oError    AS Error
        oError := Error{RuntimeState.LastRddError}
        oError:Gencode      := EG_NOTABLE
        oError:SubCode      := EDB_NOTABLE
        oError:SubSystem    := "DBCMD"
        oError:Severity     := ES_ERROR
        oError:FuncSym      := cFuncSym
        oError:CanDefault   := .T.
        RETURN oError

    INTERNAL STATIC METHOD AllocFieldNames(aStru AS ARRAY) AS _FieldNames
        VAR aNames := List<STRING>{}
        FOREACH aField AS USUAL IN aStru
            IF aField:IsArray
                aNames:Add(Upper(aField[DBS_NAME]))
            ELSE
                aNames:Add(Upper(aField))
            ENDIF
        NEXT
        RETURN _FieldNames{aNames}

    INTERNAL STATIC METHOD TargetFields  (cAlias AS STRING, aNames AS ARRAY, oJoinList OUT _JoinList) AS ARRAY

        LOCAL aNew      AS ARRAY
        LOCAL cName     AS STRING
        LOCAL aStruct   AS ARRAY
        LOCAL aDbStruct AS ARRAY
        LOCAL nFields, i AS INT
        LOCAL siPos     AS DWORD
        LOCAL siSelect  AS DWORD
        LOCAL aFldList  AS ARRAY

        aDbStruct := DbStruct()
        aStruct   := {}
        aFldList := {}

        IF ( Empty(aNames) )

            aNames     := {}
            nFields    := (INT) VoDb.FCount()
            siSelect   := VoDb.GetSelect()
            FOR i := 1 TO nFields
                cName := aDbStruct[i, DBS_NAME]
                AAdd(aFldList, {siSelect, VoDb.FieldPos(cName)})
                AAdd(aStruct, aDbStruct[i])
                AAdd(aNames, cName)
            NEXT
        ELSE
            nFields := (INT)Len(aNames)
            aNew := {}
            FOR i := 1 TO nFields
                AAdd(aNew, AllTrim(Upper(aNames[i])))
            NEXT
            aNames := aNew
            nFields := (INT)VoDb.FCount()
            siSelect := VoDb.GetSelect()
            FOR i := 1 TO nFields
                cName := aDbStruct[i, DBS_NAME]
                IF AScan(aNames, {|c| c == cName}) > 0
                    AAdd(aFldList, {siSelect, VoDb.FieldPos(cName)})
                    AAdd(aStruct, aDbStruct[i])
                ENDIF
            NEXT
        ENDIF
        siSelect := @@Select(cAlias)
        aDbStruct := DbStruct()
        nFields := (INT)Len(aNames)

        FOR i := 1 TO nFields
            IF "->" $ aNames[i]
                cName := SubStr2(aNames[i], At(">", aNames[i]) + 1)
            ELSE
                cName :=  aNames[i]
            ENDIF

            siPos := AScan(aDbStruct, {|a| a[DBS_NAME] == cName})
            IF siPos > 0 .AND. (AScan( aStruct, {|c|c[DBS_NAME]== cName }) == 0)
                AAdd(aFldList, {siSelect, VoDb.FieldPos(cName)})
                AAdd(aStruct, aDbStruct[siPos])
            ENDIF
        NEXT
        nFields := (INT)ALen(aStruct)
        oJoinList := _JoinList{nFields}
        FOR i := 1 TO nFields
            oJoinList:Fields[i]:Area := aFldList[i,1]
            oJoinList:Fields[i]:Pos  := aFldList[i,2] - 1
        NEXT
        RETURN aStruct

    INTERNAL STATIC METHOD  RddList( xDriver AS USUAL, aHidden AS USUAL ) AS ARRAY

        LOCAL   nType   AS DWORD
        LOCAL   aRdds  := NULL_ARRAY AS ARRAY
        LOCAL   n       AS DWORD
        LOCAL   i       AS DWORD
        LOCAL   lFPT  := FALSE AS LOGIC
        LOCAL   lDbf    AS LOGIC

        IF xDriver:IsArray
            nType := ARRAY
        ELSEIF xDriver:IsString
            IF SLen(xDriver) = 0
                xDriver := RddSetDefault()
            ENDIF
            nType := STRING
        ELSE
            xDriver := RddSetDefault()
            nType := STRING
        ENDIF

        IF nType == ARRAY
            aRdds := xDriver
        ELSEIF nType == STRING
            aRdds := {}
            xDriver := Upper(xDriver)
            SWITCH (STRING) xDriver
            CASE "DBFNTX"
            CASE "DBFMEMO"
            CASE "DBFDBT"
                lDbf := .T.
            CASE "_DBFCDX"
            CASE "DBFFPT"
                xDriver := "DBFFPT"
                lDbf := .T.
            CASE "DBFCDX"
                lFPT := .T.
                lDbf  := .T.
                xDriver := "DBFFPT"
            CASE "DBFMDX"
                lDbf := .T.
            OTHERWISE
                lDbf := .F.
                lFPT := .F.
            END SWITCH

            IF lDbf
                AAdd(aRdds, "DBF")
            ENDIF

            AAdd(aRdds, xDriver)

            IF lFPT
                AAdd(aRdds, "DBFCDX")
            ENDIF

        ENDIF

        IF UsualType(aHidden) == ARRAY
            n := ALen(aHidden)
            FOR i := 1 TO n
                AAdd(aRdds, aHidden[i])
            NEXT
        ENDIF
        RETURN aRdds

    INTERNAL STATIC METHOD AllocRddList(aNames AS ARRAY) AS _RddList
        VAR aList := List<STRING>{}
        FOREACH cName AS STRING IN aNames
            aList:Add(cName)
        NEXT
        RETURN _RddList{aList:ToArray()}

    INTERNAL STATIC METHOD ArrayToFieldInfo(aStruct AS ARRAY) AS RddFieldInfo[]
        VAR oList := List<RddFieldInfo>{}
        VAR nullable := 0
        VAR hasnullfield := FALSE
        FOREACH aField AS ARRAY IN aStruct
            VAR oFld := RddFieldInfo{(STRING) aField[DBS_NAME], (STRING) aField[DBS_TYPE], (LONG)aField[DBS_LEN], (LONG)aField[DBS_DEC]}
            IF ALen(aField) >= DBS_FLAGS
                oFld:Flags := (DBFFieldFlags) (LONG) aField[DBS_FLAGS]
            ENDIF
            IF oFld:IsNullable
                nullable += 1
            ENDIF
            IF oFld:FieldType == DbFieldType.NullFlags
                hasnullfield := TRUE
            ENDIF
            IF ALen(aField) >= DBS_ALIAS
                oFld:Alias := aField[DBS_ALIAS]
            ENDIF
            oList:Add(oFld)
        NEXT
        IF nullable != 0 .AND. ! hasnullfield
            VAR oFld := RddFieldInfo{_NULLFLAGS,"0", (nullable+7) % 8, 0}
            oList:Add(oFld)
        ENDIF
        RETURN oList:ToArray()

    INTERNAL STATIC METHOD OrdScopeNum(nScope)  AS INT CLIPPER
        IF !nScope:IsNumeric
            nScope := 0
        ENDIF
        nScope := INT(nScope)
        IF nScope < 0
            nScope := 0
        ENDIF
        IF nScope > 1
            nScope := 1
        ENDIF
        RETURN nScope

    INTERNAL STATIC METHOD WithoutAlias(cName AS STRING) AS STRING
        cName   := SubStr(cName, At(">", cName) + 1 )
        cName   := Trim(Upper(cName))
        RETURN cName

    INTERNAL STATIC METHOD ValidBlock(uBlock AS USUAL, bDef := NULL_CODEBLOCK AS ICodeblock) AS ICodeblock
        IF uBlock:IsCodeblock .or. uBlock:IsObject
            LOCAL oBlock    := uBlock   AS OBJECT
            IF oBlock IS ICodeblock
                RETURN (ICodeblock) oBlock
            ENDIF
        ENDIF
        RETURN bDef

INTERNAL STATIC METHOD  FieldList(aStruct AS ARRAY, aNames AS ARRAY, aMatch AS ARRAY) AS ARRAY

	LOCAL aNew      AS ARRAY
	LOCAL cName     AS STRING
	LOCAL n, i, j   AS DWORD
	LOCAL lMatch	AS LOGIC

	IF Empty(aNames)
		RETURN (aStruct)
	ENDIF

	IF Empty(aMatch)
		lMatch := .F.
	ELSE
		lMatch := .T.
	ENDIF

	aNew:= {}
	n   := Len(aNames)

	FOR i := 1 TO n
		AAdd(aNew, VoDb.WithoutAlias(AllTrim(aNames[i])))
	NEXT

	aNames  := aNew
	cName   := ""
	aNew    := {}
    // Convert AStruct to List<String>
    VAR aFields := Dictionary<String, DWORD>{StringComparer.OrdinalIgnoreCase}
    FOR i := 1 to ALen(aStruct)
        local aFld := aStruct[i] AS ARRAY
        aFields:Add(Trim(aFld[DBS_NAME]), i )
    NEXT

	FOR i := 1 TO n
		cName := Trim(aNames[i])
        IF aFields:ContainsKey(cName)
            j := aFields[cName]
			IF lMatch
				IF aMatch[i, DBS_TYPE] == aStruct[j, DBS_TYPE]
					AAdd(aNew, aStruct[j])
				ENDIF
			ELSE
				AAdd(aNew, aStruct[j])
			ENDIF
		ENDIF
	NEXT

	RETURN aNew

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 XSharp.RDD
USING XSharp.RDD.Enums
USING XSharp.RDD.Support
USING System.Collections.Generic
USING System.Linq

/// The VoDb class extends the CoreDb class with methods that take usual parameters or return usual values.

/// All other methods are identical and inherited from the CoreDb class.
PARTIAL CLASS XSharp.VoDb INHERIT XSharp.CoreDb
PRIVATE STATIC METHOD DecodeResult(oValue as OBJECT) AS USUAL
    IF oValue == DBNull.Value
        RETURN NIL
    ENDIF
    RETURN oValue

/// <inheritdoc cref='CoreDb.BlobInfo'/>

STATIC METHOD BlobInfo(nOrdinal AS DWORD,nPos AS DWORD,ptrRet REF USUAL) AS LOGIC
    LOCAL oRet := ptrRet AS OBJECT
    LOCAL result AS LOGIC
    result := CoreDb.BlobInfo(nOrdinal, nPos, REF oRet)
    ptrRet := DecodeResult(oRet)
    RETURN result

/// <inheritdoc cref='CoreDb.BlobInfo'/>
STATIC METHOD BlobInfo(nOrdinal AS DWORD,nPos AS DWORD,uValue AS USUAL) AS LOGIC
    RETURN CoreDb.BlobInfo(nOrdinal, nPos, (OBJECT) uValue)

/// <inheritdoc cref='CoreDb.FieldInfo'/>
STATIC METHOD FieldInfo(nOrdinal AS DWORD,nFldPos AS DWORD,oValue REF USUAL) AS LOGIC
    LOCAL oRet := oValue AS OBJECT
    LOCAL result AS LOGIC
    result := CoreDb.FieldInfo(nOrdinal, nFldPos, REF oRet)
    oValue := DecodeResult(oRet)
    RETURN result

/// <inheritdoc cref="CoreDb.FieldInfo"/>
STATIC METHOD FieldInfo(nOrdinal AS DWORD,nFldPos AS DWORD,oValue AS USUAL) AS LOGIC
    RETURN CoreDb.FieldInfo(nOrdinal, nFldPos, (OBJECT) oValue)

/// <inheritdoc cref="CoreDb.FieldGet"/>
STATIC METHOD FieldGet(nPos AS DWORD,uRet REF USUAL) AS LOGIC
    LOCAL lResult AS LOGIC
    LOCAL oValue := uRet AS OBJECT
    lResult := CoreDb.FieldGet(nPos, REF oValue)
    // Note: FieldGet SHOULD return DBNull.Value when needed
    uRet := oValue
    RETURN lResult

/// <inheritdoc cref="CoreDb.Info"/>
/// <remarks> <inheritdoc cref="CoreDb.Info"/>
///

 <note type="tip">The difference between VoDb.Info and CoreDb.Info is that VoDb.Info takes a USUAL parameter</note></remarks>
STATIC METHOD Info(nOrdinal AS DWORD,oValue REF USUAL) AS LOGIC
    LOCAL oRet := oValue AS OBJECT
    LOCAL result AS LOGIC
    IF oValue:IsArray
        LOCAL aValue := oValue AS ARRAY
        oRet := (OBJECT[]) aValue
    ENDIF
    result := CoreDb.Info(nOrdinal, REF oRet)
    oValue := DecodeResult(oRet)
    RETURN result

/// <inheritdoc cref='CoreDb.Info'/>
/// <remarks> <inheritdoc cref='CoreDb.Info'/>
///

 <note type="tip">The difference between VoDb.Info and CoreDb.Info is that VoDb.Info takes a USUAL parameter</note></remarks>
STATIC METHOD Info(nOrdinal AS DWORD,oValue AS USUAL) AS LOGIC
    IF oValue:IsArray
        LOCAL aValue := oValue AS ARRAY
        oValue := (OBJECT[]) aValue
    ENDIF

    RETURN CoreDb.Info(nOrdinal, (OBJECT) oValue)

/// <inheritdoc cref='CoreDb.OrderInfo'/>
/// <remarks> <inheritdoc cref='CoreDb.OrderInfo'/>
///

 <note type="tip">The difference between VoDb.OrderInfo and CoreDb.OrderInfo is that VoDb.Info takes a USUAL parameter</note></remarks>
STATIC METHOD OrderInfo(nOrdinal AS DWORD,cBagName AS STRING,uOrder AS OBJECT,oValue REF USUAL) AS LOGIC
    LOCAL oRet := oValue AS OBJECT
    LOCAL result AS LOGIC
    result := CoreDb.OrderInfo(nOrdinal, cBagName,  uOrder, REF oRet)
    IF oRet == NULL
        oValue := NIL
    ELSE
        oValue := DecodeResult(oRet)
    ENDIF
    RETURN result

/// <inheritdoc cref="CoreDb.OrderInfo" />
/// <remarks> <inheritdoc cref='CoreDb.OrderInfo'/>
///

 <note type="tip">The difference between VoDb.OrderInfo and CoreDb.OrderInfo is that VoDb.Info takes a USUAL parameter</note></remarks>
STATIC METHOD OrderInfo(nOrdinal AS DWORD,cBagName AS STRING,uOrder AS OBJECT,oValue AS USUAL) AS LOGIC
    RETURN CoreDb.OrderInfo(nOrdinal, cBagName,  uOrder, (OBJECT) oValue)

/// <inheritdoc cref='CoreDb.RddInfo'/>
/// <remarks> <inheritdoc cref='CoreDb.RddInfo'/>
///

 <note type="tip">The difference between VoDb.RddInfo and CoreDb.RddInfo is that VoDb.RddInfo takes a USUAL parameter</note></remarks>
STATIC METHOD RddInfo(nOrdinal AS DWORD,uRet REF USUAL) AS LOGIC
    LOCAL oValue AS OBJECT
    oValue := uRet
    LOCAL result := CoreDb.RddInfo(nOrdinal, REF oValue) AS LOGIC
    uRet := DecodeResult(oValue)
    RETURN result

/// <inheritdoc cref='CoreDb.RddInfo'/>
/// <remarks> <inheritdoc cref='CoreDb.RddInfo'/>
///

 <note type="tip">The difference between VoDb.RddInfo and CoreDb.RddInfo is that VoDb.RddInfo takes a USUAL parameter</note></remarks>
STATIC METHOD RddInfo(nOrdinal AS DWORD,uValue AS USUAL) AS LOGIC
    RETURN CoreDb.RddInfo(nOrdinal, (OBJECT) uValue)

/// <inheritdoc cref='CoreDb.RecordInfo'/>
/// <remarks> <inheritdoc cref='CoreDb.RecordInfo'/>
///

 <note type="tip">The difference between VoDb.RecordInfo and CoreDb.RecordInfo is that VoDb.RecordInfo takes a USUAL parameter</note></remarks>
STATIC METHOD RecordInfo(nOrdinal AS DWORD,oRecID AS USUAL,oValue REF USUAL) AS LOGIC
    LOCAL oRet := oValue AS OBJECT
    LOCAL lResult AS LOGIC
    lResult := CoreDb.RecordInfo(nOrdinal, oRecID, REF oRet)
    oValue := DecodeResult(oRet)
    RETURN lResult

/// <inheritdoc cref='CoreDb.RecordInfo'/>
/// <remarks> <inheritdoc cref='CoreDb.RecordInfo'/>
///

 <note type="tip">The difference between VoDb.RecordInfo and CoreDb.RecordInfo is that VoDb.RecordInfo takes a USUAL parameter</note></remarks>
STATIC METHOD RecordInfo(nOrdinal AS DWORD,oRecID AS USUAL,oValue AS USUAL) AS LOGIC
    RETURN CoreDb.RecordInfo(nOrdinal, oRecID,  (OBJECT) oValue)

/// <inheritdoc cref='CoreDb.Select'/>
/// <remarks> <inheritdoc cref='CoreDb.Select'/>
///

 <note type="tip">The difference between VoDb.Select and CoreDb.Select is that VoDb.Select takes a USUAL parameter</note></remarks>
STATIC METHOD Select(nNew AS DWORD,riOld OUT USUAL) AS LOGIC
    LOCAL nOld := 0 AS DWORD
    LOCAL lResult AS LOGIC
    lResult := CoreDb.Select(nNew, OUT nOld)
    riOld := nOld
    RETURN lResult

/// <inheritdoc cref='CoreDb.SetFilter'/>
/// <remarks> <note type="tip">The difference between VoDb.SetFilter and CoreDb.SetFilter is that VoDb.SetFilter takes a USUAL parameter</note></remarks>
STATIC METHOD SetFilter(oBlock AS USUAL,cFilter AS STRING) AS LOGIC
    LOCAL cb AS ICodeblock
    IF oBlock:IsCodeblock
       cb := (ICodeblock) oBlock
    ELSE
        cb := NULL
    ENDIF
    RETURN CoreDb.SetFilter(cb, cFilter)

    INTERNAL STATIC METHOD ParamError(cFuncSym AS STRING, dwArgNum  AS DWORD , dwArgType AS DWORD, cArgName := "" AS STRING) AS Error

        LOCAL oError    AS Error
        oError := Error{RuntimeState.LastRddError}
        oError:SubSystem    := "DBCMD"
        oError:Gencode      := EG_ARG
        oError:Severity     := ES_ERROR
        oError:CanDefault   := .F.
        oError:CanRetry     := .T.
        oError:CanSubstitute := .F.
        oError:ArgType      := dwArgType
        oError:ArgNum       := dwArgNum
        oError:Arg          := cArgName
        oError:FuncSym      := cFuncSym
        RETURN oError

    INTERNAL STATIC METHOD DbCmdError(cFuncSym AS STRING)  AS Error
        LOCAL oError    AS Error
        oError := Error{RuntimeState.LastRddError}
        oError:Gencode      := EG_NOTABLE
        oError:SubCode      := EDB_NOTABLE
        oError:SubSystem    := "DBCMD"
        oError:Severity     := ES_ERROR
        oError:FuncSym      := cFuncSym
        oError:CanDefault   := .T.
        RETURN oError

    INTERNAL STATIC METHOD AllocFieldNames(aStru AS ARRAY) AS _FieldNames
        VAR aNames := List<STRING>{}
        FOREACH aField AS USUAL IN aStru
            IF aField:IsArray
                aNames:Add(Upper(aField[DBS_NAME]))
            ELSE
                aNames:Add(Upper(aField))
            ENDIF
        NEXT
        RETURN _FieldNames{aNames}

    INTERNAL STATIC METHOD TargetFields  (cAlias AS STRING, aNames AS ARRAY, oJoinList OUT _JoinList) AS ARRAY

        LOCAL aNew      AS ARRAY
        LOCAL cName     AS STRING
        LOCAL aStruct   AS ARRAY
        LOCAL aDbStruct AS ARRAY
        LOCAL nFields, i AS INT
        LOCAL siPos     AS DWORD
        LOCAL siSelect  AS DWORD
        LOCAL aFldList  AS ARRAY

        aDbStruct := DbStruct()
        aStruct   := {}
        aFldList := {}

        IF ( Empty(aNames) )

            aNames     := {}
            nFields    := (INT) FCount()
            siSelect   := VoDb.GetSelect()
            FOR i := 1 TO nFields
                cName := aDbStruct[i, DBS_NAME]
                AAdd(aFldList, {siSelect, FieldPos(cName)})
                AAdd(aStruct, aDbStruct[i])
                AAdd(aNames, cName)
            NEXT
        ELSE
            nFields := (INT)Len(aNames)
            aNew := {}
            FOR i := 1 TO nFields
                AAdd(aNew, AllTrim(Upper(aNames[i])))
            NEXT
            aNames := aNew
            nFields := (INT)FCount()
            siSelect := VoDb.GetSelect()
            FOR i := 1 TO nFields
                cName := aDbStruct[i, DBS_NAME]
                IF AScan(aNames, {|c| c == cName}) > 0
                    AAdd(aFldList, {siSelect, FieldPos(cName)})
                    AAdd(aStruct, aDbStruct[i])
                ENDIF
            NEXT
        ENDIF
        siSelect := @@Select(cAlias)
        aDbStruct := DbStruct()
        nFields := (INT)Len(aNames)

        FOR i := 1 TO nFields
            IF "->" $ aNames[i]
                cName := SubStr2(aNames[i], At(">", aNames[i]) + 1)
            ELSE
                cName :=  aNames[i]
            ENDIF

            siPos := AScan(aDbStruct, {|a| a[DBS_NAME] == cName})
            IF siPos > 0 .AND. (AScan( aStruct, {|c|c[DBS_NAME]== cName }) == 0)
                AAdd(aFldList, {siSelect, FieldPos(cName)})
                AAdd(aStruct, aDbStruct[siPos])
            ENDIF
        NEXT
        nFields := (INT)ALen(aStruct)
        oJoinList := _JoinList{nFields}
        FOR i := 1 TO nFields
            oJoinList:Fields[i]:Area := aFldList[i,1]
            oJoinList:Fields[i]:Pos  := aFldList[i,2] - 1
        NEXT
        RETURN aStruct

    INTERNAL STATIC METHOD  RddList( xDriver AS USUAL, aHidden AS USUAL ) AS ARRAY

        LOCAL   nType   AS DWORD
        LOCAL   aRdds  := NULL_ARRAY AS ARRAY
        LOCAL   n       AS DWORD
        LOCAL   i       AS DWORD
        LOCAL   lFPT  := FALSE AS LOGIC
        LOCAL   lDbf    AS LOGIC

        IF xDriver:IsArray
            nType := ARRAY
        ELSEIF xDriver:IsString
            IF SLen(xDriver) = 0
                xDriver := RddSetDefault()
            ENDIF
            nType := STRING
        ELSE
            xDriver := RddSetDefault()
            nType := STRING
        ENDIF

        IF nType == ARRAY
            aRdds := xDriver
        ELSEIF nType == STRING
            aRdds := {}
            xDriver := Upper(xDriver)
            SWITCH (STRING) xDriver
            CASE "DBFNTX"
            CASE "DBFMEMO"
            CASE "DBFDBT"
                lDbf := .T.
            CASE "_DBFCDX"
            CASE "DBFFPT"
                xDriver := "DBFFPT"
                lDbf := .T.
            CASE "DBFCDX"
                lFPT := .T.
                lDbf  := .T.
                xDriver := "DBFFPT"
            CASE "DBFMDX"
                lDbf := .T.
            OTHERWISE
                lDbf := .F.
                lFPT := .F.
            END SWITCH

            IF lDbf
                AAdd(aRdds, "DBF")
            ENDIF

            AAdd(aRdds, xDriver)

            IF lFPT
                AAdd(aRdds, "DBFCDX")
            ENDIF

        ENDIF

        IF UsualType(aHidden) == ARRAY
            n := ALen(aHidden)
            FOR i := 1 TO n
                AAdd(aRdds, aHidden[i])
            NEXT
        ENDIF
        RETURN aRdds

    INTERNAL STATIC METHOD AllocRddList(aNames AS ARRAY) AS _RddList
        VAR aList := List<STRING>{}
        FOREACH cName AS STRING IN aNames
            aList:Add(cName)
        NEXT
        RETURN _RddList{aList:ToArray()}

    INTERNAL STATIC METHOD ArrayToFieldInfo(aStruct AS ARRAY) AS RddFieldInfo[]
        VAR oList := List<RddFieldInfo>{}
        VAR nullable := 0
        VAR hasnullfield := FALSE
        FOREACH aField AS ARRAY IN aStruct
            VAR oFld := RddFieldInfo{(STRING) aField[DBS_NAME], (STRING) aField[DBS_TYPE], (LONG)aField[DBS_LEN], (LONG)aField[DBS_DEC]}
            IF ALen(aField) >= DBS_FLAGS
                oFld:Flags := (DBFFieldFlags) (LONG) aField[DBS_FLAGS]
            ENDIF
            IF oFld:IsNullable
                nullable += 1
            ENDIF
            IF oFld:FieldType == DbFieldType.NullFlags
                hasnullfield := TRUE
            ENDIF
            IF ALen(aField) >= DBS_ALIAS
                oFld:Alias := aField[DBS_ALIAS]
            ENDIF
            oList:Add(oFld)
        NEXT
        IF nullable != 0 .AND. ! hasnullfield
            VAR oFld := RddFieldInfo{_NULLFLAGS,"0", (nullable+7) % 8, 0}
            oList:Add(oFld)
        ENDIF
        RETURN oList:ToArray()

    INTERNAL STATIC METHOD OrdScopeNum(nScope)  AS INT CLIPPER
        IF !nScope:IsNumeric
            nScope := 0
        ENDIF
        nScope := INT(nScope)
        IF nScope < 0
            nScope := 0
        ENDIF
        IF nScope > 1
            nScope := 1
        ENDIF
        RETURN nScope

    INTERNAL STATIC METHOD WithoutAlias(cName AS STRING) AS STRING
        cName   := SubStr(cName, At(">", cName) + 1 )
        cName   := Trim(Upper(cName))
        RETURN cName

    INTERNAL STATIC METHOD ValidBlock(uBlock AS USUAL, bDef := NULL_CODEBLOCK AS ICodeblock) AS ICodeblock
        IF uBlock:IsCodeblock .or. uBlock:IsObject
            LOCAL oBlock    := uBlock   AS OBJECT
            IF oBlock IS ICodeblock
                RETURN (ICodeblock) oBlock
            ENDIF
        ENDIF
        RETURN bDef

INTERNAL STATIC METHOD  FieldList(aStruct AS ARRAY, aNames AS ARRAY, aMatch AS ARRAY) AS ARRAY

	LOCAL aNew      AS ARRAY
	LOCAL cName     AS STRING
	LOCAL n, i, j   AS DWORD
	LOCAL lMatch	AS LOGIC

	IF Empty(aNames)
		RETURN (aStruct)
	ENDIF

	IF Empty(aMatch)
		lMatch := .F.
	ELSE
		lMatch := .T.
	ENDIF

	aNew:= {}
	n   := Len(aNames)

	FOR i := 1 TO n
		AAdd(aNew, VoDb.WithoutAlias(AllTrim(aNames[i])))
	NEXT

	aNames  := aNew
	cName   := ""
	aNew    := {}
    // Convert AStruct to List<String>
    VAR aFields := Dictionary<String, DWORD>{StringComparer.OrdinalIgnoreCase}
    FOR i := 1 to ALen(aStruct)
        local aFld := aStruct[i] AS ARRAY
        aFields:Add(Trim(aFld[DBS_NAME]), i )
    NEXT

	FOR i := 1 TO n
		cName := Trim(aNames[i])
        IF aFields:ContainsKey(cName)
            j := aFields[cName]
			IF lMatch
				IF aMatch[i, DBS_TYPE] == aStruct[j, DBS_TYPE]
					AAdd(aNew, aStruct[j])
				ENDIF
			ELSE
				AAdd(aNew, aStruct[j])
			ENDIF
		ENDIF
	NEXT

	RETURN aNew

END CLASS


[VFP] SqlStatement now uses the DbColumnInfo to store info about the columns selected from the server with the workarea.

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

USING System
USING System.Collections.Generic
USING System.Text
USING System.Data.Common
USING System.Data
USING System.Diagnostics
USING XSharp.VFP
USING System.Reflection
USING XSharp.RDD
USING XSharp.RDD.Enums

INTERNAL CLASS XSharp.VFP.SQLStatement
    PROTECT _oConnection    AS SQLConnection
    PROTECT _oNetCommand    AS DbCommand
    PROTECT _oTransaction   AS DbTransaction
    PROTECT _oLastDataReader AS DbDataReader
    PROTECT _nextCursorNo    AS LONG

    PROPERTY Connected          AS LOGIC GET Connection:State == System.Data.ConnectionState.Open
    PROPERTY Connection         AS SQLConnection GET _oConnection
    PROPERTY Asynchronous       AS LOGIC  AUTO GET SET
    PROPERTY BatchMode          AS LOGIC  AUTO GET SET
    PROPERTY ConnectBusy        AS LOGIC  GET Connection:ConnectBusy
    PROPERTY ConnectionTimeOut  AS LONG   GET Connection:ConnectionTimeOut  SET Connection:ConnectionTimeOut := Value
    PROPERTY DataSource         AS STRING GET Connection:DataSource         SET Connection:DataSource := Value
    PROPERTY UserId             AS STRING GET Connection:UserId
    PROPERTY Password           AS STRING GET Connection:Password
    PROPERTY Shared             AS LOGIC  GET Connection:Shared             SET Connection:Shared := Value
    PROPERTY ConnectionString   AS STRING GET Connection:ConnectionString
    PROPERTY ODBChdbc           AS DbConnection GET SELF:Connection:NetConnection
    PROPERTY ODBChstmt          AS DbCommand    GET _oNetCommand
    PROPERTY DisconnectRollback AS LOGIC AUTO GET SET
    PROPERTY DispWarnings       AS LOGIC AUTO GET SET
    PROPERTY IdleTimeout        AS LONG  AUTO GET SET
    PRIVATE _PacketSize         AS LONG
    PROPERTY PacketSize         AS LONG
        GET
            IF SQLReflection.GetPropertyValue(SELF:Connection:NetConnection, "PacketSize", OUT VAR result)
                RETURN (LONG) result
            ENDIF
            RETURN _PacketSize

        END GET
        SET
            _PacketSize := value
            SQLReflection.SetPropertyValue(SELF:Connection:NetConnection, "PacketSize", value)
            RETURN

    END SET
    END PROPERTY
    PROPERTY QueryTimeOut       AS LONG  GET _oNetCommand:CommandTimeout SET _oNetCommand:CommandTimeout := Value
    PROPERTY TransactionMode    AS LONG  AUTO GET SET
    PROPERTY WaitTime           AS LONG  AUTO GET SET
    PROPERTY Prepared           AS LOGIC AUTO GET PRIVATE SET
    PROPERTY CursorName         AS STRING AUTO GET SET

    METHOD SetDefaults() AS VOID
        SELF:Shared             := FALSE
        SELF:Asynchronous       := FALSE
        SELF:BatchMode          := TRUE
        SELF:DisconnectRollback := FALSE
        SELF:DispWarnings       := FALSE
        SELF:IdleTimeout        := 0
        SELF:PacketSize         := 4096
        SELF:Prepared           := FALSE
        SELF:QueryTimeOut       := 0
        SELF:TransactionMode    := DB_TRANSAUTO
        SELF:WaitTime           := 100
        SELF:_oNetCommand       := NULL
        SELF:_oConnection       := NULL
        SELF:_oTransaction      := NULL

    PRIVATE METHOD _AllocateCommand() AS VOID
        SELF:_oNetCommand := SELF:Connection:Factory:CreateCommand()
        SELF:_oNetCommand:Connection := SELF:Connection:NetConnection
        SELF:_oNetCommand:CommandText := "Select 1"
        SELF:_oNetCommand:ExecuteNonQuery()
        _oConnection:AddStatement(SELF)
        RETURN

    CONSTRUCTOR(cDataSource AS STRING, cUser AS STRING, cPassword AS STRING, lShared AS LOGIC)
        SELF:SetDefaults()
        _oConnection    := SQLConnection{cDataSource, cUser, cPassword, lShared}
        SELF:_AllocateCommand()
        RETURN

    CONSTRUCTOR(cConnectionString AS STRING, lShared AS LOGIC)
        _oConnection := SQLConnection{cConnectionString, lShared}
        SELF:_AllocateCommand()
        RETURN

    CONSTRUCTOR(oConnection AS SQLConnection)
        _oConnection := oConnection
        SELF:_AllocateCommand()
        RETURN

    METHOD Commit AS LOGIC
        IF SELF:_oTransaction != NULL
            SELF:_oTransaction:Commit()
            SELF:_oTransaction := NULL
            RETURN TRUE
        ENDIF
        RETURN FALSE

    METHOD Rollback AS LOGIC
        IF SELF:_oTransaction != NULL
            SELF:_oTransaction:Rollback()
            SELF:_oTransaction := NULL
            RETURN TRUE
        ENDIF
        RETURN FALSE

    METHOD DisConnect AS LOGIC
        IF SELF:Connected
            IF SELF:DisconnectRollback
                SELF:Rollback()
            ELSE
                SELF:Commit()
            ENDIF
            _oNetCommand:Dispose()
            IF ! SELF:Connection:Shared
                SELF:Connection:Close()
            ENDIF
        ENDIF
        _oConnection:RemoveStatement(SELF)
        _oNetCommand := NULL
        _oConnection := NULL
        RETURN TRUE

    METHOD Prepare(cCommand AS STRING, cCursorName AS STRING) AS LONG
        SELF:_oNetCommand:CommandText := cCommand
        SELF:_oNetCommand:Prepare()
        SELF:Prepared := TRUE
        SELF:CursorName := cCursorName
        RETURN 1

    METHOD Execute(cCommand AS STRING, cCursorName AS STRING, aInfo AS ARRAY) AS LONG
        SELF:_oNetCommand:CommandText := cCommand
        SELF:CursorName := cCursorName
        VAR tables := SELF:CopyToCursor()
        CopyToInfo(tables, aInfo)
        IF ALen(tables) > 0
            RETURN (LONG) ALen(tables)
        ENDIF
        RETURN 0

    METHOD Execute(aInfo AS ARRAY) AS LONG
        VAR tables := SELF:CopyToCursor()
        CopyToInfo(tables, aInfo)
        IF ALen(tables) > 0
            RETURN (LONG) ALen(tables)
        ENDIF
        RETURN 0

    PRIVATE METHOD CopyToInfo(aResult, aInfo AS ARRAY) AS VOID
        ASize(aInfo, ALen(aResult))
        ACopy(aResult, aInfo)
        RETURN

    METHOD CopyToCursor() AS ARRAY
        VAR oDataReader := SELF:_oNetCommand:ExecuteReader()
        RETURN CopyToCursor(oDataReader, 0)

    METHOD CopyToCursor(oDataReader AS DbDataReader, cursorNo AS LONG) AS ARRAY
        LOCAL result   := {} AS ARRAY
        _oLastDataReader := NULL_OBJECT
        DO WHILE TRUE
            VAR cursorName := SELF:CursorName
            IF cursorNo != 0
                cursorName += cursorNo:ToString()
            ENDIF
    		oDataReader := SELF:Connection:Factory:AfterOpen(oDataReader)
            VAR oSchema := oDataReader:GetSchemaTable()
            CreateWorkarea(oSchema, oDataReader, cursorName)
            AAdd(result, {cursorName, RecCount()})
            cursorNo += 1
            IF ! SELF:BatchMode
                _oLastDataReader := oDataReader
                _nextCursorNo    := cursorNo
                EXIT
            ENDIF
            IF ! oDataReader:NextResult()
                EXIT
            ENDIF
        ENDDO
        RETURN result

    METHOD MoreResults(cursorName AS STRING, aInfo AS ARRAY) AS LONG
        IF !String.IsNullOrEmpty(cursorName)
            SELF:CursorName := cursorName
            SELF:_nextCursorNo := 0
        ENDIF
        IF _oLastDataReader != NULL .AND. _oLastDataReader:NextResult()
            VAR tables := CopyToCursor(_oLastDataReader, _nextCursorNo)
            CopyToInfo(tables, aInfo)
            IF ALen(tables) > 0
                RETURN (LONG) ALen(tables)
            ENDIF
        ENDIF
        RETURN 0

    METHOD GetNumRestrictions(cCollectionName AS STRING) AS LONG
        LOCAL nRestrictions := 0 AS LONG
        LOCAL oTable := SELF:Connection:NetConnection:GetSchema("MetadataCollections",NULL) AS DataTable
        FOREACH oRow AS DataRow IN oTable:Rows
			IF String.Compare( (STRING)oRow:Item["CollectionName"], cCollectionName, StringComparison.OrdinalIgnoreCase) == 0
				nRestrictions := (INT) oRow:Item["NumberOfRestrictions"]
				EXIT
			ENDIF
        NEXT
        RETURN nRestrictions

    STATIC METHOD CreateWorkarea(oSchema AS DataTable, oDataReader AS DbDataReader, cCursorName AS STRING) AS LOGIC
        LOCAL aStruct AS ARRAY
        LOCAL nFields AS LONG
        LOCAL aFieldNames AS List<STRING>
        nFields := oSchema:Rows:Count
        aFieldNames := List<STRING>{}
        aStruct := ArrayNew( (DWORD) nFields)
        nFields := 1
        FOREACH schemaRow AS DataRow IN oSchema:Rows
            VAR fieldInfo    := FromSchema(schemaRow, aFieldNames)
            aStruct[nFields] := {fieldInfo:Name, fieldInfo:FieldTypeStr, fieldInfo:Length, fieldInfo:Decimals, fieldInfo:ColumnName, fieldInfo}
            nFields++
        NEXT
        nFields := aStruct:Count
        VAR cTemp := System.IO.Path.GetTempFileName()
        DbCreate(cTemp, aStruct, "DBFVFP")
        VAR nArea := _SelectString(cCursorName)
        IF nArea != 0
            DbCloseArea()
        ENDIF
        VoDbUseArea(TRUE, "DBFVFPSQL",cTemp,cCursorName,FALSE,FALSE)
        LOCAL oRDD AS IRdd
        oRDD := (IRdd) DbInfo(DbInfo.DBI_RDD_OBJECT)

        FOR VAR nI := 1 TO ALen(aStruct)
            LOCAL fieldInfo AS DbColumnInfo
            fieldInfo := aStruct[nI, 6]
            oRDD:FieldInfo(nI, DBS_COLUMNINFO, fieldInfo)
        NEXT

        LOCAL oMIGet := NULL AS MethodInfo
        // DBFVFPSQL has a method SetData to set all the values of the current row.
        oMIGet := oRDD:GetType():GetMethod("GetData", BindingFlags.Instance+BindingFlags.IgnoreCase+BindingFlags.Public)
        IF oMIGet != NULL
            VAR GetData := (SqlGetData) oMIGet:CreateDelegate(typeof(SqlGetData), oRDD)
            DO WHILE oDataReader:Read()
                oRDD:Append(TRUE)
                // Get the data array from the workarea
                VAR data := GetData()
                // and fetch its values. This automatically updates the array that is owned by the RDD
                oDataReader:GetValues(data)
            ENDDO
        ELSE
            VAR data := OBJECT[]{oSchema:Rows:Count+1}  // 1 extra for the NullFlags
            DO WHILE oDataReader:Read()
                oRDD:Append(TRUE)
                // use our local data array
                oDataReader:GetValues(data)
                // and write the values to the RDD
                FOR VAR nFld := 1 UPTO nFields
                    oRDD:PutValue(nFld, data[nFld])
                NEXT
            ENDDO
        ENDIF
        RETURN TRUE

    #region MetaData
   METHOD GetTables(cType AS STRING, cCursorName AS STRING) AS LOGIC
        SELF:CursorName := cCursorName
        VAR types := cType:Split(",":ToCharArray(),StringSplitOptions.RemoveEmptyEntries)
        VAR list  := List<STRING>{}
        FOREACH VAR type IN types
            VAR sType := type
            IF sType:StartsWith("'")
                sType := sType:Replace("'","")
            ELSEIF sType:StartsWith(e"\"")
                sType := sType:Replace(e"\"","")
            ENDIF
            list:Add(sType)
        NEXT

        LOCAL filter AS STRING[]
        LOCAL oTables AS List<DataTable>
        LOCAL oTable AS DataTable
        LOCAL nRestrictions AS LONG
        oTables := List<DataTable>{}

        nRestrictions := GetNumRestrictions("Tables")
        filter := STRING[]{nRestrictions}
        oTable := SELF:Connection:NetConnection:GetSchema("Tables", filter)
        oTables:Add(oTable)

        nRestrictions := GetNumRestrictions("Views")
        filter := STRING[]{nRestrictions}
        oTable := SELF:Connection:NetConnection:GetSchema("Views", filter)
        oTables:Add(oTable)

        LOCAL aStruct AS ARRAY
        aStruct := ArrayNew(5)
        aStruct[1] := {"TABLE_CAT","C:0",128,0}
        aStruct[2] := {"TABLE_SCHEM","C:0",128,0}
        aStruct[3] := {"TABLE_NAME","C:0",128,0}
        aStruct[4] := {"TABLE_TYPE","C:0",32,0}
        aStruct[5] := {"REMARKS","C:0",254,0}
        VAR cTemp := System.IO.Path.GetTempFileName()
        DbCreate(cTemp, aStruct, "DBFVFP")
        VAR nArea := _SelectString(cCursorName)
        IF nArea != 0
            DbCloseArea()
        ENDIF
        VoDbUseArea(TRUE, "DBFVFPSQL",cTemp,cCursorName,FALSE,FALSE)
        LOCAL oRDD AS IRdd
        oRDD := (IRdd) DbInfo(DbInfo.DBI_RDD_OBJECT)
        FOREACH VAR oT IN oTables
            FOREACH oRow AS DataRow IN oT:Rows
                VAR oValues := SELF:Connection:Factory:GetMetaDataTableValues(oRow)
                VAR match := TRUE
                IF list:Count > 0
                    match := list:Contains((STRING) oValues[4])
                ENDIF
                IF match
                    oRDD:Append(TRUE)
                    FOR VAR nFld := 1 TO 5
                        oRDD:PutValue(nFld, oValues[nFld])
                    NEXT
                ENDIF
            NEXT
       NEXT
       RETURN TRUE

   METHOD GetColumnsFox(cTableName AS STRING, cCursorName AS STRING) AS LOGIC
        SELF:_oNetCommand:CommandText := "Select * from "+cTableName+" where 1 = 0"
        VAR oDataReader := SELF:_oNetCommand:ExecuteReader()
        VAR oSchema := oDataReader:GetSchemaTable()
        VAR aFieldNames := List<STRING>{}
        VAR aStruct := ArrayNew(4)
        aStruct[1] := {"FIELD_NAME", "C", 14,0}
        aStruct[2] := {"FIELD_TYPE", "C", 1,0}
        aStruct[3] := {"FIELD_LEN", "N", 3,0}
        aStruct[4] := {"FIELD_DEC", "N", 3,0}
        VAR cTemp := System.IO.Path.GetTempFileName()
        DbCreate(cTemp, aStruct, "DBFVFP")
        VoDbUseArea(TRUE, "DBFVFPSQL",cTemp,cCursorName,FALSE,FALSE)
        LOCAL oRDD AS IRdd
        oRDD := (IRdd) DbInfo(DbInfo.DBI_RDD_OBJECT)
        FOREACH schemaRow AS DataRow IN oSchema:Rows
            VAR fieldInfo    := FromSchema(schemaRow, aFieldNames)
            oRDD:Append(TRUE)
            oRDD:PutValue(1, fieldInfo:Name)
            oRDD:PutValue(2, Left(fieldInfo:FieldTypeStr,1))
            oRDD:PutValue(3, fieldInfo:Length)
            oRDD:PutValue(4, fieldInfo:Decimals)
            oRDD:GoCold()
        NEXT
        RETURN TRUE

   METHOD GetColumnsNative(cTableName AS STRING, cCursorName AS STRING) AS LOGIC
        LOCAL filter AS STRING[]
        LOCAL oTable AS DataTable
        LOCAL nRestrictions AS LONG
        nRestrictions := GetNumRestrictions("Columns")
        filter := STRING[]{nRestrictions}
        filter[3] := cTableName
        oTable := SELF:Connection:NetConnection:GetSchema("Columns", filter)
        LOCAL aStruct AS ARRAY
        aStruct := ArrayNew(19)
        aStruct[1] := {"TABLE_CAT","C:0",128,0}
        aStruct[2] := {"TABLE_SCHE","C:0",128,0}
        aStruct[3] := {"TABLE_NAME","C",128,0}
        aStruct[4] := {"COLUMN_NAM","C",128,0}
        aStruct[5] := {"DATA_TYPE","I",4,0}
        aStruct[6] := {"TYPE_NAME","C",128,0}
        aStruct[7] := {"COLUMN_SIZ","I:0",4,0}
        aStruct[8] := {"BUFFER_LEN","I:0",4,0}
        aStruct[9] := {"DECIMAL_DI","I:0",4,0}
        aStruct[10] := {"NUM_PREC_R","I:0",4,0}
        aStruct[11] := {"NULLABLE","I",4,0}
        aStruct[12] := {"REMARKS","C:0",254,0}
        aStruct[13] := {"COLUMN_DEF","M:0",4,0}
        aStruct[14] := {"SQL_DATA_T","I",4,0}
        aStruct[15] := {"SQL_DATETI","I:0",4,0}
        aStruct[16] := {"CHAR_OCTET","I:0",4,0}
        aStruct[17] := {"ORDINAL_PO","I",4,0}
        aStruct[18] := {"IS_NULLABL","C:0",254,0}
        aStruct[19] := {"SS_DATA_TY","I:0",4,0}
        VAR cTemp := System.IO.Path.GetTempFileName()
        DbCreate(cTemp, aStruct, "DBFVFP")
        VAR nArea := _SelectString(cCursorName)
        IF nArea != 0
            DbCloseArea()
        ENDIF
        VoDbUseArea(TRUE, "DBFVFPSQL",cTemp,cCursorName,FALSE,FALSE)
        LOCAL oRDD AS IRdd
        oRDD := (IRdd) DbInfo(DbInfo.DBI_RDD_OBJECT)
        FOREACH oRow AS DataRow IN oTable:Rows
            oRDD:Append(TRUE)
            VAR oValues := SELF:Connection:Factory:GetMetaDataColumnValues(oRow)
            FOR VAR i := 1 TO oValues:Length
                oRDD:PutValue(i, oValues[i])
            NEXT
         NEXT
       RETURN TRUE

   METHOD GetColumns(cTableName AS STRING, cType AS STRING, cCursorName AS STRING) AS LOGIC
        SELF:CursorName := cCursorName
        IF String.IsNullOrEmpty(cTableName) .OR. String.IsNullOrEmpty(cType)
            RETURN FALSE
        ENDIF
        SWITCH cType
        CASE "FOXPRO"
            RETURN GetColumnsFox(cTableName, cCursorName)
        CASE "NATIVE"
            RETURN GetColumnsNative(cTableName, cCursorName)
        END SWITCH
        RETURN FALSE
    #endregion

    STATIC METHOD FromSchema(schemaRow AS DataRow, aFieldNames AS IList<STRING>) AS XSharp.RDD.DbColumnInfo
		LOCAL nLen, nDec AS LONG
		LOCAL cType AS STRING
		LOCAL oType	AS System.Type
		LOCAL TC AS TypeCode
        LOCAL result AS DbColumnInfo
        LOCAL columnName AS STRING
        columnName   := schemaRow["ColumnName"]:ToString( )
        oType   := (Type) schemaRow["DataType"]
		TC      := Type.GetTypeCode(oType)
		nDec    := 0
		SWITCH TC
		CASE TypeCode.String
			cType   := "C"
			nLen    := (Int32)schemaRow["ColumnSize"]
			// Automatically Convert Long Strings to Memos
			IF nLen  > 255 .OR. nLen < 0
				nLen    := 4
				cType   := "M"
            ENDIF
            result := DbColumnInfo{columnName,cType,nLen ,0 }

		CASE TypeCode.Boolean
			result := DbColumnInfo{columnName,"L",1 ,0 }

        CASE TypeCode.Decimal
		    result := DbColumnInfo{columnName,"Y",16 ,4 }
            result:NumericScale     := 16
            result:NumericPrecision := 4

		CASE TypeCode.Double
        CASE TypeCode.Single
			nDec := 1
			nLen := 10
            VAR nScale := Convert.ToInt32(schemaRow:Item["NumericScale"])
            VAR nPrec  := Convert.ToInt32(schemaRow:Item["NumericPrecision"])
			nDec := nScale
			nLen := nPrec
            IF nLen == 0
				// I have seen a case where nDec == 31 and nLen == 0
				// Fix this to something usefull
				nDec := 2
				nLen := 10
			ELSEIF nDec == 127
				//IF nLen == 38 .AND. oProviderType = ProviderType.Oracle // Standardvalue for calculated fields in oracle-queries, cutting decimals leads to wrong results in that case
				//	nDec := 10
				//ELSE
					nDec := 0 // Overflow abfangen
				//ENDIF
			ENDIF
            result := DbColumnInfo{columnName,"N",nLen ,nDec }
            result:NumericScale     := nScale
            result:NumericPrecision := nPrec

		CASE TypeCode.Int32		// -2147483647 - 2147483648 (2^31)
            IF (LOGIC)schemaRow["IsAutoIncrement"]
                result := DbColumnInfo{columnName,"I",4 ,0}
                result:Flags |= DBFFieldFlags.AutoIncrement
            ELSE
                result := DbColumnInfo{columnName,"I",4 ,0}
            ENDIF

		CASE TypeCode.Int64		// - 9223372036854775807 - 9223372036854775808 (2^63)
            result := DbColumnInfo{columnName,"N",21 ,0}

		CASE TypeCode.Int16	// -32767 - 32768 (2^15)
            result := DbColumnInfo{columnName,"N",6 ,0}

		CASE TypeCode.Byte
            result := DbColumnInfo{columnName,"N",4 ,0}

		CASE TypeCode.SByte	// 0 - 255 	(2^8)
            result := DbColumnInfo{columnName,"N",3 ,0}

		CASE TypeCode.UInt16	// 0 - 65535 (2^16)
            result := DbColumnInfo{columnName,"N",5 ,0}

		CASE TypeCode.UInt32		// 0 - 4294836225 (2^32)
            result := DbColumnInfo{columnName,"N",10 ,0}

		CASE TypeCode.UInt64	// 0 - 18445618199572250625 (2^64)
			nLen := 20
            result := DbColumnInfo{columnName,"N",nLen ,0}

		CASE TypeCode.DateTime
			cType   := "T"
			nLen 	:= 8
            result  := DbColumnInfo{columnName,cType,nLen ,0}

		CASE TypeCode.Object
			LOCAL lIsDate := FALSE AS LOGIC
			LOCAL oMems AS MethodInfo[]
			LOCAL lFound := FALSE AS LOGIC
			// check to see if the datatype has a dbType
			oMems := oType:GetMethods(BindingFlags.Public|BindingFlags.Static)
			FOREACH oMem AS MethodInfo IN oMems
				IF oMem:ReturnType == TypeOf(System.DateTime)  .AND. String.Compare(oMem:Name, "op_Explicit", StringComparison.OrdinalIgnoreCase) == 0
					lIsDate := TRUE
					lFound  := TRUE
					EXIT
				ENDIF
			NEXT
			IF ! lFound
				LOCAL cTypeName AS STRING
				cTypeName := oType:Name:ToUpperInvariant()
				lIsDate     := cTypeName:Contains("DATE")
			ENDIF
			IF lIsDate
				cType   := "D"
				nLen 	:= 8
			ELSE
				cType 	:= "C"
				nLen 	:= 10
			ENDIF
            result := DbColumnInfo{columnName,cType,nLen ,0}

		OTHERWISE
			cType := "C"
			nLen 	:= (Int32)schemaRow["ColumnSize"]
			IF nLen <= 0
				cType := "M"
                nLen  := 4
			ENDIF
            result := DbColumnInfo{columnName,cType,nLen ,0}

        END SWITCH
        IF (LOGIC)schemaRow["AllowDBNull"]
            result:Flags |= DBFFieldFlags.Nullable
        ENDIF
        VAR cFldName        := SQLSupport.CleanupColumnName(columnName)
        result:ColumnName   := columnName
        result:Name         := MakeFieldNameUnique(cFldName, aFieldNames)
        result:Alias        := result:Name
        result:DotNetType   := oType
		RETURN result

   STATIC METHOD MakeFieldNameUnique(cName AS STRING, aFldNames AS IList<STRING> ) AS STRING
        LOCAL dwPos, dwFld AS LONG
        LOCAL cNewname		 AS STRING
        IF String.IsNullOrEmpty(cName)
            dwFld := 0
            dwPos := 1
            DO WHILE dwPos >= 0
                ++dwFld
                cName := "FLD"+dwFld:ToString():PadLeft(3,c'0')
                dwPos := aFldNames:IndexOf(cName:ToUpper())
            ENDDO
        ELSE
            // remove column prefixes
            dwPos := cName:IndexOf(".")+1
            IF dwPos > 0
                cName := cName:Substring(dwPos)
            ENDIF
            // remove embedded spaces
            cName 	:= cName:Replace(" ", "_"):ToUpper()
            cNewname := Left(cName,10)
            dwFld 	:= 1
            DO WHILE aFldNames:IndexOf(cNewname) >= 0
                ++dwFld
                VAR tmp := dwFld:ToString()
                cNewname := cName:Substring(0, 10 - tmp:Length)+tmp
            ENDDO
            cName 	:= cNewname
        ENDIF
        aFldNames:Add(cName)
    RETURN cName

END CLASS
INTERNAL DELEGATE SqlGetData() AS OBJECT[]

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

USING System
USING System.Collections.Generic
USING System.Text
USING System.Data.Common
USING System.Data
USING System.Diagnostics
USING XSharp.VFP
USING System.Reflection
USING XSharp.RDD
USING XSharp.RDD.Enums

INTERNAL CLASS XSharp.VFP.SQLStatement
    PROTECT _oConnection    AS SQLConnection
    PROTECT _oNetCommand    AS DbCommand
    PROTECT _oTransaction   AS DbTransaction
    PROTECT _oLastDataReader AS DbDataReader
    PROTECT _nextCursorNo    AS LONG

    PROPERTY Connected          AS LOGIC GET Connection:State == System.Data.ConnectionState.Open
    PROPERTY Connection         AS SQLConnection GET _oConnection
    PROPERTY Asynchronous       AS LOGIC  AUTO GET SET
    PROPERTY BatchMode          AS LOGIC  AUTO GET SET
    PROPERTY ConnectBusy        AS LOGIC  GET Connection:ConnectBusy
    PROPERTY ConnectionTimeOut  AS LONG   GET Connection:ConnectionTimeOut  SET Connection:ConnectionTimeOut := Value
    PROPERTY DataSource         AS STRING GET Connection:DataSource         SET Connection:DataSource := Value
    PROPERTY UserId             AS STRING GET Connection:UserId
    PROPERTY Password           AS STRING GET Connection:Password
    PROPERTY Shared             AS LOGIC  GET Connection:Shared             SET Connection:Shared := Value
    PROPERTY ConnectionString   AS STRING GET Connection:ConnectionString
    PROPERTY ODBChdbc           AS DbConnection GET SELF:Connection:NetConnection
    PROPERTY ODBChstmt          AS DbCommand    GET _oNetCommand
    PROPERTY DisconnectRollback AS LOGIC AUTO GET SET
    PROPERTY DispWarnings       AS LOGIC AUTO GET SET
    PROPERTY IdleTimeout        AS LONG  AUTO GET SET
    PRIVATE _PacketSize         AS LONG
    PROPERTY PacketSize         AS LONG
        GET
            IF SQLReflection.GetPropertyValue(SELF:Connection:NetConnection, "PacketSize", OUT VAR result)
                RETURN (LONG) result
            ENDIF
            RETURN _PacketSize

        END GET
        SET
            _PacketSize := value
            SQLReflection.SetPropertyValue(SELF:Connection:NetConnection, "PacketSize", value)
            RETURN

    END SET
    END PROPERTY
    PROPERTY QueryTimeOut       AS LONG  GET _oNetCommand:CommandTimeout SET _oNetCommand:CommandTimeout := Value
    PROPERTY TransactionMode    AS LONG  AUTO GET SET
    PROPERTY WaitTime           AS LONG  AUTO GET SET
    PROPERTY Prepared           AS LOGIC AUTO GET PRIVATE SET
    PROPERTY CursorName         AS STRING AUTO GET SET

    METHOD SetDefaults() AS VOID
        SELF:Shared             := FALSE
        SELF:Asynchronous       := FALSE
        SELF:BatchMode          := TRUE
        SELF:DisconnectRollback := FALSE
        SELF:DispWarnings       := FALSE
        SELF:IdleTimeout        := 0
        SELF:PacketSize         := 4096
        SELF:Prepared           := FALSE
        SELF:QueryTimeOut       := 0
        SELF:TransactionMode    := DB_TRANSAUTO
        SELF:WaitTime           := 100
        SELF:_oNetCommand       := NULL
        SELF:_oConnection       := NULL
        SELF:_oTransaction      := NULL

    PRIVATE METHOD _AllocateCommand() AS VOID
        SELF:_oNetCommand := SELF:Connection:Factory:CreateCommand()
        SELF:_oNetCommand:Connection := SELF:Connection:NetConnection
        SELF:_oNetCommand:CommandText := "Select 1"
        SELF:_oNetCommand:ExecuteNonQuery()
        _oConnection:AddStatement(SELF)
        RETURN

    CONSTRUCTOR(cDataSource AS STRING, cUser AS STRING, cPassword AS STRING, lShared AS LOGIC)
        SELF:SetDefaults()
        _oConnection    := SQLConnection{cDataSource, cUser, cPassword, lShared}
        SELF:_AllocateCommand()
        RETURN

    CONSTRUCTOR(cConnectionString AS STRING, lShared AS LOGIC)
        _oConnection := SQLConnection{cConnectionString, lShared}
        SELF:_AllocateCommand()
        RETURN

    CONSTRUCTOR(oConnection AS SQLConnection)
        _oConnection := oConnection
        SELF:_AllocateCommand()
        RETURN

    METHOD Commit AS LOGIC
        IF SELF:_oTransaction != NULL
            SELF:_oTransaction:Commit()
            SELF:_oTransaction := NULL
            RETURN TRUE
        ENDIF
        RETURN FALSE

    METHOD Rollback AS LOGIC
        IF SELF:_oTransaction != NULL
            SELF:_oTransaction:Rollback()
            SELF:_oTransaction := NULL
            RETURN TRUE
        ENDIF
        RETURN FALSE

    METHOD DisConnect AS LOGIC
        IF SELF:Connected
            IF SELF:DisconnectRollback
                SELF:Rollback()
            ELSE
                SELF:Commit()
            ENDIF
            _oNetCommand:Dispose()
            IF ! SELF:Connection:Shared
                SELF:Connection:Close()
            ENDIF
        ENDIF
        _oConnection:RemoveStatement(SELF)
        _oNetCommand := NULL
        _oConnection := NULL
        RETURN TRUE

    METHOD Prepare(cCommand AS STRING, cCursorName AS STRING) AS LONG
        SELF:_oNetCommand:CommandText := cCommand
        SELF:_oNetCommand:Prepare()
        SELF:Prepared := TRUE
        SELF:CursorName := cCursorName
        RETURN 1

    METHOD Execute(cCommand AS STRING, cCursorName AS STRING, aInfo AS ARRAY) AS LONG
        SELF:_oNetCommand:CommandText := cCommand
        SELF:CursorName := cCursorName
        VAR tables := SELF:CopyToCursor()
        CopyToInfo(tables, aInfo)
        IF ALen(tables) > 0
            RETURN (LONG) ALen(tables)
        ENDIF
        RETURN 0

    METHOD Execute(aInfo AS ARRAY) AS LONG
        VAR tables := SELF:CopyToCursor()
        CopyToInfo(tables, aInfo)
        IF ALen(tables) > 0
            RETURN (LONG) ALen(tables)
        ENDIF
        RETURN 0

    PRIVATE METHOD CopyToInfo(aResult, aInfo AS ARRAY) AS VOID
        ASize(aInfo, ALen(aResult))
        ACopy(aResult, aInfo)
        RETURN

    METHOD CopyToCursor() AS ARRAY
        VAR oDataReader := SELF:_oNetCommand:ExecuteReader()
        RETURN CopyToCursor(oDataReader, 0)

    METHOD CopyToCursor(oDataReader AS DbDataReader, cursorNo AS LONG) AS ARRAY
        LOCAL result   := {} AS ARRAY
        _oLastDataReader := NULL_OBJECT
        DO WHILE TRUE
            VAR cursorName := SELF:CursorName
            IF cursorNo != 0
                cursorName += cursorNo:ToString()
            ENDIF
    		oDataReader := SELF:Connection:Factory:AfterOpen(oDataReader)
            VAR oSchema := oDataReader:GetSchemaTable()
            CreateWorkarea(oSchema, oDataReader, cursorName)
            AAdd(result, {cursorName, RecCount()})
            cursorNo += 1
            IF ! SELF:BatchMode
                _oLastDataReader := oDataReader
                _nextCursorNo    := cursorNo
                EXIT
            ENDIF
            IF ! oDataReader:NextResult()
                EXIT
            ENDIF
        ENDDO
        RETURN result

    METHOD MoreResults(cursorName AS STRING, aInfo AS ARRAY) AS LONG
        IF !String.IsNullOrEmpty(cursorName)
            SELF:CursorName := cursorName
            SELF:_nextCursorNo := 0
        ENDIF
        IF _oLastDataReader != NULL .AND. _oLastDataReader:NextResult()
            VAR tables := CopyToCursor(_oLastDataReader, _nextCursorNo)
            CopyToInfo(tables, aInfo)
            IF ALen(tables) > 0
                RETURN (LONG) ALen(tables)
            ENDIF
        ENDIF
        RETURN 0

    METHOD GetNumRestrictions(cCollectionName AS STRING) AS LONG
        LOCAL nRestrictions := 0 AS LONG
        LOCAL oTable := SELF:Connection:NetConnection:GetSchema("MetadataCollections",NULL) AS DataTable
        FOREACH oRow AS DataRow IN oTable:Rows
			IF String.Compare( (STRING)oRow:Item["CollectionName"], cCollectionName, StringComparison.OrdinalIgnoreCase) == 0
				nRestrictions := (INT) oRow:Item["NumberOfRestrictions"]
				EXIT
			ENDIF
        NEXT
        RETURN nRestrictions

    STATIC METHOD CreateWorkarea(oSchema AS DataTable, oDataReader AS DbDataReader, cCursorName AS STRING) AS LOGIC
        LOCAL aStruct AS ARRAY
        LOCAL nFields AS LONG
        LOCAL aFieldNames AS List<STRING>
        nFields := oSchema:Rows:Count
        aFieldNames := List<STRING>{}
        aStruct := ArrayNew( (DWORD) nFields)
        nFields := 1
        FOREACH schemaRow AS DataRow IN oSchema:Rows
            VAR fieldInfo    := VfpFieldInfo.FromSchema(schemaRow, aFieldNames)
            aStruct[nFields] := {fieldInfo:FieldName, fieldInfo:Type, fieldInfo:Length, fieldInfo:Decimals, fieldInfo:ColumnName}
            nFields++
        NEXT
        nFields := aStruct:Count
        VAR cTemp := System.IO.Path.GetTempFileName()
        DbCreate(cTemp, aStruct, "DBFVFP")
        VAR nArea := _SelectString(cCursorName)
        IF nArea != 0
            DbCloseArea()
        ENDIF
        VoDbUseArea(TRUE, "DBFVFPSQL",cTemp,cCursorName,FALSE,FALSE)
        LOCAL oRDD AS IRdd
        oRDD := (IRdd) DbInfo(DbInfo.DBI_RDD_OBJECT)
        LOCAL oMIGet := NULL AS MethodInfo
        // DBFVFPSQL has a method SetData to set all the values of the current row.
        oMIGet := oRDD:GetType():GetMethod("GetData", BindingFlags.Instance+BindingFlags.IgnoreCase+BindingFlags.Public)
        IF oMIGet != NULL
            VAR GetData := (SqlGetData) oMIGet:CreateDelegate(typeof(SqlGetData), oRDD)
            DO WHILE oDataReader:Read()
                oRDD:Append(TRUE)
                // Get the data array from the workarea
                VAR data := GetData()
                // and fetch its values. This automatically updates the array that is owned by the RDD
                oDataReader:GetValues(data)
            ENDDO
        ELSE
            VAR data := OBJECT[]{oSchema:Rows:Count+1}  // 1 extra for the NullFlags
            DO WHILE oDataReader:Read()
                oRDD:Append(TRUE)
                // use our local data array
                oDataReader:GetValues(data)
                // and write the values to the RDD
                FOR VAR nFld := 1 UPTO nFields
                    oRDD:PutValue(nFld, data[nFld])
                NEXT
            ENDDO
        ENDIF
        RETURN TRUE

    #region MetaData
   METHOD GetTables(cType AS STRING, cCursorName AS STRING) AS LOGIC
        SELF:CursorName := cCursorName
        VAR types := cType:Split(",":ToCharArray(),StringSplitOptions.RemoveEmptyEntries)
        VAR list  := List<STRING>{}
        FOREACH VAR type IN types
            VAR sType := type
            IF sType:StartsWith("'")
                sType := sType:Replace("'","")
            ELSEIF sType:StartsWith(e"\"")
                sType := sType:Replace(e"\"","")
            ENDIF
            list:Add(sType)
        NEXT

        LOCAL filter AS STRING[]
        LOCAL oTables AS List<DataTable>
        LOCAL oTable AS DataTable
        LOCAL nRestrictions AS LONG
        oTables := List<DataTable>{}

        nRestrictions := GetNumRestrictions("Tables")
        filter := STRING[]{nRestrictions}
        oTable := SELF:Connection:NetConnection:GetSchema("Tables", filter)
        oTables:Add(oTable)

        nRestrictions := GetNumRestrictions("Views")
        filter := STRING[]{nRestrictions}
        oTable := SELF:Connection:NetConnection:GetSchema("Views", filter)
        oTables:Add(oTable)

        LOCAL aStruct AS ARRAY
        aStruct := ArrayNew(5)
        aStruct[1] := {"TABLE_CAT","C:0",128,0}
        aStruct[2] := {"TABLE_SCHEM","C:0",128,0}
        aStruct[3] := {"TABLE_NAME","C:0",128,0}
        aStruct[4] := {"TABLE_TYPE","C:0",32,0}
        aStruct[5] := {"REMARKS","C:0",254,0}
        VAR cTemp := System.IO.Path.GetTempFileName()
        DbCreate(cTemp, aStruct, "DBFVFP")
        VAR nArea := _SelectString(cCursorName)
        IF nArea != 0
            DbCloseArea()
        ENDIF
        VoDbUseArea(TRUE, "DBFVFPSQL",cTemp,cCursorName,FALSE,FALSE)
        LOCAL oRDD AS IRdd
        oRDD := (IRdd) DbInfo(DbInfo.DBI_RDD_OBJECT)
        FOREACH VAR oT IN oTables
            FOREACH oRow AS DataRow IN oT:Rows
                VAR oValues := SELF:Connection:Factory:GetMetaDataTableValues(oRow)
                VAR match := TRUE
                IF list:Count > 0
                    match := list:Contains((STRING) oValues[4])
                ENDIF
                IF match
                    oRDD:Append(TRUE)
                    FOR VAR nFld := 1 TO 5
                        oRDD:PutValue(nFld, oValues[nFld])
                    NEXT
                ENDIF
            NEXT
       NEXT
       RETURN TRUE

   METHOD GetColumnsFox(cTableName AS STRING, cCursorName AS STRING) AS LOGIC
        SELF:_oNetCommand:CommandText := "Select * from "+cTableName+" where 1 = 0"
        VAR oDataReader := SELF:_oNetCommand:ExecuteReader()
        VAR oSchema := oDataReader:GetSchemaTable()
        VAR aFieldNames := List<STRING>{}
        VAR aStruct := ArrayNew(4)
        aStruct[1] := {"FIELD_NAME", "C", 14,0}
        aStruct[2] := {"FIELD_TYPE", "C", 1,0}
        aStruct[3] := {"FIELD_LEN", "N", 3,0}
        aStruct[4] := {"FIELD_DEC", "N", 3,0}
        VAR cTemp := System.IO.Path.GetTempFileName()
        DbCreate(cTemp, aStruct, "DBFVFP")
        VoDbUseArea(TRUE, "DBFVFPSQL",cTemp,cCursorName,FALSE,FALSE)
        LOCAL oRDD AS IRdd
        oRDD := (IRdd) DbInfo(DbInfo.DBI_RDD_OBJECT)
        FOREACH schemaRow AS DataRow IN oSchema:Rows
            VAR fieldInfo    := VfpFieldInfo.FromSchema(schemaRow, aFieldNames)
            oRDD:Append(TRUE)
            oRDD:PutValue(1, fieldInfo:FieldName)
            oRDD:PutValue(2, Left(fieldInfo:Type,1))
            oRDD:PutValue(3, fieldInfo:Length)
            oRDD:PutValue(4, fieldInfo:Decimals)
            oRDD:GoCold()
        NEXT
        RETURN TRUE

   METHOD GetColumnsNative(cTableName AS STRING, cCursorName AS STRING) AS LOGIC
        LOCAL filter AS STRING[]
        LOCAL oTable AS DataTable
        LOCAL nRestrictions AS LONG
        nRestrictions := GetNumRestrictions("Columns")
        filter := STRING[]{nRestrictions}
        filter[3] := cTableName
        oTable := SELF:Connection:NetConnection:GetSchema("Columns", filter)
        LOCAL aStruct AS ARRAY
        aStruct := ArrayNew(19)
        aStruct[1] := {"TABLE_CAT","C:0",128,0}
        aStruct[2] := {"TABLE_SCHE","C:0",128,0}
        aStruct[3] := {"TABLE_NAME","C",128,0}
        aStruct[4] := {"COLUMN_NAM","C",128,0}
        aStruct[5] := {"DATA_TYPE","I",4,0}
        aStruct[6] := {"TYPE_NAME","C",128,0}
        aStruct[7] := {"COLUMN_SIZ","I:0",4,0}
        aStruct[8] := {"BUFFER_LEN","I:0",4,0}
        aStruct[9] := {"DECIMAL_DI","I:0",4,0}
        aStruct[10] := {"NUM_PREC_R","I:0",4,0}
        aStruct[11] := {"NULLABLE","I",4,0}
        aStruct[12] := {"REMARKS","C:0",254,0}
        aStruct[13] := {"COLUMN_DEF","M:0",4,0}
        aStruct[14] := {"SQL_DATA_T","I",4,0}
        aStruct[15] := {"SQL_DATETI","I:0",4,0}
        aStruct[16] := {"CHAR_OCTET","I:0",4,0}
        aStruct[17] := {"ORDINAL_PO","I",4,0}
        aStruct[18] := {"IS_NULLABL","C:0",254,0}
        aStruct[19] := {"SS_DATA_TY","I:0",4,0}
        VAR cTemp := System.IO.Path.GetTempFileName()
        DbCreate(cTemp, aStruct, "DBFVFP")
        VAR nArea := _SelectString(cCursorName)
        IF nArea != 0
            DbCloseArea()
        ENDIF
        VoDbUseArea(TRUE, "DBFVFPSQL",cTemp,cCursorName,FALSE,FALSE)
        LOCAL oRDD AS IRdd
        oRDD := (IRdd) DbInfo(DbInfo.DBI_RDD_OBJECT)
        FOREACH oRow AS DataRow IN oTable:Rows
            oRDD:Append(TRUE)
            VAR oValues := SELF:Connection:Factory:GetMetaDataColumnValues(oRow)
            FOR VAR i := 1 TO oValues:Length
                oRDD:PutValue(i, oValues[i])
            NEXT
         NEXT
       RETURN TRUE

   METHOD GetColumns(cTableName AS STRING, cType AS STRING, cCursorName AS STRING) AS LOGIC
        SELF:CursorName := cCursorName
        IF String.IsNullOrEmpty(cTableName) .OR. String.IsNullOrEmpty(cType)
            RETURN FALSE
        ENDIF
        SWITCH cType
        CASE "FOXPRO"
            RETURN GetColumnsFox(cTableName, cCursorName)
        CASE "NATIVE"
            RETURN GetColumnsNative(cTableName, cCursorName)
        END SWITCH
        RETURN FALSE
    #endregion

    [DebuggerDisplay("{FieldName,nq} ({Type,nq} {Length} {Decimals})")];
    INTERNAL CLASS VfpFieldInfo
        PROPERTY ColumnName AS STRING   AUTO GET PRIVATE SET
        PROPERTY FieldName  AS STRING   AUTO GET PRIVATE SET
        PROPERTY Type       AS STRING   AUTO GET PRIVATE SET
        PROPERTY Length     AS LONG     AUTO GET PRIVATE SET
        PROPERTY Decimals   AS LONG     AUTO GET PRIVATE SET
        PRIVATE CONSTRUCTOR(cType AS STRING, nLength AS LONG, nDecimals AS LONG)
            SELF:Type := cType
            SELF:Length := nLength
            SELF:Decimals := nDecimals
            RETURN

        STATIC METHOD FromSchema(schemaRow AS DataRow, aFieldNames AS IList<STRING>) AS VfpFieldInfo
		    LOCAL nLen, nDec AS LONG
		    LOCAL cType AS STRING
		    LOCAL oType	AS System.Type
		    LOCAL TC AS TypeCode
            LOCAL result AS VfpFieldInfo
            oType   := (Type) schemaRow["DataType"]
		    TC      := Type.GetTypeCode(oType)
		    nDec    := 0
		    SWITCH TC
		    CASE TypeCode.String
			    cType   := "C"
			    nLen    := (Int32)schemaRow["ColumnSize"]
			    // Automatically Convert Long Strings to Memos
			    IF nLen  > 255 .OR. nLen < 0
				    nLen    := 4
				    cType   := "M"
                ENDIF
                result := VfpFieldInfo{cType,nLen ,0 }

		    CASE TypeCode.Boolean
			    result := VfpFieldInfo{"L",1 ,0 }

            CASE TypeCode.Decimal
		        result := VfpFieldInfo{"Y",16 ,4 }
		    CASE TypeCode.Double
            CASE TypeCode.Single
			    nDec := 1
			    nLen := 10
			    nDec := Convert.ToInt32(schemaRow:Item["NumericScale"])
			    nLen := Convert.ToInt32(schemaRow:Item["NumericPrecision"])
                IF nLen == 0
				    // I have seen a case where nDec == 31 and nLen == 0
				    // Fix this to something usefull
				    nDec := 2
				    nLen := 10
			    ELSEIF nDec == 127
				    //IF nLen == 38 .AND. oProviderType = ProviderType.Oracle // Standardvalue for calculated fields in oracle-queries, cutting decimals leads to wrong results in that case
				    //	nDec := 10
				    //ELSE
					    nDec := 0 // Overflow abfangen
				    //ENDIF
			    ENDIF
            result := VfpFieldInfo{"N",nLen ,nDec }

		    CASE TypeCode.Int32		// -2147483647 - 2147483648 (2^31)
                IF (LOGIC)schemaRow["IsAutoIncrement"]
                    result := VfpFieldInfo{"I:+",4 ,0}
                ELSE
                    result := VfpFieldInfo{"I",4 ,0}
                ENDIF

		    CASE TypeCode.Int64		// - 9223372036854775807 - 9223372036854775808 (2^63)
                result := VfpFieldInfo{"N",21 ,0}

		    CASE TypeCode.Int16	// -32767 - 32768 (2^15)
                result := VfpFieldInfo{"N",6 ,0}

		    CASE TypeCode.Byte
                result := VfpFieldInfo{"N",4 ,0}

		    CASE TypeCode.SByte	// 0 - 255 	(2^8)
                result := VfpFieldInfo{"N",3 ,0}

		    CASE TypeCode.UInt16	// 0 - 65535 (2^16)
                result := VfpFieldInfo{"N",5 ,0}

		    CASE TypeCode.UInt32		// 0 - 4294836225 (2^32)
                result := VfpFieldInfo{"N",10 ,0}

		    CASE TypeCode.UInt64	// 0 - 18445618199572250625 (2^64)
			    nLen := 20
                result := VfpFieldInfo{"N",nLen ,0}

		    CASE TypeCode.DateTime
			    cType   := "T"
			    nLen 	:= 8
                result  := VfpFieldInfo{cType,nLen ,0}

		    CASE TypeCode.Object
			    LOCAL lIsDate := FALSE AS LOGIC
			    LOCAL oMems AS MethodInfo[]
			    LOCAL lFound := FALSE AS LOGIC
			    // check to see if the datatype has a dbType
			    oMems := oType:GetMethods(BindingFlags.Public|BindingFlags.Static)
			    FOREACH oMem AS MethodInfo IN oMems
				    IF oMem:ReturnType == TypeOf(System.DateTime)  .AND. String.Compare(oMem:Name, "op_Explicit", StringComparison.OrdinalIgnoreCase) == 0
					    lIsDate := TRUE
					    lFound  := TRUE
					    EXIT
				    ENDIF
			    NEXT
			    IF ! lFound
				    LOCAL cTypeName AS STRING
				    cTypeName := oType:Name:ToUpperInvariant()
				    lIsDate     := cTypeName:Contains("DATE")
			    ENDIF
			    IF lIsDate
				    cType   := "D"
				    nLen 	:= 8
			    ELSE
				    cType 	:= "C"
				    nLen 	:= 10
			    ENDIF
                result := VfpFieldInfo{cType,nLen ,0}

		    OTHERWISE
			    cType := "C"
			    nLen 	:= (Int32)schemaRow["ColumnSize"]
			    IF nLen <= 0
				    cType := "M"
                    nLen  := 4
			    ENDIF
                result := VfpFieldInfo{cType,nLen ,0}

            END SWITCH
            IF (LOGIC)schemaRow["AllowDBNull"]
                cType := result:Type
                IF cType:Contains(":")
                    cType += "0"
                ELSE
                    cType += ":0"
                ENDIF
                result:Type := cType

            ENDIF
            result:ColumnName   := schemaRow["ColumnName"]:ToString( )
            VAR cFldName        := SQLSupport.CleanupColumnName(result:ColumnName)
            result:FieldName    := MakeFieldNameUnique(cFldName, aFieldNames)
		    RETURN result

        STATIC METHOD MakeFieldNameUnique(cName AS STRING, aFldNames AS IList<STRING> ) AS STRING
		    LOCAL dwPos, dwFld AS LONG
		    LOCAL cNewname		 AS STRING
		    IF Empty(cName)
			    dwFld := 0
			    dwPos := 1
			    DO WHILE dwPos >= 0
				    ++dwFld
				    cName := "FLD"+StrZero(dwFld,3,0)
				    dwPos := aFldNames:IndexOf(cName:ToUpper())
			    ENDDO
		    ELSE
			    // remove column prefixes
			    dwPos := cName:IndexOf(".")+1
			    IF dwPos > 0
				    cName := cName:Substring(dwPos)
			    ENDIF
			    // remove embedded spaces
			    cName 	:= StrTran(cName, " ", "_"):ToUpper()
			    cNewname := Left(cName,10)
			    dwFld 	:= 1
			    DO WHILE aFldNames:IndexOf(cNewname) >= 0
				    ++dwFld
                    VAR tmp := dwFld:ToString()
				    cNewname := cName:Substring(0, 10 - tmp:Length)+tmp
			    ENDDO
			    cName 	:= cNewname
		    ENDIF
		    aFldNames:Add(cName)
		    RETURN cName
    END CLASS
    INTERNAL DELEGATE SqlGetData() AS OBJECT[]

END CLASS