Skip to content

Instantly share code, notes, and snippets.

@nathanborror
Created October 24, 2017 22:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nathanborror/478ce582b51d54eb56e7d98a8ce83664 to your computer and use it in GitHub Desktop.
Save nathanborror/478ce582b51d54eb56e7d98a8ce83664 to your computer and use it in GitHub Desktop.
/*
Package entity implements functions to store and manipulate entity records
in a schema-less database. The current implementation supports Postgres only.
Inspired by: https://backchannel.org/blog/friendfeed-schemaless-mysql
Example implementation:
type Account struct {
ID string
Name string
Email string
Password string
Created time.Time
Modified time.Time
}
func (a *Account) EntityID() string { return a.ID }
func (a *Account) EntityKind() string { return "app_account" }
func (a *Account) EntityPrepare(id string, created, modified time.Time) {
a.ID = AccountID(id)
a.Created = created
a.Modified = modified
}
Writing a new entity:
newAccount := Account{"Nathan", "nathan@example.com", "n"}
ent := entity.NewEntity(newAccount, db)
ent.Write()
var account state.Account
ent.Unmarshal(&account)
*/
package entity
import (
"encoding/json"
"time"
"github.com/jmoiron/sqlx"
"github.com/jmoiron/sqlx/types"
uuid "github.com/satori/go.uuid"
)
const Schema = `
CREATE TABLE IF NOT EXISTS entity (
added_id serial PRIMARY KEY,
id uuid NOT NULL UNIQUE,
kind varchar(32) NOT NULL,
data jsonb,
created timestamp NOT NULL DEFAULT current_timestamp,
modified timestamp NOT NULL DEFAULT current_timestamp
)`
type EntityKind interface {
EntityID() string
EntityKind() string
EntityPrepare(id string, created, modified time.Time)
}
type Entity struct {
db *sqlx.DB
err error
AddedID int `db:"added_id"`
ID string
Kind string
Data types.JSONText
Created time.Time
Modified time.Time
}
func NewEntity(in EntityKind, db *sqlx.DB) *Entity {
e := Entity{
db: db,
ID: in.EntityID(),
Kind: in.EntityKind(),
}
if e.ID == "" {
e.ID = uuid.NewV4().String()
}
if e.Created.IsZero() {
e.Created = time.Now().UTC()
}
if e.Modified.IsZero() {
e.Modified = time.Now().UTC()
}
in.EntityPrepare(e.ID, e.Created, e.Modified)
e.Data, e.err = json.Marshal(in)
return &e
}
func GetEntity(id string, db *sqlx.DB) *Entity {
var ent Entity
ent.err = db.Get(&ent, "SELECT * FROM entity WHERE id = $1", id)
return &ent
}
func (e *Entity) Write() {
e.Modified = time.Now().UTC()
tx := e.db.MustBegin()
tx.NamedExec(`
INSERT INTO entity
(id, kind, data) VALUES (:id, :kind, :data)
ON CONFLICT (id) DO UPDATE SET
(data, modified) = (EXCLUDED.data, EXCLUDED.modified)`, &e)
tx.Get(&e, "SELECT * FROM entity WHERE id = $1", e.ID)
e.err = tx.Commit()
}
func (e *Entity) Delete() {
tx := e.db.MustBegin()
tx.NamedExec("DELETE from entity WHERE id = :id", &e)
e.err = tx.Commit()
}
func (e *Entity) Unmarshal(dest EntityKind) {
e.err = e.Data.Unmarshal(dest)
dest.EntityPrepare(e.ID, e.Created, e.Modified)
}
func (e *Entity) Err() error {
return e.err
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment