#include "oohg.ch"

// Estos INCLUDE son solo necesarios para este prg
#include "adordd.ch"
#include "Hbclass.ch"

#xcommand TRY  => BEGIN SEQUENCE WITH {| oErr | Break( oErr ) }
#xcommand CATCH [<!oErr!>] => RECOVER [USING <oErr>] <-oErr->
#xcommand FINALLY => ALWAYS

// Definir como PUBLICAS en MAIN()
MEMVAR oConn, hSql

Function Conectar(Servidor, Database, User, Pass, Engine)
*-----------------------------------------------------------------------------*
Local r, oDlg, oC
If !EMPTY(hSql)
	DEFAULT Servidor TO hSql["Servidor"]
	DEFAULT Database TO hSql["Database"]
	DEFAULT User 		TO hSql["User"]
	DEFAULT Pass 		TO hSql["Pass"]
	DEFAULT Engine 	TO hSql["Engine"]
Endif

	DEFINE WINDOW Config AT 147,142 OBJ oDlg WIDTH 540 HEIGHT 230 TITLE 'Datos Conexion ' ; 
		NOMAXIMIZE NOSIZE 

			r := 13
			@ r+=22,  5 LABEL "0" WIDTH 80 HEIGHT 12  RIGHTALIGN VALUE 'Servidor' TRANSPARENT
			@ r+=22, 5 LABEL "0" WIDTH 80 HEIGHT 12  RIGHTALIGN VALUE 'Database' TRANSPARENT 
			@ r+=22, 5 LABEL "0" WIDTH 80 HEIGHT 12  RIGHTALIGN VALUE 'Usuario' TRANSPARENT 
			@ r+=22, 5 LABEL "0" WIDTH 80 HEIGHT 12  RIGHTALIGN VALUE 'Contraseña' TRANSPARENT 

			r := 13

			@ r+=20, 110 TEXTBOX Servidor  WIDTH 180 VALUE Servidor
			@ r, 300 COMBOBOX Engine WIDTH 80 ;
				ITEMS {'MYSQL','MSSQL', "MariaDB", "SQLite", "DBase", "Excel"} ;
				VALUESOURCE ({'MYSQL','MSSQL',"MARIADB","SQLITE","DBASE","EXCEL"}) ;
				VALUE Engine
			@ r+=24, 110 TEXTBOX Database  WIDTH 180 HEIGHT 20 VALUE Database
			@ r+=22, 110 TEXTBOX User  WIDTH 180 HEIGHT 20 VALUE User
			@ r+=22, 110 TEXTBOX Pass  WIDTH 180 HEIGHT 20 VALUE Pass

		@ 15, 445 BUTTON OK 		CAPTION 'Conectar' WIDTH 80 HEIGHT 28 ;
			ACTION {|| 	GuardaInfo(oDlg),;			// se puede quitar GuardaIni() si no desean guardar la información
							oC := AdoConn(oDlg:Servidor:Value, oDlg:Database:Value, oDlg:User:Value, oDlg:Pass:Value, oDlg:Engine:Value),;
							oDlg:Release()}
		@ 45, 445 BUTTON Cancel CAPTION 'Salir' 	WIDTH 80 HEIGHT 28 ACTION {|| oDlg:Release()} CANCEL   
	END WINDOW
	oDlg:Activate()
	
Return oC

Procedure GuardaInfo(oDlg)

	hSql := hb_IniNew(.F.)
	hSql["Servidor"] 	:= oDlg:Servidor:Value
	hSql["Database"] 	:= oDlg:Database:Value
	hSql["User"] 		:= oDlg:User:Value
	hSql["Pass"] 		:= oDlg:Pass:Value
	hSql["Engine"] 	:= oDlg:Engine:Value
//	hSql["Port"] 		:= oDlg:Port:Value
Return


/* Funcion AdoConn realiza la conexion con el servidor
Parametros:
Server = Puede ser una direccion www.xxx.com o una IP o LocalHost
Database = Opcional - Nombre de la base de datos que deseen abrir
User = Nombre de usuario del servidor SQL
Pass = Contraseña del usuario
Port = Opcional - Puerto de acceso
Engine = Motor SQL a usar (MSSQL, MYSQL, MARIADB, etc) 
Nota: Yo uso MariaDB sin embargo conecto con MySQL. Parece ser mas avanzada su interface.
		Para usar MySql o MariaDb se debe descargar e instalar las interfaces ODBC 
			eg: mysql-connector-odbc-5.3.9-win32.msi
*/
Function AdoConn(Server, Database, User, Pass, Engine, Port)
Local oC, sConection, nOption

	oC := win_oleCreateObject( "ADODB.Connection" )
	oC:CursorLocation := adUseClient
	oC:ConnectionTimeout := 10
	oC:CommandTimeOut := 15 //30

	DO CASE
	CASE Engine  == "MARIADB"
		nOption := 3+4194304+67108864	// dont know + auto_reconect + multiple_statementes_allowed
		sconection := "Driver={MariaDB ODBC 3.1 Driver};" + ;
				"Server=" + Server + ;
				";Database=" + Database + ;
				";User=" + user + ;
				";Password=" + pass + ;
				";Option="+str(INT(nOption))+if(Port <> NIL, ";Port="+Port ,";")
	
	CASE Engine  == "MYSQL"
		nOption := 3+4194304+67108864	// dont know + auto_reconect + multiple_statementes_allowed
		sconection := "Provider=MSDASQL;Driver={MySQL ODBC 5.3 ANSI Driver};" + ;
							"Server=" + Server + ;
							";Database=" + Database + ;
							";User=" + User + ;
							";Password=" + Pass + ;
							";Option="+str(nOption)+if(Port <> NIL, ";Port="+Port ,"") +;
							";UseCompression=True;Pooling=True;"

	CASE Engine == "MSSQL"
		sconection := "Provider=SQLOLEDB;" + ;			//"Provider=SQLNCLI10;"  
							"server=" + server + if(Port <> NIL, ","+Port ,"") +;
							";database=" + if(EMPTY(database), "master", database) + ;
							iif(EMPTY(User), ";Integrated Security=SSPI;",";uid=" + User + ";pwd=" + Pass )
				
	CASE Engine == "SQLITE"		
		sconection := "Driver=SQLite3 ODBC Driver" + ;
							";Database=" + Database   + ;
							";LongNames=0;Timeout=1000;NoTXN=0;SyncPragma=NORMAL;StepAPI=0;" 
							
	CASE Engine == "DBASE"
// oConexion:Open('Provider=Microsoft.Jet.OLEDB.4.0;Data Source="e:\jirehw\kor_2018\nueva carpeta\";Extended Properties=dBASE IV;User ID=Admin;Password=;')  // Standard Este conecta

		sconection := "Provider=Microsoft.Jet.OLEDB.4.0"+;
							";Extended Properties=dBASE IV"+;
							If(!EMPTY(Server), 	";Server="+Server,"")+;
							If(!EMPTY(Database),	";Data Source="+Database,"")+;
							If(!EMPTY(User),		";User ID="+User,"")+;	//Admin
							If(!EMPTY(Pass),		";Password=;","")
	
	CASE Engine == "EXCEL"

		sconection := "Driver={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};DBQ="+Database+";"
		/*"Provider=Microsoft.Jet.OLEDB.4.0"+;
							If(!EMPTY(Server), 	";Server="+Server,"")+;
							";Extended Properties="+'"Excel 8.0"'
							If(!EMPTY(Database),	";Data Source="+Database,"")+;
							If(!EMPTY(User),		";User ID="+User,"")+;	//Admin
							If(!EMPTY(Pass),		";Password=;","")	*/
	ENDCASE
//	msgbox(sconection)
	oC:ConnectionString := sconection
	TRY
		oC:Open()
		If !(oC:State > 0)
			Msgbox("No se realizó la conexión")			
		Endif
		
	CATCH
		ADOError(oC, Database, Engine)
	END SEQUENCE 
Return oC

Procedure ADOError(oC, Database, Engine)
		IF Engine == "MYSQL"		// A mi experiencia, NativeError lo maneja cada SQL en forma distinta.
			if oC:Errors[0]:NativeError = 160	.or. oC:Errors(0):NativeError = 0	// no existe el ODBC
					MsgStop("Falta de complementos ODBC")
			Elseif oC:Errors[0]:NativeError = 1045
				MsgStop("Error en nombre de usuario o contraseña")
			Elseif oC:Errors[0]:NativeError = 1049		// Base de datos no existe
				MsgStop("Base de Datos "+Database+" no existe", "Error")
			Else
				msgbox("Native Error "+hb_CStr(oC:Errors[0]:NativeError)+Hb_OsNewLine()+oC:Errors[0]:Description,"Error" )
			Endif
		Else
				msgbox("Native Error "+hb_CStr(oC:Errors[0]:NativeError)+Hb_OsNewLine()+oC:Errors[0]:Description,"Error" )
*			msgbox(oC:Errors[0]:Description,"Native Error "+hb_CStr(oC:Errors[0]:NativeError) )
		Endif
Return

*-----------------------------------------------------------------------------*
/* Function Ado(<SQL command>, {<ADO conection to use>})
Crea un recordset con los registros solicitados por cSql
Para mas información ver https://www.w3schools.com/asp/ado_ref_recordset.asp
Es importante ver los usos de CursorLocation, CursorType y LockType
Cuando CursorLocation = adUserCliente el único CursorType que puede ser usado es adOpenStatic
*/
Function Ado(cSql, oC)
*-----------------------------------------------------------------------------*
Local oRs
DEFAULT oC TO oConn

	oRs := win_oleCreateObject( "ADODB.Recordset" )
	oRs:CursorLocation 	:= adUseClient					// adUseServer
	oRs:CacheSize 			:= 50
	oRs:LockType 			:= adLockBatchOptimistic	// 1 adLockReadOnly 2 adLockPessimistic, 3 adLockOptimistic, 4 adLockBatchOptimistic
	oRs:CursorType 		:= adOpenStatic				// adOpenDynamic(2) adOpenStatic(3) adOpenKeyset(1) adOpenForwardOnly(0)
	oRs:ActiveConnection := oC
	
	oRs:Open( cSql )	 
Return oRs

*-----------------------------------------------------------------------------*
/* Esta función regresa en un array multidimensional los valores de los campos solicitados en cSQL 
Eg: GetRows("SELECT nombre, clave FROM departamentos")   util para COMBOBOX
=> { {nombre1, nombre2, ...}, {clave1, clave2, ...} }

La funcion aTranspose cambia el array al contrario de como está.
Si lToRec lo cambia a forma registro para que cada arreglo sea como un registro
Eg: GetRows("SELECT nombre, clave FROM departamentos", .T.)   util para GRID
=> { {nombre1, clave1}, {nombre2, clave2}, ...}
*/
Function GetRows(cSql, lToRec, oC)
*-----------------------------------------------------------------------------*
Local aArray, oRs 
DEFAULT lToRec TO .F.
DEFAULT oC TO oConn
	
	oRs := oC:Execute(cSql)
	If oRs:Eof
		aArray := ARRAY(oRs:Fields:Count(),1)
	Else
		aArray := oRs:GetRows()
	Endif
	oRs:Close()
Return If(lToRec, aTranspose(aArray), aArray )

*-----------------------------------------------------------------------------*
Function aTranspose(aArray)		// Cambia formato de array Eg, {3,2} a {2,3}
*-----------------------------------------------------------------------------*
Local aArray2 
	If LEN(aArray) > 0
		aArray2:= ARRAY(LEN(aArray[1]),0)
		AEVAL(aArray, {|a| AEVAL(a, {|b,x| AADD(aArray2[x],b) }) })
	Else
		aArray2 := aArray
	Endif
Return aArray2

*-----------------------------------------------------------------------------*
Function ADO_Value(cStr, oC)
*-----------------------------------------------------------------------------*
Local oRs, xRet
DEFAULT oC TO oConn
	oRs := oC:Execute(cStr)
	If oRs:Eof
		xRet := ADO_FIELDSTRUCT( oRs, 0 )[5]
	Else
		xRet := oRs:Fields[0]:Value
	Endif
	oRs:Close()
Return xRet

/*-----------------------------------------------------------------------------
Funcion que simplifica la forma de llamar a la clase DBADO.
Igualmente pueden usar oRs := DBADO():New("SELECT * FROM clientes") o ADODB("SELECT * FROM clientes")
-----------------------------------------------------------------------------*/
Function ADODB(cSql, oC)
Return DBADO():New(cSql, oC)

/*-----------------------------------------------------------------------------*
Clase que maneja el RecordSet con funciones similares de DBF
*-----------------------------------------------------------------------------*/
CLASS DBAdo 
	DATA oRs
	DATA cAlias__ INIT nil
	DATA STRUCT
	
	VAR 	State				IS State TO oRs
	VAR 	Status			IS Status TO oRs 	// OJO NO DEFINIR CAMPO EN TABLAS LLAMADO status
	VAR 	Filter			IS	Filter TO oRs
	VAR	cSQl				IS Source TO oRs
	
	METHOD 	New( cSql, oC )	INLINE 	::oRs := ADO(cSql, oC), ::STRUCT := ADOSTRUCT(::oRs), Self
	MESSAGE 	Append				INLINE 	::oRs:AddNew()
	MESSAGE 	BOF					TO			oRs
	METHOD 	CancelUpdate		INLINE 	IF(!::oRs:BOF .and. !::oRs:EOF, ::oRs:CancelBatch(), ::GoTop())
	METHOD   DBEVAL(bBlock, bFor, bWhile, nNextRecords, nRecord, lRest) 
	MESSAGE 	Delete				TO			oRs
	MESSAGE 	EOF					TO			oRs
   METHOD 	FCount()			INLINE		::oRs:Fields:Count()
   METHOD 	FieldBlock(n)		INLINE		ADOBlock(Self, n)
   METHOD 	FieldGet(n)		INLINE		::oRs:Fields(n-1):Value
   METHOD 	FieldName(n) 		INLINE 	::oRs:Fields( n-1 ):Name             // Field's name
	METHOD	FieldPos(c)		
	METHOD	FieldPut(n, x)		INLINE		::oRs:Fields(n-1):Value := x
	MESSAGE 	Find(x)			TO			oRs
	MESSAGE 	GetRows()			TO			oRs
	METHOD 	GoTo(n)			INLINE 	if( n>=1 .and. n<= ::oRs:RecordCount, ::oRs:AbsolutePosition :=  n, )
	METHOD 	GoTop				INLINE 	if(::oRs:RecordCount > 0, ::oRs:MoveFirst(), )
	METHOD 	GoBottom			INLINE 	if(::oRs:RecordCount > 0, ::oRs:MoveLast(), )
	METHOD 	OrdKeyCount		INLINE 	::oRs:RecordCount
	METHOD 	OrdKeyGoTo(n)		INLINE 	::oRs:AbsolutePosition := n
	METHOD 	OrdKeyNo			INLINE 	::oRs:AbsolutePosition 
	METHOD 	RecCount			INLINE 	::oRs:RecordCount
	METHOD 	Refresh(cSql)	
	MESSAGE 	Requery			INLINE 	::oRs:Requery()
	METHOD 	RecNo				INLINE 	if( ::oRs:AbsolutePosition < 1, ::oRs:RecordCount, ::oRs:AbsolutePosition )
	METHOD 	SKIP(n)			INLINE 	n := IF(HB_ISNil(n), 1, n), ::oRs:Move(n)
	METHOD 	Skipper(nSkip)
	METHOD 	Update()			
	METHOD 	Close				INLINE 	::oRs:Close(), ::oRs := NIL, Self := NIL

   ERROR HANDLER FieldAssign


ENDCLASS

*-----------------------------------------------------------------------------*
METHOD Procedure DBEVAL(bBlock, bFor, bWhile, nNextRecords, nRecord, lRest) CLASS DBADO
*-----------------------------------------------------------------------------*
Local n := 0, nRec := ::Recno()
	DEFAULT bBlock TO {|| .T.}
	DEFAULT bFor TO {|| .T. }
	DEFAULT bWhile TO {|| .T.}
	DEFAULT nNextRecords TO 0
	DEFAULT nRecord TO 0
	DEFAULT lRest TO .F.

	IF !lRest
		::GoTop()
	ENDIF
	WHILE !::oRs:EOF .and. n <= nNextRecords .and. EVAL(bWhile, Self)
		// Omitir registros borrados pero no actualizados
		If (::oRs:EditMode = 4 .or. ::oRs:Status = adRecDeleted .or. ::oRs:Status = adRecDBDeleted)
			::oRs:Move(1)
			Loop
		Endif

		IF EVAL(bFor, Self)
			EVAL(bBlock, Self)
		ENDIF
		::oRs:Move(1)
		IF nNextRecords > 0
			n++
		ENDIF
	ENDDO
	::Goto(nRec)
Return


*-----------------------------------------------------------------------------*
/* Este Método permite adquirir y asignar valores de la siguiente manera:
oCli := DBADO():New("SELECT * FROM clientes")
Clave := oCli:Clave
oCli:Clave := "123"
*/
METHOD FieldAssign( xValue ) CLASS DBADO
*-----------------------------------------------------------------------------*
LOCAL nPos := 0, cMessage, uRet, oRs, n

	oRs := ::oRs	// Es un poco más rápido usar una variable normal que una de instancia
	cMessage := __GetMessage()
	If LEFT(cMessage,1) == "_"
		cMessage := SUBSTR( cMessage, 2 )
	Endif
	For n := 1 TO oRs:Fields:Count()
		If UPPER(oRs:Fields(n-1):Name) == cMessage
			nPos := n 
			EXIT
		Endif
	Next
		
	If nPos > 0
		If PCOUNT() == 0		// Regresa valor del campo de la tabla
			If oRs:BOF .or. oRs:EOF .or. oRs:RecordCount = 0 
				uRet := ADO_FIELDSTRUCT( oRs, nPos-1 )[5]				// Asigna valor por dBase por defecto a
			Else
				uRet := oRs:Fields(nPos-1):Value
			Endif
		Else						// Asigna valor al campo de la tabla
			oRs:Fields(nPos-1):Value := xValue
		Endif
	Else
		If pCount() = 1
			oRs:&cMessage(xValue)
		Else
			oRs:&cMessage()
		Endif
//		::MsgNotFound( "RecordSet:"+cMessage )
		uRet := NIL
	Endif
RETURN uRet

*-----------------------------------------------------------------------------*
METHOD Function FieldPos(c) CLASS DBADO
*-----------------------------------------------------------------------------*
Return ASCAN(::Struct, {|a| a[1] == UPPER(c) })

*-----------------------------------------------------------------------------*
METHOD Procedure Refresh(cSql) CLASS DBADO
*-----------------------------------------------------------------------------*
DEFAULT cSql TO ::oRs:Source
	IF ::oRs:State = 0	// RecordSet is closed
		::oRs:Open(cSql)
	Else
		::oRs:Close()
		::oRs:Open(cSql)
	Endif
Return

*-----------------------------------------------------------------------------*
METHOD Update() CLASS DBADO
*-----------------------------------------------------------------------------*
Local oErr, lRet := .T., OleError
	TRY
		IF ::oRs:LockType = adLockBatchOptimistic
			If !::EOF
				::oRs:UpdateBatch()
			Endif
		ELSE
			::oRs:Update()
		ENDIF
	CATCH oErr
//		lRet := MyDefError(oErr, .F., self)  // Mi manejadr de errores
		OleError := ::oRs:ActiveConnection:Errors[0]
		MsgStop(oErr:Description+hb_OsNewLine()+"Native Error "+STR(OleError:NativeError),"Error al guardar")
		lRet := .F.
	END
Return lRet

*-----------------------------------------------------------------------------*
METHOD Function Skipper(nSkip) CLASS DBADO
*-----------------------------------------------------------------------------*
Local n := 0,  oRs
	oRs := ::oRs

	// Esto es necesario para cuando el recordset está vacio, de lo contrario da error
	IF (oRs:EOF .and. nSkip > 0) .or. (oRs:BOF .and. nSkip < 0)
		Return 0
	ENDIF
	// Fin necesario
	
	While nSkip <> n
		IF nSkip > 0 
			oRs:Move( 1 )
			If oRs:EOF
				oRs:Move(-1)	// Enero/2020
				exit
			// si está borrardo y aun no aceptada
			ElseIf (oRs:EditMode = 4 .or. oRs:Status = adRecDeleted .or. oRs:Status = adRecDBDeleted)			// usar con LockType := adLockBatchOptimistic
			Else
				n ++
			Endif
		ELSE	//IF nSkip < 0 .AND. !oRs:Bof
			oRs:Move(-1)
			IF oRs:BOF		
				oRs:Move(1)
				Exit
			ElseIf (oRs:EditMode = 4 .or. oRs:Status = adRecDeleted .or. oRs:Status = adRecDBDeleted)			// usar con LockType := adLockBatchOptimistic
			Else
				n --
			Endif
		ENDIF
	ENDDO
Return n		

*-----------------------------------------------------------------------------*
Function ADOBlock( Self, nPos ) //CLASS ADO
*-----------------------------------------------------------------------------*
Local Block
	IF HB_ISNUMERIC(nPos)
		Block := { | x | IF( PCOUNT() > 0, ;
			if(::Struct[nPos][2] = 'D' .and. EMPTY(x), ;
				(::oRs:Fields( nPos-1 ):Value := CTOD("0000-00-00")), ::oRs:Fields( nPos-1 ):Value := x),;
			::oRs:Fields( nPos-1 ):Value ) }
	ELSE
		Block := { | x | IF( PCOUNT() > 0, ::oRs:Fields( (nPos) ):Value := x, ::oRs:Fields( (nPos) ):Value ) }
	ENDIF
Return Block

FUNCTION ADOSTRUCT( oRs )
LOCAL aStruct  := {}, n
   FOR n := 1 TO oRs:Fields:Count()
      AADD( aStruct, ADO_FIELDSTRUCT( oRs, n-1 ) )
   NEXT
RETURN aStruct

FUNCTION ADO_FIELDSTRUCT( oRs, n ) // ( oRs, nFld ) where nFld is 0 based
                                    // ( oRs, oField ) or ( oRs, cFldName )
                                    // ( oField )

LOCAL oField, nType, xDefault
LOCAL cType := 'C', nLen := 10, nDec := 0 //, lRW := .t.,nDBFFieldType :=  HB_FT_STRING // default
LOCAL nFWAdoMemoSizeThreshold := 255

   oField      := oRs:Fields( n )
   nType       := oField:Type

	SWITCH nType
   CASE adBoolean
      cType    := 'L'
      nLen     := 1
		xDefault := .F.
		EXIT
	CASE adDate
	CASE adDBDate
	CASE adDBTime
	CASE adDBTimeStamp
      cType    := 'D'
      nLen     := IF( nType=adDBTimeStamp, 8, oField:DefinedSize)
		xDefault := CTOD("00/00/0000")
		EXIT

	CASE adTinyInt
	CASE adSmallInt
	CASE adInteger
	CASE adBigInt
	CASE adUnsignedTinyInt
	CASE adUnsignedSmallInt
	CASE adUnsignedInt
	CASE adUnsignedBigInt
		cType    := 'N'
      nLen     := IF(nType=adUnsignedTinyInt, 1, oField:Precision )//+ 1  // added 1 for - symbol
		xDefault := 0
		EXIT
	CASE adSingle
	CASE adDouble
      cType    := 'N'
      nLen     := Min( 19, oField:Precision  ) //Max( 19, oField:Precision + 2 )
      nDec  := 2
		xDefault := 0
		EXIT
	CASE adCurrency
      cType    := 'N'      // 'Y'
      nLen     := Min( 19, oField:Precision  )  //19
      nDec     := 2
		xDefault := 0
		EXIT
	CASE adDecimal
	CASE adNumeric
	CASE adVarNumeric
      cType    := 'N'
      nLen     :=  Min( 19, oField:Precision  ) //Max( 19, oField:Precision + 2 )

      IF oField:NumericScale > 0 .AND. oField:NumericScale < nLen
         nDec  := oField:NumericScale
      ENDIF
		xDefault := 0
		EXIT
	CASE adBSTR
	CASE adChar
	CASE adVarChar
	CASE adLongVarChar
	CASE adWChar
	CASE adVarWChar
	CASE adLongVarWChar
      nLen     := oField:DefinedSize
      cType := "C"

      IF nType != adChar .AND. nType != adWChar .AND. nLen > nFWAdoMemoSizeThreshold
         cType := 'M'
         nLen  := 10
      ENDIF
		xDefault := ""
		EXIT
	CASE adBinary
	CASE adVarBinary
	CASE adLongVarBinary
      nLen     := oField:DefinedSize
      IF nType != adBinary .AND. nLen > nFWAdoMemoSizeThreshold
         cType := 'B'
         nLen  := 10
      ENDIF
		xDefault := ""
		EXIT
	CASE adChapter
	CASE adPropVariant
      cType    := 'O'
		xDefault := ""
		EXIT
	CASE adVariant
	CASE adIUnknown
      cType := "V"
		xDefault := ""
		EXIT
	CASE adGUID
		xDefault := ""
		EXIT
		
	CASE adFileTime
      cType := "T"
		xDefault := ""

	END
RETURN { UPPER(oField:Name), cType, nLen, nDec, xDefault }
