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 ```