Generating UUID in Golang and Insert Read ID's using BBolt DB.



UUID is the most commonly used in backend development. for generating unique ids in the system for different components. to uniquely identify each component. read more about UUID 

In this article, we will see how to generate a UUID in golang based on user input.
user will provide how many character id want to generate and how many id's want to create.
I create it has a package you can directly use it.
code is self-explained with comments for each function. 
this is more practical rather than theory.
The small project contains.


1. Generating UUID based on Base62 algorithm you can use more efficient third party packages for the same some examples. I keep STD package
2. Putting ( inserting generated ids into a bucket ( we are using bbolt database) sequential write.
3. Checking Duplicate ID's While inserting make sure there should not a duplicate id  we know it should be unique but for the sanity check also good to test (but time-consuming)
4. Reading IDs from Bucket ( Sequential Read its super-fast as compare to other databases)
    BBolt is Key-Value database can read more BBolt DB
5.Unit Test and Benchmark Test


package db
import "go.etcd.io/bbolt"
// BBoltDB stucture
type BBoltDB struct {
DB *bbolt.DB
}
// Connection opens a new connection
// It requires db path and it returns databse object and error
func Connection(dbPath string) (*BBoltDB, error) {
db, err := bbolt.Open(dbPath+"/uuid.db", 0600, nil)
if err != nil {
return nil, err
}
return &BBoltDB{DB: db}, nil
}
view raw connection.go hosted with ❤ by GitHub
package db
import (
"bytes"
"fmt"
"github.com/amolasg/golang-programs/generating-uid-in-go/id"
"go.etcd.io/bbolt"
)
// UUIDBucket as Root Bucket
// DBConnectionPath is a Database path.
const (
UUIDBucket RootBucket = "uuid"
DBConnectionPath string = "/home/amol/Desktop/databases"
)
// RootBucket its type used for Bucket
type RootBucket string
// checkDuplicate - Check Is any duplicate UUID at time of inserting an UUIDs
// It requires Cursor for iterate over bucket with an new UUID for comparing
// It returns a true or false
func checkDuplicate(c *bbolt.Cursor, newUUID string) bool {
for k, v := c.First(); k != nil; k, v = c.Next() {
if bytes.Equal([]byte(newUUID), v) {
fmt.Println("duplicated key found", newUUID)
return true
}
}
return false
}
// Get - It displays (Reterive) an UUIDs which are in a bucket.
// It Requires RootBucket which contains a Key(UUIDs)
// It returns an error
func (b *BBoltDB) Get(rootbucket RootBucket) (err error) {
db := b.DB
if err := db.View(func(tx *bbolt.Tx) error {
bucket := tx.Bucket([]byte(rootbucket))
if bucket == nil {
return fmt.Errorf("Bucket %v not found", rootbucket)
}
if err := bucket.ForEach(func(k, v []byte) error {
fmt.Printf("key=%s, value=%s\n", k, v)
return nil
}); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
// Put - Inserts an UUID into a Bucket.
// It Require a Bucket rootbucket,no of maxChar and No of records as Param and returns an error.
func (b *BBoltDB) Put(rootbucket RootBucket, maxChar, records int) (err error) {
db := b.DB
for i := 0; i < records; i++ {
newUUID := id.GetNewID(maxChar)
if err := db.Update(func(tx *bbolt.Tx) error {
// Check bucket is exist or not
bucket, err := tx.CreateBucketIfNotExists([]byte(rootbucket))
if err != nil {
return err
}
// Validate duplicate uuid
if !checkDuplicate(bucket.Cursor(), newUUID) {
err = bucket.Put([]byte(newUUID), []byte(newUUID))
if err != nil {
return err
}
}
return nil
}); err != nil {
return err
}
}
return nil
}
view raw crud.go hosted with ❤ by GitHub
package db
import (
"log"
"testing"
"go.etcd.io/bbolt"
)
func NewTestConnection() *BBoltDB {
conn, err := bbolt.Open("/home/amol/Desktop/testDatabases/testDb.db", 0644, nil)
if err != nil {
log.Panic(err)
}
return &BBoltDB{DB: conn}
}
func TestBBoltDB_Put(t *testing.T) {
testDbConn := NewTestConnection()
defer testDbConn.DB.Close()
type fields struct {
DB *bbolt.DB
}
type args struct {
name RootBucket
maxChar int
record int
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "test",
fields: fields{
DB: testDbConn.DB,
},
args: args{
name: "testBucket",
maxChar: 8,
record: 1000,
},
wantErr: false,
},
{
name: "test",
fields: fields{
DB: testDbConn.DB,
},
args: args{
name: "testBucket",
maxChar: 16,
record: 100,
},
wantErr: false,
},
{
name: "test",
fields: fields{
DB: testDbConn.DB,
},
args: args{
name: "testBucket",
maxChar: 22,
record: 1000,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &BBoltDB{
DB: tt.fields.DB,
}
if err := b.Put(tt.args.name, tt.args.maxChar, tt.args.record); (err != nil) != tt.wantErr {
t.Errorf("BBoltDB.Put() error = %v, wantErr %v", err, tt.wantErr)
}
if err := b.Get(tt.args.name); (err != nil) != tt.wantErr {
t.Errorf("BBoltDB.Get() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
// Benchmark
func BenchmarkTestBBoltDB_Put(b *testing.B) {
for n := 0; n < b.N; n++ {
testDbConn := NewTestConnection()
defer testDbConn.DB.Close()
type fields struct {
DB *bbolt.DB
}
type args struct {
name RootBucket
maxChar int
record int
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "test",
fields: fields{
DB: testDbConn.DB,
},
args: args{
name: "testBucket",
},
wantErr: false,
},
}
for _, tt := range tests {
b.Run(tt.name, func(b *testing.B) {
bb := &BBoltDB{
DB: tt.fields.DB,
}
if err := bb.Put(tt.args.name, tt.args.maxChar, tt.args.record); (err != nil) != tt.wantErr {
b.Errorf("BBoltDB.Put() error = %v, wantErr %v", err, tt.wantErr)
}
if err := bb.Get(tt.args.name); (err != nil) != tt.wantErr {
b.Errorf("BBoltDB.Get() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
}
view raw crud_test.go hosted with ❤ by GitHub
package id
// GetNewID is public function can access anywhere
// it gives an new unique id
func GetNewID(maxChar int) string {
return newID(maxChar)
}
view raw id.go hosted with ❤ by GitHub
package id
import (
"bytes"
"math/rand"
"time"
)
// Constants
const (
alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
base62 = 62
)
// GetNewID - Gives a new id based on param
// maxChar is provided by command line arg, to generate that much long Char id.
func newID(maxChar int) string {
currentID := make([]int, maxChar)
rand.Seed(time.Now().UTC().UnixNano())
for j := maxChar - 1; j >= 0; j-- {
currentID[j] = rand.Intn(base62)
}
var buffer bytes.Buffer
// Encodes each integer in the current array to a base62 token
// and adds it to the buffer
for i := 0; i < maxChar; i++ {
buffer.WriteString(encode(currentID[i]))
}
return buffer.String()
}
// Converts int to base62 token
func encode(number int) string {
if number == 0 {
return string(alphabet[0])
}
chars := make([]byte, 0)
length := len(alphabet)
for number > 0 {
result := number / length
remainder := number % length
chars = append(chars, alphabet[remainder])
number = result
}
for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 {
chars[i], chars[j] = chars[j], chars[i]
}
return string(chars)
}
view raw utilities.go hosted with ❤ by GitHub
Thanks for Reading, Keep Learning.

Comments

Popular posts from this blog

Best GitHub repositories for Go

Golang Interview Questions Part 1 Theory Top 50 Question

Complete Golang Development setup on Linux (Ubuntu 20.04 )