Created
October 10, 2019 10:53
-
-
Save tauraamui/47a966d425951adec4cd34ef94880fce to your computer and use it in GitHub Desktop.
Golang SQL vs NOSQL usage
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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