github.com/mgulsoy/arnedb@v1.2.2-alpha/README.md (about)

     1  # ArneDB
     2  ArneDB is an embedded document (NoSql) database. There is no server implementation.
     3  Just import the package and GO.
     4  
     5  ArneDB is not a high-performance database. There are a lot of database products which
     6  can achieve high performance. Arnedb provides a lightweight database implementation
     7  which is embeddable in any GO app.
     8  
     9  The design goals of Arnedb are:
    10  
    11  * Low memory usage: Can be run in resource constrained environments
    12  * Simplicity: There are only 10 functions.
    13  * Text file storage: All the data is stored in text based JSON files
    14  
    15  ### Minimum Go Version
    16  
    17  Go 1.19+
    18  
    19  ## Overview
    20  
    21  * [Installation](#installation)
    22  * [Usage](#usage)
    23      * [Db Management](#db-management)
    24      * [Collection Operations And Query](#collection-operations-and-query)
    25          * [Adding Documents](#adding-documents)
    26          * [Querying](#querying)
    27            * [GetFirst](#getfirst)
    28            * [GetAll](#getall)
    29          * [Manipulation](#manipulation)
    30  
    31  # Installation
    32  
    33  This module can be installed with the `go get` command:
    34  
    35      go get github.com/mgulsoy/arnedb
    36  
    37  This module is pure __GO__ implementation. No external libraries required. Only standart
    38  libraries used.
    39  
    40  # Usage
    41  
    42  After installing the library, import it into your app file:
    43  
    44  ```go
    45  import (
    46      "github.com/mgulsoy/arnedb"
    47  )
    48  ```
    49  ### Db Management
    50  
    51  ArneDB uses a folder to store data. To create or open a database use `Open` function:
    52  
    53  ```go
    54  func main() {
    55      ptrDbInstance, err := arnedb.Open("baseDir","databaseName")
    56      if err != nil {
    57          panic(err)
    58      }
    59  }
    60  ```
    61  
    62  The `Open` function checks whether `baseDir` exists and then creates `databaseName` database.
    63  A `baseDir` can contain multiple databases. The database requires no closing operation.
    64  
    65  To store documents at first we need to create a collection. To create a collection we use
    66  `CreateColl` function:
    67  
    68  ```go
    69  func main() {
    70      // Open or create a collection 
    71      ptrDbInstance, err := arnedb.Open("baseDir","databaseName")
    72      if err != nil {
    73          panic(err)
    74      }
    75  
    76      //Create a collection
    77      ptrACollection, err := ptrDbInstance.CreateColl("aCollection")
    78      if err != nil {
    79          panic(err)
    80      }
    81  }
    82  ```
    83  
    84  The `CreateColl` function returns a pointer to a `Coll` struct. This will enable to
    85  interact with the collections created. The `Open` function loads existing collections.
    86  If we want to delete a collection we can use the `DeleteColl` function.
    87  
    88  ```go
    89  func main() {
    90      // ...
    91      err := ptrDbInstance.DeleteColl("anotherCollection")
    92      if err != nil {
    93          panic(err) // Not found or file system error	
    94      }
    95  }
    96  ```
    97  
    98  To get a slice (array) of the names of the collections we can use the `GetCollNames`
    99  function:
   100  
   101  ```go
   102  func main() {
   103      // ...
   104      collNames := ptrDbInstance.GetCollNames()
   105      if collNames == nil {
   106          // there is no collection
   107          fmt.Println("There is no collection")
   108      }
   109  }
   110  ```
   111  
   112  To get a collection we use the `GetColl` function:
   113  
   114  ```go
   115  func main() {
   116      // ...
   117      collNames := ptrDbInstance.GetCollNames()
   118      if collNames == nil {
   119          // there is no collection
   120          fmt.Println("There is no collection")
   121      }
   122  }
   123  ```
   124  
   125  ### Collection Operations And Query
   126  
   127  #### Adding Documents
   128  
   129  Once we get a collection from db we can add data to it. To add a document to a collection
   130  we use the `Add` function:
   131  
   132  ```go
   133  func main() {
   134      // ... 
   135      
   136      err := ptrToAColl.Add(someData)
   137      if err != nil {
   138          panic(err)
   139      }
   140  }
   141  ```
   142  
   143  `Add` function returns `error` if something goes wrong. If no error is returned then adding
   144  is successful.
   145  
   146  If we want to add multiple documents at once, we use `AddAll` function.
   147  
   148  ```go
   149  func main() {
   150      // ... 
   151      dataArray := []RecordInstance{data1, data2, data3, data4}
   152      // This function is a variadic function! Don't forget the ...
   153      numberOfAdded, err := ptrToAColl.AddAll(dataArray...)
   154      if err != nil {
   155          panic(err)
   156      }
   157  }
   158  ```
   159  
   160  The function returns the number of added records. This function writes/commits data to disk
   161  at once.
   162  
   163  #### Querying
   164  
   165  After adding data, we need to query and get the data from the store. There is no special
   166  query language. Query mechanism works with the predicate functions. The operation is similar
   167  to the LINQ. A `Coll` provides these methods:
   168  
   169  * [GetFirst](*getfirst)
   170  * [GetAll](*getall)
   171  * [GetFirstAsInterface](#getfirstasinterface)
   172  * [GetAllAsInterface](#getallasinterface)
   173  * [Count](#count)
   174  
   175  Also there are function from the package using generics:
   176  * [GetFirstAs](#getfirstas)
   177  * [GetAllAs](#getallas)
   178  
   179  
   180  ##### GetFirst
   181  To get a single data we use the `GetFirst` function. This function runs the
   182  predicate and returns the first match in a collection. The predicate function signature
   183  must match the `QueryPredicate` type.
   184  
   185  ```go
   186  func main() {
   187      // ... 
   188      // This predicate checks the records for the id is greater then 34
   189      queryPredicate := func(instance RecordInstance) bool {
   190          return instance["id"].(float64) > 34
   191      }
   192      
   193      data, err := ptrToAColl.GetFirst(queryPredicate)
   194      if err != nil {
   195          panic(err)
   196      }
   197      
   198      if data == nil {
   199          // This means the predicate matches no data.
   200          fmt.Println("No data matched!")
   201          return
   202      }
   203      
   204      fmt.Printf("Data: %+v",data) // show data on the console
   205  }
   206  ```
   207  
   208  The function returns `nil` if there is no match.
   209  
   210  ##### GetAll
   211  If we want to get all the records that the predicate match, we use the `GetAll` function.
   212  
   213  ```go
   214  func main() {
   215      // ... 
   216      // This predicate checks the records for the id is greater then 34
   217      queryPredicate := func(instance RecordInstance) bool {
   218          return instance["id"].(float64) > 34
   219      }
   220      
   221      dataSlice, err := ptrToAColl.GetAll(queryPredicate)
   222      if err != nil {
   223          panic(err)
   224      }
   225      
   226      if len(dataSlice) == 0 {
   227          // This means the predicate matches no data.
   228          fmt.Println("No data matched!")
   229          return
   230      }
   231      
   232      fmt.Printf("Data: %+v",dataSlice) // show data on the console
   233  }
   234  ```
   235  
   236  If the predicate does not match any records, the function returns an empty slice.
   237  
   238  ##### GetFirstAsInterface
   239  There is also `GetFirstAsInterface` function. This function tries to return data as a struct
   240  used in the application. This function works a little different with the `GetFirst` function.
   241  Check the example:
   242  
   243  ```go
   244  type SomeDataType stuct {
   245  	Id              int
   246  	SomeValue       string
   247  	SomeOtherValue  float64
   248  }
   249  
   250  func main() {
   251  	// ...
   252  	
   253      var dataHolder SomeDataType
   254      var queryPredicate = func(instance interface{}) bool {
   255          i := instance.(*SomeDataType) // this typecast is required
   256          return i.Id == 13
   257      }
   258  
   259      // The holder (3rd) parameter must be an address of a variable
   260      found, err := ptrToAColl.GetFirstAsInterface(queryPredicate, &dataHolder)
   261      if err != nil {
   262          //handle error
   263          // ...
   264      }
   265  
   266      if found {
   267          // data found. You can reach the data with dataHolder
   268          fmt.Println("Data: ", dataHolder)
   269          // ...
   270      } else {
   271          // Not found, no match
   272          // if so dataHolder will be nil
   273          // handle this state ...
   274      }
   275  }
   276  ```
   277  
   278  ##### GetAllAsInterface
   279  There is also `GetAllAsInterface` function. This function hands the found document to an
   280  argument named `harvestCallback`. This is a callback function. Inside this function you
   281  can harvest the data as you wish. Check the example:
   282  
   283  ```go
   284  type SomeDataType stuct {
   285  	Id              int
   286  	SomeValue       string
   287  	SomeOtherValue  float64
   288  }
   289  
   290  func main() {
   291  	
   292      // ...
   293  	
   294      var dataHolder SomeDataType
   295      var queryPredicate = func(instance interface{}) bool {
   296          i := instance.(*SomeDataType) // this typecast is required
   297          return i.Id > 0
   298      }
   299  
   300      var resultCollection = make([]SomeDataType,0) // create an empty slice
   301      var harvestCB = func(instance interface{}) bool {
   302          // this is a double indirection. Please pay attention to the * operators!
   303          i := *instance.(*SomeDataType) // this typecast is required
   304          resultCollection = append(resultCollection, i) // harvest as you need
   305          return true // always return true
   306      }
   307  	
   308  	// The holder (3rd) parameter must be an address of a variable!
   309      count, err := ptrToAColl.GetAllAsInterface(queryPredicate, harvestCB, &dataHolder)
   310      if err != nil {
   311         //handle error
   312         // ...
   313      }
   314      if count > 0 {
   315          // query result will be in resultCollection
   316          fmt.Println("Data: ", resultCollection)
   317          // ...
   318      } else {
   319          // Not found, no match
   320          // if so resultCollection will be empty
   321          // handle this state ...
   322      }
   323  }
   324  ```
   325  
   326  ##### Count
   327  If you want to get the count of the documents stored, there is the `Count` function. 
   328  Here is an example of how to use it:
   329  
   330  ```go
   331  func main() {
   332  	
   333      queryPredicate := func(q RecordInstance) bool {
   334         return true // we want to count all the records. You can also give conditions here.	
   335      }
   336  	
   337      n, err := ptrToAColl.Count(queryPredicate)
   338      if err != nil {
   339          // handle error...
   340      } else {
   341         // no error
   342         fmt.Println("Record count:",n)
   343      }
   344  }
   345  ```
   346  
   347  ##### GetFirstAs
   348  This is a function from the package. This function works like [GetFirstAsInterface](#getfirstasinterface) method.
   349  But this function uses generics and easier to query. The predicate function must have one argument
   350  which is a pointer to generic type and must return bool. If a record is found then the record
   351  pointer is returned. If nothing found then nil returned.
   352  
   353  ```go
   354  type SomeDataType stuct {
   355      Id              int
   356      SomeValue       string
   357      SomeOtherValue  float64
   358  }
   359  
   360  func main() {
   361      // ...
   362  	
   363      record, err := arnedb.GetFirstAs[SomeDataType](ptrToAColl, func(i *SomeDataType) bool{
   364          return i.Id == 455
   365      })
   366      if err != nil {
   367          //handle error
   368          // ...
   369      } else {
   370          //if there is no error
   371          if record == nil {
   372              // no records found.	
   373          } else {
   374              // found record
   375          }
   376      }
   377  
   378      
   379  }
   380  ```
   381  
   382  ##### GetAllAs
   383  This is a function from the package. This function works like [GetAllAsInterface](#getallasinterface) method.
   384  But this function uses generics and easier to query. The predicate function must have one argument
   385  which is a pointer to generic type and must return bool. If a record is found then the record
   386  pointer is returned. If nothing found then nil returned.
   387  
   388  ```go
   389  type SomeDataType stuct {
   390      Id              int
   391      SomeValue       string
   392      SomeOtherValue  float64
   393  }
   394  
   395  func main() {
   396      // ...
   397  	
   398      records, err := arnedb.GetAllAs[SomeDataType](ptrToAColl, func(i *SomeDataType) bool{
   399          return i.SomeOtherValue > 12
   400      })
   401      if err != nil {
   402          //handle error
   403          // ...
   404      } else {
   405          //if there is no error
   406          if len(records)==0 {
   407              // no records found.	
   408          } else {
   409              // found record
   410          }
   411      }
   412  
   413      
   414  }
   415  ```
   416  
   417  #### Manipulation
   418  
   419  We can delete records by using `DeleteFirst` and `DeleteAll` functions. The functions accept
   420  a `QueryPredicate` function as an argument and returns the count of deleted records. If the
   421  count is 0 this means no deletion occurred.
   422  
   423  ```go
   424  func main() {
   425      // ... 
   426      // This predicate checks the records for the id is greater then 34
   427      queryPredicate := func(instance RecordInstance) bool {
   428          return instance["id"].(float64) > 34
   429      }
   430      
   431      delCount, err := ptrToAColl.DeleteFirst(queryPredicate)
   432      if err != nil {
   433          panic(err)
   434      }
   435      
   436      if delCount == 0 {
   437          // This means the predicate matches no data.
   438          fmt.Println("No data matched!")
   439          return
   440      }
   441      
   442      delCount, err = ptrToAColl.DeleteAll(queryPredicate)
   443      if err != nil {
   444          panic(err)
   445      }
   446      
   447      if delCount == 0 {
   448          // This means the predicate matches no data.
   449          fmt.Println("No data matched!")
   450          return
   451      } 
   452  }
   453  ```
   454  
   455  We can replace or update records by using these functions:
   456  
   457  * `ReplaceFirst` : Replaces the first record matched by the query predicate with the given one in place.
   458  * `ReplaceAll` : Replaces all the records matched by the query predicate with the given one in place.
   459  * `UpdateFirst` : Updates the first record matched by the query predicate by using the update function in place.
   460  * `UpdateAll` : Updates all the records matched by the query predicate by using the update function in place.
   461  
   462  All these functions return the count of altered records and error. If an error is returned
   463  this means there is a problem with the operation and records are not updated. If the count
   464  returned is 0 then the query predicate matched no record.
   465  
   466  ```go
   467  func main() {
   468      // ... 
   469      // This predicate checks the records for the id is greater then 34
   470      queryPredicate := func(instance RecordInstance) bool {
   471          return instance["id"].(float64) > 34
   472      }
   473      
   474      nCount, err := ptrToAColl.ReplaceFirst(queryPredicate, someNewData)
   475      if err != nil {
   476          panic(err)
   477      }
   478      
   479      if nCount == 0 {
   480          // This means the predicate matches no data.
   481          fmt.Println("No data matched!")
   482          return
   483      }
   484      
   485      nCount, err = ptrToAColl.ReplaceAll(queryPredicate, someNewData)
   486      if err != nil {
   487          panic(err)
   488      }
   489      
   490      if nCount == 0 {
   491          // This means the predicate matches no data.
   492          fmt.Println("No data matched!")
   493          return
   494      } 
   495  }
   496  ```
   497  
   498  The **Update** operation accepts an updater function. The function signature must match with
   499  the `UpdateFunc` type.
   500  
   501  ```go
   502  func main() {
   503      // ... 
   504      // This predicate checks the records for the id is greater then 34
   505      queryPredicate := QueryPredicate(func(instance RecordInstance) bool {
   506          return instance["id"].(float64) > 34
   507      })
   508      
   509      fUpdt := UpdateFunc(func(ptrRecord *RecordInstance) *RecordInstance {
   510          (*ptrRecord)["user"] = "Updated First" // change whatever needed to change
   511          return ptrRecord // and return the result
   512      })
   513      
   514      nCount, err := ptrToAColl.UpdateFirst(queryPredicate, fUpdt)
   515      if err != nil {
   516          panic(err)
   517      }
   518      
   519      if nCount == 0 {
   520          // This means the predicate matches no data.
   521          fmt.Println("No data matched!")
   522          return
   523      }
   524      
   525      nCount, err = ptrToAColl.UpdateAll(queryPredicate, fUpdt)
   526      if err != nil {
   527          panic(err)
   528      }
   529      
   530      if nCount == 0 {
   531          // This means the predicate matches no data.
   532          fmt.Println("No data matched!")
   533          return
   534      } 
   535  }
   536  ```