Alaska Software Inc. - Xbase++ meet ArangoDB
Username: Password:
AuthorTopic: Xbase++ meet ArangoDB
Osvaldo RamirezXbase++ meet ArangoDB
on Wed, 08 Dec 2021 11:49:46 -0700
Hello guys

I want to shared some of my code, this is a little code that I am 
working on, It is not finish yet and there is not exist documentation yet!


If some one found usefull, and want to see it in action, let me know
and I can do an skype meeting.

Note: It work with Xbase++ 1.9/2.0 using some stuffs from Pablo Botella.

Regards
Osvaldo Ramirez


///
/// Autor : Osvaldo Ramirez
/// Arango Class

#include "xbp.ch"
#include "common.ch"


CLASS ArangoDB

    EXPORTED:

       VAR url           string
       VAR port          numeric
       VAR jwt           string
       VAR http          string
       VAR user          string
       VAR password      strgin
       VAR LastResponse  Last response string of the last call
       VAR LastStatus    Last response string of the last call
       VAR currentDB

       METHOD init()    Create Objet

       METHOD login

       METHOD CreateDatabase
       METHOD CurrentDatabase        Current Database
       METHOD ListDatabase
       METHOD SetDatabase

       METHOD CreateCollection       Create Table = DBCREATE()
       METHOD DropCollection         Drop Table   = DELETEFILE
       METHOD TruncateCollection     Truncate Table  = DELETE ALL
       METHOD ImportDBF

       METHOD InsertDocument     Insert Record
       METHOD ReplaceDocument     Update Record
       METHOD UpdateDocument     Delete Record
       METHOD RemoveDocument     Delete Record
       METHOD QueryDocument     Delete Record

       METHOD execute

ENDCLASS


 Init class, this method is execute when the class is initialized ():new()
METHOD ArangoDB:init(cURL ,nPort )

    DEFAULT cURL      TO "http://127.0.0.1"
    DEFAULT nPort     TO 8529

    ::url   := cURL
    ::Port  := nPort
    ::jwt   := ""

RETURN SELF

 This method is reposible of all call api function,
METHOD ArangoDB:execute( cMetodo , cURL , cJson )
* Begin

   local lError := .f.
   local oHttp   we declared local becuase every http call, it finish

   if  valtype( cJson ) = 'O'     Si the cJson is an object, the we 
cast it to character type,
     cJson := json_serialize( cJson )
   endif

   oHttp := TServerXMLHTTPRequest():New()    /// Pablo Botella
   oHttp:Open( cMetodo , cUrl , .F. )
   oHttp:SetReQuestHeader( 'Connection', 'Keep-Alive')

   if ! ( "_open/auth" $ cURL )  If the cAPI is not login, then we use 
the jwt, previusley got from login.
     oHttp:SetReQuestHeader( 'Authorization'   , 'Bearer ' + ::jwt )
   endif

   oHttp:send( cJson )

    Save the response
   ::LastResponse  := json_unserialize( oHttp:responseText , @lError)
   ::LastStatus    := oHttp:status

RETURN ::LastStatus


   /*
   {
   "username": "root",
   "password": ""
   }
   */

METHOD ArangoDB:login(cUser,cPassword)
* Begin

   local oJson

   oJson  := json_Container():New()     Pablo Botella Wrapper to 
create a Json file
   oJson:username := cuser
   oJson:password := cPassword

   if ::execute( "POST" , ::url + ":" + alltrim( var2char(::Port)) + 
"/_open/auth" , oJson ) ==  200
       ::jwt := ::LastResponse:jwt
   endif

RETURN ( ::LastStatus == 200 )




 GET /_api/database/current
 Se graba un json en currentDB y solo regresa name del result
//{
  "error" : false,
  "code" : 200,
  "result" : {
    "id" : "1",
    "name" : "_system",
    "isSystem" : true,
    "path" : "none"
  }
//}

METHOD ArangoDB:CurrentDatabase(lReConnect)
* Begin
   local cAPI    := "/_api/database/current"

   default lReConnect  TO .f.

   if lReConnect .or. ::CurrentDB = NIL
     ::CurrentDB := NIL
     ::execute( "GET" ,  cAPI , NIL )
     if valtype( ::LastResponse:error ) = 'O'
       IF .NOT. ::LastResponse:error
         ::CurrentDB := ::LastResponse:result:name
       ENDIF
     endif
   endif
RETURN ::currentDB

/// POST "/_api/collection"
///
/// Sample
///
///   {
///     "name" : "testCollectionUsers",
///     "keyOptions" : {
///       "type" : "autoincrement",
///       "increment" : 5,
///       "allowUserKeys" : true
///    }
///   }

METHOD ArangoDB:CreateCollection( cCollectionName )

   local oJson
   local oKeyOptions

   oKeyOptions := json_Container():New()
   oKeyOptions:type := "autoincrement"
   oKeyOptions:increment := 5
   oKeyOptions:allowUserKeys := .t.

   oJson            := json_Container():New()     Pablo Botella Wrapper
   oJson:name       := cCollectionName
   oJson:keyOptions := oKeyOptions

   ::execute( "POST" , ::url + ":" + alltrim( var2char(::Port)) + 
"/_db/" + ::currentDB+"/_api/collection" , oJson )


RETURN ( ::LastStatus == 200 .or. ::LastStatus = 409 )

METHOD ArangoDB:DropCollection( cCollectionName )

   ::execute( "DELETE" , ::url + ":" + alltrim( var2char(::Port)) + 
"/_db/" + ::currentDB+"/_api/collection/" + cCollectionName )

RETURN ( ::LastStatus == 200 .or. ::LastStatus = 409 )

METHOD ArangoDB:TruncateCollection( cCollectionName )

   ::execute( "PUT" , ::url + ":" + alltrim( var2char(::Port)) + "/_db/" 
+ ::currentDB+"/_api/collection/" + cCollectionName )

RETURN ( ::LastStatus == 200 .or. ::LastStatus = 409 )

/// POST
///
///_api/database
///
/// Json Sample ...
///
/// {
///  "name" : "mydb8",
///  "users" : [
///    {
///      "username" : "admin",
///      "passwd" : "secret",
///      "active" : true
///    },
///    {
///      "username" : "tester",
///      "passwd" : "test001",
///      "active" : false
///    }
///  ]
///}
///
/// Tutorial
///
/// 
https://www.arangodb.com/docs/stable/http/database-database-management.html
///
/// This function is the same that execute function, just return True o 
False, but and execute return the status code
METHOD ArangoDB:CreateDatabase( cJson )
RETURN ( ::execute( "POST" ,::url + ":" + alltrim( var2char(::Port)) + 
"/_api/database" , cJson ) == 201)

METHOD ArangoDB:ListDatabase()
   ::execute( "GET" , ::url + ":" + alltrim( var2char(::Port)) + 
"/_api/database" )
RETURN ::LastResponse:result

METHOD ArangoDB:SetDatabase(cDB)

   local lReturn  := .f.
   local aListDB := ::ListDatabase()

   if valtype( aListDB ) = 'A'
     if ascan( aListDB , cDB ) > 0   Si existe la base de datos, por 
lo tanto la ponemos como actual.
        ::CurrentDB := cDB
        lReturn := .t.
     endif
   endif

RETURN lReturn


METHOD ArangoDB:InsertDocument( cTable, cJsonDocument )

   ::execute( "POST" , ::url + ":" + alltrim( var2char(::Port)) + 
"/_db/" + ::currentDB+"/_api/document/"+cTable , cJsonDocument )

RETURN  ::LastStatus

/// xCriteria, Could be the document "_key"
/// replaceDocument es diferente a updatedocumento, ver la ayuda para un 
mejor entendimiento
METHOD ArangoDB:ReplaceDocument( cTable, cJsonDocument , nKey )

   ::execute( "PUT" , ::url + ":" + alltrim( var2char(::Port)) + "/_db/" 
+ ::currentDB+"/_api/document/"+cTable+"/"+var2char(nKey) , cJsonDocument )

RETURN ( ::LastStatus == 201 .or. ::LastStatus == 202 )

/// Ver la documentaion, ya que update, puede ser usando para inserta 
tambien otro json dentro del json
/// ?mergeObjects=false or ?mergeObjects=true
/// Tambien ver estos parametros ?returnOld=true, ?returnNew=true
/// Ya que un update nos puede arrojar el antiguo valor o el nuevo valor 
y con esto saber si se inserto bien, aparte del status

METHOD ArangoDB:UpdateDocument( cTable, cJsonDocument , nKey )

   ::execute( "UPDATE" , ::url + ":" + alltrim( var2char(::Port)) + 
"/_db/" + ::currentDB+"/_api/document/"+cTable+"/"+var2char(nKey) , 
cJsonDocument )

RETURN ( ::LastStatus == 201 .or. ::LastStatus == 202 )

METHOD ArangoDB:RemoveDocument( cTable, nKey )

   ::execute( "DELETE" , ::url + ":" + alltrim( var2char(::Port)) + 
"/_db/" + ::currentDB+"/_api/document/"+cTable+"/"+var2char(nKey)  )

RETURN ( ::LastStatus == 200 .or. ::LastStatus == 202 )


/// { "query" : "FOR u IN contactos LIMIT 5 RETURN u", "count" : true, 
"batchSize" : 2 }
/// las respuesta puede tener el valor hasmore = true y tambien tien un 
Id, es el el cual podemos usar para recuperar el cursor
METHOD ArangoDB:QueryDocument( xCriteria )

   if valtype( xCriteria ) ='N'  Signfica que ya hubo una busqueda y 
se requiere mas datos del cursor , PAGINACION
     ::execute( "POST" , ::url + ":" + alltrim( var2char(::Port)) + 
"/_db/" + ::currentDB+"/_api/cursor/"+var2char(xCriteria)  )
   elseif valtype( xCriteria ) $ 'OC'   si es un json o un caracter, 
entonces apenas vamos a hacer el query, PAGINACION
     ::execute( "POST" , ::url + ":" + alltrim( var2char(::Port)) + 
"/_db/" + ::currentDB+"/_api/cursor" , xCriteria  )
   endif

RETURN  ::LastResponse



/// { "query" : "FOR u IN contactos LIMIT 5 RETURN u", "count" : true, 
"batchSize" : 2 }
/// las respuesta puede tener el valor hasmore = true y tambien tien un 
Id, es el el cual podemos usar para recuperar el cursor
METHOD ArangoDB:ImportDBF( cDBF , cCollectionName )
* Begin
   local  nFields
   local  aFields
   local  oJsonTemplate
   local  oJson
   local  xValue , i
   local  lReturn := .f.

   DEFAULT cCollectionName TO ""

   if FExists( cDBF )

     IF ! empty( cCollectionName )
        cCollectionName := lower( cCollectionName )
        SELECT 0
        use ( cDBF ) SHARED alias ( cCollectionName )
     ELSE
        SELECT 0
        use ( cDBF ) SHARED
        cCollectionName := lower(  alias() )
     ENDIF

     IF neterr()
        RETURN .f.
     ENDIF

     aFields := dbstruct()
     nFields := len( aFields )


      Creamos la tabla
     if ::CreateCollection(cCollectionName)

         Preparamos un json vacio con la estructura
        oJsonTemplate  := json_Container():New()     Pablo Botella 
Wrapper to create a Json file
        for i := 1 to nFields
           oJsonTemplate:&(lower(aFields[i][1])) := NIL
        next

        do while ! ( cCollectionName )->(eof())
           oJson := oJsonTemplate

           for i := 1 to nFields

             xValue := &( cCollectionName + "->"+aFields[i][1])

             do case
               case aFields[i][2] = 'C'
                  xValue := alltrim(xValue)
               case aFields[i][2] = 'D'
                   xValue := dtos( xValue )
               case aFields[i][2] = 'L'
                   xValue := iif( xvalue , "TRUE","FALSE" )
             endcase

             oJson:&(lower(aFields[i][1])) := xValue

           next

           ::InsertDocument(cCollectionName, json_serialize( oJson ))

           (cCollectionName)->(DbSkip(1))
        enddo

        lReturn := .t.

     endif
     close all// ( cCollectionName )
   endif

RETURN lReturn