examples/ado.prg: updated

examples/ado.prg: updated

/*

  Qt4xHb Project - Example Program

  Copyright (C) 2020 Marcos Antonio Gambeta

  E-mail:
  marcosgambeta AT outlook DOT com

  Website:
  https://github.com/marcosgambeta/Qt4xHb

*/

#include "qt4xhb.ch"

// constantes do ADO utilizadas neste exemplo
#define adOpenKeyset     1
#define adLockOptimistic 3
#define adBookmarkFirst  1
#define adInteger        3
#define adVarWChar       202

// Ateno:
// Este exemplo  compatvel com o Harbour, requerendo ajustes para funcionar com o xHarbour.
// Requer a biblioteca hbwin na compilao:
// hbmk2 ado ..\qt4xhb.hbc hbwin.hbc

PROCEDURE Main()

   LOCAL oApp
   LOCAL oWindow
   LOCAL oModel
   LOCAL oView
   LOCAL oConnection
   LOCAL oRecordset

   CriarBancoDeDados()

   oConnection := win_OleCreateObject("ADODB.Connection")
   oConnection:Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source="+QDir():currentPath()+"\teste.mdb")
   oRecordset := win_OleCreateObject("ADODB.Recordset")
   oRecordset:Open("SELECT * FROM cadastro",oConnection,adOpenKeyset,adLockOptimistic)

   oApp := QApplication():new()

   oWindow := QWidget():new()
   oWindow:setWindowTitle("Exemplo de uso do ADO com Qt")
   oWindow:resize(800,600)

   // cria e configura o modelo
   oModel := AdoTableModel():new()
   oModel:setRowCountCB({||oRecordset:recordCount}) // total de linhas
   oModel:setColumnCountCB({||oRecordset:Fields:count()}) // total de colunas
   oModel:setDisplayRoleCB({|nRow,nCol|oRecordset:move(nRow,adBookmarkFirst),oRecordset:Fields(nCol):value}) // contedo da clula
   oModel:setHorizontalHeaderDisplayRoleCB({|nCol|oRecordset:Fields(nCol):name}) // ttulos das colunas (usa o nome do campo)

   // cria o visualizador e associa ao modelo
   oView := QTableView():new(oWindow)
   oView:move(10,10)
   oView:resize(800-20,600-20)
   oView:setModel(oModel)

   oWindow:show()

   oApp:exec()

   oRecordset := NIL

   oConnection := NIL

   oModel:delete()

   oWindow:delete()

   oApp:delete()

RETURN

/*
  A funo CriarBancoDeDados cria um banco de dados do Access, na pasta
  corrente. Neste banco,  criada uma tabela chamada 'cadastro', com trs
  campos: 'codigo', 'nome' e 'valor'. Nesta tabela so inseridos 1000
  registros.
*/

STATIC FUNCTION CriarBancoDeDados()

   LOCAL oCatalog
   LOCAL oTable
   LOCAL oConnection
   LOCAL oRecordset
   LOCAL n

   // comente esta linha para o arquivo no ser recriado
   // cada vez que o exemplo for executado
   ferase("teste.mdb")

   IF !file(QDir():currentPath()+"\teste.mdb")

      oCatalog := win_OleCreateObject("ADOX.Catalog")

      // cria o mdb
      oCatalog:Create("Provider=Microsoft.Jet.OLEDB.4.0;Data Source="+QDir():currentPath()+"\teste.mdb")
      oCatalog:ActiveConnection := "Provider=Microsoft.Jet.OLEDB.4.0;Data Source="+QDir():currentPath()+"\teste.mdb"

      // cria a tabela
      oTable := win_OleCreateObject("ADOX.Table")
      oTable:Name := "cadastro"
      oTable:Columns:Append("codigo",adInteger)
      oTable:Columns:Append("nome",adVarWChar,40)
      oTable:Columns:Append("valor",adInteger)
      oCatalog:Tables:Append(oTable)
      oTable := NIL
      oCatalog := NIL

      // adiciona 1000 registros na tabela
      oConnection := win_OleCreateObject("ADODB.Connection")
      oConnection:Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source="+QDir():currentPath()+"\teste.mdb")
      oRecordset := win_OleCreateObject("ADODB.Recordset")
      oRecordset:Open("SELECT * FROM cadastro",oConnection,adOpenKeyset,adLockOptimistic)
      FOR n := 1 TO 1000
         oRecordset:AddNew()
         oRecordset:Fields(0):Value := n
         oRecordset:Fields(1):Value := "Nome do cliente "+alltrim(str(n))
         oRecordset:Fields(2):Value := n*10
         oRecordset:Update()
      NEXT n
      oRecordset := NIL
      oConnection := NIL

   ENDIF

RETURN NIL

#include "hbclass.ch"

// A classe AdoTableModel deriva da classe HAbstractTableModel,
// sendo que no adiciona nenhum recurso extra. O desenvolvedor
// poder expandir a classe, conforme suas necessidades.

CLASS AdoTableModel INHERIT HAbstractTableModel
END CLASS

/*

  Qt4xHb Project - Example Program

  Copyright (C) 2020 Marcos Antonio Gambeta

  E-mail:
  marcosgambeta AT outlook DOT com

  Website:
  https://github.com/marcosgambeta/Qt4xHb

*/

#include "qt4xhb.ch"

// constantes do ADO utilizadas neste exemplo
#define adOpenKeyset     1
#define adLockOptimistic 3
#define adBookmarkFirst  1
#define adInteger        3
#define adVarWChar       202

// Ateno:
// Este exemplo  compatvel com o Harbour, requerendo ajustes para funcionar com o xHarbour.
// Requer a biblioteca hbwin na compilao:
// hbmk2 ado ..\qt5xhb.hbc hbwin.hbc

PROCEDURE Main ()

   LOCAL oApp
   LOCAL oWindow
   LOCAL oModel
   LOCAL oView
   LOCAL oConnection
   LOCAL oRecordset

   CriarBancoDeDados()

   oConnection := win_OleCreateObject("ADODB.Connection")
   oConnection:Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source="+QDir():currentPath()+"\teste.mdb")
   oRecordset := win_OleCreateObject("ADODB.Recordset")
   oRecordset:Open("SELECT * FROM cadastro",oConnection,adOpenKeyset,adLockOptimistic)

   oApp := QApplication():new()

   oWindow := QWidget():new():setWindowTitle("Exemplo de uso do ADO com Qt"):resize(800,600)

   // cria e configura o modelo
   oModel := AdoTableModel():new()
   oModel:setRowCountCB({||oRecordset:recordCount}) // total de linhas
   oModel:setColumnCountCB({||oRecordset:Fields:count()}) // total de colunas
   oModel:setDisplayRoleCB({|nRow,nCol|oRecordset:move(nRow,adBookmarkFirst),oRecordset:Fields(nCol):value}) // contedo da clula
   oModel:setHorizontalHeaderDisplayRoleCB({|nCol|oRecordset:Fields(nCol):name}) // ttulos das colunas (usa o nome do campo)

   // cria o visualizador e associa ao modelo
   oView := QTableView():new(oWindow):move(10,10):resize(800-20,600-20):setModel(oModel)

   oWindow:show()

   oApp:exec()

   oRecordset := NIL

   oConnection := NIL

   oModel:delete()

   oWindow:delete()

   oApp:delete()

RETURN

/*
  A funo CriarBancoDeDados cria um banco de dados do Access, na pasta
  corrente. Neste banco,  criada uma tabela chamada 'cadastro', com trs
  campos: 'codigo', 'nome' e 'valor'. Nesta tabela so inseridos 1000
  registros.
*/

STATIC FUNCTION CriarBancoDeDados ()

   LOCAL oCatalog
   LOCAL oTable
   LOCAL oConnection
   LOCAL oRecordset
   LOCAL n

   // comente esta linha para o arquivo no ser recriado
   // cada vez que o exemplo for executado
   ferase("teste.mdb")

   IF !file(QDir():currentPath()+"\teste.mdb")
      oCatalog := win_OleCreateObject("ADOX.Catalog")
      // cria o mdb
      oCatalog:Create("Provider=Microsoft.Jet.OLEDB.4.0;Data Source="+QDir():currentPath()+"\teste.mdb")
      oCatalog:ActiveConnection := "Provider=Microsoft.Jet.OLEDB.4.0;Data Source="+QDir():currentPath()+"\teste.mdb"
      // cria a tabela
      oTable := win_OleCreateObject("ADOX.Table")
      oTable:Name := "cadastro"
      oTable:Columns:Append("codigo",adInteger)
      oTable:Columns:Append("nome",adVarWChar,40)
      oTable:Columns:Append("valor",adInteger)
      oCatalog:Tables:Append(oTable)
      oTable := NIL
      oCatalog := NIL
      // adiciona 1000 registros na tabela
      oConnection := win_OleCreateObject("ADODB.Connection")
      oConnection:Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source="+QDir():currentPath()+"\teste.mdb")
      oRecordset := win_OleCreateObject("ADODB.Recordset")
      oRecordset:Open("SELECT * FROM cadastro",oConnection,adOpenKeyset,adLockOptimistic)
      FOR n := 1 TO 1000
         oRecordset:AddNew()
         oRecordset:Fields(0):Value := n
         oRecordset:Fields(1):Value := "Nome do cliente "+alltrim(str(n))
         oRecordset:Fields(2):Value := n*10
         oRecordset:Update()
      NEXT n
      oRecordset := NIL
      oConnection := NIL
   ENDIF

RETURN NIL

#include "hbclass.ch"

// A classe AdoTableModel deriva da classe HAbstractTableModel,
// sendo que no adiciona nenhum recurso extra. O desenvolvedor
// poder expandir a classe, conforme suas necessidades.

CLASS AdoTableModel INHERIT HAbstractTableModel
END CLASS


[Runtime] a DbOrderInfo with Numeric value 0 is now also considered to be empty and should select the current order, so IndexKey() without parameters returns the key for the current order

//
// 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.IO
USING XSharp.RDD.Enums
USING System.Collections.Generic
USING System.Linq
USING XSharp.RDD
// The classes below are simple. No properties, but all public fields.

BEGIN NAMESPACE XSharp.RDD.Support

/// Helper class to store the scope and codeblock for a DbEval() operation.
CLASS DbEvalInfo

	/// A code block to be evaluated with DbEval() on each row of the work area that is in the range defined by ScopeInfo.
	PUBLIC Block	 AS ICodeblock

	/// A DbScopeInfo structure limiting the evaluation of Block.
	PUBLIC ScopeInfo AS DbScopeInfo
    /// Construct a DbEvalInfo object.
    CONSTRUCTOR()
        SELF:ScopeInfo := DbScopeInfo{}
        RETURN
END CLASS

/// Helper class to store a filter condition for a table.
CLASS DbFilterInfo
	/// A string representing the source code for itmCobExpr.
	PUBLIC FilterText	AS STRING

	/// A code block representing the condition that is evaluated at each cursor location.  If the result of the evaluation is FALSE, the cursor location requested is invalid according to the current filter condition.
	PUBLIC FilterBlock	AS ICodeblock

	/// A flag that is TRUE if a filter is active.
	PUBLIC Active		AS LOGIC

	/// A flag that is TRUE if a filter is optimized.
	PUBLIC Optimized	AS LOGIC

	///Clear the filter fields.
	METHOD Clear() AS VOID
		SELF:FilterBlock := NULL
		SELF:FilterText	 := NULL
		SELF:Active		 := FALSE
		SELF:Optimized	 := FALSE
		RETURN

	///Clone the filter object.
	METHOD Clone AS DbFilterInfo
		LOCAL oClone AS DbFilterInfo
		oClone := DbFilterInfo{}
		oClone:FilterBlock := SELF:FilterBlock
		oClone:FilterText  := SELF:FilterText
		oClone:Optimized   := SELF:Optimized
		oClone:Active	   := FALSE
		RETURN oClone

    /// Construct a DbFilterInfo object.
    CONSTRUCTOR
        SELF:Clear()
        RETURN

	METHOD Compile(oRDD AS IRdd) AS VOID
        IF SELF:FilterBlock == NULL .AND. ! String.IsNullOrWhiteSpace(SELF:FilterText)
            SELF:FilterBlock := oRDD:Compile(SELF:FilterText)
        ENDIF
END CLASS

/// Helper structure to store information needed to lock a row or table for exclusive access.
STRUCTURE DbLockInfo
	/// An Item indicating the ID of the row to lock.  This member is meaningful only if Method is set to EXCLUSIVE or MULTIPLE.
	PUBLIC RecId		AS OBJECT

	/// A constant indicating the type of lock to obtain.  The possible values are of the Lockmethod enum.
	PUBLIC @@Method AS LockMethod

	/// A flag that is TRUE if the lock operation was successful.
	PUBLIC Result		AS LOGIC
	/// List of possible Locking Methods
	ENUM LockMethod
		/// Lock a row, releasing currently locked rows.
		MEMBER Exclusive := 1
		/// Lock a row, maintaining currently locked rows.
		MEMBER Multiple  := 2
		/// Lock a table, releasing locks currently held.
		MEMBER File 	 := 3
	END ENUM
END STRUCTURE

/// Helper class to store information needed to open a table.
CLASS DbOpenInfo
	/// Should the table be opened Readonly?
	PUBLIC ReadOnly 	AS LOGIC
	/// Should the table be opened Shared?
	PUBLIC Shared		AS LOGIC
	/// Unique Alias that the table has. This must be a valid XBase Identifier
	PUBLIC Alias		AS STRING
	/// The extension for the table that must be opened.
	PUBLIC Extension    AS STRING
	/// The filename (optionally includig a path) for the table that must be opened.
	PUBLIC FileName		AS STRING
	/// Workarea number in which the table will be opened.
	PUBLIC Workarea		AS DWORD
    /// Construct a DbOpenInfo object.

	CONSTRUCTOR()
        SUPER()
    /// Construct a DbOpenInfo object.
	CONSTRUCTOR(sFileName AS STRING, sAlias AS STRING, dwWorkarea AS DWORD, lShared AS LOGIC, lReadOnly AS LOGIC)
		FileName 	:= sFileName
        Extension   := Path.GetExtension(sFileName)
		IF String.IsNullOrEmpty( sAlias )
			Alias := Path.GetFileNameWithoutExtension( sFileName )
		ELSE
			Alias	 	:= sAlias
		ENDIF
		Workarea	:= dwWorkarea
		Shared		:= lShared
		ReadOnly	:= lReadOnly
	/// Return the numeric FileMode based on the Shared and Readonly flags
	PUBLIC PROPERTY FileMode AS DWORD
	GET
		LOCAL nMode AS DWORD
		nMode := FO_COMPAT
		IF (Shared)
			nMode |= FO_SHARED
		ELSE
			nMode |= FO_EXCLUSIVE
		ENDIF
		IF (ReadOnly)
			nMode |= FO_READ
		ELSE
			nMode |= FO_READWRITE
		ENDIF
		RETURN nMode
	END GET
	END PROPERTY
    PUBLIC METHOD Clone() AS DbOpenInfo
        RETURN (DbOpenInfo) SELF:MemberwiseClone()
END CLASS

/// Helper class to store information needed to create a conditional order.
CLASS DbOrderCondInfo
	///  A flag that is TRUE if one or more valid conditions have been specified in the structure.
	PUBLIC Active			AS LOGIC
	///  A flag that is TRUE if open orders should remain open while the new order is being created.
	PUBLIC Additive			AS LOGIC
	///  A flag that is TRUE if all rows are to be processed during order creation.
	PUBLIC All				AS LOGIC
	/// A flag that is TRUE if the new order will be a custom built order.
	PUBLIC Custom			AS LOGIC
	///  A flag that is TRUE if the order should be created in descending order.
	PUBLIC @@Descending     AS LOGIC
	/// A code block defining the expression to evaluate every StepSize rows during the creation of the order.  The code block referenced should return a logical value: TRUE indicates that creation of the order should continue normally, and FALSE indicates that order creation should terminate.
	PUBLIC EvalBlock		AS ICodeblock
	/// A code block defining the for condition to use for the creation and maintenance of the order.
	PUBLIC ForBlock			AS ICodeblock
	/// A string defining the for condition to use for the creation and maintenance of the order.
	PUBLIC ForExpression	AS STRING
	/// The number of rows to process for order creation.
	PUBLIC NextCount		AS LONG
	/// A flag that is TRUE if the for condition may NOT be optimized,
	PUBLIC NoOptimize		AS LOGIC
	///  A single row number to include in the order.
	PUBLIC RecNo			AS LONG
	/// A flag that is TRUE if only the rows specified by lStartRecno through end-of-file are to be included in the order.
	PUBLIC Rest				AS LOGIC
	/// A flag indicating whether the order is to be scoped. fScoped will be TRUE if WhileBlock, NextCount, Recno, Rest, or All is specified.
	PUBLIC Scoped			AS LOGIC
	/// The row at which to begin processing when either the NextCount or Rest scoping options are specified.
	PUBLIC StartRecNo		AS LONG
	/// The frequency of the evaluation of EvalBlock.
	PUBLIC StepSize			AS LONG
	/// A flag that is TRUE if only rows in the controlling order are to be included in the order.
	PUBLIC UseCurrent		AS LOGIC
	/// A code block defining the while condition to use for the creation of the order.  An empty value indicates that no while condition is being imposed.
	PUBLIC WhileBlock		AS ICodeblock
	/// A string defining the for while condition to use for the creation and maintenance of the order.
	PUBLIC WhileExpression	AS STRING

    METHOD Compile(oRDD AS IRdd) AS VOID
        IF SELF:WhileBlock == NULL .AND. ! String.IsNullOrWhiteSpace(SELF:WhileExpression)
            SELF:WhileBlock := oRDD:Compile(SELF:WhileExpression)
        ENDIF
        IF SELF:ForBlock == NULL .AND. ! String.IsNullOrWhiteSpace(SELF:ForExpression)
            SELF:ForBlock := oRDD:Compile(SELF:ForExpression)
        ENDIF
    METHOD Validate() AS VOID
        SELF:Active := FALSE
        SELF:Scoped := FALSE
        IF SELF:All .OR. SELF:ForBlock != NULL .OR. SELF:WhileBlock != NULL .OR.  ! String.IsNullOrEmpty(SELF:ForExpression ) ;
            .OR. SELF:NextCount != 0 .OR. SELF:RecNo != 0 .OR. SELF:Rest  .OR. SELF:Descending .OR. ! String.IsNullOrEmpty(SELF:WhileExpression)
            SELF:Active := TRUE
        ENDIF
        IF SELF:EvalBlock != NULL .AND. SELF:StepSize == 0
            SELF:StepSize := 1
        ENDIF
        IF SELF:Active
            IF SELF:All .OR. SELF:WhileBlock != NULL .OR. SELF:NextCount != 0 .OR. SELF:RecNo != 0 .OR. SELF:Rest
                SELF:Scoped := TRUE
            ENDIF
        ENDIF

    METHOD Clone() AS DbOrderCondInfo
        RETURN (DbOrderCondInfo) SELF:MemberwiseClone()

END CLASS

/// Helper class to store information needed to create a new order.
CLASS DbOrderCreateInfo
	/// The index file name.
	PUBLIC BagName		AS STRING
	/// The order name or number to create in BagName.
	PUBLIC Order		AS OBJECT
	/// The key expression defining the order.
	PUBLIC Expression	AS STRING
	/// A flag that is TRUE if the order should contain only unique keys.
	PUBLIC Unique		AS LOGIC
	/// A code block containing the key expression defining the order imposed on the work area.
	PUBLIC Block		AS ICodeblock
	/// A DbOrderCondInfo object containing information about the condition (if any) for the order.
	PUBLIC OrdCondInfo	AS DbOrderCondInfo

    METHOD Compile(oRDD AS IRdd) AS VOID
        IF SELF:Block == NULL .AND. ! String.IsNullOrWhiteSpace(SELF:Expression)
            SELF:Block := oRDD:Compile(SELF:Expression)
        ENDIF
        IF SELF:OrdCondInfo != NULL
            SELF:OrdCondInfo:Compile(oRDD)
        ENDIF

   METHOD Clone() AS DbOrderCreateInfo
        RETURN (DbOrderCreateInfo) SELF:MemberwiseClone()

END CLASS

/// Helper class to store information needed to open/address an order.
CLASS DbOrderInfo
	/// A flag that is TRUE if all tags of the index file must be opened.
	PUBLIC AllTags		AS LOGIC
	/// A code block containing the key expression defining the order imposed on the work area.
	PUBLIC Expression	AS ICodeblock
	/// An object containing the order name or number
	PUBLIC Order		AS OBJECT
	/// The index file name.
	PUBLIC BagName		AS STRING
	/// Return value for some order operations.
	PUBLIC Result		AS OBJECT

    METHOD Clone() AS DbOrderInfo
        RETURN (DbOrderInfo) SELF:MemberwiseClone()
    PROPERTY IsEmpty AS LOGIC
        GET
            IF String.IsNullOrEmpty(BagName)
                IF Order == NULL
                    RETURN TRUE
                ELSEIF Order IS LONG VAR nOrder
                    RETURN nOrder == 0
                ELSEIF Order IS STRING VAR cOrder
                    RETURN String.IsNullOrEmpty(cOrder)
                ELSEIF RuntimeState.Dialect == XSharpDialect.FoxPro .AND. Order IS LOGIC VAR lValue
                    // VFP NIL is represented by a logic FALSE
                    RETURN ! lValue
                ENDIF
            ENDIF
            RETURN FALSE
        END GET
    END PROPERTY
END CLASS

/// Helper class to store a list of relational information.
CLASS DbRelInfo
	/// The expression used to reposition the cursor of the child table when this relation is resolved.
	PUBLIC Key			AS STRING
	/// A code block used to reposition the cursor of the child table when this relation is resolved.
	PUBLIC Block		AS ICodeblock
	/// A reference to the child RDD for the relation.
	PUBLIC Child		AS IRdd
	/// A reference to the parent RDD for the relation.
	PUBLIC Parent		AS IRdd

    /// name of the relation. Defaults to the parent alias = '_' + child alias.
	PUBLIC Name         AS STRING
    METHOD Compile() AS VOID
        IF SELF:Block == NULL .AND. SELF:Parent != NULL .AND. ! String.IsNullOrWhiteSpace(SELF:Key)
            SELF:Block := SELF:Parent:Compile(SELF:Key)
        ENDIF

    METHOD Clone() AS DbRelInfo
        RETURN (DbRelInfo) SELF:MemberwiseClone()

END CLASS

/// Helper class to store references to all of the scope clause expressions.
CLASS DbScopeInfo
	/// A flag that is TRUE if a process should ignore duplicate key values.
	PUBLIC IgnoreDuplicates AS LOGIC
	/// A flag that is TRUE if a process should ignore any filter condition imposed on the current work area.
	PUBLIC IgnoreFilter		AS LOGIC
	///  A flag that is TRUE if a process should include deleted rows.
	PUBLIC IncludeDeleted	AS LOGIC
	/// A code block representing the conditional for clause.  A for condition is, essentially, a filter that hides rows for which the condition evaluates to FALSE.  The string value is provided for storage, while the code block is provided as a parameter for the EvalBlock() method.
	PUBLIC ForBlock			AS ICodeblock
	/// A string representing the conditional for clause.  A for condition is, essentially, a filter that hides rows for which the condition evaluates to FALSE.  The string value is provided for storage, while the code block is provided as a parameter for the EvalBlock() method.
	PUBLIC ForExpression	AS STRING
	/// A flag that is TRUE if the last row of the current scope is required.
	PUBLIC Last				AS LOGIC
	/// Permits continuation of a process for the next lNext rows, while obeying for and while clauses.
	PUBLIC NextCount		AS LONG
	/// Permits continuation of a process for a single row number, while obeying for and while clauses.
	PUBLIC RecId			AS OBJECT
	/// A flag that is TRUE if a process should continue stepping through data from the current work area cursor position until logical end-of-file.
	PUBLIC Rest				AS LOGIC
	/// A code block representing the conditional while clause.  A while condition permits continuation of a process that steps through rows until the condition evaluates to FALSE.  The string value is provided for storage, while the code block is provided as a parameter for the EvalBlock() method.
	PUBLIC WhileBlock		AS ICodeblock
	/// A string representing the conditional while clause.  A while condition permits continuation of a process that steps through rows until the condition evaluates to FALSE.  The string value is provided for storage, while the code block is provided as a parameter for the EvalBlock() method.
	PUBLIC WhileExpression	AS STRING
	/// Construct a DbScopeInfo object.
	CONSTRUCTOR()
		SELF:Clear()

	///Clear the scope fields.
	PUBLIC METHOD Clear() AS VOID
		IgnoreDuplicates := FALSE
		IgnoreFilter	 := FALSE
		IncludeDeleted 	 := FALSE
		ForBlock		 := NULL
		ForExpression	 := NULL
		Last			 := FALSE
		NextCount		 := 0
		RecId			 := NULL
		Rest			 := FALSE
		WhileBlock		 := NULL
		WhileExpression  := NULL

	///Clone the scopeinfo object.
	METHOD Clone AS DbScopeInfo
		RETURN (DbScopeInfo) SELF:MemberwiseClone()

	METHOD Compile(oRDD AS IRdd) AS VOID
        IF SELF:WhileBlock == NULL .AND. ! String.IsNullOrWhiteSpace(SELF:WhileExpression)
            SELF:WhileBlock := oRDD:Compile(SELF:WhileExpression)
        ENDIF
        IF SELF:ForBlock == NULL .AND. ! String.IsNullOrWhiteSpace(SELF:ForExpression)
            SELF:ForBlock := oRDD:Compile(SELF:ForExpression)
        ENDIF
END CLASS

/// Helper structure to store information needed to perform a seek operation
STRUCTURE DbSeekInfo
	/// A flag that is TRUE if the last occurrence of the specified key value is to be sought, rather than the first.
	PUBLIC Last 	AS LOGIC
	/// A flag that is TRUE if a soft seek is to be performed.
	PUBLIC SoftSeek AS LOGIC
	/// An object containing the key value to find.
	PUBLIC Value AS OBJECT
END STRUCTURE

/// Helper class to store information needed to perform a physical sort.
CLASS DbSortInfo
	/// A DbTransInfo object holding the destination work area, column transfer information, and scoping information for the Sort() method.
	PUBLIC TransInfo  AS DbTransInfo
	/// An array of DbSortItem structures defining the key values for the sort.  Note that the key values are processed in the order that they appear in this array.
	PUBLIC Items	  AS DbSortItem[]
	/// Number of items in the Items array.
    PROPERTY ItemCount  AS LONG GET Items:Length
    /// Construct a DbSortInfo object.
    CONSTRUCTOR(transItemCount AS LONG, sortItemCount AS LONG)
        SELF:TransInfo := DbTransInfo{transItemCount}
        SELF:Items     := DbSortItem[]{sortItemCount}
        RETURN
END CLASS

/// Helper structure to store information about a single sort key value.
STRUCTURE DbSortItem
	/// A one-based index indicating the column on which the sort is based.
	PUBLIC FieldNo 	AS LONG
	/// The offset of the field in the Workarea buffer.
	PUBLIC OffSet	AS LONG
	/// The length of the field in the Workarea buffer.
	PUBLIC Length	AS LONG
	/// One or more constants that function as sort optimization and control flags.
    /// They are passed to your RDD Sort() routine from the high-level wrapper function for the DBSort() function.
	PUBLIC Flags	AS  DbSortFlags

END STRUCTURE

/// Helper class to store information needed for the global transfer of data items from one work area to another.
CLASS DbTransInfo
	/// A DbScopeInfo object describing the limits of the scope of the transfer.
	PUBLIC Scope		AS DbScopeInfo
	/// The source work area.
	PUBLIC Source		AS IRdd
	/// The destination work area.
	PUBLIC Destination 	AS IRdd
	/// An array of DbTransItem structures defining the items to transfer to the destination work area. This is usually a list of column mappings from the source to the destination.
	PUBLIC Items		AS DbTransItem[]
	/// Transfer attributes specified using one or more of the constants Match or PutRec.
	PUBLIC Flags		AS DbTransInfoFlags
    /// Number of items in the Items array.
    PUBLIC PROPERTY ItemCount AS LONG AUTO
    /// Construct a DbTransInfo object.
    CONSTRUCTOR(itemCount AS LONG)
        SELF:Items := DbTransItem[]{itemCount}
        SELF:Scope := DbScopeInfo{}
        SELF:ItemCount := itemCount
        SELF:Flags := DbTransInfoFlags.None
END CLASS

/// Helper structure to store information about a single piece of data (usually a column) to transfer from one work area to another.
STRUCTURE DbTransItem
	/// A one-based field index in the source work area.
	PUBLIC Source 		AS LONG
	/// A one-based field index in the destination work area.
	PUBLIC Destination 	AS LONG
END STRUCTURE

/// Helper class for the RDD system to store field information
CLASS RddFieldInfo
    /// Name, normally max 10 characters
	PUBLIC Name 		AS STRING
    /// Field Type
	PUBLIC FieldType 	AS DbFieldType
    /// Physical length in the table
	PUBLIC Length 		AS LONG
    /// Decimal positions
	PUBLIC Decimals 	AS LONG
    /// Alternative name, no length limit. This is the Caption for VFP fields
	PUBLIC Alias 		AS STRING
    /// Flags, such as Nullable, AutoIncrement, Binary etc.
    PUBLIC Flags        AS DBFFieldFlags
    /// Offset in the record buffer for DBF fields.
	PUBLIC Offset       AS LONG

    /// Construct a RddFieldInfo object.
    /// <param name="sName">Name</param>
    /// <param name="sType">Type, may also contain flags in the form of a colon follwed by N,0,B,+,Z,E,U</param>
    /// <param name="nLength">Length 'DBF style', so length in Buffer</param>
    /// <param name="nDecimals">Number of decimals. </param>
    /// <param name="nOffSet">Offset in record buffer (optional).</param>
	CONSTRUCTOR(sName AS STRING, sType AS STRING, nLength AS LONG, nDecimals AS LONG, nOffSet := -1 AS LONG)
		Name 		:= sName
		Length 		:= nLength
		Decimals 	:= nDecimals
        Flags       := DBFFieldFlags.None
		FieldType := (DbFieldType) Char.ToUpper(sType[0])
		IF FieldType:IsBinary()
			Flags |= DBFFieldFlags.Binary
		ENDIF
        IF sType:IndexOf(":") >= 0
            sType := sType:Substring(1)
            FOREACH VAR cChar IN sType
                SWITCH cChar
                CASE 'N'
                CASE '0'
			        Flags |= DBFFieldFlags.Nullable
                CASE 'B'
                    Flags |= DBFFieldFlags.Binary
                CASE '+'
                    Flags |= DBFFieldFlags.AutoIncrement
                CASE 'Z'
                    Flags |= DBFFieldFlags.Compressed
                CASE 'E'
                    Flags |= DBFFieldFlags.Encrypted
                CASE 'U'
                    Flags |= DBFFieldFlags.Unicode
                END SWITCH
            NEXT
		ENDIF
		IF String.Compare(Name, _NULLFLAGS, TRUE) == 0
		    Flags |= DBFFieldFlags.System
		    Flags |= DBFFieldFlags.Binary
		ENDIF
		Alias       := sName
		SELF:Offset := nOffSet
		SELF:Validate()
		RETURN
    /// Construct a RddFieldInfo object.
    /// <param name="sName">Name</param>
    /// <param name="nType">Type</param>
    /// <param name="nLength">Length 'DBF style', so length in Buffer</param>
    /// <param name="nDecimals">Number of decimals. </param>
    /// <param name="nOffSet">Offset in record buffer (optional)</param>
    /// <param name="nFlags">Flags (optional)</param>
	CONSTRUCTOR(sName AS STRING, nType AS DbFieldType, nLength AS LONG, nDecimals AS LONG, nOffSet := -1 AS LONG, nFlags := DBFFieldFlags.None AS DBFFieldFlags)
		SELF:Name 		:= sName
		SELF:FieldType 	:= nType
		SELF:Length 	:= nLength
		SELF:Decimals 	:= nDecimals
		SELF:Alias      := sName
		SELF:Offset     := nOffSet
		SELF:Flags      := nFlags
		SELF:Validate()
		RETURN

    /// Construct a RddFieldInfo object.
    /// <param name="oInfo">Object to copy values from.</param>
    CONSTRUCTOR(oInfo AS RddFieldInfo)
        SUPER()
        SELF:CopyValues(oInfo)
		SELF:Validate()

   /// Copy values from one object to another.
   /// <param name="oInfo">Object to copy values to.</param>
    METHOD CopyValues(oInfo AS RddFieldInfo) AS VOID
 		SELF:Name 		:= oInfo:Name
		SELF:FieldType 	:= oInfo:FieldType
		SELF:Length 	:= oInfo:Length
		SELF:Alias      := oInfo:Alias
		SELF:Flags      := oInfo:Flags
		SELF:Offset     := oInfo:Offset
		IF SELF:FieldType:HasDecimals()  .OR. SELF:FieldType == DbFieldType.Character  // Support for char fields > 255 characters
        	SELF:Decimals 	:= oInfo:Decimals
		ENDIF
        RETURN

    /// Clone a RddFieldInfo object.
	METHOD Clone() AS RddFieldInfo
        VAR info := (RddFieldInfo) SELF:MemberwiseClone()
        RETURN info

    /// Check if two fields match in type, length and decimals.
    METHOD SameType(oFld AS RddFieldInfo) AS LOGIC
        RETURN SELF:FieldType == oFld:FieldType .AND. SELF:Length == oFld:Length .AND. SELF:Decimals == oFld:Decimals

    /// Validate combinations of type, length and decimals.
    VIRTUAL METHOD Validate() AS LOGIC
        SWITCH SELF:FieldType
            CASE DbFieldType.Date
                SELF:Length := 8
                SELF:Decimals := 0
            CASE DbFieldType.Logic
                SELF:Length := 1
                SELF:Decimals := 0
            CASE DbFieldType.Currency
                SELF:Length   := 8
                SELF:Decimals := 4
            CASE DbFieldType.Memo
                SELF:Decimals := 0
        END SWITCH
        RETURN TRUE

    OVERRIDE METHOD ToString() AS STRING
        RETURN SELF:Name+" ('"+SELF:FieldTypeStr+"',"+SELF:Length:ToString()+","+SELF:Decimals:ToString()+","+SELF:Flags:ToString("G")+")"

    /// Field type as 1 character string.
    PROPERTY FieldTypeStr       AS STRING GET ((CHAR) SELF:FieldType):ToString()
    /// Is it a memo ?
    PROPERTY IsMemo             AS LOGIC GET SELF:FieldType:IsMemo()
    /// Is it binary ?
    PROPERTY IsBinary           AS LOGIC GET SELF:FieldType:IsBinary() .OR. SELF:Flags:HasFlag(DBFFieldFlags.Binary)
    /// Is it nullable ?
    PROPERTY IsNullable         AS LOGIC GET SELF:Flags:HasFlag(DBFFieldFlags.Nullable)
    /// Is it an autoincrement ?
    PROPERTY IsAutoIncrement    AS LOGIC GET SELF:Flags:HasFlag(DBFFieldFlags.AutoIncrement)
    /// Is it a standard Dbase 3 field (CDLMN) ?
    PROPERTY IsStandard         AS LOGIC GET SELF:FieldType:IsStandard()
    /// Is it a VFP extended field ?
    PROPERTY IsVfp              AS LOGIC GET SELF:FieldType:IsVfp()
    /// Is it a variable length field ?
    PROPERTY IsVarLength        AS LOGIC GET SELF:FieldType:IsVarLength()
    /// Is it a unicode text ?
    PROPERTY IsUnicode          AS LOGIC GET SELF:Flags:HasFlag(DBFFieldFlags.Unicode)
    /// Is it an encryped field (not implemented yet)?
    PROPERTY IsEncrypted        AS LOGIC GET SELF:Flags:HasFlag(DBFFieldFlags.Encrypted)
    /// Is it a  compressed field (not implemented yet) ?
    PROPERTY IsCompressed       AS LOGIC GET SELF:Flags:HasFlag(DBFFieldFlags.Compressed)
    /// Can the field be sorted?
    PROPERTY CanSort            AS LOGIC
        GET
            SWITCH SELF:FieldType
            CASE DbFieldType.Character
            CASE DbFieldType.Date
            CASE DbFieldType.DateTime
            CASE DbFieldType.Number
            CASE DbFieldType.Logic
            CASE DbFieldType.Currency
            CASE DbFieldType.Integer
            CASE DbFieldType.Float
            CASE DbFieldType.Double
                RETURN TRUE
            OTHERWISE
                IF SELF:IsBinary .OR. SELF:IsVarLength
                    RETURN FALSE
                ENDIF
            END SWITCH
            RETURN FALSE

        END GET
    END PROPERTY

END CLASS

/// <exclude/>
STATIC CLASS RDDExtensions
    STATIC METHOD IsMemo(SELF eType AS DbFieldType ) AS LOGIC
        SWITCH eType
        CASE DbFieldType.Memo
        CASE DbFieldType.Picture
        CASE DbFieldType.General
            RETURN TRUE
        END SWITCH
        RETURN FALSE

    STATIC METHOD IsVarLength(SELF eType AS DbFieldType ) AS LOGIC
        SWITCH eType
        CASE DbFieldType.VarBinary
        CASE DbFieldType.VarChar
            RETURN TRUE
        END SWITCH
        RETURN FALSE

    STATIC METHOD IsStandard(SELF eType AS DbFieldType ) AS LOGIC
        SWITCH eType
        CASE DbFieldType.Character
        CASE DbFieldType.Date
        CASE DbFieldType.Logic
        CASE DbFieldType.Memo
        CASE DbFieldType.Number
            RETURN TRUE
        END SWITCH
        RETURN FALSE

    STATIC METHOD IsBinary(SELF eType AS DbFieldType ) AS LOGIC
        SWITCH eType
        CASE DbFieldType.Integer
        CASE DbFieldType.Currency
        CASE DbFieldType.Double
        CASE DbFieldType.Picture
        CASE DbFieldType.Blob
        CASE DbFieldType.VarBinary
        CASE DbFieldType.VarChar
        CASE DbFieldType.DateTime
            RETURN TRUE
        END SWITCH
        RETURN FALSE
    STATIC METHOD HasDecimals(SELF eType AS DbFieldType ) AS LOGIC
        SWITCH eType
        CASE DbFieldType.Double
        CASE DbFieldType.Float
        CASE DbFieldType.Number
        CASE DbFieldType.Currency
            RETURN TRUE
        END SWITCH
        RETURN FALSE
    STATIC METHOD IsVfp(SELF eType AS DbFieldType ) AS LOGIC
        SWITCH eType
        CASE DbFieldType.Character
        CASE DbFieldType.Blob
        CASE DbFieldType.Currency
        CASE DbFieldType.Date
        CASE DbFieldType.DateTime
        CASE DbFieldType.Double
        CASE DbFieldType.Float
        CASE DbFieldType.General
        CASE DbFieldType.Integer
        CASE DbFieldType.Logic
        CASE DbFieldType.Memo
        CASE DbFieldType.Number
        CASE DbFieldType.Picture
        CASE DbFieldType.VarBinary
        CASE DbFieldType.VarChar
        CASE DbFieldType.NullFlags
            RETURN TRUE
        END SWITCH
        RETURN FALSE
END CLASS
END NAMESPACE

BEGIN NAMESPACE XSharp

/// Helper class for VoDbTrans and VoDbSort()
CLASS _FieldNames
    /// List of field names.
    PUBLIC Fields AS STRING[]
    /// Number of fields in the list.
    PROPERTY FieldCount AS LONG GET Fields:Length
    /// Construct a _FieldNames object.
    CONSTRUCTOR (aFields AS IList<STRING>)
        SELF:Fields := aFields:ToArray()
        RETURN
END CLASS

/// Helper class for DbJoin()
CLASS _JoinList
    /// Area number of destination Workarea.
    PUBLIC uiDestSel AS DWORD
    /// List of field areas and positions.
    PUBLIC Fields AS _JoinField[]
    /// Number of fields in the list.
    PUBLIC PROPERTY Count AS LONG GET Fields:Length
    /// Construct a _JoinList object.
    PUBLIC CONSTRUCTOR(nFields AS LONG)
        SELF:Fields := _JoinField[]{nFields}
        RETURN
END CLASS

/// Helper structure for DbJoin()
STRUCTURE _JoinField
    /// Source Workarea number.
    PUBLIC Area AS DWORD
    /// Source field position.
    PUBLIC Pos  AS DWORD
END STRUCTURE

/// Helper structure to store information for a list of RDD names for DbUseArea()
STRUCTURE _RddList
    /// List of RDD names.
    EXPORT atomRddName AS STRING[]
    /// Number of names in the list.
    PROPERTY uiRddCount AS DWORD GET (DWORD) atomRddName:Length

    /// Construct _RddList from class Tree.
    CONSTRUCTOR(oRDD AS Workarea)
        VAR names := List<STRING>{}
        VAR type  := oRDD:GetType()
        DO WHILE type != typeof(Workarea)
            VAR name := type:Name:ToUpper()
            // map names to VO compatible names
            IF name == "DBF"
                names:Add("CAVODBF")
            ELSE
                names:Add(name)
            ENDIF
            type := type:BaseType
        ENDDO
        names:Reverse()
        atomRddName := names:ToArray()
    /// Construct _RddList from list of names.
    CONSTRUCTOR(aNames AS STRING[])
        atomRddName := aNames
        RETURN

END STRUCTURE
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.IO
USING XSharp.RDD.Enums
USING System.Collections.Generic
USING System.Linq
USING XSharp.RDD
// The classes below are simple. No properties, but all public fields.

BEGIN NAMESPACE XSharp.RDD.Support

/// Helper class to store the scope and codeblock for a DbEval() operation.
CLASS DbEvalInfo

	/// A code block to be evaluated with DbEval() on each row of the work area that is in the range defined by ScopeInfo.
	PUBLIC Block	 AS ICodeblock

	/// A DbScopeInfo structure limiting the evaluation of Block.
	PUBLIC ScopeInfo AS DbScopeInfo
    /// Construct a DbEvalInfo object.
    CONSTRUCTOR()
        SELF:ScopeInfo := DbScopeInfo{}
        RETURN
END CLASS

/// Helper class to store a filter condition for a table.
CLASS DbFilterInfo
	/// A string representing the source code for itmCobExpr.
	PUBLIC FilterText	AS STRING

	/// A code block representing the condition that is evaluated at each cursor location.  If the result of the evaluation is FALSE, the cursor location requested is invalid according to the current filter condition.
	PUBLIC FilterBlock	AS ICodeblock

	/// A flag that is TRUE if a filter is active.
	PUBLIC Active		AS LOGIC

	/// A flag that is TRUE if a filter is optimized.
	PUBLIC Optimized	AS LOGIC

	///Clear the filter fields.
	METHOD Clear() AS VOID
		SELF:FilterBlock := NULL
		SELF:FilterText	 := NULL
		SELF:Active		 := FALSE
		SELF:Optimized	 := FALSE
		RETURN

	///Clone the filter object.
	METHOD Clone AS DbFilterInfo
		LOCAL oClone AS DbFilterInfo
		oClone := DbFilterInfo{}
		oClone:FilterBlock := SELF:FilterBlock
		oClone:FilterText  := SELF:FilterText
		oClone:Optimized   := SELF:Optimized
		oClone:Active	   := FALSE
		RETURN oClone

    /// Construct a DbFilterInfo object.
    CONSTRUCTOR
        SELF:Clear()
        RETURN

	METHOD Compile(oRDD AS IRdd) AS VOID
        IF SELF:FilterBlock == NULL .AND. ! String.IsNullOrWhiteSpace(SELF:FilterText)
            SELF:FilterBlock := oRDD:Compile(SELF:FilterText)
        ENDIF
END CLASS

/// Helper structure to store information needed to lock a row or table for exclusive access.
STRUCTURE DbLockInfo
	/// An Item indicating the ID of the row to lock.  This member is meaningful only if Method is set to EXCLUSIVE or MULTIPLE.
	PUBLIC RecId		AS OBJECT

	/// A constant indicating the type of lock to obtain.  The possible values are of the Lockmethod enum.
	PUBLIC @@Method AS LockMethod

	/// A flag that is TRUE if the lock operation was successful.
	PUBLIC Result		AS LOGIC
	/// List of possible Locking Methods
	ENUM LockMethod
		/// Lock a row, releasing currently locked rows.
		MEMBER Exclusive := 1
		/// Lock a row, maintaining currently locked rows.
		MEMBER Multiple  := 2
		/// Lock a table, releasing locks currently held.
		MEMBER File 	 := 3
	END ENUM
END STRUCTURE

/// Helper class to store information needed to open a table.
CLASS DbOpenInfo
	/// Should the table be opened Readonly?
	PUBLIC ReadOnly 	AS LOGIC
	/// Should the table be opened Shared?
	PUBLIC Shared		AS LOGIC
	/// Unique Alias that the table has. This must be a valid XBase Identifier
	PUBLIC Alias		AS STRING
	/// The extension for the table that must be opened.
	PUBLIC Extension    AS STRING
	/// The filename (optionally includig a path) for the table that must be opened.
	PUBLIC FileName		AS STRING
	/// Workarea number in which the table will be opened.
	PUBLIC Workarea		AS DWORD
    /// Construct a DbOpenInfo object.

	CONSTRUCTOR()
        SUPER()
    /// Construct a DbOpenInfo object.
	CONSTRUCTOR(sFileName AS STRING, sAlias AS STRING, dwWorkarea AS DWORD, lShared AS LOGIC, lReadOnly AS LOGIC)
		FileName 	:= sFileName
        Extension   := Path.GetExtension(sFileName)
		IF String.IsNullOrEmpty( sAlias )
			Alias := Path.GetFileNameWithoutExtension( sFileName )
		ELSE
			Alias	 	:= sAlias
		ENDIF
		Workarea	:= dwWorkarea
		Shared		:= lShared
		ReadOnly	:= lReadOnly
	/// Return the numeric FileMode based on the Shared and Readonly flags
	PUBLIC PROPERTY FileMode AS DWORD
	GET
		LOCAL nMode AS DWORD
		nMode := FO_COMPAT
		IF (Shared)
			nMode |= FO_SHARED
		ELSE
			nMode |= FO_EXCLUSIVE
		ENDIF
		IF (ReadOnly)
			nMode |= FO_READ
		ELSE
			nMode |= FO_READWRITE
		ENDIF
		RETURN nMode
	END GET
	END PROPERTY
    PUBLIC METHOD Clone() AS DbOpenInfo
        RETURN (DbOpenInfo) SELF:MemberwiseClone()
END CLASS

/// Helper class to store information needed to create a conditional order.
CLASS DbOrderCondInfo
	///  A flag that is TRUE if one or more valid conditions have been specified in the structure.
	PUBLIC Active			AS LOGIC
	///  A flag that is TRUE if open orders should remain open while the new order is being created.
	PUBLIC Additive			AS LOGIC
	///  A flag that is TRUE if all rows are to be processed during order creation.
	PUBLIC All				AS LOGIC
	/// A flag that is TRUE if the new order will be a custom built order.
	PUBLIC Custom			AS LOGIC
	///  A flag that is TRUE if the order should be created in descending order.
	PUBLIC @@Descending     AS LOGIC
	/// A code block defining the expression to evaluate every StepSize rows during the creation of the order.  The code block referenced should return a logical value: TRUE indicates that creation of the order should continue normally, and FALSE indicates that order creation should terminate.
	PUBLIC EvalBlock		AS ICodeblock
	/// A code block defining the for condition to use for the creation and maintenance of the order.
	PUBLIC ForBlock			AS ICodeblock
	/// A string defining the for condition to use for the creation and maintenance of the order.
	PUBLIC ForExpression	AS STRING
	/// The number of rows to process for order creation.
	PUBLIC NextCount		AS LONG
	/// A flag that is TRUE if the for condition may NOT be optimized,
	PUBLIC NoOptimize		AS LOGIC
	///  A single row number to include in the order.
	PUBLIC RecNo			AS LONG
	/// A flag that is TRUE if only the rows specified by lStartRecno through end-of-file are to be included in the order.
	PUBLIC Rest				AS LOGIC
	/// A flag indicating whether the order is to be scoped. fScoped will be TRUE if WhileBlock, NextCount, Recno, Rest, or All is specified.
	PUBLIC Scoped			AS LOGIC
	/// The row at which to begin processing when either the NextCount or Rest scoping options are specified.
	PUBLIC StartRecNo		AS LONG
	/// The frequency of the evaluation of EvalBlock.
	PUBLIC StepSize			AS LONG
	/// A flag that is TRUE if only rows in the controlling order are to be included in the order.
	PUBLIC UseCurrent		AS LOGIC
	/// A code block defining the while condition to use for the creation of the order.  An empty value indicates that no while condition is being imposed.
	PUBLIC WhileBlock		AS ICodeblock
	/// A string defining the for while condition to use for the creation and maintenance of the order.
	PUBLIC WhileExpression	AS STRING

    METHOD Compile(oRDD AS IRdd) AS VOID
        IF SELF:WhileBlock == NULL .AND. ! String.IsNullOrWhiteSpace(SELF:WhileExpression)
            SELF:WhileBlock := oRDD:Compile(SELF:WhileExpression)
        ENDIF
        IF SELF:ForBlock == NULL .AND. ! String.IsNullOrWhiteSpace(SELF:ForExpression)
            SELF:ForBlock := oRDD:Compile(SELF:ForExpression)
        ENDIF
    METHOD Validate() AS VOID
        SELF:Active := FALSE
        SELF:Scoped := FALSE
        IF SELF:All .OR. SELF:ForBlock != NULL .OR. SELF:WhileBlock != NULL .OR.  ! String.IsNullOrEmpty(SELF:ForExpression ) ;
            .OR. SELF:NextCount != 0 .OR. SELF:RecNo != 0 .OR. SELF:Rest  .OR. SELF:Descending .OR. ! String.IsNullOrEmpty(SELF:WhileExpression)
            SELF:Active := TRUE
        ENDIF
        IF SELF:EvalBlock != NULL .AND. SELF:StepSize == 0
            SELF:StepSize := 1
        ENDIF
        IF SELF:Active
            IF SELF:All .OR. SELF:WhileBlock != NULL .OR. SELF:NextCount != 0 .OR. SELF:RecNo != 0 .OR. SELF:Rest
                SELF:Scoped := TRUE
            ENDIF
        ENDIF

    METHOD Clone() AS DbOrderCondInfo
        RETURN (DbOrderCondInfo) SELF:MemberwiseClone()

END CLASS

/// Helper class to store information needed to create a new order.
CLASS DbOrderCreateInfo
	/// The index file name.
	PUBLIC BagName		AS STRING
	/// The order name or number to create in BagName.
	PUBLIC Order		AS OBJECT
	/// The key expression defining the order.
	PUBLIC Expression	AS STRING
	/// A flag that is TRUE if the order should contain only unique keys.
	PUBLIC Unique		AS LOGIC
	/// A code block containing the key expression defining the order imposed on the work area.
	PUBLIC Block		AS ICodeblock
	/// A DbOrderCondInfo object containing information about the condition (if any) for the order.
	PUBLIC OrdCondInfo	AS DbOrderCondInfo

    METHOD Compile(oRDD AS IRdd) AS VOID
        IF SELF:Block == NULL .AND. ! String.IsNullOrWhiteSpace(SELF:Expression)
            SELF:Block := oRDD:Compile(SELF:Expression)
        ENDIF
        IF SELF:OrdCondInfo != NULL
            SELF:OrdCondInfo:Compile(oRDD)
        ENDIF

   METHOD Clone() AS DbOrderCreateInfo
        RETURN (DbOrderCreateInfo) SELF:MemberwiseClone()

END CLASS

/// Helper class to store information needed to open/address an order.
CLASS DbOrderInfo
	/// A flag that is TRUE if all tags of the index file must be opened.
	PUBLIC AllTags		AS LOGIC
	/// A code block containing the key expression defining the order imposed on the work area.
	PUBLIC Expression	AS ICodeblock
	/// An object containing the order name or number
	PUBLIC Order		AS OBJECT
	/// The index file name.
	PUBLIC BagName		AS STRING
	/// Return value for some order operations.
	PUBLIC Result		AS OBJECT

    METHOD Clone() AS DbOrderInfo
        RETURN (DbOrderInfo) SELF:MemberwiseClone()
    PROPERTY IsEmpty AS LOGIC
        GET
            IF Order == NULL
                RETURN String.IsNullOrEmpty(BagName)
            ENDIF
            // VFP NIL is represented by a logic FALSE
            IF Order IS LOGIC VAR lValue .AND. ! lValue
                RETURN String.IsNullOrEmpty(BagName)
            ENDIF
            RETURN FALSE
        END GET
    END PROPERTY
END CLASS

/// Helper class to store a list of relational information.
CLASS DbRelInfo
	/// The expression used to reposition the cursor of the child table when this relation is resolved.
	PUBLIC Key			AS STRING
	/// A code block used to reposition the cursor of the child table when this relation is resolved.
	PUBLIC Block		AS ICodeblock
	/// A reference to the child RDD for the relation.
	PUBLIC Child		AS IRdd
	/// A reference to the parent RDD for the relation.
	PUBLIC Parent		AS IRdd

    /// name of the relation. Defaults to the parent alias = '_' + child alias.
	PUBLIC Name         AS STRING
    METHOD Compile() AS VOID
        IF SELF:Block == NULL .AND. SELF:Parent != NULL .AND. ! String.IsNullOrWhiteSpace(SELF:Key)
            SELF:Block := SELF:Parent:Compile(SELF:Key)
        ENDIF

    METHOD Clone() AS DbRelInfo
        RETURN (DbRelInfo) SELF:MemberwiseClone()

END CLASS

/// Helper class to store references to all of the scope clause expressions.
CLASS DbScopeInfo
	/// A flag that is TRUE if a process should ignore duplicate key values.
	PUBLIC IgnoreDuplicates AS LOGIC
	/// A flag that is TRUE if a process should ignore any filter condition imposed on the current work area.
	PUBLIC IgnoreFilter		AS LOGIC
	///  A flag that is TRUE if a process should include deleted rows.
	PUBLIC IncludeDeleted	AS LOGIC
	/// A code block representing the conditional for clause.  A for condition is, essentially, a filter that hides rows for which the condition evaluates to FALSE.  The string value is provided for storage, while the code block is provided as a parameter for the EvalBlock() method.
	PUBLIC ForBlock			AS ICodeblock
	/// A string representing the conditional for clause.  A for condition is, essentially, a filter that hides rows for which the condition evaluates to FALSE.  The string value is provided for storage, while the code block is provided as a parameter for the EvalBlock() method.
	PUBLIC ForExpression	AS STRING
	/// A flag that is TRUE if the last row of the current scope is required.
	PUBLIC Last				AS LOGIC
	/// Permits continuation of a process for the next lNext rows, while obeying for and while clauses.
	PUBLIC NextCount		AS LONG
	/// Permits continuation of a process for a single row number, while obeying for and while clauses.
	PUBLIC RecId			AS OBJECT
	/// A flag that is TRUE if a process should continue stepping through data from the current work area cursor position until logical end-of-file.
	PUBLIC Rest				AS LOGIC
	/// A code block representing the conditional while clause.  A while condition permits continuation of a process that steps through rows until the condition evaluates to FALSE.  The string value is provided for storage, while the code block is provided as a parameter for the EvalBlock() method.
	PUBLIC WhileBlock		AS ICodeblock
	/// A string representing the conditional while clause.  A while condition permits continuation of a process that steps through rows until the condition evaluates to FALSE.  The string value is provided for storage, while the code block is provided as a parameter for the EvalBlock() method.
	PUBLIC WhileExpression	AS STRING
	/// Construct a DbScopeInfo object.
	CONSTRUCTOR()
		SELF:Clear()

	///Clear the scope fields.
	PUBLIC METHOD Clear() AS VOID
		IgnoreDuplicates := FALSE
		IgnoreFilter	 := FALSE
		IncludeDeleted 	 := FALSE
		ForBlock		 := NULL
		ForExpression	 := NULL
		Last			 := FALSE
		NextCount		 := 0
		RecId			 := NULL
		Rest			 := FALSE
		WhileBlock		 := NULL
		WhileExpression  := NULL

	///Clone the scopeinfo object.
	METHOD Clone AS DbScopeInfo
		RETURN (DbScopeInfo) SELF:MemberwiseClone()

	METHOD Compile(oRDD AS IRdd) AS VOID
        IF SELF:WhileBlock == NULL .AND. ! String.IsNullOrWhiteSpace(SELF:WhileExpression)
            SELF:WhileBlock := oRDD:Compile(SELF:WhileExpression)
        ENDIF
        IF SELF:ForBlock == NULL .AND. ! String.IsNullOrWhiteSpace(SELF:ForExpression)
            SELF:ForBlock := oRDD:Compile(SELF:ForExpression)
        ENDIF
END CLASS

/// Helper structure to store information needed to perform a seek operation
STRUCTURE DbSeekInfo
	/// A flag that is TRUE if the last occurrence of the specified key value is to be sought, rather than the first.
	PUBLIC Last 	AS LOGIC
	/// A flag that is TRUE if a soft seek is to be performed.
	PUBLIC SoftSeek AS LOGIC
	/// An object containing the key value to find.
	PUBLIC Value AS OBJECT
END STRUCTURE

/// Helper class to store information needed to perform a physical sort.
CLASS DbSortInfo
	/// A DbTransInfo object holding the destination work area, column transfer information, and scoping information for the Sort() method.
	PUBLIC TransInfo  AS DbTransInfo
	/// An array of DbSortItem structures defining the key values for the sort.  Note that the key values are processed in the order that they appear in this array.
	PUBLIC Items	  AS DbSortItem[]
	/// Number of items in the Items array.
    PROPERTY ItemCount  AS LONG GET Items:Length
    /// Construct a DbSortInfo object.
    CONSTRUCTOR(transItemCount AS LONG, sortItemCount AS LONG)
        SELF:TransInfo := DbTransInfo{transItemCount}
        SELF:Items     := DbSortItem[]{sortItemCount}
        RETURN
END CLASS

/// Helper structure to store information about a single sort key value.
STRUCTURE DbSortItem
	/// A one-based index indicating the column on which the sort is based.
	PUBLIC FieldNo 	AS LONG
	/// The offset of the field in the Workarea buffer.
	PUBLIC OffSet	AS LONG
	/// The length of the field in the Workarea buffer.
	PUBLIC Length	AS LONG
	/// One or more constants that function as sort optimization and control flags.
    /// They are passed to your RDD Sort() routine from the high-level wrapper function for the DBSort() function.
	PUBLIC Flags	AS  DbSortFlags

END STRUCTURE

/// Helper class to store information needed for the global transfer of data items from one work area to another.
CLASS DbTransInfo
	/// A DbScopeInfo object describing the limits of the scope of the transfer.
	PUBLIC Scope		AS DbScopeInfo
	/// The source work area.
	PUBLIC Source		AS IRdd
	/// The destination work area.
	PUBLIC Destination 	AS IRdd
	/// An array of DbTransItem structures defining the items to transfer to the destination work area. This is usually a list of column mappings from the source to the destination.
	PUBLIC Items		AS DbTransItem[]
	/// Transfer attributes specified using one or more of the constants Match or PutRec.
	PUBLIC Flags		AS DbTransInfoFlags
    /// Number of items in the Items array.
    PUBLIC PROPERTY ItemCount AS LONG AUTO
    /// Construct a DbTransInfo object.
    CONSTRUCTOR(itemCount AS LONG)
        SELF:Items := DbTransItem[]{itemCount}
        SELF:Scope := DbScopeInfo{}
        SELF:ItemCount := itemCount
        SELF:Flags := DbTransInfoFlags.None
END CLASS

/// Helper structure to store information about a single piece of data (usually a column) to transfer from one work area to another.
STRUCTURE DbTransItem
	/// A one-based field index in the source work area.
	PUBLIC Source 		AS LONG
	/// A one-based field index in the destination work area.
	PUBLIC Destination 	AS LONG
END STRUCTURE

/// Helper class for the RDD system to store field information
CLASS RddFieldInfo
    /// Name, normally max 10 characters
	PUBLIC Name 		AS STRING
    /// Field Type
	PUBLIC FieldType 	AS DbFieldType
    /// Physical length in the table
	PUBLIC Length 		AS LONG
    /// Decimal positions
	PUBLIC Decimals 	AS LONG
    /// Alternative name, no length limit. This is the Caption for VFP fields
	PUBLIC Alias 		AS STRING
    /// Flags, such as Nullable, AutoIncrement, Binary etc.
    PUBLIC Flags        AS DBFFieldFlags
    /// Offset in the record buffer for DBF fields.
	PUBLIC Offset       AS LONG

    /// Construct a RddFieldInfo object.
    /// <param name="sName">Name</param>
    /// <param name="sType">Type, may also contain flags in the form of a colon follwed by N,0,B,+,Z,E,U</param>
    /// <param name="nLength">Length 'DBF style', so length in Buffer</param>
    /// <param name="nDecimals">Number of decimals. </param>
    /// <param name="nOffSet">Offset in record buffer (optional).</param>
	CONSTRUCTOR(sName AS STRING, sType AS STRING, nLength AS LONG, nDecimals AS LONG, nOffSet := -1 AS LONG)
		Name 		:= sName
		Length 		:= nLength
		Decimals 	:= nDecimals
        Flags       := DBFFieldFlags.None
		FieldType := (DbFieldType) Char.ToUpper(sType[0])
		IF FieldType:IsBinary()
			Flags |= DBFFieldFlags.Binary
		ENDIF
        IF sType:IndexOf(":") >= 0
            sType := sType:Substring(1)
            FOREACH VAR cChar IN sType
                SWITCH cChar
                CASE 'N'
                CASE '0'
			        Flags |= DBFFieldFlags.Nullable
                CASE 'B'
                    Flags |= DBFFieldFlags.Binary
                CASE '+'
                    Flags |= DBFFieldFlags.AutoIncrement
                CASE 'Z'
                    Flags |= DBFFieldFlags.Compressed
                CASE 'E'
                    Flags |= DBFFieldFlags.Encrypted
                CASE 'U'
                    Flags |= DBFFieldFlags.Unicode
                END SWITCH
            NEXT
		ENDIF
		IF String.Compare(Name, _NULLFLAGS, TRUE) == 0
		    Flags |= DBFFieldFlags.System
		    Flags |= DBFFieldFlags.Binary
		ENDIF
		Alias       := sName
		SELF:Offset := nOffSet
		SELF:Validate()
		RETURN
    /// Construct a RddFieldInfo object.
    /// <param name="sName">Name</param>
    /// <param name="nType">Type</param>
    /// <param name="nLength">Length 'DBF style', so length in Buffer</param>
    /// <param name="nDecimals">Number of decimals. </param>
    /// <param name="nOffSet">Offset in record buffer (optional)</param>
    /// <param name="nFlags">Flags (optional)</param>
	CONSTRUCTOR(sName AS STRING, nType AS DbFieldType, nLength AS LONG, nDecimals AS LONG, nOffSet := -1 AS LONG, nFlags := DBFFieldFlags.None AS DBFFieldFlags)
		SELF:Name 		:= sName
		SELF:FieldType 	:= nType
		SELF:Length 	:= nLength
		SELF:Decimals 	:= nDecimals
		SELF:Alias      := sName
		SELF:Offset     := nOffSet
		SELF:Flags      := nFlags
		SELF:Validate()
		RETURN

    /// Construct a RddFieldInfo object.
    /// <param name="oInfo">Object to copy values from.</param>
    CONSTRUCTOR(oInfo AS RddFieldInfo)
        SUPER()
        SELF:CopyValues(oInfo)
		SELF:Validate()

   /// Copy values from one object to another.
   /// <param name="oInfo">Object to copy values to.</param>
    METHOD CopyValues(oInfo AS RddFieldInfo) AS VOID
 		SELF:Name 		:= oInfo:Name
		SELF:FieldType 	:= oInfo:FieldType
		SELF:Length 	:= oInfo:Length
		SELF:Alias      := oInfo:Alias
		SELF:Flags      := oInfo:Flags
		SELF:Offset     := oInfo:Offset
		IF SELF:FieldType:HasDecimals()  .OR. SELF:FieldType == DbFieldType.Character  // Support for char fields > 255 characters
        	SELF:Decimals 	:= oInfo:Decimals
		ENDIF
        RETURN

    /// Clone a RddFieldInfo object.
	METHOD Clone() AS RddFieldInfo
        VAR info := (RddFieldInfo) SELF:MemberwiseClone()
        RETURN info

    /// Check if two fields match in type, length and decimals.
    METHOD SameType(oFld AS RddFieldInfo) AS LOGIC
        RETURN SELF:FieldType == oFld:FieldType .AND. SELF:Length == oFld:Length .AND. SELF:Decimals == oFld:Decimals

    /// Validate combinations of type, length and decimals.
    VIRTUAL METHOD Validate() AS LOGIC
        SWITCH SELF:FieldType
            CASE DbFieldType.Date
                SELF:Length := 8
                SELF:Decimals := 0
            CASE DbFieldType.Logic
                SELF:Length := 1
                SELF:Decimals := 0
            CASE DbFieldType.Currency
                SELF:Length   := 8
                SELF:Decimals := 4
            CASE DbFieldType.Memo
                SELF:Decimals := 0
        END SWITCH
        RETURN TRUE

    OVERRIDE METHOD ToString() AS STRING
        RETURN SELF:Name+" ('"+SELF:FieldTypeStr+"',"+SELF:Length:ToString()+","+SELF:Decimals:ToString()+","+SELF:Flags:ToString("G")+")"

    /// Field type as 1 character string.
    PROPERTY FieldTypeStr       AS STRING GET ((CHAR) SELF:FieldType):ToString()
    /// Is it a memo ?
    PROPERTY IsMemo             AS LOGIC GET SELF:FieldType:IsMemo()
    /// Is it binary ?
    PROPERTY IsBinary           AS LOGIC GET SELF:FieldType:IsBinary() .OR. SELF:Flags:HasFlag(DBFFieldFlags.Binary)
    /// Is it nullable ?
    PROPERTY IsNullable         AS LOGIC GET SELF:Flags:HasFlag(DBFFieldFlags.Nullable)
    /// Is it an autoincrement ?
    PROPERTY IsAutoIncrement    AS LOGIC GET SELF:Flags:HasFlag(DBFFieldFlags.AutoIncrement)
    /// Is it a standard Dbase 3 field (CDLMN) ?
    PROPERTY IsStandard         AS LOGIC GET SELF:FieldType:IsStandard()
    /// Is it a VFP extended field ?
    PROPERTY IsVfp              AS LOGIC GET SELF:FieldType:IsVfp()
    /// Is it a variable length field ?
    PROPERTY IsVarLength        AS LOGIC GET SELF:FieldType:IsVarLength()
    /// Is it a unicode text ?
    PROPERTY IsUnicode          AS LOGIC GET SELF:Flags:HasFlag(DBFFieldFlags.Unicode)
    /// Is it an encryped field (not implemented yet)?
    PROPERTY IsEncrypted        AS LOGIC GET SELF:Flags:HasFlag(DBFFieldFlags.Encrypted)
    /// Is it a  compressed field (not implemented yet) ?
    PROPERTY IsCompressed       AS LOGIC GET SELF:Flags:HasFlag(DBFFieldFlags.Compressed)
    /// Can the field be sorted?
    PROPERTY CanSort            AS LOGIC
        GET
            SWITCH SELF:FieldType
            CASE DbFieldType.Character
            CASE DbFieldType.Date
            CASE DbFieldType.DateTime
            CASE DbFieldType.Number
            CASE DbFieldType.Logic
            CASE DbFieldType.Currency
            CASE DbFieldType.Integer
            CASE DbFieldType.Float
            CASE DbFieldType.Double
                RETURN TRUE
            OTHERWISE
                IF SELF:IsBinary .OR. SELF:IsVarLength
                    RETURN FALSE
                ENDIF
            END SWITCH
            RETURN FALSE

        END GET
    END PROPERTY

END CLASS

/// <exclude/>
STATIC CLASS RDDExtensions
    STATIC METHOD IsMemo(SELF eType AS DbFieldType ) AS LOGIC
        SWITCH eType
        CASE DbFieldType.Memo
        CASE DbFieldType.Picture
        CASE DbFieldType.General
            RETURN TRUE
        END SWITCH
        RETURN FALSE

    STATIC METHOD IsVarLength(SELF eType AS DbFieldType ) AS LOGIC
        SWITCH eType
        CASE DbFieldType.VarBinary
        CASE DbFieldType.VarChar
            RETURN TRUE
        END SWITCH
        RETURN FALSE

    STATIC METHOD IsStandard(SELF eType AS DbFieldType ) AS LOGIC
        SWITCH eType
        CASE DbFieldType.Character
        CASE DbFieldType.Date
        CASE DbFieldType.Logic
        CASE DbFieldType.Memo
        CASE DbFieldType.Number
            RETURN TRUE
        END SWITCH
        RETURN FALSE

    STATIC METHOD IsBinary(SELF eType AS DbFieldType ) AS LOGIC
        SWITCH eType
        CASE DbFieldType.Integer
        CASE DbFieldType.Currency
        CASE DbFieldType.Double
        CASE DbFieldType.Picture
        CASE DbFieldType.Blob
        CASE DbFieldType.VarBinary
        CASE DbFieldType.VarChar
        CASE DbFieldType.DateTime
            RETURN TRUE
        END SWITCH
        RETURN FALSE
    STATIC METHOD HasDecimals(SELF eType AS DbFieldType ) AS LOGIC
        SWITCH eType
        CASE DbFieldType.Double
        CASE DbFieldType.Float
        CASE DbFieldType.Number
        CASE DbFieldType.Currency
            RETURN TRUE
        END SWITCH
        RETURN FALSE
    STATIC METHOD IsVfp(SELF eType AS DbFieldType ) AS LOGIC
        SWITCH eType
        CASE DbFieldType.Character
        CASE DbFieldType.Blob
        CASE DbFieldType.Currency
        CASE DbFieldType.Date
        CASE DbFieldType.DateTime
        CASE DbFieldType.Double
        CASE DbFieldType.Float
        CASE DbFieldType.General
        CASE DbFieldType.Integer
        CASE DbFieldType.Logic
        CASE DbFieldType.Memo
        CASE DbFieldType.Number
        CASE DbFieldType.Picture
        CASE DbFieldType.VarBinary
        CASE DbFieldType.VarChar
        CASE DbFieldType.NullFlags
            RETURN TRUE
        END SWITCH
        RETURN FALSE
END CLASS
END NAMESPACE

BEGIN NAMESPACE XSharp

/// Helper class for VoDbTrans and VoDbSort()
CLASS _FieldNames
    /// List of field names.
    PUBLIC Fields AS STRING[]
    /// Number of fields in the list.
    PROPERTY FieldCount AS LONG GET Fields:Length
    /// Construct a _FieldNames object.
    CONSTRUCTOR (aFields AS IList<STRING>)
        SELF:Fields := aFields:ToArray()
        RETURN
END CLASS

/// Helper class for DbJoin()
CLASS _JoinList
    /// Area number of destination Workarea.
    PUBLIC uiDestSel AS DWORD
    /// List of field areas and positions.
    PUBLIC Fields AS _JoinField[]
    /// Number of fields in the list.
    PUBLIC PROPERTY Count AS LONG GET Fields:Length
    /// Construct a _JoinList object.
    PUBLIC CONSTRUCTOR(nFields AS LONG)
        SELF:Fields := _JoinField[]{nFields}
        RETURN
END CLASS

/// Helper structure for DbJoin()
STRUCTURE _JoinField
    /// Source Workarea number.
    PUBLIC Area AS DWORD
    /// Source field position.
    PUBLIC Pos  AS DWORD
END STRUCTURE

/// Helper structure to store information for a list of RDD names for DbUseArea()
STRUCTURE _RddList
    /// List of RDD names.
    EXPORT atomRddName AS STRING[]
    /// Number of names in the list.
    PROPERTY uiRddCount AS DWORD GET (DWORD) atomRddName:Length

    /// Construct _RddList from class Tree.
    CONSTRUCTOR(oRDD AS Workarea)
        VAR names := List<STRING>{}
        VAR type  := oRDD:GetType()
        DO WHILE type != typeof(Workarea)
            VAR name := type:Name:ToUpper()
            // map names to VO compatible names
            IF name == "DBF"
                names:Add("CAVODBF")
            ELSE
                names:Add(name)
            ENDIF
            type := type:BaseType
        ENDDO
        names:Reverse()
        atomRddName := names:ToArray()
    /// Construct _RddList from list of names.
    CONSTRUCTOR(aNames AS STRING[])
        atomRddName := aNames
        RETURN

END STRUCTURE
END NAMESPACE