/* QBDEMgr.cc
  
   Description: Contains QdBEngineManager Base Class from which to subclass
                database engine specific manager classes (for the BDE
                and other future planned database engines).
  
                Contains class QBDEManager a subclass of dBEngineManager
                used to obtain lists of Aliases and Drivers available
                from the BDE and Create or Delete Aliases.


   Version:     1.028,  07/23/2012   Started: 9/5/2000                         
              
  
   Authors:     Martin Kay based on functions provided by Jim Sare
                and server specific details from Charles Miedzinski

   Copyright:   dataBased Intelligence, Inc, 2001 - 2012

   Rights:		 You have the right to freely include this code in any compiled
   				 dBASE Web application. You do not have the right to reproduce
                or publish this source code without the explicit written 
                permission of dataBased Intelligence, Inc., Vestal, NY

   Changes
   ---------------------------------------------------------------
   1.004        Fixed truncated string problem in GetDriverList()

                Modified CreateAlias() - ODBC case to only use
                property cODBCDSN instead of also requiring
                cDatabaseName

   1.005        Added #define DB_DB2_DSN
                Modified CreateAlias() - DB2 case to use DB_DB2_DSN

                Removed DBIDRIVERNAMELEN added in rev 1.004 which
                did not fix the problem.

   1.006        Implemented CreateAlias() case for Oracle s

   1.007        Added GetLogicalTypes method.

   1.008        Correctly masked all BDE function calls to obtain correct
                16-bit return value.

   1.009        Revised functions DBCSToSBCSZ and GetSBString to handle double-byte 
                characters

   1.010        Added method ValidateName() to class qBDEManager and corrected
                parameter types for various extern function declarations
                in order to fix some mystery GPFs in the BDE - M.Kay
                02/13/2003

   1.011        M.Kay - 02/28/2003 - In class QBDEManager, Added methods:
                              ReloadODBCInfo()
                              ConfigODBCDSN()
                              CreateODBCDSN()
                              CreateWizODBCDSN()
                              Added class ODBCException

   1.012        M.Kay - 03/19/2003 - Modified FoxPro and Visual FoxPro
                        cases in CreateODBCDSN() so that they set Null = "No".

                        This prevents the Fox ODBC driver from returning
                        errors when adding a new row and a field is left empty.

                        In CreateWizODBCDSN() added this.DBCSToSBCSZ() when
                        passing DSN Name to dB_SQLCreateDataSource.

   1.013        M.Kay - QAID: 5036 - 10/06/2004 - Added methods GetAutoInc()
                       and SetAutoInc() to class QBDEManager.

   1.014        M.Kay - QAID: 5085 - 11/11/2004 - Add methods GetFieldTypes()
                        and GetTableTypes() to class QBDEManager.
                        
   1.015        M.Kay - Removed debugging traps in GetFieldTypes() and GetTableTypes() 

   1.016			 D.Brooks - QAID: 5128 - 01/13/2005 - Add method 
   							GetODBCDriverFromDSN() to class QBDEManager.

   1.017			 D.Brooks - QAID: 4991 - 03/10/2005 - Add method
   							HasPrimaryKey() to class QBDEManager.                      

   1.018        M.Kay - QAID: 5274 - 03/15/2005 - Added class QBDETablesList

   1.019			 D.Brooks - QAID: 4991 - 06/15/2005 - Added class QBDEIndexList,
   							add GetPrimaryKey(), GetUniqueKey() to QBDEManger 

   1.020			 D.Brooks - QAID: 5433 - 07/20/2005 - Expanded QBDEIndexList to 
   							also retrieve the subset condition (szKeyCond)

   1.021        M.Kay - QAID: 5569 - 09/27/2005 - Fixed bug causing incorrect query name
                        from being created for a query generated by dropping
                        a .sql file onto design surface that queries dbase tables.
                        In class QBDETablesList, modified method GetNextTable by adding
                        cTable to list of local variables


   1.022        M.Kay - QAID: 5660 - 12/07/2005 - Added ability to retrieve list of drivers
                        from BDE config file to qBDEManager.GetDriverList().
                        Added support for a parameter value of "INCONFIG".


   1.023        M.Kay - QAID: 5695 - 01/09/2006 - Corrected extern return type
                        of all BDE function prototypes to use CUSHORT instead of
                        CINT or CUINT
                        This allowed us to remove bitand( ..., 0xFFFF) around
                        each BDE function call to correctly mask out the
                        high order 16 bits from the return values

   1.024        Dave Casler - QAID:5719 - 02/10/2006 
                        Modified function GetODBCDriverFromDSN() to add an
                        optional second parameter to select whether the 
                        search list includes only User DSNs or only System DSNs
                        or both, which was the old method and is now the 
                        default if no second parameter is provided.

   1.025        M.Kay - QAID: xxxx - Added methods GetConfigInfo() and 
                        GetDSNForAlias() to class qBDEManager.

   1.026        M.Kay - QAID: ? - 1/15/2011 - Added support for User BDE Aliases so they
                        can be saved to or deleted from the user's plus.ini
                        or application .ini file.

                        Added new methods SaveUserAlias(), DeleteUserAlias(),
                        GetUserAliasNumber(), and GetNextUserAliasNumber()

                        Added new property: lSaveToIni

                        Modified methods:
                           CreateAlias() to call SaveUserAlias() if lSaveToIni is True

                           DeleteAlias() to call DeleteUserAlias() if lSaveToIni is True



   1.027        M.Kay - QAID: ? - 7/22/2012 - Upgraded method GetAliasDesc() to first set
                        the sescfgmode2 property to cfgmAll so that a DBDesc structure
                        can be retrieved for any alias including User BDE Aliases.
                        Without this, sescfgmode2 would usually be set to cfgmNone
                        which may only allow aliases in the BDE config file to be
                        retrieved.  The szDBDesc wrapper object for a DBDesc structure
                        contains the szPhyName() method - which returns the directory
                        path assigned to a dBASE or Paradox BDE Alias.

   1.028        M.Kay - 7/23/2012 - Fixed GetNextUserAliasNumber() method so it returns
                        the very next available number without skipping one.
                        Previously, it was returning the next available number+1


   === Note ===         Add update comments above this line and change the 
                        version number and date at top of file to match.
*/


// Strings used to define new BDE Aliases
#define DB_SERVER_NAME    "SERVER NAME:"
#define DB_DATABASE_NAME  "DATABASE NAME:"
#define DB_USER_NAME      "USER NAME:"
#define DB_SQLQRYMODE     "SQLQRYMODE:"
#define DB_PATH           "PATH:"
#define DB_ODBC_DSN       "ODBC DSN:"
#define DB_DB2_DSN        "DB2 DSN:"


// BDE specific constants, structure sizes, structure field offsets, and
//   structure field sizes needed below.
#define DBIERR_NONE        0 // BDE Error Code indicating no error occurred
#define DBIERR_EOF    0x2202 // BDE Error Code indicating that cursor is at end of file

#define dbiNOLOCK          0 // BDE Locking Mode indicating that no locks should be used

#define DBIMAXPATHLEN    261 // BDE Max path length and size of DBIPATH type
#define DBIMAXTBLNAMELEN 260 // BDE Max table name length
#define DBINAMELEN        32 // BDE length of DBINAME type field
#define DBIMAXNAMELEN     31 // BDE Max name length   


#define DBDescSize       357 // Size of BDE DBDesc structure
#define DBDesc_szName      0          // Offset of szName field in DBDesc structure
#define DBDesc_szNameLen   DBINAMELEN // Len in bytes of field szName

#define CURPropsSize         427 // Size of BDE CURProps structure
#define CURProps_iRecBufSize 299 // Offset of CURProps iRecBufSize field

#define TBLBaseDescSize  574 // Size of BDE TBLBaseDesc structure
#define dB_szName          0 // Offset of szName field in TBLBaseDescSize structure
#define dB_szNameLen       DBIMAXTBLNAMELEN  // Len in bytes of field szName

#define DRVTypeLen       156 // Size of BDE DRVType structure

#define DBDescLen        357 // Size of BDE DBDesc structure

#define DRVCatLen          4  // Size of BDE DRVCat Enumerated value (integer)
#define BOOL16Len          2  // Size of 16 bit boolean field in bytes
#define UINT16Len          2


#define curGETEXTENDEDINFO  0x04050003	// Property Identifier for Get Extended Info
#define curAUTOINCVALUE     0xff050003 // Property Identifier for Auto Inc Value 
#define UINT32Len          4
#define MAXAUTOINCVALUE   4294967296  // 2^32

// Defines for BDE Session property: sesCFGMODE2
#define  sesCFGMODE2   0x0002000C

// Allowed values for sesCFGMODE2
#define cfgmNone       0x00  // Use sesCFGMODE (for backward compatibility).

#define cfgmVirtual    0x01  // Show only ODBC specific drivers or aliases,
                             // whether they're in the configuration file or not.

#define cfgmPersistent 0x02  // Show only aliases or drivers saved in the
                             // configuration file.

#define cfgmSession    0x04  // Show only aliases and drivers added to the
                             // current BDE session.

#define cfgmAll        0x07  // Show all aliases and drivers (shortcut for
                             // combining all settings).

// eOpenMode
#define dbiREADWRITE     0   // Read + Write   (Default)
#define dbiREADONLY      1   // Read only

// eConfigMode - CFGMode options
#define cfgPersistent    0   // Persistent only
#define cfgSession       1   // Session relative only
#define cfgAll           2   // All (system and single session)


//////////////////// Class QdBEngineManager ////////////////////////////////
//	Purpose:          Base Class from which to subclass database engine
//                   specific manager classes (for the BDE and other future
//                   planned database engines).
// Params: 				None  ///////////////////////////////////////////////
//
// Owner:				Martin Kay
///////////////////////////////////////////////////////////////////////////
CLASS QdBEngineManager
   PROTECT this.cParameters


   // Constructor Code
   this.baseClassName = "QdBEngineManager"  // Specifies this baseclass

   this.className     = this.baseClassName  // Specifies the current class

   this.EngineName    = ""       // Name of Database Engine

   this.cAlias        = ""       // Alias name

   this.cPath         = ""       // Path to database folder (for local tables)
                                 // or database file (if SQL database) pointed
                                 // to by alias

   this.cServerName   = ""       // Specifies the name of the database server
                                 // Needed for most SQL database engines

   this.cDatabaseName = ""       // Specifies the name of the database
                                 // Needed for many SQL database engines

   this.cDriverType   = "DBASE"  // Type of driver alias is for

   this.lPersistent   = True     // True if Alias should be stored
                                 // in config file otherwise false

   this.cUserName     = ""       // Default user name for login (if needed)

   this.cSqlQueryMode = "SERVER" // Specifies if SQL statements should be
                                 // executed on the SERVER or by a LOCAL SQL
                                 // engine (such as in the BDE)

   this.cODBCDSN      = ""       // Name of ODBC DSN (Data Source Name)
                                 // to use if cDriverType specifies an ODBC driver

   // Protected properties
   this.cParameters   = ""       // String to hold list of parameters
                                 // that may be set for alias


   // Methods

   ////// Method:		CreateAlias //////////////////////////////////////////////////
   ////// Purpose:	Attempts to create a database alias
   //////            for property cAlias. If successful, returns True.
   ////// Notes:     This is the base class stub version
   FUNCTION CreateAlias()
      RETURN True

   ////// Method:		DeleteAlias //////////////////////////////////////////////////
   ////// Purpose:	Attempts to delete database alias
   //////            specified by property cAlias.
   //////            If successful, returns True.
   ////// Notes:     This is the base class stub version
   FUNCTION DeleteAlias()
      RETURN True


   ////// Method:    GetAliasList /////////////////////////////////////////////////
   ////// Purpose:   Returns a single column array containing a list
   //////            of database aliases.
   //////            Returns an empty array if no aliases are found
   ////// Notes:     This is the base class stub version
   FUNCTION GetAliasList()
      // Dummy implementation for this method
      local aTableList

      aTableList = new Array()   // array to return

      RETURN aTableList


   ////// Method:    GetODBCAliasList /////////////////////////////////////////////
   ////// Purpose:   Returns a single column array containing a list
   //////            of ODBC database aliases.
   //////            Returns an empty array if no aliases are found
   ////// Notes:     This is the base class stub version
   FUNCTION GetODBCAliasList()
      // Dummy implementation for this method
      local aTableList

      aTableList = new Array()   // array to return

      RETURN aTableList


   ////// Method:    GetODBCDriverList ////////////////////////////////////////////
   ////// Purpose:   Returns a single column array containing a list
   //////            of available ODBC database driver names.
   //////            Returns an empty array if no drivers are found
   ////// Notes:     This is the base class stub version
   FUNCTION GetODBCDriverList()
      // Dummy implementation for this method
      local aTableList

      aTableList = new Array()   // array to return

      RETURN aTableList


   ////// Method:    GetODBCDSNList ////////////////////////////////////////////
   ////// Purpose:   Returns a single column array containing a list
   //////            of ODBC Data Source Names that currently exist
   //////            for the specified ODBC Driver (cODBCDriver).
   //////            Returns an empty array if no DSN's are found
   ////// Notes:     This is the base class stub version
   FUNCTION GetODBCDSNList( cODBCDriver )
      local aDSNList

      aDSNList = new Array()   // array to return

      RETURN aDSNList


   ////// Method:    GetDriverList ////////////////////////////////////////////
   ////// Purpose:   Returns a single column array containing a list
   //////            of available database driver names.
   //////            Returns an empty array if no drivers are found
   ////// Notes:     This is the base class stub version
   FUNCTION GetDriverList()
      // Dummy implementation for this method
      local aTableList

      aTableList = new Array()   // array to return

      RETURN aTableList


   ////// Method:    GetAliasDesc ////////////////////////////////////////////
   ////// Purpose:   Returns an object reference to an object
   //////            that contains additional information about
   //////            the specified Alias (cAlias), such as:
   //////               the driver name the alias is associated with
   //////               the physical path represented by the alias (if appropriate)
   ////// Notes:     This is the base class stub version
   FUNCTION GetAliasDesc( cAlias )
      RETURN null


   ////// Method:    GetDatabaseTablesList ////////////////////////////////////////////
   ////// Purpose:   Returns a single column array containing a list
   //////            of table names that exist for the database (or Alias)
   //////            specified by the supplied handle (nDbHandle).
   //////            Returns an empty array if no tables are found.
   ////// Notes:     This is the base class stub version
   FUNCTION GetDataBaseTablesList( nDbHandle )
      // Dummy implementation for this method
      local aTableList

      aTableList = new Array()   // array to return

      RETURN aTableList


   ////// Method:		DoesAliasExist  //////////////////////////////////////////////////
   ////// Purpose		Tests if supplied alias name (cAlias)
   //////            is already an alias
   ////// Parameter: cAlias - string containing an alias name to test

   FUNCTION DoesAliasExist( cAlias )
      RETURN True


   ////// Method:    IsODBCDriver ////////////////////////////////////////////
   ////// Purpose:   Returns True if the specified driver name (cDriver)
   //////            is an ODBC driver name, otherwise, returns False.
   ////// Notes:     This is the base class stub version
   FUNCTION IsODBCDriver( cDriver )
      RETURN True

ENDCLASS   // DataBaseAlias



//////////////////// Class QBDEManager ////////////////////////////////
//	Purpose:          A subclass of QdBEngineManager used to obtain lists
//                   of Aliases and Drivers available from the BDE
//                   and to Create or Delete Aliases.
// Params: 				None  ///////////////////////////////////////////////
//
// Owner:				Martin Kay
///////////////////////////////////////////////////////////////////////////
CLASS QBDEManager OF QdBEngineManager CUSTOM

   this.className     = "QBDEManager"  // Specifies the current class

   this.EngineName    = "BDE"          // Name of Database Engine


   // QAID: ? - M.Kay, dataBI Inc. - 01/15/2011
   // Add support for storing User BDE Aliases in user's ini file instead
   //  of BDE config file
   this.lSaveToIni    = False      // Default to not saving in ini file
   if type("_app.useUACPaths") = "L"
      if _app.useUACPaths
         this.lPersistent = False  // Create Session BDE Alias (not stored in BDE config file)
         this.lSaveToIni  = True   // Store Settings for alias in user's .ini file (_app.inifile)
      endif
   endif
   // End QAID: ?


   ////// Method:		CreateAlias //////////////////////////////////////////////////
   ////// Purpose:	Attempts to create a BDE alias
   //////            for property cAlias.
   //////            If successful, returns True, otherwise, False.

   ////// Notes:     See case statements below for a description
   //////            of which properties should be set for each kind
   //////            of database driver.
   FUNCTION CreateAlias
      // Create a BDE alias
      // Returns: True if successful/False if unsuccessful
      local lcDriverType, lOk, lnBDEError

      if type("dB_DbiAddAlias") # "FP"
         extern CUSHORT dB_DbiAddAlias(CHANDLE, CPTR, CPTR, CPTR, CLOGICAL) IDAPI32;
                From "DbiAddAlias"
      endIf

      lOk          = False
      lnBDEError   = -1

      if not empty(this.cDriverType)
         lcDriverType = upper(ltrim(rtrim(this.cDriverType)))

         do case
         case lcDriverType $ "DBASE|PARADOX|FOXPRO|ASCIIDRV"
            // For standard driver type alias (DBASE, PARADOX, FOXPRO, or ASCIIDRV):
            // Must supply:
            //    cDriverType (ex: DBASE)
            //    cPath containing path to folder containing tables
            if not empty(this.cPath)
               this.cParameters = DB_PATH + this.cPath
               lOk = True
            endif

         case lcDriverType $ "MSACCESS"
            // Must supply:
            //    cDriverType = 'MSACCESS'
            //    cPath must contain full path to Access database file
            //      including filename and extension

            if not empty(this.cPath)
               this.cParameters = DB_DATABASE_NAME + this.cPath
               lOk = True
            endif
         
         case lcDriverType == "INTRBASE"
            // Must supply for Interbase 5.x:
            //    cDriverType   = "INTRBASE"
            //    cPath         = <drive>:<\path\Database.gdb>
            //    cServerName   = <Hostname> (if remote server else "")
            //    cUserName     = default user name for login or ""
            //    cSqlQueryMode = "SERVER", "LOCAL", or "" (default is "SERVER"

            // Must supply all of the above for Interbase 6.x plus (maybe):
            //    cDatabaseName = the database file name (ex: database.gdb)

            // Interbase6 SQL Links:
            // USER NAME     <string>
            // SERVER NAME   <Hostname:<drive>:\path\Database.gdb> // hostname required for
            //    remote connection
            // SQLQRYMODE    <SERVER or LOCAL>
            // DATABASE NAME <string>   ?? Is this available for Interbase 6?

            if not empty(this.cPath)
               this.cParameters = DB_SERVER_NAME + iif( empty(this.cServerName), "", this.cServerName)+":" ;
                                                 + this.cPath     + ";" ;
                                 +DB_USER_NAME   + this.cUserName + ";" ;
                                 +DB_SQLQRYMODE  + this.cSqlQueryMode

               lOk = True
            endif

         case lcDriverType == "ORACLE"
            // May need to set OBJECT MODE = FALSE for Oracle 8 so we don't
            // try to use Oracle Objects
            // OBJECT MODE is not used by Oracle 7
            // When adding new Oracle alias, a USER NAME="MyName" is set
            // May need to clear this or override default USER NAME
            // Uses SERVER NAME to specify name of oracle server.
            //    Default = ORA_SERVER
            // We very likely need to specify a *real* SERVER NAME
            // How do we specify which oracle database we want to work with?

            if (not empty(this.cServerName))
               this.cParameters = DB_SERVER_NAME   + this.cServerName   + ";" ;
                                 +DB_USER_NAME     + this.cUserName     + ";" ;
                                 +DB_SQLQRYMODE    + this.cSqlQueryMode

               lOk = True

            endif
            

         case lcDriverType == "MSSQL"
            // MS SQL Server SQL Links:
            //   SERVER NAME   <string>
            //   DATABASE NAME <string>

            if (not empty(this.cServerName)) and (not empty(this.cDatabaseName))
               this.cParameters = DB_SERVER_NAME   + this.cServerName   + ";" ;
                                 +DB_DATABASE_NAME + this.cDatabaseName + ";" ;
                                 +DB_USER_NAME     + this.cUserName     + ";" ;
                                 +DB_SQLQRYMODE    + this.cSqlQueryMode

               lOk = True
            endif


         case lcDriverType == "SYBASE"
            // Assume same settings as for MSSQL
            if (not empty(this.cServerName)) and (not empty(this.cDatabaseName))
               this.cParameters = DB_SERVER_NAME   + this.cServerName   + ";" ;
                                 +DB_DATABASE_NAME + this.cDatabaseName + ";" ;
                                 +DB_USER_NAME     + this.cUserName     + ";" ;
                                 +DB_SQLQRYMODE    + this.cSqlQueryMode

               lOk = True
            endif

         case lcDriverType == "DB2"
            // Updated to use correct DB2 settings
            if not empty(this.cDatabaseName)
               this.cParameters = DB_DB2_DSN + this.cDatabaseName
               lOk = True
            endif

         case lcDriverType == "INFORMIX"
            // Informix - SQL Links
            //   SERVER NAME   <string>
            //   DATABASE NAME <string>
            if (not empty(this.cServerName)) and (not empty(this.cDatabaseName))
               this.cParameters = DB_SERVER_NAME   + this.cServerName   + ";" ;
                                 +DB_DATABASE_NAME + this.cDatabaseName + ";" ;
                                 +DB_USER_NAME     + this.cUserName     + ";" ;
                                 +DB_SQLQRYMODE    + this.cSqlQueryMode

               lOk = True
            endif

         otherwise 
            // For ODBC alias:
            // Must supply cDriverType containing name of ODBC driver
            // and cParameters containing:
            //     "DATABASE NAME:"+cAlias
            //     "ODBC DSN:"+cODBCDSN  (ODBC Data Source Name defined for alias)


            // Check if cDriverType matches one of the available ODBC drivers
            //  available to the BDE.
            if this.IsODBCDriver( lcDriverType )

               if not empty(this.cODBCDSN)
                  this.cParameters = DB_ODBC_DSN + this.cODBCDSN
                  lOk = True

                  // MS SQL Server ODBC: SQL Server
                  // USER NAME     <string>
                  // SERVER NAME   <string>
                  // SQLQRYMODE    <server or local>
                  // ODBC DSN      <string> // must match DSN setup
                  // DATABASE NAME <string>


                  // Interbase6 ODBC:
                  // The current Visigenic Interbase 5 driver will not work.
                  // Checking into new driver availability.
               endif

            endif
         endcase


         if lOk
         
            lnBDEError := dB_DbiAddAlias(null,;
                                        this.DBCSToSBCSZ(this.cAlias),;
                                        this.DBCSToSBCSZ(this.cDriverType),;
                                        this.DBCSToSBCSZ(this.cParameters),;
                                        this.lPersistent)
            lOk := (lnBDEError = 0)

            // QAID: ? - M.Kay, dataBI Inc. - 01/15/2011
            if lOk
               if this.lSaveToIni
                  this.SaveUserAlias()  // Save current alias settings to _app.iniFile
               endif
            endif
            // End QAID: ?

         endif
      endif

      RETURN lOk



   ////// Method:		DeleteAlias //////////////////////////////////////////////////
   ////// Purpose:	Attempts to delete database alias
   //////            specified by property cAlias.
   //////            If successful, returns True, otherwise, False.
   FUNCTION DeleteAlias()
      // Delete a BDE alias
      // Returns: True if successful/False if unsuccessful
      local lOk, lnBDEError, ;
            nHoldSessionHandle, nSessionHandle, nHoldPropValue, nHoldPropLen, ;
            nSessionProperty, nPropValue, nSetPropResult

      if type("dB_DbiDeleteAlias") # "FP"
         extern CUSHORT dB_DbiDeleteAlias(CHANDLE, CPTR) IDAPI32;
                From "DbiDeleteAlias"
      endIf

      if type("dB_DbiSetCurrSession") # "FP"
         extern CUSHORT dB_DbiSetCurrSession( CHANDLE ) IDAPI32 ;
            from "DbiSetCurrSession"
      endif

      if type("dB_DbiGetCurrSession") # "FP"
         extern CUSHORT dB_DbiGetCurrSession( CPTR CUINT ) IDAPI32 ;
            from "DbiGetCurrSession"
      endif

      if type("dB_DbiSetProp") # "FP"
         extern CUSHORT dB_DbiSetProp( CHANDLE, CUINT, CUINT ) IDAPI32 ;
            from "DbiSetProp"
      endif

      if type("dB_DbiGetProp") # "FP"
         extern CUSHORT dB_DbiGetProp( CHANDLE, CUINT, CPTR CUINT, CUSHORT, CPTR CUSHORT ) IDAPI32 ;
            from "DbiGetProp"
      endif


      lOk = False
      if not empty(this.cAlias)

         //////////////////////////////////////////////////////////////////////////
         // Get handle for current BDE session 
         nHoldSessionHandle = 0
         lnBDEResult = dB_DbiGetCurrSession( nHoldSessionHandle )


         if lnBDEResult == DBIERR_NONE

            nHoldPropValue = 0
            nHoldPropLen   = 4

            nSessionHandle = _app.session.handle

            // Must set current session before doing anything to it
            lnBDEResult = dB_DbiSetCurrSession( nSessionHandle )

            if lnBDEResult == DBIERR_NONE
               nSessionProperty = sesCFGMODE2

               // Get the current value for sesCFGMODE2 so we can reset it
               // back before this method returns
               lnBDEResult = dB_DbiGetProp( nSessionHandle, nSessionProperty, ;
                                                  nHoldPropValue, 4, nHoldPropLen )


               if this.lPersistent
                  nPropValue = cfgmPersistent // Delete a persistent BDE Alias (in BDE config file)
               else
                  nPropValue = cfgmSession  // Delete a Session (non-persistent) BDE Alias
               endif
  
               nSetPropResult = dB_DbiSetProp( nSessionHandle, nSessionProperty, nPropValue)
         //////////////////////////////////////////////////////////////////////////

               lnBDEError = dB_DbiDeleteAlias(null, this.DBCSToSBCSZ(this.cAlias))
               lOk := (lnBDEError = 0)

               // QAID: ? - M.Kay, dataBI Inc. - 01/15/2011
               if lOk
                  if this.lSaveToIni
                     this.DeleteUserAlias()  // Delete current alias settings from _app.iniFile
                  endif
               endif
               // End QAID: ?


         //////////////////////////////////////////////////////////////////////////
               // Reset sesCFGMODE2 back to original value
               nSetPropResult = dB_DbiSetProp( nSessionHandle, nSessionProperty, nHoldPropValue)

            endif

            // Set current BDE session back to original session
            lnBDEResult = dB_DbiSetCurrSession( nHoldSessionHandle )

         endif
         //////////////////////////////////////////////////////////////////////////

      endif

      RETURN lOk


    ////// Method:		DBCSToSBCSZ  //////////////////////////////////////////////////
    ////// Purpose		Converts a double byte character string
    //////              to a single byte character string.
    ////// Parameter:   c - double byte string to be converted
    ////// Notes:       This is needed when passing native dBASE strings
    //////              to BDE API functions.
    //////  Modified   Rick Fillman Jan 2002 for Asian.
    FUNCTION DBCSToSBCSZ(c)
      // Create a string of single-byte characters from
      // a double byte (Unicode) string
      LOCAL cTemp, x

      cTemp = Replicate(Chr(0), ((Len(c) + 1) / 2) + ((Len(c) + 1) % 2))

      For x = 1 To Len(c)
         cK = SubStr( c, x, 2 ) // grab two *bytes*
         //  check to see if this is double-byte character
         if isDBCS( cK )
            // It is a double-byte character.
            //   push out TWO bytes - Lead-byte and Trail-Byte 
            cTemp.SetByte(x - 1, htoi( subs( itoh( asc( cK )),1,2)))
            cTemp.SetByte(x,     htoi( subs( itoh( asc( cK )),3,2)))
            x++
         else
            cTemp.SetByte(x - 1, Asc(SubStr(c, x)))
         endif
      EndFor
      RETURN cTemp



   ////// Method:    GetAliasList /////////////////////////////////////////////////
   ////// Purpose:   Returns a single column array containing a list
   //////            of available BDE aliases.
   //////            Returns an empty array if no aliases are found

   ////// Parameter: cAliasFilter - can be "ALL", "ODBC", "INCONFIG", or null
   //////               ALL  = return all aliases, persistent and not.
   //////               ODBC = return only ODBC aliases
   //////               INCONFIG = returns a list of persistent Aliases, ones
   //////                          which are in the BDE config file.

   //////               null = defaults to "ALL"
   FUNCTION GetAliasList( cAliasFilter )
      local aTableList, nBDEResult, nCursorHandle, cBuffer, nRecBufSize, ;
            nRecBufSizeHigh, nRecBufSizeLow, nSessionHandle, nSessionProperty,;
            nPropValue, nSetPropResult, nHoldSessionHandle, nHoldPropValue, ;
            nHoldPropLen

      if argcount() < 1
         cAliasFilter = "ALL"
      endif


      // Prototypes for needed BDE API functions
      if type("dB_DbiOpenDatabaseList") # "FP"
         extern CUSHORT dB_DbiOpenDatabaseList( CPTR CLONG ) IDAPI32 ;
            from "DbiOpenDatabaseList"
      endif                                                                        

      if type("dB_DbiGetCursorProps") # "FP"
         extern CUSHORT dB_DbiGetCursorProps( CHANDLE, CPTR ) IDAPI32 ;
            from "DbiGetCursorProps"
      endif

      if type("dB_DbiGetNextRecord") # "FP"                                      
         // KayTest
         //  extern CUSHORT dB_DbiGetNextRecord( CHANDLE, CINT, CPTR, CPTR) IDAPI32
         extern CUSHORT dB_DbiGetNextRecord( CHANDLE, CSHORT, CPTR, CPTR) IDAPI32 ;
            from "DbiGetNextRecord"
      endif

      if type("dB_DbiCloseCursor") # "FP"
         //extern CUSHORT dB_DbiCloseCursor( CHANDLE ) IDAPI32
         extern CUSHORT dB_DbiCloseCursor( CPTR CUINT ) IDAPI32 ;
            from "DbiCloseCursor"
      endif                                                                        

      if type("dB_DbiSetCurrSession") # "FP"
         extern CUSHORT dB_DbiSetCurrSession( CHANDLE ) IDAPI32 ;
            from "DbiSetCurrSession"
      endif

      if type("dB_DbiGetCurrSession") # "FP"
         extern CUSHORT dB_DbiGetCurrSession( CPTR CUINT ) IDAPI32 ;
            from "DbiGetCurrSession"
      endif

      if type("dB_DbiSetProp") # "FP"
         extern CUSHORT dB_DbiSetProp( CHANDLE, CUINT, CUINT ) IDAPI32 ;
            from "DbiSetProp"
      endif

      if type("dB_DbiGetProp") # "FP"
         extern CUSHORT dB_DbiGetProp( CHANDLE, CUINT, CPTR CUINT, CUSHORT, CPTR CUSHORT ) IDAPI32 ;
            from "DbiGetProp"
      endif


      // Initialize variables
      aAliasList = new Array()   // array to return

      nBDEResult     = 0  // Receives error code from BDE calls 
      nSetPropResult = 0  // Receives return value from DbiSetProp()
      nCursorHandle  = -1 // BDE Cursor Handle returned by DbiOpenDatabaseList containing list of Aliases

      nHoldSessionHandle = 0  // Used to hold current BDE Session handle
                              // so we can restore it before method returns

      nHoldPropValue = 0  // Used to hold current setting for sesCFGMODE2
                          // so we can restore it befor method returns

      nHoldPropLen   = 0  // Used to hold length of return value for sesCFGMODE2

      // Get handle for current BDE session 
      nBDEResult = dB_DbiGetCurrSession( nHoldSessionHandle )

      if nBDEResult == DBIERR_NONE

         nSessionHandle = _app.session.handle

         // Must set current session before doing anything to it
         nBDEResult = dB_DbiSetCurrSession( nSessionHandle )

         if nBDEResult == DBIERR_NONE
            nSessionProperty = sesCFGMODE2

            // Get the current value for sesCFGMODE2 so we can reset it
            // back before this method returns
            nBDEResult = dB_DbiGetProp( nSessionHandle, nSessionProperty, ;
                                               nHoldPropValue, 4, nHoldPropLen )

            // Set sesCFGMODE2 to either the default or specified value
            // (based on parameter cAliasFilter)
            nPropValue       = cfgmAll     // Get all Aliases

            cAliasFilter = upper(ltrim(rtrim(cAliasFilter)))

            do case
            case cAliasFilter == "ODBC"
               nPropValue = cfgmVirtual    // Only get ODBC Aliases

            case cAliasFilter == "INCONFIG"
               nPropValue = cfgmPersistent // Only get Aliases in BDE config file
            endcase

            // M.Kay - 02/27/2003
            if cAliasFilter == "ALL" or cAliasFilter == "ODBC"
               this.ReloadODBCInfo( true )
            endif

            nSetPropResult = dB_DbiSetProp( nSessionHandle, nSessionProperty, nPropValue)


            // Call BDE to get a list of BDE Aliases
            nBDEResult = dB_DbiOpenDatabaseList( nCursorHandle )

            if nBDEResult == DBIERR_NONE
               // Call DbiGetCursorProps() in order to get the record length
               // for the BDE cursor referenced via nCursorHandle
               cCursorPropsBuffer = replicate( chr(0), int(CURPropsSize/2)+1)

               nBDEResult = dB_DbiGetCursorProps( nCursorHandle, cCursorPropsBuffer)

               if nBDEResult == DBIERR_NONE
                  // Get value from iRecBufSize field.
                  nRecBufSizeLow  = cCursorPropsBuffer.getByte(CURProps_iRecBufSize)
                  nRecBufSizeHigh = cCursorPropsBuffer.getByte(CURProps_iRecBufSize+1)

                  nRecBufSize = bitlshift(nRecBufSizeHigh,8) + nRecBufSizeLow

                  // Allocate buffer to receive data from DbiGetNextRecord
                  //    and init to nulls
                  // Note: Since dB2K strings contain 2 bytes per character,
                  //    we need only allocate structure size divided by 2.
                  cBuffer = replicate( chr(0), int( nRecBufSize/2)+1)
      

                  // Iterate through the BDE cursor
                  do while dB_DbiGetNextRecord( nCursorHandle, dbiNOLOCK, cBuffer, 0) # DBIERR_EOF
                     // Retrieve field szName (containing the BDE Alias name)
                     // from cBuffer and add it to the array.
                     aAliasList.Add(this.GetSBString(cBuffer, DBDesc_szName, DBDesc_szNameLen))
                  enddo

               endif

               // Close the BDE Cursor we opened
               nBDEResult = dB_DbiCloseCursor( nCursorHandle )

            endif

            // Reset sesCFGMODE2 back to original value
            nSetPropResult = dB_DbiSetProp( nSessionHandle, nSessionProperty, nHoldPropValue)

         endif

         // Set current BDE session back to original session
         nBDEResult = dB_DbiSetCurrSession( nHoldSessionHandle )

      endif

      RETURN aAliasList



   ////// Method:    GetDatabaseTablesList ////////////////////////////////////////////
   ////// Purpose:   Returns a single column array containing a list
   //////            of table names that exist for the database (or Alias)
   //////            specified by the supplied handle (nDbHandle).
   //////            Returns an empty array if no tables are found.
   FUNCTION GetDataBaseTablesList( nDbHandle )
      local aTableList, nBDEResult, nCursorHandle, cBuffer, cWildCard


      // Prototypes for needed BDE API functions
      if type("dB_DbiOpenTableList") # "FP"
         extern CUSHORT dB_DbiOpenTableList( CHANDLE, CLOGICAL, CLOGICAL, CSTRING, CPTR CUINT) IDAPI32 ;
            from "DbiOpenTableList"
      endif                                                                        

      if type("dB_DbiGetNextRecordTableList") # "FP"                                      
         extern CUSHORT dB_DbiGetNextRecordTableList( CHANDLE, CSHORT, CPTR, CPTR) IDAPI32 ;
            from "DbiGetNextRecord"
      endif

      if type("dB_DbiCloseCursor") # "FP"
         extern CUSHORT dB_DbiCloseCursor( CPTR CUINT ) IDAPI32 ;
            from "DbiCloseCursor"
      endif                                                                        


      // Initialize variables
      aTableList = new Array()   // array to return

      nBDEResult    = 0 // Receives error code from BDE calls 
      nCursorHandle = 0 // BDE Cursor Handle returned by DbiOpenTableList containing list of tables

      cWildCard = "*.*"+chr(0)+chr(0)    // Specifies table filter to use of *.* all tables
      // Allocate buffer to receive data from DbiGetNextRecord
      //    and init to nulls
      // Note: Since dB2K strings contain 2 bytes per character,
      //    we need only allocate structure size divided by 2.
      cBuffer = replicate( chr(0), int(TBLBaseDescSize/2)+1)


      // Call BDE to get a list of tables contained in the database
      // referenced by nDbHandle.

      nBDEResult = dB_DbiOpenTableList( nDbHandle, False, False, cWildCard, nCursorHandle)
//msgbox("GetDatabaseTablesList Cursor Handle=>"+str(nCursorHandle)+"<=")

      // If no error occurred, iterate through the BDE cursor
      if nBDEResult = DBIERR_NONE
         //if nCursorHandle <> 0
            do while dB_DbiGetNextRecordTableList( nCursorHandle, dbiNOLOCK, cBuffer, 0) # DBIERR_EOF
               // Retrieve field szName (containing the table name)
               // from cBuffer and add the table name to the array.
               aTableList.Add(this.GetSBString(cBuffer, dB_szName, dB_szNameLen))
            enddo
         //else
         //   msgbox("Error - In GetDatabaseTablesList(), nCursorHandle=>"+str(nCursorHandle)+"<=")
         //endif

         // Close the BDE Cursor we opened
         nBDEResult = dB_DbiCloseCursor( nCursorHandle )
      endif

      return aTableList

   //////  Method:    DBCSisLeadByte  ////////////////////////////////////////////////
   //////  Purpose:   lead-byte ranges for known common Asian code pages
   //////  Parameters:  numVal - the decimal value of the incoming byte.
   ////// 
   //////  From:      Rick Fillman Jan 2002 for Asian.
   FUNCTION DBCSisLeadByte( nByteD )
      local retVal
      retVal = false
      if '932' $ _app.charset // Japanese Shift-JIS
                              // lead-byte range (non-Contiguous) 
                              //  81h - 9Fh and then E0h - FCh
           retVal = ( nByteD >= 129 .and. nByteD <= 159 ) .or.; 
                    ( nByteD >= 224 .and. nByteD <= 239 )
      elseif '936' $ _app.charset //  Simplified Chinese
                              //  lead byte range A1h - FEh
           retVal = ( nByteD >= 161 .and. nByteD <= 254 )
      elseif '949' $ _app.charset // Korean
                              // leadByte range 81h - FEh
           retVal = ( nByteD >= 129 .and. nByteD <= 254 )
      elseif '950' $ _app.charset // Traditional Chinese
                              // lead byte range A1h - FEh       
           retVal = ( nByteD >= 161 .and. nByteD <= 254 )
      endif
   Return retVal

   //////  Method:    GetSBString  ////////////////////////////////////////////////
   //////  Purpose:   Given a single byte string, converts the specified substring
   //////             into a double byte string
   //////  Parameters: xSBBUFF - Single byte string
   //////              nIndex  - Starting position of substring to convert
   //////              nLen    - Length in bytes of substring.
   //////  Usage:     Used to read character string structure fields
   //////             out of a string containing the structure.
   //////  From:      Jim Sare
   //////  Modified   Rick Fillman Jan 2002 for Asian.
   FUNCTION GetSBString(xSBBUFF, nIndex, nLen)
	   LOCAL c, nByte1, i, bIsLeadByte
      c = ""
      bIsLeadByte = false
      For i = nIndex To nIndex + (nLen - 1)
         If xSBBUFF.GetByte(i) = 0
            Exit
         EndIf
         nByte1 = xSBBUFF.GetByte(i)
         #ifdef __asian__
           // speculating about leadBytes 
           //   serves no purpose on US or European machines.
            bIsLeadByte = this.DBCSisLeadByte( nByte1 ) 
           // end Asian only block
         #endif
         if bIsLeadByte
            // we have a lead byte of a DBCS pair
            // must grab the trail byte, and then 
            //   then convert *the*whole*thing* to a character
            i++
            c += chr( htoi( itoh( nByte1 ) + itoh( xSBBUFF.GetByte(i)) ))
         else
            c += Chr( nByte1 )
         endif
      EndFor
      RETURN c



   ////// Method:    GetODBCDriverList ////////////////////////////////////////////
   ////// Purpose:   Returns a single column array containing a list
   //////            of available ODBC database driver names.
   //////            Returns an empty array if no drivers are found
   FUNCTION GetODBCDriverList()
      RETURN this.GetDriverList( "ODBC" )


   ////// Method:    GetODBCAliasList /////////////////////////////////////////////
   ////// Purpose:   Returns a single column array containing a list
   //////            of ODBC database aliases.
   //////            Returns an empty array if no aliases are found
   FUNCTION GetODBCAliasList()
      RETURN this.GetAliasList( "ODBC" )


   ////// Method:    GetDriverList ////////////////////////////////////////////
   ////// Purpose:   Returns a single column array containing a list
   //////            of available database driver names.
   //////            Returns an empty array if no drivers are found
   //////
   ////// Parameter: cDriverFilter - can be "ALL", "ODBC", "INCONFIG", or null
   //////               ALL  = return all drivers, persistent and not.
   //////               ODBC = return only ODBC drivers
   //////               INCONFIG = return a list of persistent drivers, ones
   //////                          which are in the BDE config file.
   //////
   //////               null = defaults to "ALL"
   ////// Notes:     

  FUNCTION GetDriverList( cDriverFilter )

      local aDriverList, nBDEResult, nCursorHandle, nSessionProperty, ;
            nPropValue, nHoldSessionHandle, nHoldSessionHandle, nHoldPropValue, ;
            nHoldPropLen

      if argcount() < 1
         cDriverFilter = "ALL"
      endif

      if type("dB_DbiOpenDriverList") # "FP"
         extern CUSHORT dB_DbiOpenDriverList( CPTR CUINT ) IDAPI32 ;
            from "DbiOpenDriverList"
      endif

      if type("dB_DbiGetNextRecord") # "FP"                                      
         extern CUSHORT dB_DbiGetNextRecord( CHANDLE, CSHORT, CPTR, CPTR) IDAPI32 ;
            from "DbiGetNextRecord"
      endif

      if type("dB_DbiCloseCursor") # "FP"
         extern CUSHORT dB_DbiCloseCursor( CPTR CUINT ) IDAPI32 ;
            from "DbiCloseCursor"
      endif

      if type("dB_DbiSetCurrSession") # "FP"
         extern CUSHORT dB_DbiSetCurrSession( CHANDLE ) IDAPI32 ;
            from "DbiSetCurrSession"
      endif

      if type("dB_DbiGetCurrSession") # "FP"
         extern CUSHORT dB_DbiGetCurrSession( CPTR CUINT ) IDAPI32 ;
            from "DbiGetCurrSession"
      endif

      if type("dB_DbiSetProp") # "FP"
         extern CUSHORT dB_DbiSetProp( CHANDLE, CUINT, CUINT ) IDAPI32 ;
            from "DbiSetProp"
      endif

      if type("dB_DbiGetProp") # "FP"
         extern CUSHORT dB_DbiGetProp( CHANDLE, CUINT, CPTR CUINT, CUSHORT, CPTR CUSHORT ) IDAPI32 ;
            from "DbiGetProp"
      endif

      nBDEResult     = 0  // Receives error code from BDE calls 
      nSetPropResult = 0  // Receives return value from DbiSetProp()
      nCursorHandle  = -1 // BDE Cursor Handle returned by DbiOpenDatabaseList containing list of Aliases

      nHoldSessionHandle = 0  // Used to hold current BDE Session handle
                              // so we can restore it before method returns

      nHoldPropValue = 0  // Used to hold current setting for sesCFGMODE2
                          // so we can restore it before method returns

      nHoldPropLen   = 0  // Used to hold length of return value for sesCFGMODE2


      // Initialize variables
      aDriverList = new Array()   // array to return

      // Get handle for current BDE session 
      nBDEResult = dB_DbiGetCurrSession( nHoldSessionHandle )

      if nBDEResult == DBIERR_NONE
         nSessionHandle = _app.session.handle

         // Must set current session before doing anything to it
         nBDEResult = dB_DbiSetCurrSession( nSessionHandle )
         if nBDEResult == DBIERR_NONE
            nSessionProperty = sesCFGMODE2

            // Get the current value for sesCFGMODE2 so we can reset it
            // back before this method returns
            nBDEResult = dB_DbiGetProp( nSessionHandle, nSessionProperty, ;
                                        nHoldPropValue, 4, nHoldPropLen )

            // Set sesCFGMODE2 to either the default or specified value
            // (based on parameter cDriverFilter)
            nPropValue       = cfgmAll

            cDriverFilter = upper(ltrim(rtrim(cDriverFilter)))

            if cDriverFilter == "ODBC"
               nPropValue = cfgmVirtual

            // QAID: 5660 - M.Kay, dBI Inc. - 12/07/2005
            elseif cDriverFilter == "INCONFIG"
               nPropValue = cfgmPersistent
            // End QAID: 5660
            endif

            // M.Kay - 02/27/2003
            if cDriverFilter == "ALL" or cDriverFilter == "ODBC"
               this.ReloadODBCInfo( true )
            endif


            nSetPropResult = dB_DbiSetProp( nSessionHandle, nSessionProperty, nPropValue)

                                                                              

            // Call BDE to get a list of BDE Aliases
            nBDEResult = dB_DbiOpenDriverList( nCursorHandle )
      
            if nBDEResult == DBIERR_NONE
               // Allocate buffer to receive data from DbiGetNextRecord
               //    and init to nulls
               // Note: Since dB2K strings contain 2 bytes per character,
               //    we need only allocate structure size divided by 2.
               cBuffer = replicate( chr(0), int( DBINAMELEN /2)+1)

               // Iterate through the BDE cursor
               do while dB_DbiGetNextRecord( nCursorHandle, dbiNOLOCK, cBuffer, 0) # DBIERR_EOF
                  // Retrieve field containing the Driver name
                  // from cBuffer and add it to the array.
                  aDriverList.Add(this.GetSBString(cBuffer, 0, DBINAMELEN ))
               enddo

               // Close the BDE Cursor we opened
               nBDEResult = dB_DbiCloseCursor( nCursorHandle )
            endif

            // Reset sesCFGMODE2 back to original value
            nSetPropResult = dB_DbiSetProp( nSessionHandle, nSessionProperty, nHoldPropValue)

         endif

         // Set current BDE session back to how we found it
         nBDEResult = dB_DbiSetCurrSession( nHoldSessionHandle )

      endif

      RETURN aDriverList




   //////  Method:    GetAliasDesc() ///////////////////////////
   //////  Purpose:   Returns object reference to an sDBDesc object
   //////             containing details about the specified Alias (cDBAlias).
   //////
   //////             The sDBDesc object's methods can then be used
   //////             to read the data from the DBDesc structure
   //////             contained within it.
   //////
   //////             If error occurs retrieving DBDesc structure, a null
   //////             value is returned.
   FUNCTION GetAliasDesc( cDBAlias )
      local cBuffer, nBDEResult, oAliasDesc, cDBAliasSB, nSessionHandle, nSessionProperty,;
            nPropValue, nSetPropResult, nHoldSessionHandle, nHoldPropValue, ;
            nHoldPropLen


      if type("dB_DbiGetDatabaseDesc") # "FP"
         extern CUSHORT dB_DbiGetDatabaseDesc( CPTR, CPTR ) IDAPI32 ;
            from "DbiGetDatabaseDesc"
      endif


      // QAID: ? - M.Kay, dBase, LLC - 07/22/2012
      // Add code to set the sessions, cfgmode2 property to cfgmAll before
      // attempting to retrieve DBDesc structure in order to ensure that all
      // BDE Aliases (virtual, persistent, and session aliases) can be accessed
      // (User BDE Aliases are session aliases)
      // Without this change User BDE Aliases will not be found when calling
      //  dB_DbiGetDatabaseDesc().
      //
      //  The szPhyName() method of the szDBDesc object returns the physical path
      //  assigned to a dBASE or Paradox BDE Alias
      if type("dB_DbiSetCurrSession") # "FP"
         extern CUSHORT dB_DbiSetCurrSession( CHANDLE ) IDAPI32 ;
            from "DbiSetCurrSession"
      endif

      if type("dB_DbiGetCurrSession") # "FP"
         extern CUSHORT dB_DbiGetCurrSession( CPTR CUINT ) IDAPI32 ;
            from "DbiGetCurrSession"
      endif

      if type("dB_DbiSetProp") # "FP"
         extern CUSHORT dB_DbiSetProp( CHANDLE, CUINT, CUINT ) IDAPI32 ;
            from "DbiSetProp"
      endif

      if type("dB_DbiGetProp") # "FP"
         extern CUSHORT dB_DbiGetProp( CHANDLE, CUINT, CPTR CUINT, CUSHORT, CPTR CUSHORT ) IDAPI32 ;
            from "DbiGetProp"
      endif


      nSetPropResult = 0  // Receives return value from DbiSetProp()

      nHoldSessionHandle = 0  // Used to hold current BDE Session handle
                              // so we can restore it before method returns

      nHoldPropValue = 0  // Used to hold current setting for sesCFGMODE2
                          // so we can restore it befor method returns

      nHoldPropLen   = 0  // Used to hold length of return value for sesCFGMODE2


      // Get handle for current BDE session 
      nBDEResult = dB_DbiGetCurrSession( nHoldSessionHandle )

      if nBDEResult == DBIERR_NONE

         nSessionHandle = _app.session.handle

         // Must set current session before doing anything to it
         nBDEResult = dB_DbiSetCurrSession( nSessionHandle )

         if nBDEResult == DBIERR_NONE
            nSessionProperty = sesCFGMODE2

            // Get the current value for sesCFGMODE2 so we can reset it
            // back before this method returns
            nBDEResult = dB_DbiGetProp( nSessionHandle, nSessionProperty, ;
                                               nHoldPropValue, 4, nHoldPropLen )

            // Set sesCFGMODE2 to make all Aliases visible
            nPropValue       = cfgmAll

            nSetPropResult = dB_DbiSetProp( nSessionHandle, nSessionProperty, nPropValue)


            // Now prepare to retrieve DBDesc for specified alias
            cDBAliasSB = this.DBCSToSBCSZ( cDBAlias )  // convert database alias name to single byte

            set proc to :coreshared:stBDE32.cc Persistent

            oAliasDesc = new sDBDesc()   // Instantiate sDBDesc object to
                                         // receive DBDesc structure from BDE

            nBDEResult = dB_DbiGetDatabaseDesc( cDBAliasSB, oAliasDesc )
      
            if nBDEResult <> DBIERR_NONE
               // If BDE error occurred return Null value.
               oAliasDesc = Null
            endif


            // Reset sesCFGMODE2 back to original value
            nSetPropResult = dB_DbiSetProp( nSessionHandle, nSessionProperty, nHoldPropValue)

         endif

         // Set current BDE session back to original session
         nBDEResult = dB_DbiSetCurrSession( nHoldSessionHandle )

      endif
      // End QAID: ?

      RETURN oAliasDesc



   ////// Method:    GetODBCDSNList() ///////////////////////////
   ////// Purpose:   Returns array containing the list of ODBC Data
   //////            Source Names (DSN's) associated with the specified
   //////            ODBC Driver (via parameter cODBCDriver)

   //////            If no DSN's are found or cODBCDriver is invalid
   //////            an empty array will be returned.
   FUNCTION GetODBCDSNList( cODBCDriver )
      local aDSNList, nCtr

      cODBCDriver = upper(ltrim(rtrim(cODBCDriver)))
      aDSNList = new Array()   // array to return

      aODBCAlias = this.GetODBCAliasList()
      if aODBCAlias.size > 0
         // Iterate through list of ODBC Aliases
         for nCtr = 1 to aODBCAlias.size
            //    For each one, retrieve its DBDesc structure
            oDBDesc = this.GetAliasDesc( aODBCAlias[nCtr] )

            // Test if DBDesc.szDbType matches the driver name
            //  provided in parameter cODBCDriver.
            if upper(ltrim(rtrim(oDBDesc.szDbType()))) == cODBCDriver
               // If it matches, add Alias to return array aDSNList
               aDSNList.Add( aODBCAlias[nCtr] )
            endif

            oDBDesc = null
         next

      endif

      RETURN aDSNList


   ////// Method:     GetODBCDriverFromDSN ///////////////////////////
   ////// Purpose:    Returns a string containing the ODBC driver name
   ////// 				 associated with the specified DSN.  If the DSN 
   //////				 is not found, an empty string is returned.
   ////// Parameter:  cDSN - string containing the DSN to retrieve
   //////				 a driver for.
   //////
   ////// QAID:5719 - 02/09/2006 - Dave Casler
   ////// Change current search for match in list of both User DSNs
   ////// and System DSNs to allow search separately or together.
   ////// Changed to use parameters statement to make parameters private
   ////// instead of in function statement, which makes parameters local,
   ////// because local variables cannot be checked for type.
   //////
   ////// Parameter2: Optional - nDSNtype - default to 0 if not specified
   //////             0 = search both User and System DSN list
   //////             1 = search only User DSN list
   //////             2 = search only System DSN list 
   ////// 
 	FUNCTION GetODBCDriverFromDSN

      parameters cDSN, nDSNtype

      // QAID:5719 - 02/10/2006 - Dave Casler
      // Change current search for match in list of both User DSNs
      // and System DSNs to allow search separately or together.
      #define qSQL_FETCH_NEXT           1 // get next DSN in list
      #define qSQL_FETCH_FIRST          2 // select mixed list & get first DSN 
      #define qSQL_FETCH_FIRST_USER    31 // select User list & get first DSN
      #define qSQL_FETCH_FIRST_SYSTEM  32 // select System list & get first DSN

      // set default list search method (ODBC Direction parameter)
      nDSNdirection = qSQL_FETCH_FIRST

      if (type("nDSNtype") == "N")
         // Second parameter exists and is a numeric type
         if (nDSNtype == 1)
            // Search only User DSN list
            nDSNdirection = qSQL_FETCH_FIRST_USER

         elseif (nDSNtype == 2)
            // Search only System DSN list
            nDSNdirection = qSQL_FETCH_FIRST_SYSTEM

         endif

      endif
      // end QAID:5719


		if type("dB_SQLAllocHandle") # "FP"
			extern CSHORT dB_SQLAllocHandle(CSHORT, CHANDLE, CPTR CINT) ODBC32.DLL ; 
         	from "SQLAllocHandle"
		endif

		if type("dB_SQLFreeHandle") # "FP"
			extern CSHORT dB_SQLFreeHandle(CSHORT, CHANDLE) ODBC32.DLL ; 
         	from "SQLFreeHandle"
		endif

		if type("dB_SQLSetEnvAttr") # "FP"
			extern CSHORT dB_SQLSetEnvAttr(CHANDLE, CINT, CPTR, CINT) ODBC32.DLL ; 
         	from "SQLSetEnvAttr"
		endif

		if type("dB_SQLDataSources") # "FP"
			extern CSHORT dB_SQLDataSources(CHANDLE, CUSHORT, CSTRING, CSHORT, CPTR CSHORT, CSTRING, CSHORT, CPTR CSHORT) ; 
         	ODBC32.DLL from "SQLDataSources"
		endif

      newEnv = 0
		DSNName = space(64)
		DSNLength = 0
		DriverName = space(64)
		DriverLength = 0

      // Allocate an ODBC environment handle (SQL_HANDLE_ENV = 1)
		if ( dB_SQLAllocHandle(1, 0, newEnv) == 0 )

         // QAID:5719 - 02/09/2006 - Dave Casler
         // Change current search for match in list of both User DSNs
         // and System DSNs to allow search separately or together.
             
         // Check for ODBC version 3.x functionality
   		if ( dB_SQLSetEnvAttr(newEnv, 200, 3, -6) == 0 )
            // ODBC version 3.x is available

            // select the DSN list type and get the first DSN
      		lOk = dB_SQLDataSources( newEnv,          ;
                                     nDSNdirection,   ;
                                     DSNName,         ;
                                     64,              ;
                                     DSNLength,       ; 
            	                      DriverName,      ;
                                     64,              ;
                                     DriverLength )

            // search DSN list for matching DSN name
      		do while (lOk == 0 or lOk == 1)

               // exit if the specified DSN name match found
         		if upper(DSNName) == upper(cDSN)
                  // matching DSN name found
            		exit
         		endif

               // get the next DSN in the selected list
       			lOk = dB_SQLDataSources( newEnv,          ;
                                        qSQL_FETCH_NEXT, ;
                                        DSNName,         ;
                                        64,              ;
                                        DSNLength,       ; 
               	                      DriverName,      ;
                                        64,              ;
                                        DriverLength )

      		enddo 

            // if no match found, set DriverName to empty
            if ( not (lOk == 0 or lOk == 1) )
               // matching DSN not found or error
               DriverName = ""
            endif

         else
            // ODBC version 3.x is not installed
            // set DriverName to empty
            DriverName = ""
			endif
         // end QAID:5719

		endif

      dB_SQLFreeHandle(1, newEnv)

   	RETURN DriverName


   ////// Method:		DoesAliasExist  //////////////////////////////////////////////////
   ////// Purpose		Tests if supplied alias name (cAlias)
   //////            is already defined in the BDE's config file (IDAPI.cfg)
   ////// Parameter: cAlias - string containing an alias name to test

   ////// Notes:     GetAliasList() may return virtual Aliases (for ODBC DSNs)
   //////            or non-persistent Aliases (Aliases that only exist for
   //////            the current BDE session)
   //////            DoesAliasExist will only return True if the Alias exists
   //////            in the BDE config file and are therefore persistent.
   FUNCTION DoesAliasExist( cAlias )
      local nCtr, aAlias, bResult

      aAlias = this.GetAliasList("INCONFIG")

      cAlias = upper(ltrim(rtrim(cAlias)))

      bResult = False
      for nCtr = 1 to aAlias.size
         if upper(ltrim(rtrim(aAlias[nCtr]))) == cAlias
            bResult = True
            exit
         endif
      next

      RETURN bResult


   ////// Method:    IsODBCDriver ////////////////////////////////////////////
   ////// Purpose:   Returns True if the specified driver name (cDriver)
   //////            is an ODBC driver name, otherwise, returns False.
   FUNCTION IsODBCDriver( cODBCDriver )
      local nCtr, aODBCDriver, bResult

      aODBCDriver = this.GetODBCDriverList()

      cODBCDriver = upper(ltrim(rtrim(cODBCDriver)))

      bResult = False
      for nCtr = 1 to aODBCDriver.size
         if upper(ltrim(rtrim(aODBCDriver[nCtr]))) == cODBCDriver
            bResult = True
            exit
         endif
      next

      RETURN bResult


   ////// Method:    GetLogicalTypes /////////////////////////////////////////
   ////// Purpose:   Adds custom property members:
   //////               typeLogical    - Integer representing logical datatype.
   //////               typeLogicalSub - Integer represengint logical subdatatype.
   //////               (See IDAPI.H for integer definitions.)
   //////            to each field of the active rowset passed as oRowset.
   ////// Returns:   0 on success.
   //////            Negative integer error code for errors detected by method.
   //////            BDE error code for BDE error returned as a result of
   //////            dB_DbiGetFieldDescs function call.
   FUNCTION GetLogicalTypes
      PARAMETERS oRowset
      LOCAL xBUFF, nBDEErr, nInd

      if (Type("oRowset") # "O") or Empty(oRowset)
         RETURN -1                   // Invalid object passed.
      endIf
      if (Not oRowset.parent.active) or (oRowset.fields.size < 1)
         RETURN -2                   // Inactive rowset passed.
      endIf
      if type("dB_DbiGetFieldDescs") # "FP"
         extern CUSHORT dB_DbiGetFieldDescs(CHANDLE, CPTR) IDAPI32;
                 from "DbiGetFieldDescs"
      endIf
      xBUFF = replicate(Chr(0), (oRowset.fields.size * 62) / 2)
      nBDEErr = dB_DbiGetFieldDescs(oRowset.handle, xBUFF)
      if nBDEErr # DBIERR_NONE
         RETURN nBDEErr              // BDE detected error. See IDAPI.H.
      endIf
      for nInd = 0 to oRowset.fields.size - 1
         oRowset.fields[nInd + 1].typeLogical = Int(xBUFF.getByte((nInd * 62) + 34) +;
                                          bitLShift(xBUFF.getByte((nInd * 62) + 35), 8))
         oRowset.fields[nInd + 1].typeLogicalSub = Int(xBUFF.getByte((nInd * 62) + 36) +;
                                             bitLShift(xBUFF.getByte((nInd * 62) + 37), 8))
      endFor
      RETURN 0



   ////// Method:    ValidateName ////////////////////////////////////////////
   //////
   ////// Purpose:   Checks if the object name passed in (via cName)
   //////            is valid or not and returns a string containing
   //////            the value from cName (if its valid)
   //////            OR a corrected name (if cName is not valid)
   //////             
   //////            Returns an empty string if validation could not
   //////            be performed.
   //////            
   //////            Can validate Table, Index, Index Tag, and Field names
   //////            
   ////// Parameters: cDriverType - a string containing the name of
   //////                the BDE Database Driver OR ODBC Driver to use
   //////                ex: the string from rowset.tableDriver
   //////                See CASEs in method CreateAlias() for list
   //////                of non-ODBC drivers.
   //////            
   //////             nObjectType - a number specifying which kind of
   //////                object the name passed in cName is for:
   //////            
   //////                    Table Name     = 0
   //////                    Field Name     = 1
   //////                    Index Name     = 2
   //////                    Index Tag Name = 3
   //////            
   //////             cName - a string containing the name to be validated
   //////            
   //////             cLangDriver - a string containing the name of the 
   //////                language driver to use when validating cName.
   //////             
   FUNCTION ValidateName( cDriverType, nObjectType, cName, cLangDriver )

      local cValidName, cValidNameSB, nBDEResult

      if empty( cDriverType ) or empty( cName ) or empty( cLangDriver ) ;
         or ((str(nObjectType,1) $ "0123") == 0)

         return ""
      endif


      if type("dB_DbiValidateName") # "FP"                                      
         extern CUSHORT dB_DbiValidateName( CPTR, CPTR, CPTR, CINT, CPTR) IDAPI32 ;
            from "DbiValidateName"
      endif


      cValidName = cName

      // Convert cValidName to single byte
      cValidNameSB = this.DBCSToSBCSZ(cValidName)


      // Call BDE to validate name
      //   Corrected name will be returned in cValidNameSB
      nBDEResult =  dB_DbiValidateName( this.DBCSToSBCSZ(cDriverType), ;
                                               this.DBCSToSBCSZ(cName),       ;
                                               cValidNameSB,                  ;
                                               nObjectType,                   ;
                                               this.DBCSToSBCSZ(cLangDriver) )

      if not empty( cValidNameSB )
         // Convert corrected name back to Unicode
         cValidName = this.GetSBString( cValidNameSB, 0, len(cName) )
      endif


      RETURN cValidName


   ////// Method:    CreateWizODBCDSN ////////////////////////////////////////////
   //////
   ////// Purpose:   Opens wizard to create the specified DSN
   //////
   ////// Returns:   true if operation was succesful, otherwise,
   //////               returns false.
   //////            
   ////// Parameters: 

   FUNCTION CreateWizODBCDSN( hWndParent, cDataSourceName )
      local bOk

      if type("dB_SQLCreateDataSource") # "FP"                                      
         extern CLOGICAL dB_SQLCreateDataSource( CHANDLE, CPTR ) ODBCCP32.DLL ;
            from "SQLCreateDataSource"
      endif

      bOk = dB_SQLCreateDataSource( hWndParent, this.DBCSToSBCSZ(cDataSourceName) )

      RETURN bOk



   ////// Method:    ConfigODBCDSN ////////////////////////////////////////////
   //////
   ////// Purpose:   Adds, modifies, or removes an ODBC DSN from the registry
   //////
   ////// Returns:   true if operation was succesful, otherwise,
   //////               returns false.
   //////            
   //////            This method should be called from within a try...catch
   //////            statement. If an error occurs while calling 
   //////            dB_SQLConfigDataSource() an ODBCException object is
   //////            created containing the error code and error message.
   //////            This ODBCException is then thrown to the catch in
   //////            in the calling try...catch statement.
   //////            
   ////// Parameters:
   //////    hWndParent  - hWnd of a window to be the parent for any
   //////                  dialog that may be opened by the specified
   //////                  driver to obtain additional information.
   //////
   //////                  If set to _app.frameWin.hWnd, a Create ODBC DSN dialog
   //////                  provided by MS Windows will open modally and allow
   //////                  the user to interactively create a DSN.
   //////
   //////                  If set to 0 (zero), a window will only open if
   //////                  the ODBC Driver specified requires additional
   //////                  information beyond what has already been provided.
   //////
   //////    iAction     - 1 = Add data source (DSN)
   //////                  2 = Modify (edit) data source
   //////                  3 = Remove data source
   //////                  4 = Add a system DSN
   //////                  5 = Configure (modify) a system DSN
   //////                  6 = Remove a system DSN
   //////                  7 = Remove the default DSN
   //////
   //////    cDriverType - a string containing the name of the ODBC Driver
   //////                  for which to Add, Modify, or Remove a DSN
   //////   
   //////    aAttributes - an assocArray containing a list of name value pairs
   //////                  specifying the name and settings for a DSN.
   //////
   //////                  The first entry in aAttributes should be:
   //////                    aAttributes["DSN"] = "<a DSN Name>"
   //////
   //////                  The remaining attributes are driver specific.
   //////
   //////        For: Microsoft Access Driver (*.mdb)
   //////           aAttributes["DSN"]        = "MyGov"
   //////           aAttributes["Description"]= "Government info database"
   //////           aAttributes["DBQ"]        = "F:\MSAccessDb\Government.mdb"
   //////
   //////        See method CreateODBCDSN for more examples.

   FUNCTION ConfigODBCDSN( hWndParent, iAction, cDriverType, aAttributes )

      local   lOk, cAttributes, cKey, ctr

      // Validate parameters
      if (not str(iAction,1,1) $ "1234567") or empty(cDriverType) or aAttributes = null
         return false
      endif

      if aAttributes.baseclassname <> "ASSOCARRAY"
         return false
      endif

      if aAttributes.count() < 1
         return false
      endif

      // Declare function prototypes for the ODBC API calls needed
      if type("dB_SQLConfigDataSource") # "FP"                                      
         extern CLOGICAL dB_SQLConfigDataSource( CHANDLE, CUINT, CPTR, CPTR ) ODBCCP32.DLL ;
            from "SQLConfigDataSource"
      endif

      if type("dB_SQLInstallerError") # "FP"
         extern CSHORT dB_SQLInstallerError( CUSHORT, CPTR CULONG, CSTRING, CUSHORT, CPTR CUSHORT ) ;
            ODBCCP32.DLL from "SQLInstallerError"
      endif

      cAttributes = ""
      cKey = aAttributes.firstkey
      if not empty(cKey)
         ctr = 1
         // Concatenate name value pairs from aAttributes into a
         // single string (cAttributes) to pass to dB_SQLConfigDataSource()
         do while ctr <= aAttributes.count()
            cAttributes = cAttributes + ckey + "=" + aAttributes[cKey] + chr(0)
            cKey = aAttributes.nextkey(cKey)
            ctr++
         enddo

         cAttributes = cAttributes + chr(0)   // Add extra null at end of string
      endif
/* trap  to see connecct string
cOut = cAttributes
   i = at(chr(0),cOut)
   do while i > 0
      cOut = substr(cOut,1,i-1)+substr(cOut,i+1)
      i = at(chr(0),cOut)

   enddo

   msgbox('cOut:'+cOut)
   //end trap
*/
      lOk = dB_SQLConfigDataSource( hWndParent,  ;
                                    iAction,     ;
                                    this.DBCSToSBCSZ(cDriverType), ;
                                    this.DBCSToSBCSZ(cAttributes) )

      if not lOk
         // Error has occurred
         nErrorCode = 0
         nHoldErrorCode = 0

#define SQL_MAX_MESSAGE_LENGTH   512

         cErrorMsg  = space( SQL_MAX_MESSAGE_LENGTH + 1)
         nMsgLen    = 0

         aErrorMsg = new array()
         nResult = 0

         // Retrieve error code and message from ODBC error handler
         // Error message may have up to eight parts.
         //for iError = 1 to 8
         bDone = false
         iError = 1
         do while iError <= 8 or not bDone
            nResult =  dB_SQLInstallerError( iError,     ;
                                             nErrorCode, ;
                                             cErrorMsg, ;
                                             SQL_MAX_MESSAGE_LENGTH-1,;
                                             nMsgLen )

            iError++

            if nMsgLen = 0 or len(trim(cErrorMsg)) = 0
               bDone = true
               loop
               // exit
            endif


            if nHoldErrorCode  = 0
               nHoldErrorCode = nErrorCode
            endif

            aErrorMsg.add( cErrorMsg )   // cErrorMsg should be autoconverted to UNICODE

            // re-init cErrorMsg
            cErrorMsg  = space( SQL_MAX_MESSAGE_LENGTH + 1)
            nErrorCode = 0
         enddo
         //next

         if aErrorMsg.size > 0 or nHoldErrorCode <> 0
            // Build an error object to throw
            e = new ODBCException()
            e.code = nHoldErrorCode

            for ctr=1 to aErrorMsg.size
               e.message = e.message + iif(ctr>1, chr(13), "") + aErrorMsg[ctr]
            next

            throw e
         endif
      endif

      
      RETURN lOk


   ////// Method:    ReloadODBCInfo ////////////////////////////////////////////
   //////
   ////// Purpose:   Reloads the current ODBC Driver List and Data Source List
   //////            into the BDE thus updating the BDE with any changes since
   //////            the BDE was started or last updated.
   //////
   ////// Returns:   true if succesful, otherwise, returns false.
   //////            
   ////// Parameters: none
   //////
   FUNCTION ReloadODBCInfo( lPersistent )

      if type("dB_DbiImportODBC") # "FP"
         extern CUSHORT dB_DbiImportODBC( CHANDLE, CLOGICAL ) IDAPI32 ;
            from "DbiImportODBC"
      endif

      return ( dB_DbiImportODBC( 0, lPersistent ) = DBIERR_NONE)



   ////// Method:    CreateODBCDSN ////////////////////////////////////////////
   //////
   ////// Purpose:   Wrapper for method ConfigODBCDSN
   //////            Handles parameter formatting for the following
   //////            ODBC Drivers (via hard coded cases
   //////               Access 2000
   //////               FoxPro (Both free tables and database containers)
   //////
   //////            This method should be called from within a try...catch
   //////            statement. The catch should check for an ODBCException.
   //////
   ////// Parameters: cDriverType - Specifies one of the hard-coded drivers
   //////                           as follows:
   //////                    ACCESS - for Access 2000
   //////                    FOXPRO - for FoxPro free tables
   //////                    VFP    - for Visual FoxPro Database Container
   //////
   //////               If no cDriverType is passed, calls CreateWizODBCSN()
   //////               to run the Microsoft Create DSN Wizard
   //////                               
   //////
   //////             cDataSourceName - string containing the name of the 
   //////                               Data Source to create
   //////                               
   //////                               
   //////             cDataSource - string containing either the full path
   //////                           and filename of a database file
   //////                           OR the full path for the folder containing
   //////                           the table files for a free table database
   //////                           (such as for FOXPRO free tables)
   //////
   //////                   Ex:     c:\myData\myData.mdb     // for Access
   //////                           c:\yourData\yourData.dbc // for VFP
   //////                           c:\someData\             // for FOXPRO
   //////                               
   ////// Returns:   true if operation was succesful, otherwise,
   //////               returns false.
   //////
   //////            If an error occurs within ConfigODBCDSN() an ODBCException
   //////            object will be created and thrown up to the calling
   //////            procedure.
   //////            
   //////            
   ////// Parameters:
   FUNCTION CreateODBCDSN( cDriverType, cDataSourceName, cDataSource )
      local bOk, cDriverName, hWndParent

      hWndParent = 0

      aAttributes = new assocArray()

      do case
      case cDriverType == "ACCESS"
         cDriverName = "Microsoft Access Driver (*.mdb)"

         aAttributes["DSN"]         = cDataSourceName
         aAttributes["Description"] = cDataSourceName

         aAttributes["DBQ"]         = cDataSource

      case cDriverType == "FOXPRO" or cDriverType == "VFP"
         cDriverName = "Microsoft Visual Foxpro Driver"

         aAttributes["DSN"] = cDataSourceName
         aAttributes["Description"] = cDataSourceName

         aAttributes["SourceDB"]   = cDataSource
         aAttributes["SourceType"] = iif( cDriverType=="VFP", "DBC", "DBF")
         aAttributes["Null"] = "No"


      otherwise
         cDriverName = ""
         hWndParent = _app.frameWin.hWnd
      endcase

      if empty( cDriverName )
         // Invoke Create DSN Wizard
         bOk = this.CreateWizODBCDSN( hWndParent, cDataSourceName)
      else
         // Create DSN without opening wizard or dialog
         bOk = false

         try
            bOk = this.ConfigODBCDSN( hWndParent, 1, cDriverName, aAttributes)
         catch (ODBCException e)
            throw e
         endtry
      endif

      return bOk

   // QAID: 5036 - M.Kay, dBI Inc. - 10/06/2004
   //////  GetAutoInc
   //////
   ////// Retrieves the next autoincrement value from a dBASE Level 7 table
   //////    or from a Paradox table containing an autoincrement field
   //////
   //////  Parameters:   hCursor   - the BDE Cursor handle
   //////                            Available from the rowset.handle property
   //////  Returns:      The next available autoincrement value for the table
   //////                or -1 if unable to retrieve the value.
   FUNCTION GetAutoInc
      parameters hCursor

      private iAutoInc, iReturnLen, nBDEResult

      if type("hCursor") <> "N"
         return -1
      endif

      if hCursor = 0
         return -1
      endif

      if type("dB_DbiGetProp") # "FP"
         extern CUSHORT dB_DbiGetProp( CHANDLE, CUINT, CPTR CUINT, CUSHORT, CPTR CUSHORT ) IDAPI32 ;
            from "DbiGetProp"
      endif

      iAutoInc   = 0
      iReturnLen = 0
      nBDEResult = 0

      nBDEResult =  dB_DbiGetProp( hCursor, curAUTOINCVALUE, iAutoInc, UINT32Len, iReturnLen)
      if nBDEResult <> DBIERR_NONE
         iAutoInc = -1
      endif
         
      return iAutoInc


   ////// Method:    SetAutoInc ////////////////////////////////////////////
   //////
   //////
   ////// Sets specified dBASE (level 7) or Paradox table's next auto increment value
   //////    to the specified value.
   //////
   ////// Parameters:  hCursor          = BDE Cursor handle of table to modify (Use rowset.handle)
   //////              iNewAutoIncValue = New auto inc value to set (Valid Range 1 thru 2^32)
   //////
   ////// Returns:     true if successful
   //////              false if invalid parameter or unable to set autoinc value
   FUNCTION SetAutoInc
      parameters hCursor, iNewAutoIncValue

      local nBDEResult, lOk

      if type("hCursor") <> "N" or type("iNewAutoIncValue") <> "N"
         return false
      endif

      if hCursor = 0 or iNewAutoIncValue < 1 or iNewAutoIncValue > MAXAUTOINCVALUE
         return false
      endif


      if type("dB_DbiSetProp") # "FP"
         extern CUSHORT dB_DbiSetProp( CHANDLE, CUINT, CUINT ) IDAPI32 ;
            from "DbiSetProp"
      endif


      nBDEResult = 0
      lOk = true

      nBDEResult = dB_DbiSetProp( hCursor, curAUTOINCVALUE, iNewAutoIncValue )
      if nBDEResult <> DBIERR_NONE
         bOk = false
      endif
   
      return lOk
   // End QAID: 5036



   //////  QAID: 5085 - M.Kay, dBI Inc. - 11/15/2004
   //////  Method:    GetFieldTypes() ///////////////////////////
   //////  Purpose:   Returns an array of object references to sFLDType objects
   //////             containing details about each supported field type
   //////
   //////             The sFLDType object's methods can then be used
   //////             to read the data from the FLDType structure
   //////             contained within it.
   //////
   //////             If error occurs retrieving FLDType structures, an empty
   //////             array is returned.

   FUNCTION GetFieldTypes
      parameters cDriverType, cTableType

      local aFldType, nBDEResult, nCursorHandle, lcTableType

      nBDEResult    =  0 // Receives error code from BDE calls 
      nCursorHandle = -1 // BDE Cursor Handle returned by DbiOpenDatabaseList containing list of Aliases

      aFldType = new array()


      set procedure to :coreshared:stbde32.cc additive

      //DBIResult DBIFN DbiOpenFieldTypesList (pszDriverType, [pszTblType], phCur);

      if type("dB_DbiOpenFieldTypesList") # "FP"
         extern CUSHORT dB_DbiOpenFieldTypesList( CPTR, CPTR, CPTR CUINT ) IDAPI32 ;
            from "DbiOpenFieldTypesList"
      endif

      if type("dB_DbiGetNextRecord") # "FP"                                      
         extern CUSHORT dB_DbiGetNextRecord( CHANDLE, CSHORT, CPTR, CPTR) IDAPI32 ;
            from "DbiGetNextRecord"
      endif

      if type("dB_DbiCloseCursor") # "FP"
         extern CUSHORT dB_DbiCloseCursor( CPTR CUINT ) IDAPI32 ;
            from "DbiCloseCursor"
      endif

      if empty( cTableType )
         lcTableType = null
      else
         lcTableType = cTableType
      endif


      cDriverTypeSB = this.DBCSToSBCSZ( cDriverType )  // convert database driver name to single byte
      cTableTypeSB  = this.DBCSToSBCSZ( lcTableType )  // convert table type name to single byte

      // Call BDE to get a list of field types
	   nBDEResult = dB_DbiOpenFieldTypesList( cDriverTypeSB, cTableTypeSB, nCursorHandle )
      
      if nBDEResult == DBIERR_NONE
         // Allocate buffer to receive data from DbiGetNextRecord
         //    and init to nulls
         // Note: Since dB2K strings contain 2 bytes per character,
         //    we need only allocate structure size divided by 2.
         oFLDType = new sFLDType()
         // Iterate through the BDE cursor
         do while dB_DbiGetNextRecord( nCursorHandle, dbiNOLOCK, oFLDType, 0) # DBIERR_EOF
            // Retrieve field containing the Driver name
            // from cBuffer and add it to the array.
            aFLDType.Add( oFLDType.szName() )

            oFLDType = null
            oFLDType = new sFLDType()

         enddo

         // Close the BDE Cursor we opened
         nBDEResult = dB_DbiCloseCursor( nCursorHandle )
      endif


      return aFldType




   //////  Method:    GetTableTypes() ///////////////////////////
   //////  Purpose:   Returns an array of table type names
   //////             for the supplied driver type
   //////
   //////             If error occurs or an invalid parameter is specified,
   //////             an empty array is returned.

   FUNCTION GetTableTypes
      parameters cDriverType

      local aFldType, nBDEResult, nCursorHandle, lcTableType

      nBDEResult    =  0 // Receives error code from BDE calls 
      nCursorHandle = -1 // BDE Cursor Handle returned by DbiOpenDatabaseList containing list of Aliases

      aTableType = new array()

      set procedure to :coreshared:stbde32.cc additive

      // DBIResult DBIFN DbiOpenTableTypesList (pszDriverType, phCur);

      if type("dB_DbiOpenTableTypesList") # "FP"
         extern CUSHORT dB_DbiOpenTableTypesList( CPTR, CPTR CUINT ) IDAPI32 ;
            from "DbiOpenTableTypesList"
      endif

      if type("dB_DbiGetNextRecord") # "FP"                                      
         extern CUSHORT dB_DbiGetNextRecord( CHANDLE, CSHORT, CPTR, CPTR) IDAPI32 ;
            from "DbiGetNextRecord"
      endif

      if type("dB_DbiCloseCursor") # "FP"
         extern CUSHORT dB_DbiCloseCursor( CPTR CUINT ) IDAPI32 ;
            from "DbiCloseCursor"
      endif


      cDriverTypeSB = this.DBCSToSBCSZ( cDriverType )  // convert database driver name to single byte

      // Call BDE to get a list of table types
	   nBDEResult = dB_DbiOpenTableTypesList( cDriverTypeSB, nCursorHandle )
      
      if nBDEResult == DBIERR_NONE
         // Allocate buffer to receive data from DbiGetNextRecord
         //    and init to nulls
         // Note: Since dB2K strings contain 2 bytes per character,
         //    we need only allocate structure size divided by 2.

         oTableType = new sTBLType()

         // Iterate through the BDE cursor
         do while dB_DbiGetNextRecord( nCursorHandle, dbiNOLOCK, oTableType, 0) # DBIERR_EOF
            // Retrieve Table Type name from cBuffer and add it to the array.
            aTableType.Add( oTableType.szName() )

            oTableType = null
            oTableType = new sTBLType()
         enddo

         // Close the BDE Cursor we opened
         nBDEResult = dB_DbiCloseCursor( nCursorHandle )
      endif


      return aTableType
      // End QAID: 5085


   ////// Method:    GetPrimaryKey //////////////////////////////////
   ////// Purpose:   Returns a table's primary key if it has one.
   ////// Parameter: oRowset - Rowset object to check for a primary key.
	FUNCTION GetPrimaryKey ( oRowset )

      local oIndexList, i, bPrimary, cPK
   	oIndexList = new QBDEIndexList( oRowset )
      cPK = ""

      for i = 1 to oIndexList.size

         bPrimary = oIndexList.GetPrimary(oIndexList[i])
         if bPrimary
         	cPK = oIndexList.GetName(oIndexList[i])
            exit
         endif

      endfor

      oIndexList = null

      return cPK


   ////// Method:    GetUniqueKey //////////////////////////////////
   ////// Purpose:   Returns a unique key for a table if it has any.
   ////// Parameter: oRowset - Rowset object to check for a primary key.
	FUNCTION GetUniqueKey ( oRowset )

      local oIndexList, i, bUnique, cUK
   	oIndexList = new QBDEIndexList( oRowset )
      cUK = ""

      for i = 1 to oIndexList.size

         bUnique = oIndexList.GetUnique(oIndexList[i])
         if bUnique
         	cUK = oIndexList.GetName(oIndexList[i])
            exit
         endif

      endfor

      oIndexList = null

      return cUK



   ////// Method:    GetConfigInfo //////////////////////////////////
   ////// Purpose:   Returns array of CFGDesc structures for the specified node within
   //////            the BDE config file.
   //////            
   ////// Parameter: cConfigPath - Path to the BDE Config information
   function GetConfigInfo( cConfigPath )

      // Prototypes for needed BDE API functions
      if type("dB_DbiOpenCfgInfoList") # "FP"
         extern CUSHORT dB_DbiOpenCfgInfoList( CUINT, CINT, CINT, CSTRING, CPTR CUINT ) IDAPI32 ;
            from "DbiOpenCfgInfoList"

         // CUSHORT                            CUINT, CINT,     CINT,        CSTRING,    CPTR CUINT
         //DBIResult DBIFN DbiOpenCfgInfoList (hCfg, eOpenMode, eConfigMode, pszCfgPath, phCur)
      endif

      if type("dB_DbiGetNextRecord") # "FP"                                      
         extern CUSHORT dB_DbiGetNextRecord( CHANDLE, CSHORT, CPTR, CPTR) IDAPI32 ;
            from "DbiGetNextRecord"
      endif

      if type("dB_DbiCloseCursor") # "FP"
         extern CUSHORT dB_DbiCloseCursor( CPTR CUINT ) IDAPI32 ;
            from "DbiCloseCursor"
      endif

      set proc to :coreshared:stBDE32.cc Persistent

      nBDEResult    =  0  // Receives error code from BDE calls
      nCursorHandle = -1  // Receives cursor handle of result table

      nBDEResult = dB_DbiOpenCfgInfoList( 0, dbiREADONLY, cfgPersistent, cConfigPath, nCursorHandle)

      aConfigInfo = new array()

      if nBDEResult = DBIERR_NONE
         // phCur is a cursor handle to a table of CFGDesc structures.
         // Iterate through table to retrieve desired data.

         oCFGDesc = new sCFGDesc()

         // Iterate through the BDE cursor
         do while dB_DbiGetNextRecord( nCursorHandle, dbiNOLOCK, oCFGDesc, 0) # DBIERR_EOF
            // Retrieve Table Type name from cBuffer and add it to the array.
            aConfigInfo.Add( oCFGDesc )

            oCFGDesc = null
            oCFGDesc = new sCFGDesc()
         enddo

         // Close the BDE Cursor we opened
         nBDEResult = dB_DbiCloseCursor( nCursorHandle )

      endif

      return aConfigInfo




   ////// Method:    GetDSNForAlias //////////////////////////////////
   ////// Purpose:   Returns the ODBC DSN setting for a BDE Alias
   //////            
   ////// Parameter: cAlias - BDE Alias to retrieve ODBC DSN string for
   //////                     If not found, returns an empty string
   function GetDSNForAlias( cAlias )
       private aAliasProps, cDSN

       cDSN = ""
       // Get array of sCFGDesc objects - each one contains one property
       //   for cAlias
       aAliasProps = this.getConfigInfo("\databases\"+cAlias+"\DB OPEN")

       // Iterate through array looking for a node for "ODBC DSN"
       for i = 1 to aAliasProps.size
          if aAliasProps[i].szNodeName() = "ODBC DSN"
             cDSN = aAliasProps[i].szValue()
             exit
          endif
       next

       return cDSN




   // QAID: ? - M.Kay, dBI Inc. - 11/03/2010
   // Add SaveAlias() method to save non-persistent BDE Alias
   // info in _app.inifile to make these aliases persistent

   ////// Method:    SaveUserAlias //////////////////////////////////
   ////// Purpose:   Saves BDE Alias in user's .INI file
   //////            so they can be reloaded at dBASE Plus startup
   //////            
   function SaveUserAlias()
      local lResult, ctr

      lResult = false

      if type('_app.iniFile')='C'
         if not empty(_app.iniFile)
            // Check if there is anything to save
            if not empty(this.cAlias) and not empty( this.cDriverType ) ;
                  and not empty(this.cParameters)
               

               set procedure to :coreshared:coreini.cc additive

               this.oIni = new coreini( _app.iniFile )

               // initialize ctr to next available alias number
               // must iterate through aliases in .ini to determine next
               // available number
               ctr = this.GetNextUserAliasNumber( this.oIni )

               // Create secion [UserBDEAliases] if it doesn't exit
               //    Add <n>=<aliasname> to this section
               if this.oIni.SetValue( "UserBDEAliases", ltrim(rtrim(str(ctr))), this.cAlias )

                  // Create section with name of alias and add Driver value beneath it
                  if this.oIni.SetValue( this.cAlias, "Driver", this.cDriverType )

                     // Add Options setting for this alias
                     this.oIni.SetValue( this.cAlias, "Options", this.cParameters )
                  endif

               endif

               this.oIni.flush()  // flush changes to ini file
               this.oIni = null

               close procedure :coreshared:coreini.cc
            endif
            
         endif
      endif

      return lResult



   ////// Method:    DeleteUserAlias //////////////////////////////////
   ////// Purpose:   Deletes BDE Alias specified via cAlias property 
   //////            from user's .INI file
   //////            
   function DeleteUserAlias()
      local lResult, aliasCtr


      lResult = false

      if not empty( this.cAlias )
         if type('_app.iniFile')='C'
            if not empty(_app.iniFile)
               set procedure to :coreshared:coreini.cc additive

               this.oIni = new coreini( _app.iniFile )

               // initialize ctr to next available alias number
               // must iterate through aliases in .ini to determine next
               // available number
               //ctr = this.GetNextUserAliasNumber( this.oIni )

               aliasCtr = this.GetUserAliasNumber( this.oIni )
               if aliasCtr >= 0
               //try
                  // Delete Each Entry for cDelAlias within .ini file
                  this.oIni.DeleteEntry( this.cAlias, "Options")
                  this.oIni.DeleteEntry( this.cAlias, "Driver")
                  this.oIni.DeleteSection( this.cAlias )


                  // Delete Alias entry from within UserBDEAliases section
                  this.oIni.DeleteEntry( "UserBDEAliases", ltrim(rtrim(str(aliasCtr))) )
               //catch ( exception e )

               //endtry
               endif

               this.oIni.flush()  // flush changes to ini file
               this.oIni = null

               close procedure :coreshared:coreini.cc
            
            endif
         endif
      endif

      return lResult


   // GetNextUserAliasNumber
   //
   //    Determine next available alias number to use when saving a User BDE Alias
   //    to the .ini file
   //
   //
   //    Retrieves list of User BDE Aliases from ini file
   //    and loads them into an array.
   //    Retrieve length of array and adjust if first element is empty
   //
   //    Note - there is a 32KB limit on size of data in an ini file section
   //       this limits # of aliases that can be stored there
   function GetNextUserAliasNumber( objIni )
      local nextctr, aUserAlias, chkAlias

      nextctr = 0

      if not empty( objIni )
         aUserAlias = objIni.GetSection("UserBDEAliases")

         nextctr = iif( empty(aUserAlias[1]), 0, aUserAlias.size )
         // Check if last existing alias number matches aUserAlias.size
         //lastnum = val( substr(aUserAlias[ aUserAlias.size ], 1, at("=",aUserAlias[ aUserAlias.size ])-1) )
         //if lastnum >= nextctr
         //   One or more aliases  have been deleted, so increment nextctr and search for available number

         do while true
            chkAlias = objIni.GetValue("UserBDEAliases", ltrim(rtrim(str(nextctr))) )
            if empty( chkAlias )
               // nextctr is available, so return it
               exit
            endif

            nextctr++
         enddo

         //endif 
      endif

      return nextctr

   

   // GetUserAliasNumber
   //
   //    Returns alias number from ini file for alias specified in this.cAlias
   function GetUserAliasNumber( objIni )
      local nctr, ctr, aUserAlias

      nctr = -1
      if not empty( objIni )
         aUserAlias = objIni.GetSection("UserBDEAliases")

         for ctr=1 to aUserAlias.size
            if upper(trim(substr(aUserAlias[ctr], at("=", aUserAlias[ctr])+1) )) == upper(this.cAlias)
               nctr = ctr-1
               exit            
            endif
         next
      endif

      return nctr

   // End QAID: ?



//   FUNCTION MapFieldTypes

      // Use DbiTranslateRecordStructure() to obtain destination field types
      //   given source field types

/*(
DBIResult DBIFN DbiTranslateRecordStructure (pszSrcDriverType, iFlds, pfldsSrc, pszDstDriverType, pszLangDriver, pfldsDst, bCreatable);

pszSrcDriverType 	Type: pCHAR    -> CPTR     (Input)  Pointer to the source driver type. If NULL, it is assumed that the source fields are logical with a NULL driver type. 
iFlds 	         Type: UINT16   -> CUSHORT  (Input)  Specifies the number of fields. pfldsSrc          Type: pFLDDesc -> CPTR     (Input)  Pointer to an array of the logical or physical types of the source fields.pszDstDriverType 	Type: pCHAR    -> CPTR	   (Input)  Pointer to the destination driver type. If NULL, it is assumed that the destination fields are logical with a NULL driver type. pszLangDriver 	   Type: pCHAR    -> CPTR     (Input)  Pointer to the destination driver's language driver name. This language driver is used to validate the destination field names after the translation.
pfldsDst 	      Type: pFLDDesc -> CPTR     (Output) Pointer to an array of the destination fields.bCreatable	      Type: BOOL     -> CLOGICAL (Input)  If True, map to creatable fields only*/
//      if type("dB_DbiTranslateRecordStructure") # "FP"
//         extern CUSHORT dB_DbiTranslateRecordStructure( CPTR, CUSHORT, CPTR, CPTR, CPTR, CPTR, CLOGICAL ) IDAPI32 ;
//            from "DbiTranslateRecordStructure"
//      endif

//      srcFLDDesc  = new stFLDDesc()
//      destFLDDesc = new stFLDDesc()

//      dB_DbiTranslateRecordStructure( NULL, 1, )

//      msgbox()

//      return 

ENDCLASS




CLASS ODBCException of Exception
   CLASSNAME = "ODBCException"
   this.message = "ODBC Error: "

ENDCLASS


CLASS QBDETablesList

   this.nDbHandle     = 0   // Database Handle of database to retrieve table list from

   this.nCursorHandle = 0   // Cursor Handle of table containing table list

   this.nBDEResult    = 0 // Receives error code from BDE calls 


   this.nNumTables    = 0


   //cBuffer       = replicate( chr(0), int(TBLBaseDescSize/2)+1)



   ////// Method:    OpenDatabaseTablesList ////////////////////////////////////////////
   ////// Purpose:   Returns a single column array containing a list
   //////            of table names that exist for the database (or Alias)
   //////            specified by the supplied handle (nDbHandle).
   //////            Returns true if successful, otherwise false
   FUNCTION OpenDataBaseTablesList( nDbHandle )
      local lOk, cWildCard, nCursorHandle


      // Prototypes for needed BDE API functions
      if type("dB_DbiOpenTableList") # "FP"
         extern CUSHORT dB_DbiOpenTableList( CHANDLE, CLOGICAL, CLOGICAL, CSTRING, CPTR CUINT) IDAPI32 ;
            from "DbiOpenTableList"
      endif                                                                        


      lOk = false
      cWildCard = "*.*"+chr(0)+chr(0)    // Specifies table filter to use of *.* all tables

      // Call BDE to get a list of tables contained in the database referenced by nDbHandle.
      nCursorHandle = 0
      nBDEResult = dB_DbiOpenTableList( nDbHandle, False, False, cWildCard, nCursorHandle)

      // If no error occurred, iterate through the BDE cursor
      if nBDEResult = DBIERR_NONE
         this.nDbHandle     = nDbHandle
         this.nCursorHandle = nCursorHandle
         lOk = true
      endif

      return lOk



   ////// Method:    GetNextTable ////////////////////////////////////////////
   ////// Purpose:   Returns a character string containing the next table name
   //////            If no more tables, returns an empty string
   FUNCTION GetNextTable()
      local nCursorHandle, cBuffer, cTable

      if type("dB_DbiGetNextRecordTableList") # "FP"                                      
         extern CUSHORT dB_DbiGetNextRecordTableList( CHANDLE, CSHORT, CPTR, CPTR) IDAPI32 ;
            from "DbiGetNextRecord"
      endif

      nCursorHandle = this.nCursorHandle

      cBuffer    = replicate( chr(0), int(TBLBaseDescSize/2)+1)

      nBDEResult = dB_DbiGetNextRecordTableList( nCursorHandle, dbiNOLOCK, cBuffer, 0)

      if nBDEResult # DBIERR_EOF
         // Retrieve field szName (containing the table name)
         // from cBuffer and add the table name to the array.
         cTable = this.GetSBString(cBuffer, dB_szName, dB_szNameLen)
      else
         cTable = ""
      endif

      return cTable


   FUNCTION CloseDatabaseTablesList()
      local bOK, nCursorHandle

      if type("dB_DbiCloseCursor") # "FP"
         extern CUSHORT dB_DbiCloseCursor( CPTR CUINT ) IDAPI32 ;
            from "DbiCloseCursor"
      endif                                                                        

      // Close the BDE Cursor we opened
      nCursorHandle = this.nCursorHandle
      nBDEResult = dB_DbiCloseCursor( nCursorHandle )

      if nBDEResult = DBIERR_NONE
         bOK = true
      else
         bOK = false
      endif

      this.nCursorHandle = 0
      this.nDbHandle = 0
      this.numTables = 0
      //this.cBuffer   = replicate( chr(0), int(TBLBaseDescSize/2)+1)
      this.nBDEResult = 0

      return bOK



   FUNCTION GetNumberOfTables()
      local nCursorHandle, nNumTables


      if type("dB_DbiGetRecordCount") # "FP"                                      
         extern CUSHORT dB_DbiGetRecordCount( CHANDLE, CPTR CUINT ) IDAPI32 ;
            from "DbiGetRecordCount"
      endif

      nCursorHandle = this.nCursorHandle
      nNumTables = 0

      this.nBDEResult =  dB_DbiGetRecordCount( nCursorHandle, nNumTables )

      if this.nBDEResult = DBIERR_NONE
         this.nNumTables = nNumTables
      endif

      return nNumTables


  //////  Method:    GetSBString  ////////////////////////////////////////////////
   //////  Purpose:   Given a single byte string, converts the specified substring
   //////             into a double byte string
   //////  Parameters: xSBBUFF - Single byte string
   //////              nIndex  - Starting position of substring to convert
   //////              nLen    - Length in bytes of substring.
   //////  Usage:     Used to read character string structure fields
   //////             out of a string containing the structure.
   //////  From:      Jim Sare
   //////  Modified   Rick Fillman Jan 2002 for Asian.
   FUNCTION GetSBString( xSBBUFF, nIndex, nLen)
	   LOCAL c, nByte1, i, bIsLeadByte
      c = ""
      bIsLeadByte = false
      For i = nIndex To nIndex + (nLen - 1)
         If xSBBUFF.GetByte(i) = 0
            Exit
         EndIf
         nByte1 = xSBBUFF.GetByte(i)
         #ifdef __asian__
           // speculating about leadBytes 
           //   serves no purpose on US or European machines.
            bIsLeadByte = this.DBCSisLeadByte( nByte1 ) 
           // end Asian only block
         #endif
         if bIsLeadByte
            // we have a lead byte of a DBCS pair
            // must grab the trail byte, and then 
            //   then convert *the*whole*thing* to a character
            i++
            c += chr( htoi( itoh( nByte1 ) + itoh( xSBBUFF.GetByte(i)) ))
         else
            c += Chr( nByte1 )
         endif
      EndFor
      RETURN c



   //////  Method:    DBCSisLeadByte  ////////////////////////////////////////////////
   //////  Purpose:   lead-byte ranges for known common Asian code pages
   //////  Parameters:  numVal - the decimal value of the incoming byte.
   ////// 
   //////  From:      Rick Fillman Jan 2002 for Asian.
   FUNCTION DBCSisLeadByte( nByteD )
      local retVal
      retVal = false
      if '932' $ _app.charset // Japanese Shift-JIS
                              // lead-byte range (non-Contiguous) 
                              //  81h - 9Fh and then E0h - FCh
           retVal = ( nByteD >= 129 .and. nByteD <= 159 ) .or.; 
                    ( nByteD >= 224 .and. nByteD <= 239 )
      elseif '936' $ _app.charset //  Simplified Chinese
                              //  lead byte range A1h - FEh
           retVal = ( nByteD >= 161 .and. nByteD <= 254 )
      elseif '949' $ _app.charset // Korean
                              // leadByte range 81h - FEh
           retVal = ( nByteD >= 129 .and. nByteD <= 254 )
      elseif '950' $ _app.charset // Traditional Chinese
                              // lead byte range A1h - FEh       
           retVal = ( nByteD >= 161 .and. nByteD <= 254 )
      endif
   Return retVal


ENDCLASS


//////////////////// Class QBDEIndexList ///////////////////////////////////
//	Purpose:				Retrieves the list of indexes and their descriptors        
//               		for a rowset.   
// Params: 				oRowset - The object of the rowset to retrieve indexes for.
///////////////////////////////////////////////////////////////////////////
CLASS QBDEIndexList ( oRowset ) of Array

	this.oRowset = oRowset

   // Descriptor information held in assocArrays - index name stored in upper case
   // For example: aUnique['INDEXNAME'] = true
   this.aName 			= new assocArray()	//	name descriptors
   this.aTagName		= new assocArray()	// tag name descriptors (dBASE & FoxPro)
   this.aPrimary 		= new assocArray()	//	primary descriptors
   this.aUnique 		= new assocArray()	//	unique descriptors
   this.aDescend 		= new assocArray()	//	descend descriptors
   this.aExpression	= new assocArray()	//	expression descriptors
   this.aFields		= new assocArray()	// field numbers descriptors
   //QAID: 5433 - D.Brooks - 07/20/2005
   this.aCondition	= new assocArray()	// subset condition descriptors

   this.GetIndexList()


   //////  Method:    	GetIndexList  /////////////////////////////////////
   //////  Purpose:   	Populates the list of indexes for the rowset with names
   //////					in upper case.
   FUNCTION GetIndexList 

   	local i, j, nResult, oIDXDesc, cIndexName

      aReturn = new Array()

      if type("dB_DbiSetProp") # "FP"
         extern CUSHORT dB_DbiSetProp( CHANDLE, CUINT, CUINT ) IDAPI32 ;
            from "DbiSetProp"
      endif

      if type("dB_DbiGetIndexDesc") # "FP"
      	extern CUSHORT dB_DbiGetIndexDesc( CHANDLE, CWORD, CPTR ) IDAPI32 ;
         	from "DbiGetIndexDesc"
      endif

      try 
      	set procedure to :coreshared:stbde32.cc additive
      	oIDXDesc = new sIDXDesc()

      	nResult = dB_DbiSetProp( this.oRowset.handle, curGETEXTENDEDINFO, 1 )

         i = 1

         // Loop through all indexes in the rowset and store descriptor information.
      	do while dB_DbiGetIndexDesc(this.oRowset.handle, i, oIDXDesc) = DBIERR_NONE
         	
            // If a tag name exists (dBASE or FoxPro), use that as the 
            // index name, otherwise use the name. 
				cIndexName = iif( not empty(oIDXDesc.szTagName()), ;
            						upper(oIDXDesc.szTagName()), ;
                              upper(oIDXDesc.szName()) )

            this.add(cIndexName)

            this.aName[cIndexName] = oIDXDesc.szName()
            this.aTagName[cIndexName] = oIDXDesc.szTagName()
            this.aPrimary[cIndexName] = oIDXDesc.bPrimary()
            this.aUnique[cIndexName] = oIDXDesc.bUnique()
            this.aDescend[cIndexName] = oIDXDesc.bDescending()
            this.aExpression[cIndexName] = iif( oIDXDesc.bExpIdx(), oIDXDesc.szKeyExp(), "" )
            //QAID: 5433 - D.Brooks - 07/20/2005
            this.aCondition[cIndexName] = iif( oIDXDesc.bSubset(), oIDXDesc.szKeyCond(), "" )

            // If index has fields descriptor, populate array of field numbers
            if oIDXDesc.iFldsInKey() > 0
               local aTemp
               aTemp = new Array()
             	for j=1 to oIDXDesc.iFldsInKey()
               	aTemp.add(oIDXDesc.aiKeyFld(j))				
               endfor
               this.aFields[cIndexName] = aTemp
               aTemp = null
            else
            	this.aFields[cIndexName] = null	
            endif

         	i++
         
      	enddo

      catch (exception e) 

      finally
         //clean up
         oIDXDesc = null
         close procedure :coreshared:stbde32.cc      
      endtry

		return aReturn


   //////  Method:    	GetName  //////////////////////////////////////////
   //////  Purpose:   	Given the name of an index, returns the index name 
   //////				   descriptor.
   //////  Parameters: 	cIndexName - The name of the index.
	FUNCTION GetName( cIndexName )

      return iif( this.aName.isKey(cIndexName), this.aName[cIndexName], "")


   //////  Method:    	GetName  //////////////////////////////////////////
   //////  Purpose:   	Given the name of an index, returns the index tag
   //////				   name descriptor.
   //////  Parameters: 	cIndexName - The tag name of the index (dBASE & FoxPro).
	FUNCTION GetTagName( cIndexName )

      return iif( this.aTagName.isKey(cIndexName), this.aTagName[cIndexName], "")


   //////  Method:    	GetPrimary  ///////////////////////////////////////
   //////  Purpose:   	Given the name of an index, returns the primary  
   //////				   descriptor.
   //////  Parameters: 	cIndexName - The name of the index.
   FUNCTION GetPrimary( cIndexName )

   	return iif( this.aPrimary.isKey(cIndexName), this.aPrimary[cIndexName], false)


   //////  Method:    	GetUnique  ////////////////////////////////////////
   //////  Purpose:   	Given the name of an index, returns the unique
   //////				   descriptor.
   //////  Parameters: 	cIndexName - The name of the index.
   FUNCTION GetUnique( cIndexName )

   	return iif( this.aUnique.isKey(cIndexName), this.aUnique[cIndexName], false)


   //////  Method:    	GetDescend  ///////////////////////////////////////
   //////  Purpose:   	Given the name of an index, returns the descend 
   //////				   descriptor.
   //////  Parameters: 	cIndexName - The name of the index.
   FUNCTION GetDescend( cIndexName )

   	return iif( this.aDescend.isKey(cIndexName), this.aDescend[cIndexName], false)


   //////  Method:    	GetExpression  ////////////////////////////////////
   //////  Purpose:   	Given the name of an index, returns the expression
   //////				   descriptor.
   //////  Parameters: 	cIndexName - The name of the index.
   FUNCTION GetExpression( cIndexName )

   	return iif( this.aExpression.isKey(cIndexName), this.aExpression[cIndexName], "")


   //////  Method:    	GetFields  ////////////////////////////////////////
   //////  Purpose:   	Given the name of an index, returns the array of
   //////				   field numbers descriptor.
   //////  Parameters: 	cIndexName - The name of the index.
   FUNCTION GetFields( cIndexName )

   	return iif( this.aFields.isKey(cIndexName), this.aFields[cIndexName], null)

   //////  Method:    	GetCondition  ////////////////////////////////////
   //////  Purpose:   	Given the name of an index, returns the subset 
   //////				   condition descriptor.
   //////  Parameters: 	cIndexName - The name of the index.
   FUNCTION GetCondition( cIndexName )

   	return iif( this.aCondition.isKey(cIndexName), this.aCondition[cIndexName], "")


ENDCLASS
