Skip to content

Instantly share code, notes, and snippets.

@tauraamui
Created October 10, 2019 10:53
Show Gist options
  • Save tauraamui/47a966d425951adec4cd34ef94880fce to your computer and use it in GitHub Desktop.
Save tauraamui/47a966d425951adec4cd34ef94880fce to your computer and use it in GitHub Desktop.
Golang SQL vs NOSQL usage
// This function uses the generic store any data structure here
// the downside of using NoSQL is there isn't any enforced inherent
// relationship between the data, but long term cloud software dev
// had made the industry realise that this isn't actually an advantage
// most of the time. Cascading deletes? Who cares right?
// The store implementation used can be switched via cmd line flag to
// use either MongoDB or Google Cloud Datastore, so this impl. just
// checks which one is in use and stores the ID of the correct type.
// Save saves contact record to given db instance
func (c *Contact) Save(ctx context.Context, passphrase string, store db.Store, log echo.Logger) error {
c.encrypt(passphrase)
var id interface{}
var err error
if c.ObjectID != nil {
id, err = store.Save(ctx, c.ObjectID.Hex(), c)
} else if c.Key != nil {
keyID := strconv.Itoa(int(c.Key.ID))
id, err = store.Save(ctx, keyID, c)
}
if c.ObjectID == nil && c.Key == nil {
id, err = store.Save(ctx, "", c)
}
if err != nil {
return err
}
if objectID, ok := id.(*primitive.ObjectID); ok {
c.ID = objectID.Hex()
c.ObjectID = objectID
}
if key, ok := id.(*datastore.Key); ok {
c.ID = strconv.Itoa(int(key.ID))
c.Key = key
}
log.Debug(fmt.Sprintf("Inserted contact of ID %v\n", c.ID))
return nil
}
package db
import (
"context"
"errors"
"fmt"
"reflect"
"strconv"
"go.mongodb.org/mongo-driver/bson"
"cloud.google.com/go/datastore"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
type Store interface {
Save(ctx context.Context, id string, model interface{}) (interface{}, error)
Delete(ctx context.Context, id string) error
Find(ctx context.Context, id string, dst interface{}) error
FindAll(ctx context.Context, key string, value string, dst interface{}) error
Disconnect(ctx context.Context) error
}
type MongoDBStore struct {
db *mongo.Collection
}
func NewMongoStore(db *mongo.Collection) Store {
return MongoDBStore{
db: db,
}
}
func (store MongoDBStore) Save(
ctx context.Context,
id string,
model interface{},
) (interface{}, error) {
if id == "" {
result, err := store.db.InsertOne(ctx, model)
if err != nil {
return nil, err
}
if objectID, ok := result.InsertedID.(primitive.ObjectID); ok {
return &objectID, nil
}
// TODO: this error is shit, improve it
return nil, errors.New("unable to cast ID from DB to primitive.ObjectID")
}
objID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
result, err := store.db.UpdateOne(ctx, objID, model, nil)
if err != nil {
return nil, err
}
if objectID, ok := result.UpsertedID.(primitive.ObjectID); ok {
return &objectID, nil
}
// TODO: this error is shit, improve it
return nil, errors.New("unable to cast ID from DB to primitive.ObjectID")
}
func (store MongoDBStore) Delete(
ctx context.Context,
id string,
) error {
if id == "" {
return errors.New("id must be set to something")
}
objID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
_, err = store.db.DeleteOne(ctx, objID, nil)
if err != nil {
return err
}
return nil
}
func (store MongoDBStore) Find(
ctx context.Context,
id string,
dst interface{},
) error {
dv := reflect.ValueOf(dst)
if dv.Kind() != reflect.Ptr || dv.IsNil() {
return errors.New("must be of type pointer")
}
dv = dv.Elem()
elemType := dv.Type().Elem()
if id == "" {
return errors.New("id must be set to something")
}
objID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
cursor, err := store.db.Find(ctx, objID, nil)
if err != nil {
return err
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
ev := reflect.New(elemType.Elem())
err := cursor.Decode(ev.Interface)
if err != nil {
return err
}
dv.Set(ev)
}
return nil
}
func (store MongoDBStore) FindAll(
ctx context.Context,
key string,
value string,
dst interface{},
) error {
dv := reflect.ValueOf(dst)
if dv.Kind() != reflect.Ptr || dv.IsNil() {
return errors.New("must be of type pointer")
}
dv = dv.Elem()
elemType := dv.Type().Elem()
cursor, err := store.db.Find(ctx, bson.D{{key, value}})
if err != nil {
return err
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
ev := reflect.New(elemType.Elem())
err := cursor.Decode(ev.Interface())
if err != nil {
return err
}
dv.Set(reflect.Append(dv, ev))
}
return nil
}
func (store MongoDBStore) Disconnect(ctx context.Context) error {
return store.db.Database().Client().Disconnect(ctx)
}
type GCStore struct {
db *datastore.Client
kind string
}
func NewGCStore(client *datastore.Client, kind string) Store {
return GCStore{
db: client,
kind: kind,
}
}
func (store GCStore) Save(
ctx context.Context,
id string,
model interface{},
) (interface{}, error) {
if id == "" {
id = "0"
}
idInt, err := strconv.Atoi(id)
if err != nil {
return nil, err
}
var key *datastore.Key
if idInt <= 0 {
key = datastore.IncompleteKey(store.kind, nil)
} else {
key = datastore.IDKey(store.kind, int64(idInt), nil)
}
savedKey, err := store.db.Put(ctx, key, model)
if err != nil {
return nil, err
}
return savedKey, nil
}
func (store GCStore) Delete(
ctx context.Context,
id string,
) error {
if id == "" {
return errors.New("id must be set to something")
}
idInt, err := strconv.Atoi(id)
if err != nil {
return err
}
key := datastore.IDKey(store.kind, int64(idInt), nil)
err = store.db.Delete(ctx, key)
if err != nil {
return err
}
return nil
}
func (store GCStore) Find(
ctx context.Context,
id string,
dst interface{},
) error {
if id == "" {
return errors.New("id to find by cannot be empty")
}
idInt, err := strconv.Atoi(id)
if err != nil {
return err
}
key := datastore.IDKey(store.kind, int64(idInt), nil)
err = store.db.Get(ctx, key, dst)
if err != nil {
if err == datastore.ErrNoSuchEntity {
return nil
}
return err
}
return nil
}
func (store GCStore) FindAll(
ctx context.Context,
key string,
value string,
dst interface{},
) error {
q := datastore.NewQuery(store.kind).Filter(fmt.Sprintf("%s =", key), value)
_, err := store.db.GetAll(ctx, q, dst)
if err != nil {
if err == datastore.ErrNoSuchEntity {
return nil
}
return err
}
return nil
}
func (store GCStore) Disconnect(ctx context.Context) error {
return store.db.Close()
}
//UsersTable describes the table structure for UsersTable in db
type UsersTable struct {
Userid int `tbl:"PKNNAIUI"`
CreatedDateTime int64 `tbl:"NNDT"`
Userroleid int `tbl:"NN"`
UUID string `tbl:"NNUI"`
Username string `tbl:"NNUI"`
Authhash string `tbl:"NN"`
Firstname string `tbl:"NN"`
Lastname string `tbl:"NN"`
Email string `tbl:"NNUI"`
}
//Init carries out default data entry
func (ut *UsersTable) Init(db *sql.DB) {}
//Name gets the table name, have to implement to make UsersTable inherit Table
func (ut *UsersTable) Name() string { return "users" }
//RootUserExists checks if at least one root user exists
func (ut *UsersTable) RootUserExists() bool {
rows, err := ut.Select(Conn, "userid", fmt.Sprintf("userroleid = %d", ROOT_USER))
if err != nil {
logging.Error(err.Error())
return false
}
defer rows.Close()
var i = 0
for rows.Next() {
i++
if i > 0 {
break
}
}
return i > 0
}
//InsertMultiple takes a slice of user structs and passes them all to 'Insert'
func (ut *UsersTable) InsertMultiple(db *sql.DB, us []*User) error {
if len(us) == 0 {
return errors.New("At least one user must be in list")
}
var valuesToInsert []interface{}
insertStatement := ut.buildPreparedInsertStatement(us[0])
for i, u := range us {
if err := u.Validate(); err != nil {
return err
}
if i > 0 && i < len(us) {
insertStatement += ", "
}
if i > 0 {
insertStatement += buildValuesList(u)
}
if u.UUID == "" {
newUUID, err := uuid.NewV4()
if err != nil {
return err
}
u.UUID = newUUID.String()
if u.UserroleId == 0 {
u.UserroleId = 3
}
}
valuesToInsert = append(valuesToInsert, u.CreatedDateTime)
valuesToInsert = append(valuesToInsert, u.UserroleId)
valuesToInsert = append(valuesToInsert, u.UUID)
valuesToInsert = append(valuesToInsert, u.Username)
valuesToInsert = append(valuesToInsert, u.AuthHash)
valuesToInsert = append(valuesToInsert, u.FirstName)
valuesToInsert = append(valuesToInsert, u.LastName)
valuesToInsert = append(valuesToInsert, u.Email)
}
logging.Debug(fmt.Sprintf("Running insert statement %s", insertStatement))
_, err := db.Exec(insertStatement, valuesToInsert...)
return err
}
//Insert adds user struct to users table, it also sets default values
func (ut *UsersTable) Insert(db *sql.DB, u *User) error {
//TODO: change this to simply call validate()
if u.UUID != "" {
return fmt.Errorf("User to insert already has UUID %s", u.UUID)
}
err := u.Validate()
if err != nil {
return err
}
if u.UUID == "" {
newUUID, err := uuid.NewV4()
if err != nil {
return err
}
u.UUID = newUUID.String()
if u.UserroleId == 0 {
u.UserroleId = 3
}
insertStatement := ut.buildPreparedInsertStatement(u)
logging.Debug(fmt.Sprintf("Running insert statement %s", insertStatement))
_, err = db.Exec(insertStatement, u.CreatedDateTime, u.UserroleId, u.UUID, u.Username, u.AuthHash, u.FirstName, u.LastName, u.Email)
if err != nil {
return err
}
}
return nil
}
//Select returns table rows from a select using the passed where condition
func (ut *UsersTable) Select(db *sql.DB, whatToSelect string, whereClause string) (*sql.Rows, error) {
if len(whereClause) > 0 {
return db.Query(fmt.Sprintf("SELECT %s FROM %s WHERE %s", whatToSelect, ut.Name(), whereClause))
} else {
return db.Query(fmt.Sprintf("SELECT %s FROM %s", whatToSelect, ut.Name()))
}
}
func (ut *UsersTable) SelectRootUser(db *sql.DB) (*User, error) {
u := &User{}
rows, err := db.Query(fmt.Sprintf("SELECT * FROM %s WHERE userroleid = %d", ut.Name(), int(ROOT_USER)))
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
err = rows.Scan(&u.UserId, &u.CreatedDateTime, &u.UserroleId, &u.UUID, &u.Username, &u.AuthHash, &u.FirstName, &u.LastName, &u.Email)
if err != nil {
return nil, err
}
}
return u, nil
}
func (ut *UsersTable) SelectByUsername(db *sql.DB, username string) (*User, error) {
u := &User{}
rows, err := db.Query(fmt.Sprintf("SELECT * FROM %s WHERE username = '%s'", ut.Name(), username))
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
err = rows.Scan(&u.UserId, &u.CreatedDateTime, &u.UserroleId, &u.UUID, &u.Username, &u.AuthHash, &u.FirstName, &u.LastName, &u.Email)
if err != nil {
return nil, err
}
}
return u, nil
}
func (ut *UsersTable) SelectByUUID(db *sql.DB, uuid string) (*User, error) {
u := &User{}
rows, err := db.Query(fmt.Sprintf("SELECT * FROM %s WHERE uuid = '%s'", ut.Name(), uuid))
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
err = rows.Scan(&u.UserId, &u.CreatedDateTime, &u.UserroleId, &u.UUID, &u.Username, &u.AuthHash, &u.FirstName, &u.LastName, &u.Email)
if err != nil {
return nil, err
}
}
return u, nil
}
func (ut *UsersTable) DeleteByUUID(db *sql.DB, uuid string) (int64, error) {
res, err := db.Exec(fmt.Sprintf("DELETE FROM %s WHERE uuid = ?", ut.Name()), uuid)
if err != nil {
return 0, err
}
numDeleted, err := res.RowsAffected()
if err != nil {
return 0, err
}
return numDeleted, nil
}
//BuildFields takes the table struct and maps all of the struct fields to their own struct
func (ut *UsersTable) buildFields() []Field {
return buildFieldsFromTable(ut)
}
func (ut *UsersTable) buildInsertStatement(m Model) string {
return buildInsertStatementFromTable(ut, m)
}
func (ut *UsersTable) buildPreparedInsertStatement(m Model) string {
return buildPreparedInsertStatementFromTable(ut, m)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment